mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
core, iOS: support for self-destruct password (#2412)
* core, iOS: support for self-destruct password * disable test logging * core: fix tests, iOS: remove notifications on removal * change alerts
This commit is contained in:
committed by
GitHub
parent
57801fde1f
commit
0b8d9d11e2
@@ -192,7 +192,7 @@ struct ContentView: View {
|
||||
private func justAuthenticate() {
|
||||
userAuthorized = false
|
||||
let laMode = privacyLocalAuthModeDefault.get()
|
||||
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason")) { laResult in
|
||||
authenticate(reason: NSLocalizedString("Unlock app", comment: "authentication reason"), selfDestruct: true) { laResult in
|
||||
logger.debug("authenticate callback: \(String(describing: laResult))")
|
||||
switch (laResult) {
|
||||
case .success:
|
||||
|
||||
@@ -42,7 +42,7 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
changeActiveUser(userId, viewPwd: nil)
|
||||
}
|
||||
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
|
||||
let chatId = content.userInfo["chatId"] as? String {
|
||||
let chatId = content.userInfo["chatId"] as? String {
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
|
||||
Task { await acceptContactRequest(contactRequest) }
|
||||
} else {
|
||||
@@ -107,8 +107,8 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
// in another chat
|
||||
return recent ? [.banner, .list] : [.sound, .banner, .list]
|
||||
}
|
||||
// this notification is deliverd from the notifications server
|
||||
// when the app is in foreground it does not need to be shown
|
||||
// this notification is deliverd from the notifications server
|
||||
// when the app is in foreground it does not need to be shown
|
||||
case ntfCategoryCheckMessage: return []
|
||||
case ntfCategoryCallInvitation: return []
|
||||
default: return [.sound, .banner, .list]
|
||||
@@ -247,8 +247,12 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func removeNotifications(_ ids : [String]){
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ids)
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
|
||||
func removeAllNotifications() async {
|
||||
let nc = UNUserNotificationCenter.current()
|
||||
let settings = await nc.notificationSettings()
|
||||
if settings.authorizationStatus == .authorized {
|
||||
nc.removeAllPendingNotificationRequests()
|
||||
nc.removeAllDeliveredNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func apiGetActiveUser() throws -> User? {
|
||||
}
|
||||
}
|
||||
|
||||
func apiCreateActiveUser(_ p: Profile) throws -> User {
|
||||
func apiCreateActiveUser(_ p: Profile?) throws -> User {
|
||||
let r = chatSendCmdSync(.createActiveUser(profile: p))
|
||||
if case let .activeUser(user) = r { return user }
|
||||
throw r
|
||||
|
||||
@@ -317,10 +317,7 @@ struct DatabaseView: View {
|
||||
private func stopChat() {
|
||||
Task {
|
||||
do {
|
||||
try await apiStopChat()
|
||||
ChatReceiver.shared.stop()
|
||||
await MainActor.run { m.chatRunning = false }
|
||||
appStateGroupDefault.set(.stopped)
|
||||
try await stopChatAsync()
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
runChat = true
|
||||
@@ -374,9 +371,7 @@ struct DatabaseView: View {
|
||||
progressIndicator = true
|
||||
Task {
|
||||
do {
|
||||
try await apiDeleteStorage()
|
||||
_ = kcDatabasePassword.remove()
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
try await deleteChatAsync()
|
||||
await operationEnded(.chatDeleted)
|
||||
appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory())
|
||||
} catch let error {
|
||||
@@ -468,6 +463,19 @@ struct DatabaseView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func stopChatAsync() async throws {
|
||||
try await apiStopChat()
|
||||
ChatReceiver.shared.stop()
|
||||
await MainActor.run { ChatModel.shared.chatRunning = false }
|
||||
appStateGroupDefault.set(.stopped)
|
||||
}
|
||||
|
||||
func deleteChatAsync() async throws {
|
||||
try await apiDeleteStorage()
|
||||
_ = kcDatabasePassword.remove()
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
}
|
||||
|
||||
struct DatabaseView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DatabaseView(showSettings: Binding.constant(false), chatItemTTL: .none)
|
||||
|
||||
@@ -30,19 +30,26 @@ struct LocalAuthRequest {
|
||||
var title: LocalizedStringKey? // if title is null, reason is shown
|
||||
var reason: String
|
||||
var password: String
|
||||
var selfDestruct: Bool
|
||||
var completed: (LAResult) -> Void
|
||||
|
||||
static var sample = LocalAuthRequest(title: "Enter Passcode", reason: "Authenticate", password: "", completed: { _ in })
|
||||
static var sample = LocalAuthRequest(title: "Enter Passcode", reason: "Authenticate", password: "", selfDestruct: false, completed: { _ in })
|
||||
}
|
||||
|
||||
func authenticate(title: LocalizedStringKey? = nil, reason: String, completed: @escaping (LAResult) -> Void) {
|
||||
func authenticate(title: LocalizedStringKey? = nil, reason: String, selfDestruct: Bool = false, completed: @escaping (LAResult) -> Void) {
|
||||
logger.debug("authenticate")
|
||||
switch privacyLocalAuthModeDefault.get() {
|
||||
case .system: systemAuthenticate(reason, completed)
|
||||
case .passcode:
|
||||
if let password = kcAppPassword.get() {
|
||||
DispatchQueue.main.async {
|
||||
ChatModel.shared.laRequest = LocalAuthRequest(title: title, reason: reason, password: password, completed: completed)
|
||||
ChatModel.shared.laRequest = LocalAuthRequest(
|
||||
title: title,
|
||||
reason: reason,
|
||||
password: password,
|
||||
selfDestruct: selfDestruct && UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT),
|
||||
completed: completed
|
||||
)
|
||||
}
|
||||
} else {
|
||||
completed(.unavailable(authError: NSLocalizedString("No app password", comment: "Authentication unavailable")))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct LocalAuthView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@@ -15,6 +16,13 @@ struct LocalAuthView: View {
|
||||
|
||||
var body: some View {
|
||||
PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit") {
|
||||
if let sdPassword = kcSelfDestructPassword.get(), authRequest.selfDestruct && password == sdPassword {
|
||||
deleteStorageAndRestart(sdPassword) { r in
|
||||
m.laRequest = nil
|
||||
authRequest.completed(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
let r: LAResult = password == authRequest.password
|
||||
? .success
|
||||
: .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry"))
|
||||
@@ -25,6 +33,41 @@ struct LocalAuthView: View {
|
||||
authRequest.completed(.failed(authError: NSLocalizedString("Authentication cancelled", comment: "PIN entry")))
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) {
|
||||
Task {
|
||||
do {
|
||||
try await stopChatAsync()
|
||||
try await deleteChatAsync()
|
||||
_ = kcAppPassword.set(password)
|
||||
_ = kcSelfDestructPassword.remove()
|
||||
await NtfManager.shared.removeAllNotifications()
|
||||
let displayName = UserDefaults.standard.string(forKey: DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME)
|
||||
UserDefaults.standard.removeObject(forKey: DEFAULT_LA_SELF_DESTRUCT)
|
||||
UserDefaults.standard.removeObject(forKey: DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME)
|
||||
await MainActor.run {
|
||||
m.chatDbChanged = true
|
||||
m.chatInitialized = false
|
||||
}
|
||||
resetChatCtrl()
|
||||
try initializeChat(start: true)
|
||||
m.chatDbChanged = false
|
||||
appStateGroupDefault.set(.active)
|
||||
if m.currentUser != nil { return }
|
||||
var profile: Profile? = nil
|
||||
if let displayName = displayName, displayName != "" {
|
||||
profile = Profile(displayName: displayName, fullName: "")
|
||||
}
|
||||
m.currentUser = try apiCreateActiveUser(profile)
|
||||
onboardingStageDefault.set(.onboardingComplete)
|
||||
m.onboardingStage = .onboardingComplete
|
||||
try startChat()
|
||||
completed(.success)
|
||||
} catch {
|
||||
completed(.failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalAuthView_Previews: PreviewProvider {
|
||||
|
||||
@@ -10,6 +10,9 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct SetAppPasscodeView: View {
|
||||
var passcodeKeychain: KeyChainItem = kcAppPassword
|
||||
var title: LocalizedStringKey = "New Passcode"
|
||||
var reason: String?
|
||||
var submit: () -> Void
|
||||
var cancel: () -> Void
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@@ -27,7 +30,7 @@ struct SetAppPasscodeView: View {
|
||||
submitEnabled: { pwd in pwd == enteredPassword }
|
||||
) {
|
||||
if passcode == enteredPassword {
|
||||
if kcAppPassword.set(passcode) {
|
||||
if passcodeKeychain.set(passcode) {
|
||||
enteredPassword = ""
|
||||
passcode = ""
|
||||
dismiss()
|
||||
@@ -38,7 +41,7 @@ struct SetAppPasscodeView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setPasswordView(title: "New Passcode", submitLabel: "Save") {
|
||||
setPasswordView(title: title, submitLabel: "Save") {
|
||||
enteredPassword = passcode
|
||||
passcode = ""
|
||||
confirming = true
|
||||
@@ -51,7 +54,7 @@ struct SetAppPasscodeView: View {
|
||||
}
|
||||
|
||||
private func setPasswordView(title: LocalizedStringKey, submitLabel: LocalizedStringKey, submitEnabled: (((String) -> Bool))? = nil, submit: @escaping () -> Void) -> some View {
|
||||
PasscodeView(passcode: $passcode, title: title, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) {
|
||||
PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) {
|
||||
dismiss()
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -129,6 +129,8 @@ struct CreateProfile: View {
|
||||
m.onboardingStage = .step3_CreateSimpleXAddress
|
||||
}
|
||||
} else {
|
||||
onboardingStageDefault.set(.onboardingComplete)
|
||||
m.onboardingStage = .onboardingComplete
|
||||
dismiss()
|
||||
m.users = try listUsers()
|
||||
try getUserChatData()
|
||||
|
||||
@@ -13,6 +13,7 @@ struct IncognitoHelp: View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Incognito mode")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.vertical)
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
@@ -102,9 +102,13 @@ struct SimplexLockView: View {
|
||||
@AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false
|
||||
@State private var laMode: LAMode = privacyLocalAuthModeDefault.get()
|
||||
@AppStorage(DEFAULT_LA_LOCK_DELAY) private var laLockDelay = 30
|
||||
@State var performLA: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
|
||||
@State private var performLA: Bool = UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA)
|
||||
@State private var selfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
|
||||
@State private var currentSelfDestruct: Bool = UserDefaults.standard.bool(forKey: DEFAULT_LA_SELF_DESTRUCT)
|
||||
@AppStorage(DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME) private var selfDestructDisplayName = ""
|
||||
@State private var performLAToggleReset = false
|
||||
@State private var performLAModeReset = false
|
||||
@State private var performLASelfDestructReset = false
|
||||
@State private var showPasswordAction: PasswordAction? = nil
|
||||
@State private var showChangePassword = false
|
||||
@State var laAlert: LASettingViewAlert? = nil
|
||||
@@ -116,6 +120,8 @@ struct SimplexLockView: View {
|
||||
case laUnavailableTurningOffAlert
|
||||
case laPasscodeSetAlert
|
||||
case laPasscodeChangedAlert
|
||||
case laSeldDestructPasscodeSetAlert
|
||||
case laSeldDestructPasscodeChangedAlert
|
||||
case laPasscodeNotChangedAlert
|
||||
|
||||
var id: Self { self }
|
||||
@@ -124,7 +130,10 @@ struct SimplexLockView: View {
|
||||
enum PasswordAction: Identifiable {
|
||||
case enableAuth
|
||||
case toggleMode
|
||||
case changePassword
|
||||
case changePasscode
|
||||
case enableSelfDestruct
|
||||
case changeSelfDestructPasscode
|
||||
case selfDestructInfo
|
||||
|
||||
var id: Self { self }
|
||||
}
|
||||
@@ -159,12 +168,34 @@ struct SimplexLockView: View {
|
||||
}
|
||||
}
|
||||
if showChangePassword && laMode == .passcode {
|
||||
Button("Change Passcode") {
|
||||
Button("Change passcode") {
|
||||
changeLAPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if performLA && laMode == .passcode {
|
||||
Section("Self-destruct passcode") {
|
||||
Toggle(isOn: $selfDestruct) {
|
||||
HStack(spacing: 6) {
|
||||
Text("Enable self-destruct")
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
}
|
||||
.onTapGesture {
|
||||
showPasswordAction = .selfDestructInfo
|
||||
}
|
||||
}
|
||||
if selfDestruct {
|
||||
TextField("New display name", text: $selfDestructDisplayName)
|
||||
Button("Change self-destruct passcode") {
|
||||
changeSelfDestructPassword()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: performLA) { performLAToggle in
|
||||
@@ -192,6 +223,13 @@ struct SimplexLockView: View {
|
||||
updateLAMode()
|
||||
}
|
||||
}
|
||||
.onChange(of: selfDestruct) { _ in
|
||||
if performLASelfDestructReset {
|
||||
performLASelfDestructReset = false
|
||||
} else if prefPerformLA {
|
||||
toggleSelfDestruct()
|
||||
}
|
||||
}
|
||||
.alert(item: $laAlert) { alertItem in
|
||||
switch alertItem {
|
||||
case .laTurnedOnAlert: return laTurnedOnAlert()
|
||||
@@ -200,6 +238,8 @@ struct SimplexLockView: View {
|
||||
case .laUnavailableTurningOffAlert: return laUnavailableTurningOffAlert()
|
||||
case .laPasscodeSetAlert: return passcodeAlert("Passcode set!")
|
||||
case .laPasscodeChangedAlert: return passcodeAlert("Passcode changed!")
|
||||
case .laSeldDestructPasscodeSetAlert: return selfDestructPasscodeAlert("Self-destruct passcode enabled!")
|
||||
case .laSeldDestructPasscodeChangedAlert: return selfDestructPasscodeAlert("Self-destruct passcode changed!")
|
||||
case .laPasscodeNotChangedAlert: return mkAlert(title: "Passcode not changed!")
|
||||
}
|
||||
}
|
||||
@@ -223,12 +263,27 @@ struct SimplexLockView: View {
|
||||
} cancel: {
|
||||
revertLAMode()
|
||||
}
|
||||
case .changePassword:
|
||||
case .changePasscode:
|
||||
SetAppPasscodeView {
|
||||
showLAAlert(.laPasscodeChangedAlert)
|
||||
} cancel: {
|
||||
showLAAlert(.laPasscodeNotChangedAlert)
|
||||
}
|
||||
case .enableSelfDestruct:
|
||||
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, title: "Set passcode", reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")) {
|
||||
updateSelfDestruct()
|
||||
showLAAlert(.laSeldDestructPasscodeSetAlert)
|
||||
} cancel: {
|
||||
revertSelfDestruct()
|
||||
}
|
||||
case .changeSelfDestructPasscode:
|
||||
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")) {
|
||||
showLAAlert(.laSeldDestructPasscodeChangedAlert)
|
||||
} cancel: {
|
||||
showLAAlert(.laPasscodeNotChangedAlert)
|
||||
}
|
||||
case .selfDestructInfo:
|
||||
selfDestructInfoView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
@@ -239,6 +294,30 @@ struct SimplexLockView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func selfDestructInfoView() -> some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Self-desctruct")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.vertical)
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
Group {
|
||||
Text("If you enter your self-desctruct passcode while opening the app:")
|
||||
VStack(spacing: 8) {
|
||||
textListItem("1.", "All app data is deleted.")
|
||||
textListItem("2.", "App passcode is replaced with self-desctruct passcode.")
|
||||
textListItem("3.", "An empty chat profile with the provided name is created, and the app opens as usual.")
|
||||
}
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func showLAAlert(_ a: LASettingViewAlert) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
laAlert = a
|
||||
@@ -276,11 +355,39 @@ struct SimplexLockView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleSelfDestruct() {
|
||||
authenticate(reason: NSLocalizedString("Change self-destruct mode", comment: "authentication reason")) { laResult in
|
||||
switch laResult {
|
||||
case .failed:
|
||||
revertSelfDestruct()
|
||||
laAlert = .laFailedAlert
|
||||
case .success:
|
||||
if selfDestruct {
|
||||
showPasswordAction = .enableSelfDestruct
|
||||
} else {
|
||||
resetSelfDestruct()
|
||||
}
|
||||
case .unavailable:
|
||||
disableUnavailableLA()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func changeLAPassword() {
|
||||
authenticate(title: "Current Passcode", reason: NSLocalizedString("Change passcode", comment: "authentication reason")) { laResult in
|
||||
switch laResult {
|
||||
case .failed: laAlert = .laFailedAlert
|
||||
case .success: showPasswordAction = .changePassword
|
||||
case .success: showPasswordAction = .changePasscode
|
||||
case .unavailable: disableUnavailableLA()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func changeSelfDestructPassword() {
|
||||
authenticate(reason: NSLocalizedString("Change self-destruct passcode", comment: "authentication reason")) { laResult in
|
||||
switch laResult {
|
||||
case .failed: laAlert = .laFailedAlert
|
||||
case .success: showPasswordAction = .changeSelfDestructPasscode
|
||||
case .unavailable: disableUnavailableLA()
|
||||
}
|
||||
}
|
||||
@@ -329,6 +436,7 @@ struct SimplexLockView: View {
|
||||
_ = kcAppPassword.remove()
|
||||
laLockDelay = 30
|
||||
showChangePassword = false
|
||||
resetSelfDestruct()
|
||||
}
|
||||
|
||||
private func resetLAEnabled(_ onOff: Bool) {
|
||||
@@ -347,9 +455,29 @@ struct SimplexLockView: View {
|
||||
privacyLocalAuthModeDefault.set(laMode)
|
||||
}
|
||||
|
||||
private func resetSelfDestruct() {
|
||||
_ = kcSelfDestructPassword.remove()
|
||||
selfDestruct = false
|
||||
updateSelfDestruct()
|
||||
}
|
||||
|
||||
private func revertSelfDestruct() {
|
||||
performLASelfDestructReset = true
|
||||
withAnimation { selfDestruct = currentSelfDestruct }
|
||||
}
|
||||
|
||||
private func updateSelfDestruct() {
|
||||
UserDefaults.standard.set(selfDestruct, forKey: DEFAULT_LA_SELF_DESTRUCT)
|
||||
currentSelfDestruct = selfDestruct
|
||||
}
|
||||
|
||||
private func passcodeAlert(_ title: LocalizedStringKey) -> Alert {
|
||||
mkAlert(title: title, message: "Please remember or store it securely - there is no way to recover a lost passcode!")
|
||||
}
|
||||
|
||||
private func selfDestructPasscodeAlert(_ title: LocalizedStringKey) -> Alert {
|
||||
mkAlert(title: title, message: "If you enter this passcode when opening the app, all app data will be irreversibly removed!")
|
||||
}
|
||||
}
|
||||
|
||||
struct PrivacySettings_Previews: PreviewProvider {
|
||||
|
||||
@@ -21,6 +21,8 @@ let DEFAULT_LA_NOTICE_SHOWN = "localAuthenticationNoticeShown"
|
||||
let DEFAULT_PERFORM_LA = "performLocalAuthentication"
|
||||
let DEFAULT_LA_MODE = "localAuthenticationMode"
|
||||
let DEFAULT_LA_LOCK_DELAY = "localAuthenticationLockDelay"
|
||||
let DEFAULT_LA_SELF_DESTRUCT = "localAuthenticationSelfDestruct"
|
||||
let DEFAULT_LA_SELF_DESTRUCT_DISPLAY_NAME = "localAuthenticationSelfDestructDisplayName"
|
||||
let DEFAULT_NOTIFICATION_ALERT_SHOWN = "notificationAlertShown"
|
||||
let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay"
|
||||
let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers"
|
||||
@@ -53,6 +55,7 @@ let appDefaults: [String: Any] = [
|
||||
DEFAULT_PERFORM_LA: false,
|
||||
DEFAULT_LA_MODE: LAMode.system.rawValue,
|
||||
DEFAULT_LA_LOCK_DELAY: 30,
|
||||
DEFAULT_LA_SELF_DESTRUCT: false,
|
||||
DEFAULT_NOTIFICATION_ALERT_SHOWN: false,
|
||||
DEFAULT_WEBRTC_POLICY_RELAY: true,
|
||||
DEFAULT_CALL_KIT_CALLS_IN_RECENTS: false,
|
||||
@@ -298,9 +301,8 @@ struct SettingsView: View {
|
||||
.frame(maxWidth: 24, maxHeight: 24, alignment: .center)
|
||||
.foregroundColor(chatModel.incognito ? Color.indigo : .secondary)
|
||||
Toggle(isOn: $chatModel.incognito) {
|
||||
HStack {
|
||||
HStack(spacing: 6) {
|
||||
Text("Incognito")
|
||||
Spacer().frame(width: 4)
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
.font(.system(size: 14))
|
||||
|
||||
@@ -730,8 +730,8 @@ Dostupné ve verzi 5.1</target>
|
||||
<target>Změnit</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Změna hesla</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -730,8 +730,8 @@ Verfügbar ab v5.1</target>
|
||||
<target>Ändern</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Passwort ändern</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -569,8 +569,8 @@ Available in v5.1</source>
|
||||
<source>Change</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change database passphrase?" xml:space="preserve">
|
||||
|
||||
@@ -739,9 +739,9 @@ Available in v5.1</target>
|
||||
<target>Change</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<target>Change Passcode</target>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Change passcode</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change database passphrase?" xml:space="preserve">
|
||||
|
||||
@@ -730,8 +730,8 @@ Disponible en v5.1</target>
|
||||
<target>Cambiar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Cambiar el código de acceso</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -730,8 +730,8 @@ Disponible dans la v5.1</target>
|
||||
<target>Changer</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Modifier le code d'accès</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -569,8 +569,8 @@ Available in v5.1</source>
|
||||
<source>Change</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change database passphrase?" xml:space="preserve">
|
||||
|
||||
@@ -730,8 +730,8 @@ Disponibile nella v5.1</target>
|
||||
<target>Cambia</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Cambia codice di accesso</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -730,8 +730,8 @@ Beschikbaar in v5.1</target>
|
||||
<target>Veranderen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Toegangscode wijzigen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -730,8 +730,8 @@ Dostępny w v5.1</target>
|
||||
<target>Zmień</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Zmień kod dostępu</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -569,8 +569,8 @@ Available in v5.1</source>
|
||||
<source>Change</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change database passphrase?" xml:space="preserve">
|
||||
|
||||
@@ -730,8 +730,8 @@ Available in v5.1</source>
|
||||
<target>Поменять</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>Изменить код доступа</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -730,8 +730,8 @@ Available in v5.1</source>
|
||||
<target>更改</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve">
|
||||
<source>Change passcode</source>
|
||||
<target>更改密码</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -4207,8 +4207,8 @@ SimpleX servers cannot see your profile.</source>
|
||||
<target state="translated">已取消認證</target>
|
||||
<note>PIN entry</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Change Passcode" xml:space="preserve" approved="no">
|
||||
<source>Change Passcode</source>
|
||||
<trans-unit id="Change passcode" xml:space="preserve" approved="no">
|
||||
<source>Change passcode</source>
|
||||
<target state="translated">修改密碼</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
|
||||
@@ -14,7 +14,7 @@ let jsonEncoder = getJSONEncoder()
|
||||
|
||||
public enum ChatCommand {
|
||||
case showActiveUser
|
||||
case createActiveUser(profile: Profile)
|
||||
case createActiveUser(profile: Profile?)
|
||||
case listUsers
|
||||
case apiSetActiveUser(userId: Int64, viewPwd: String?)
|
||||
case apiHideUser(userId: Int64, viewPwd: String)
|
||||
@@ -110,7 +110,11 @@ public enum ChatCommand {
|
||||
get {
|
||||
switch self {
|
||||
case .showActiveUser: return "/u"
|
||||
case let .createActiveUser(profile): return "/create user \(profile.displayName) \(profile.fullName)"
|
||||
case let .createActiveUser(profile):
|
||||
if let profile = profile {
|
||||
return "/create user \(profile.displayName) \(profile.fullName)"
|
||||
}
|
||||
return "/create user"
|
||||
case .listUsers: return "/users"
|
||||
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
|
||||
case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))"
|
||||
|
||||
@@ -13,11 +13,14 @@ private let ACCESS_POLICY: CFString = kSecAttrAccessibleAfterFirstUnlockThisDevi
|
||||
private let ACCESS_GROUP: String = "5NN7GUYB6T.chat.simplex.app"
|
||||
private let DATABASE_PASSWORD_ITEM: String = "databasePassword"
|
||||
private let APP_PASSWORD_ITEM: String = "appPassword"
|
||||
private let SELF_DESTRUCT_PASSWORD_ITEM: String = "selfDestructPassword"
|
||||
|
||||
public let kcDatabasePassword = KeyChainItem(forKey: DATABASE_PASSWORD_ITEM)
|
||||
|
||||
public let kcAppPassword = KeyChainItem(forKey: APP_PASSWORD_ITEM)
|
||||
|
||||
public let kcSelfDestructPassword = KeyChainItem(forKey: SELF_DESTRUCT_PASSWORD_ITEM)
|
||||
|
||||
public struct KeyChainItem {
|
||||
var forKey: String
|
||||
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Passwort ändern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Passwort ändern";
|
||||
"Change passcode" = "Passwort ändern";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Wechseln der Empfängeradresse";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Cambiar el código de acceso";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Cambiar el código de acceso";
|
||||
"Change passcode" = "Cambiar el código de acceso";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Cambiar servidor de recepción";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Modifier le code d'accès";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Modifier le code d'accès";
|
||||
"Change passcode" = "Modifier le code d'accès";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Changer d'adresse de réception";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Cambia codice di accesso";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Cambia codice di accesso";
|
||||
"Change passcode" = "Cambia codice di accesso";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Cambia indirizzo di ricezione";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Toegangscode wijzigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Toegangscode wijzigen";
|
||||
"Change passcode" = "Toegangscode wijzigen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Ontvangst adres wijzigen";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Zmień pin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Zmień kod dostępu";
|
||||
"Change passcode" = "Zmień kod dostępu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Zmień adres odbioru";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "Изменить код доступа";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "Изменить код доступа";
|
||||
"Change passcode" = "Изменить код доступа";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "Поменять адрес получения";
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
"Change passcode" = "更改密码";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change Passcode" = "更改密码";
|
||||
"Change passcode" = "更改密码";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Change receiving address" = "更改接收地址";
|
||||
|
||||
Reference in New Issue
Block a user