Merge branch 'master' into master-ghc8107
This commit is contained in:
commit
4296515473
@ -31,6 +31,7 @@ struct ContentView: View {
|
||||
@State private var showWhatsNew = false
|
||||
@State private var showChooseLAMode = false
|
||||
@State private var showSetPasscode = false
|
||||
@State private var waitingForOrPassedAuth = true
|
||||
@State private var chatListActionSheet: ChatListActionSheet? = nil
|
||||
|
||||
private enum ChatListActionSheet: Identifiable {
|
||||
@ -61,6 +62,10 @@ struct ContentView: View {
|
||||
}
|
||||
if !showSettings, let la = chatModel.laRequest {
|
||||
LocalAuthView(authRequest: la)
|
||||
.onDisappear {
|
||||
// this flag is separate from accessAuthenticated to show initializationView while we wait for authentication
|
||||
waitingForOrPassedAuth = accessAuthenticated
|
||||
}
|
||||
} else if showSetPasscode {
|
||||
SetAppPasscodeView {
|
||||
chatModel.contentViewAccessAuthenticated = true
|
||||
@ -73,8 +78,7 @@ struct ContentView: View {
|
||||
showSetPasscode = false
|
||||
alertManager.showAlert(laPasscodeNotSetAlert())
|
||||
}
|
||||
}
|
||||
if chatModel.chatDbStatus == nil {
|
||||
} else if chatModel.chatDbStatus == nil && AppChatState.shared.value != .stopped && waitingForOrPassedAuth {
|
||||
initializationView()
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ final class ChatModel: ObservableObject {
|
||||
@Published var chatDbChanged = false
|
||||
@Published var chatDbEncrypted: Bool?
|
||||
@Published var chatDbStatus: DBMigrationResult?
|
||||
@Published var ctrlInitInProgress: Bool = false
|
||||
// local authentication
|
||||
@Published var contentViewAccessAuthenticated: Bool = false
|
||||
@Published var laRequest: LocalAuthRequest?
|
||||
|
@ -1215,6 +1215,8 @@ private func currentUserId(_ funcName: String) throws -> Int64 {
|
||||
func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = nil, refreshInvitations: Bool = true, confirmMigrations: MigrationConfirmation? = nil) throws {
|
||||
logger.debug("initializeChat")
|
||||
let m = ChatModel.shared
|
||||
m.ctrlInitInProgress = true
|
||||
defer { m.ctrlInitInProgress = false }
|
||||
(m.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey, confirmMigrations: confirmMigrations)
|
||||
if m.chatDbStatus != .ok { return }
|
||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||
|
@ -44,10 +44,12 @@ struct SimpleXApp: App {
|
||||
chatModel.appOpenUrl = url
|
||||
}
|
||||
.onAppear() {
|
||||
if kcAppPassword.get() == nil || kcSelfDestructPassword.get() == nil {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
initChatAndMigrate()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
logger.debug("scenePhase was \(String(describing: scenePhase)), now \(String(describing: phase))")
|
||||
switch (phase) {
|
||||
|
@ -484,6 +484,7 @@ func deleteChatAsync() async throws {
|
||||
try await apiDeleteStorage()
|
||||
_ = kcDatabasePassword.remove()
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
deleteAppDatabaseAndFiles()
|
||||
}
|
||||
|
||||
struct DatabaseView_Previews: PreviewProvider {
|
||||
|
@ -13,19 +13,28 @@ struct LocalAuthView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var authRequest: LocalAuthRequest
|
||||
@State private var password = ""
|
||||
@State private var allowToReact = true
|
||||
|
||||
var body: some View {
|
||||
PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit") {
|
||||
PasscodeView(passcode: $password, title: authRequest.title ?? "Enter Passcode", reason: authRequest.reason, submitLabel: "Submit",
|
||||
buttonsEnabled: $allowToReact) {
|
||||
if let sdPassword = kcSelfDestructPassword.get(), authRequest.selfDestruct && password == sdPassword {
|
||||
allowToReact = false
|
||||
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"))
|
||||
let r: LAResult
|
||||
if password == authRequest.password {
|
||||
if authRequest.selfDestruct && kcSelfDestructPassword.get() != nil && !m.chatInitialized {
|
||||
initChatAndMigrate()
|
||||
}
|
||||
r = .success
|
||||
} else {
|
||||
r = .failed(authError: NSLocalizedString("Incorrect passcode", comment: "PIN entry"))
|
||||
}
|
||||
m.laRequest = nil
|
||||
authRequest.completed(r)
|
||||
} cancel: {
|
||||
@ -37,8 +46,27 @@ struct LocalAuthView: View {
|
||||
private func deleteStorageAndRestart(_ password: String, completed: @escaping (LAResult) -> Void) {
|
||||
Task {
|
||||
do {
|
||||
/** Waiting until [initializeChat] finishes */
|
||||
while (m.ctrlInitInProgress) {
|
||||
try await Task.sleep(nanoseconds: 50_000000)
|
||||
}
|
||||
if m.chatRunning == true {
|
||||
try await stopChatAsync()
|
||||
try await deleteChatAsync()
|
||||
}
|
||||
if m.chatInitialized {
|
||||
/**
|
||||
* The following sequence can bring a user here:
|
||||
* the user opened the app, entered app passcode, went to background, returned back, entered self-destruct code.
|
||||
* In this case database should be closed to prevent possible situation when OS can deny database removal command
|
||||
* */
|
||||
chatCloseStore()
|
||||
}
|
||||
deleteAppDatabaseAndFiles()
|
||||
// Clear sensitive data on screen just in case app fails to hide its views while new database is created
|
||||
m.chatId = nil
|
||||
m.reversedChatItems = []
|
||||
m.chats = []
|
||||
m.users = []
|
||||
_ = kcAppPassword.set(password)
|
||||
_ = kcSelfDestructPassword.remove()
|
||||
await NtfManager.shared.removeAllNotifications()
|
||||
@ -53,7 +81,7 @@ struct LocalAuthView: View {
|
||||
try initializeChat(start: true)
|
||||
m.chatDbChanged = false
|
||||
AppChatState.shared.set(.active)
|
||||
if m.currentUser != nil { return }
|
||||
if m.currentUser != nil || !m.chatInitialized { return }
|
||||
var profile: Profile? = nil
|
||||
if let displayName = displayName, displayName != "" {
|
||||
profile = Profile(displayName: displayName, fullName: "")
|
||||
|
@ -14,6 +14,8 @@ struct PasscodeView: View {
|
||||
var reason: String? = nil
|
||||
var submitLabel: LocalizedStringKey
|
||||
var submitEnabled: ((String) -> Bool)?
|
||||
@Binding var buttonsEnabled: Bool
|
||||
|
||||
var submit: () -> Void
|
||||
var cancel: () -> Void
|
||||
|
||||
@ -70,11 +72,11 @@ struct PasscodeView: View {
|
||||
@ViewBuilder private func buttonsView() -> some View {
|
||||
Button(action: cancel) {
|
||||
Label("Cancel", systemImage: "multiply")
|
||||
}
|
||||
}.disabled(!buttonsEnabled)
|
||||
Button(action: submit) {
|
||||
Label(submitLabel, systemImage: "checkmark")
|
||||
}
|
||||
.disabled(submitEnabled?(passcode) == false || passcode.count < 4)
|
||||
.disabled(submitEnabled?(passcode) == false || passcode.count < 4 || !buttonsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +87,7 @@ struct PasscodeViewView_Previews: PreviewProvider {
|
||||
title: "Enter Passcode",
|
||||
reason: "Unlock app",
|
||||
submitLabel: "Submit",
|
||||
buttonsEnabled: Binding.constant(true),
|
||||
submit: {},
|
||||
cancel: {}
|
||||
)
|
||||
|
@ -11,6 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct SetAppPasscodeView: View {
|
||||
var passcodeKeychain: KeyChainItem = kcAppPassword
|
||||
var prohibitedPasscodeKeychain: KeyChainItem = kcSelfDestructPassword
|
||||
var title: LocalizedStringKey = "New Passcode"
|
||||
var reason: String?
|
||||
var submit: () -> Void
|
||||
@ -41,7 +42,10 @@ struct SetAppPasscodeView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setPasswordView(title: title, submitLabel: "Save") {
|
||||
setPasswordView(title: title,
|
||||
submitLabel: "Save",
|
||||
// Do not allow to set app passcode == selfDestruct passcode
|
||||
submitEnabled: { pwd in pwd != prohibitedPasscodeKeychain.get() }) {
|
||||
enteredPassword = passcode
|
||||
passcode = ""
|
||||
confirming = true
|
||||
@ -54,7 +58,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, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, submit: submit) {
|
||||
PasscodeView(passcode: $passcode, title: title, reason: reason, submitLabel: submitLabel, submitEnabled: submitEnabled, buttonsEnabled: Binding.constant(true), submit: submit) {
|
||||
dismiss()
|
||||
cancel()
|
||||
}
|
||||
|
@ -11,12 +11,14 @@ import SimpleXChat
|
||||
|
||||
enum UserProfileAlert: Identifiable {
|
||||
case duplicateUserError
|
||||
case invalidDisplayNameError
|
||||
case createUserError(error: LocalizedStringKey)
|
||||
case invalidNameError(validName: String)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .duplicateUserError: return "duplicateUserError"
|
||||
case .invalidDisplayNameError: return "invalidDisplayNameError"
|
||||
case .createUserError: return "createUserError"
|
||||
case let .invalidNameError(validName): return "invalidNameError \(validName)"
|
||||
}
|
||||
@ -187,6 +189,12 @@ private func createProfile(_ displayName: String, showAlert: (UserProfileAlert)
|
||||
} else {
|
||||
showAlert(.duplicateUserError)
|
||||
}
|
||||
case .chatCmdError(_, .error(.invalidDisplayName)):
|
||||
if m.currentUser == nil {
|
||||
AlertManager.shared.showAlert(invalidDisplayNameAlert)
|
||||
} else {
|
||||
showAlert(.invalidDisplayNameError)
|
||||
}
|
||||
default:
|
||||
let err: LocalizedStringKey = "Error: \(responseError(error))"
|
||||
if m.currentUser == nil {
|
||||
@ -207,6 +215,7 @@ private func canCreateProfile(_ displayName: String) -> Bool {
|
||||
func userProfileAlert(_ alert: UserProfileAlert, _ displayName: Binding<String>) -> Alert {
|
||||
switch alert {
|
||||
case .duplicateUserError: return duplicateUserAlert
|
||||
case .invalidDisplayNameError: return invalidDisplayNameAlert
|
||||
case let .createUserError(err): return creatUserErrorAlert(err)
|
||||
case let .invalidNameError(name): return createInvalidNameAlert(name, displayName)
|
||||
}
|
||||
@ -219,6 +228,13 @@ private var duplicateUserAlert: Alert {
|
||||
)
|
||||
}
|
||||
|
||||
private var invalidDisplayNameAlert: Alert {
|
||||
Alert(
|
||||
title: Text("Invalid display name!"),
|
||||
message: Text("This display name is invalid. Please choose another name.")
|
||||
)
|
||||
}
|
||||
|
||||
private func creatUserErrorAlert(_ err: LocalizedStringKey) -> Alert {
|
||||
Alert(
|
||||
title: Text("Error creating profile!"),
|
||||
|
@ -491,14 +491,23 @@ struct SimplexLockView: View {
|
||||
showLAAlert(.laPasscodeNotChangedAlert)
|
||||
}
|
||||
case .enableSelfDestruct:
|
||||
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, title: "Set passcode", reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")) {
|
||||
SetAppPasscodeView(
|
||||
passcodeKeychain: kcSelfDestructPassword,
|
||||
prohibitedPasscodeKeychain: kcAppPassword,
|
||||
title: "Set passcode",
|
||||
reason: NSLocalizedString("Enable self-destruct passcode", comment: "set passcode view")
|
||||
) {
|
||||
updateSelfDestruct()
|
||||
showLAAlert(.laSelfDestructPasscodeSetAlert)
|
||||
} cancel: {
|
||||
revertSelfDestruct()
|
||||
}
|
||||
case .changeSelfDestructPasscode:
|
||||
SetAppPasscodeView(passcodeKeychain: kcSelfDestructPassword, reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")) {
|
||||
SetAppPasscodeView(
|
||||
passcodeKeychain: kcSelfDestructPassword,
|
||||
prohibitedPasscodeKeychain: kcAppPassword,
|
||||
reason: NSLocalizedString("Change self-destruct passcode", comment: "set passcode view")
|
||||
) {
|
||||
showLAAlert(.laSelfDestructPasscodeChangedAlert)
|
||||
} cancel: {
|
||||
showLAAlert(.laPasscodeNotChangedAlert)
|
||||
|
@ -485,6 +485,7 @@ func doStartChat() -> DBMigrationResult? {
|
||||
logger.debug("NotificationService: doStartChat")
|
||||
haskell_init_nse()
|
||||
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
|
||||
logger.debug("NotificationService: doStartChat \(String(describing: dbStatus))")
|
||||
if dbStatus != .ok {
|
||||
resetChatCtrl()
|
||||
NSEChatState.shared.set(.created)
|
||||
|
@ -29,6 +29,11 @@
|
||||
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
|
||||
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; };
|
||||
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
|
||||
5C245F192B4DB982001CC39F /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F142B4DB982001CC39F /* libgmpxx.a */; };
|
||||
5C245F1A2B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F152B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl-ghc9.6.3.a */; };
|
||||
5C245F1B2B4DB982001CC39F /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F162B4DB982001CC39F /* libgmp.a */; };
|
||||
5C245F1C2B4DB982001CC39F /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F172B4DB982001CC39F /* libffi.a */; };
|
||||
5C245F1D2B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C245F182B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl.a */; };
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
|
||||
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
|
||||
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
|
||||
@ -42,11 +47,6 @@
|
||||
5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; };
|
||||
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; };
|
||||
5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; };
|
||||
5C4E80E42B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80DF2B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a */; };
|
||||
5C4E80E52B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E02B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a */; };
|
||||
5C4E80E62B40A96C0080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E12B40A96C0080FAE2 /* libgmp.a */; };
|
||||
5C4E80E72B40A96C0080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E22B40A96C0080FAE2 /* libgmpxx.a */; };
|
||||
5C4E80E82B40A96C0080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80E32B40A96C0080FAE2 /* libffi.a */; };
|
||||
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
||||
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; };
|
||||
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
|
||||
@ -280,6 +280,11 @@
|
||||
5C13730A28156D2700F43030 /* ContactConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionView.swift; sourceTree = "<group>"; };
|
||||
5C13730C2815740A00F43030 /* DebugJSON.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DebugJSON.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; };
|
||||
5C245F142B4DB982001CC39F /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C245F152B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C245F162B4DB982001CC39F /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C245F172B4DB982001CC39F /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C245F182B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl.a"; sourceTree = "<group>"; };
|
||||
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXAPI.swift; sourceTree = "<group>"; };
|
||||
5C2E260A27A30CFA00F70299 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = "<group>"; };
|
||||
5C2E260E27A30FDC00F70299 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
|
||||
@ -294,11 +299,6 @@
|
||||
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = "<group>"; };
|
||||
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
|
||||
5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = "<group>"; };
|
||||
5C4E80DF2B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a"; sourceTree = "<group>"; };
|
||||
5C4E80E02B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
5C4E80E12B40A96C0080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C4E80E22B40A96C0080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C4E80E32B40A96C0080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
|
||||
5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
|
||||
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
|
||||
@ -521,13 +521,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C4E80E72B40A96C0080FAE2 /* libgmpxx.a in Frameworks */,
|
||||
5C245F192B4DB982001CC39F /* libgmpxx.a in Frameworks */,
|
||||
5C245F1C2B4DB982001CC39F /* libffi.a in Frameworks */,
|
||||
5C245F1D2B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl.a in Frameworks */,
|
||||
5C245F1B2B4DB982001CC39F /* libgmp.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C4E80E62B40A96C0080FAE2 /* libgmp.a in Frameworks */,
|
||||
5C245F1A2B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl-ghc9.6.3.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C4E80E82B40A96C0080FAE2 /* libffi.a in Frameworks */,
|
||||
5C4E80E52B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a in Frameworks */,
|
||||
5C4E80E42B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -589,11 +589,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C4E80E32B40A96C0080FAE2 /* libffi.a */,
|
||||
5C4E80E12B40A96C0080FAE2 /* libgmp.a */,
|
||||
5C4E80E22B40A96C0080FAE2 /* libgmpxx.a */,
|
||||
5C4E80E02B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ-ghc9.6.3.a */,
|
||||
5C4E80DF2B40A96C0080FAE2 /* libHSsimplex-chat-5.5.0.0-FwZXD1cMpkc1VLQMq43OyQ.a */,
|
||||
5C245F172B4DB982001CC39F /* libffi.a */,
|
||||
5C245F162B4DB982001CC39F /* libgmp.a */,
|
||||
5C245F142B4DB982001CC39F /* libgmpxx.a */,
|
||||
5C245F152B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl-ghc9.6.3.a */,
|
||||
5C245F182B4DB982001CC39F /* libHSsimplex-chat-5.5.0.0-K5xQiJJwtSUKGqIyB7d1Tl.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1610,6 +1610,7 @@ public enum ChatErrorType: Decodable {
|
||||
case userUnknown
|
||||
case activeUserExists
|
||||
case userExists
|
||||
case invalidDisplayName
|
||||
case differentActiveUser(commandUserId: Int64, activeUserId: Int64)
|
||||
case cantDeleteActiveUser(userId: Int64)
|
||||
case cantDeleteLastUser(userId: Int64)
|
||||
|
@ -69,13 +69,29 @@ func fileModificationDate(_ path: String) -> Date? {
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteAppDatabaseAndFiles() {
|
||||
let fm = FileManager.default
|
||||
let dbPath = getAppDatabasePath().path
|
||||
do {
|
||||
try fm.removeItem(atPath: dbPath + CHAT_DB)
|
||||
try fm.removeItem(atPath: dbPath + AGENT_DB)
|
||||
} catch let error {
|
||||
logger.error("Failed to delete all databases: \(error)")
|
||||
}
|
||||
try? fm.removeItem(atPath: dbPath + CHAT_DB_BAK)
|
||||
try? fm.removeItem(atPath: dbPath + AGENT_DB_BAK)
|
||||
try? fm.removeItem(at: getTempFilesDirectory())
|
||||
try? fm.createDirectory(at: getTempFilesDirectory(), withIntermediateDirectories: true)
|
||||
deleteAppFiles()
|
||||
_ = kcDatabasePassword.remove()
|
||||
storeDBPassphraseGroupDefault.set(true)
|
||||
}
|
||||
|
||||
public func deleteAppFiles() {
|
||||
let fm = FileManager.default
|
||||
do {
|
||||
let fileNames = try fm.contentsOfDirectory(atPath: getAppFilesDirectory().path)
|
||||
for fileName in fileNames {
|
||||
removeFile(fileName)
|
||||
}
|
||||
try fm.removeItem(at: getAppFilesDirectory())
|
||||
try fm.createDirectory(at: getAppFilesDirectory(), withIntermediateDirectories: true)
|
||||
} catch {
|
||||
logger.error("FileUtils deleteAppFiles error: \(error.localizedDescription)")
|
||||
}
|
||||
|
@ -25,13 +25,15 @@ void haskell_init(void) {
|
||||
}
|
||||
|
||||
void haskell_init_nse(void) {
|
||||
int argc = 5;
|
||||
int argc = 7;
|
||||
char *argv[] = {
|
||||
"simplex",
|
||||
"+RTS", // requires `hs_init_with_rtsopts`
|
||||
"-A1m", // chunk size for new allocations
|
||||
"-H1m", // initial heap size
|
||||
"-xn", // non-moving GC
|
||||
"-F0.5", // heap growth triggering GC
|
||||
"-Fd1", // memory return
|
||||
"-c", // compacting garbage collector
|
||||
0
|
||||
};
|
||||
char **pargv = argv;
|
||||
|
@ -174,7 +174,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
androidAppContext = this
|
||||
APPLICATION_ID = BuildConfig.APPLICATION_ID
|
||||
ntfManager = object : chat.simplex.common.platform.NtfManager() {
|
||||
override fun notifyCallInvitation(invitation: RcvCallInvitation) = NtfManager.notifyCallInvitation(invitation)
|
||||
override fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean = NtfManager.notifyCallInvitation(invitation)
|
||||
override fun hasNotificationsForChat(chatId: String): Boolean = NtfManager.hasNotificationsForChat(chatId)
|
||||
override fun cancelNotificationsForChat(chatId: String) = NtfManager.cancelNotificationsForChat(chatId)
|
||||
override fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List<Pair<NotificationAction, () -> Unit>>) = NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions.map { it.first })
|
||||
|
@ -30,7 +30,7 @@ object NtfManager {
|
||||
const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS"
|
||||
|
||||
// DO NOT change notification channel settings / names
|
||||
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_1"
|
||||
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_2"
|
||||
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
|
||||
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
|
||||
const val CallNotificationId: Int = -1
|
||||
@ -59,7 +59,7 @@ object NtfManager {
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
||||
.build()
|
||||
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
|
||||
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/raw/ring_once")
|
||||
Log.d(TAG, "callNotificationChannel sound: $soundUri")
|
||||
callChannel.setSound(soundUri, attrs)
|
||||
callChannel.enableVibration(true)
|
||||
@ -140,7 +140,7 @@ object NtfManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyCallInvitation(invitation: RcvCallInvitation) {
|
||||
fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean {
|
||||
val keyguardManager = getKeyguardManager(context)
|
||||
Log.d(
|
||||
TAG,
|
||||
@ -149,7 +149,7 @@ object NtfManager {
|
||||
"callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " +
|
||||
"onForeground ${isAppOnForeground}"
|
||||
)
|
||||
if (isAppOnForeground) return
|
||||
if (isAppOnForeground) return false
|
||||
val contactId = invitation.contact.id
|
||||
Log.d(TAG, "notifyCallInvitation $contactId")
|
||||
val image = invitation.contact.image
|
||||
@ -163,7 +163,7 @@ object NtfManager {
|
||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
} else {
|
||||
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
|
||||
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/raw/ring_once")
|
||||
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
NotificationCompat.Builder(context, CallChannel)
|
||||
.setContentIntent(chatPendingIntent(OpenChatAction, invitation.user.userId, invitation.contact.id))
|
||||
@ -206,6 +206,7 @@ object NtfManager {
|
||||
notify(CallNotificationId, notification)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun showMessage(title: String, text: String) {
|
||||
@ -280,6 +281,7 @@ object NtfManager {
|
||||
manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(MR.strings.ntf_channel_calls)))
|
||||
// Remove old channels since they can't be edited
|
||||
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION")
|
||||
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION_1")
|
||||
manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION")
|
||||
}
|
||||
|
||||
|
@ -506,6 +506,10 @@ object ChatController {
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat && r.chatError.errorType is ChatErrorType.UserExists
|
||||
) {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_duplicate_title), generalGetString(MR.strings.failed_to_create_user_duplicate_desc))
|
||||
} else if (
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat && r.chatError.errorType is ChatErrorType.InvalidDisplayName
|
||||
) {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_invalid_title), generalGetString(MR.strings.failed_to_create_user_invalid_desc))
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_title), r.details)
|
||||
}
|
||||
@ -1124,6 +1128,13 @@ object ChatController {
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun apiGetCallInvitations(rh: Long?): List<RcvCallInvitation> {
|
||||
val r = sendCmd(rh, CC.ApiGetCallInvitations())
|
||||
if (r is CR.CallInvitations) return r.callInvitations
|
||||
Log.e(TAG, "apiGetCallInvitations bad response: ${r.responseType} ${r.details}")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
suspend fun apiSendCallInvitation(rh: Long?, contact: Contact, callType: CallType): Boolean {
|
||||
val r = sendCmd(rh, CC.ApiSendCallInvitation(contact, callType))
|
||||
return r is CR.CmdOk
|
||||
@ -1880,10 +1891,35 @@ object ChatController {
|
||||
val disconnectedHost = chatModel.remoteHosts.firstOrNull { it.remoteHostId == r.remoteHostId_ }
|
||||
chatModel.remoteHostPairing.value = null
|
||||
if (disconnectedHost != null) {
|
||||
showToast(
|
||||
generalGetString(MR.strings.remote_host_was_disconnected_toast).format(disconnectedHost.hostDeviceName.ifEmpty { disconnectedHost.remoteHostId.toString() })
|
||||
val deviceName = disconnectedHost.hostDeviceName.ifEmpty { disconnectedHost.remoteHostId.toString() }
|
||||
when (r.rhStopReason) {
|
||||
is RemoteHostStopReason.ConnectionFailed -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.remote_host_was_disconnected_title),
|
||||
if (r.rhStopReason.chatError is ChatError.ChatErrorRemoteHost) {
|
||||
r.rhStopReason.chatError.remoteHostError.localizedString(deviceName)
|
||||
} else {
|
||||
generalGetString(MR.strings.remote_host_disconnected_from).format(deviceName, r.rhStopReason.chatError.string)
|
||||
}
|
||||
)
|
||||
}
|
||||
is RemoteHostStopReason.Crashed -> {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.remote_host_was_disconnected_title),
|
||||
if (r.rhStopReason.chatError is ChatError.ChatErrorRemoteHost) {
|
||||
r.rhStopReason.chatError.remoteHostError.localizedString(deviceName)
|
||||
} else {
|
||||
generalGetString(MR.strings.remote_host_disconnected_from).format(deviceName, r.rhStopReason.chatError.string)
|
||||
}
|
||||
)
|
||||
}
|
||||
is RemoteHostStopReason.Disconnected -> {
|
||||
if (r.rhsState is RemoteHostSessionState.Connected || r.rhsState is RemoteHostSessionState.Confirmed) {
|
||||
showToast(generalGetString(MR.strings.remote_host_was_disconnected_toast).format(deviceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chatModel.remoteHostId() == r.remoteHostId_) {
|
||||
chatModel.currentRemoteHost.value = null
|
||||
switchUIRemoteHost(null)
|
||||
@ -1913,6 +1949,27 @@ object ChatController {
|
||||
val sess = chatModel.remoteCtrlSession.value
|
||||
if (sess != null) {
|
||||
chatModel.remoteCtrlSession.value = null
|
||||
fun showAlert(chatError: ChatError) {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.remote_ctrl_was_disconnected_title),
|
||||
if (chatError is ChatError.ChatErrorRemoteCtrl) {
|
||||
chatError.remoteCtrlError.localizedString
|
||||
} else {
|
||||
generalGetString(MR.strings.remote_ctrl_disconnected_with_reason).format(chatError.string)
|
||||
}
|
||||
)
|
||||
}
|
||||
when (r.rcStopReason) {
|
||||
is RemoteCtrlStopReason.DiscoveryFailed -> showAlert(r.rcStopReason.chatError)
|
||||
is RemoteCtrlStopReason.ConnectionFailed -> showAlert(r.rcStopReason.chatError)
|
||||
is RemoteCtrlStopReason.SetupFailed -> showAlert(r.rcStopReason.chatError)
|
||||
is RemoteCtrlStopReason.Disconnected -> {
|
||||
/*AlertManager.shared.showAlertMsg(
|
||||
generalGetString(MR.strings.remote_ctrl_was_disconnected_title),
|
||||
)*/
|
||||
}
|
||||
}
|
||||
|
||||
if (sess.sessionState is UIRemoteCtrlSessionState.Connected) {
|
||||
switchToLocalSession()
|
||||
}
|
||||
@ -2246,6 +2303,7 @@ sealed class CC {
|
||||
class ApiShowMyAddress(val userId: Long): CC()
|
||||
class ApiSetProfileAddress(val userId: Long, val on: Boolean): CC()
|
||||
class ApiAddressAutoAccept(val userId: Long, val autoAccept: AutoAccept?): CC()
|
||||
class ApiGetCallInvitations: CC()
|
||||
class ApiSendCallInvitation(val contact: Contact, val callType: CallType): CC()
|
||||
class ApiRejectCall(val contact: Contact): CC()
|
||||
class ApiSendCallOffer(val contact: Contact, val callOffer: WebRTCCallOffer): CC()
|
||||
@ -2382,6 +2440,7 @@ sealed class CC {
|
||||
is ApiAddressAutoAccept -> "/_auto_accept $userId ${AutoAccept.cmdString(autoAccept)}"
|
||||
is ApiAcceptContact -> "/_accept incognito=${onOff(incognito)} $contactReqId"
|
||||
is ApiRejectContact -> "/_reject $contactReqId"
|
||||
is ApiGetCallInvitations -> "/_call get"
|
||||
is ApiSendCallInvitation -> "/_call invite @${contact.apiId} ${json.encodeToString(callType)}"
|
||||
is ApiRejectCall -> "/_call reject @${contact.apiId}"
|
||||
is ApiSendCallOffer -> "/_call offer @${contact.apiId} ${json.encodeToString(callOffer)}"
|
||||
@ -2505,6 +2564,7 @@ sealed class CC {
|
||||
is ApiAddressAutoAccept -> "apiAddressAutoAccept"
|
||||
is ApiAcceptContact -> "apiAcceptContact"
|
||||
is ApiRejectContact -> "apiRejectContact"
|
||||
is ApiGetCallInvitations -> "apiGetCallInvitations"
|
||||
is ApiSendCallInvitation -> "apiSendCallInvitation"
|
||||
is ApiRejectCall -> "apiRejectCall"
|
||||
is ApiSendCallOffer -> "apiSendCallOffer"
|
||||
@ -3880,6 +3940,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("sndFileError") class SndFileError(val user: UserRef, val chatItem: AChatItem): CR()
|
||||
// call events
|
||||
@Serializable @SerialName("callInvitation") class CallInvitation(val callInvitation: RcvCallInvitation): CR()
|
||||
@Serializable @SerialName("callInvitations") class CallInvitations(val callInvitations: List<RcvCallInvitation>): CR()
|
||||
@Serializable @SerialName("callOffer") class CallOffer(val user: UserRef, val contact: Contact, val callType: CallType, val offer: WebRTCSession, val sharedKey: String? = null, val askConfirmation: Boolean): CR()
|
||||
@Serializable @SerialName("callAnswer") class CallAnswer(val user: UserRef, val contact: Contact, val answer: WebRTCSession): CR()
|
||||
@Serializable @SerialName("callExtraInfo") class CallExtraInfo(val user: UserRef, val contact: Contact, val extraInfo: WebRTCExtraInfo): CR()
|
||||
@ -4027,6 +4088,7 @@ sealed class CR {
|
||||
is SndFileProgressXFTP -> "sndFileProgressXFTP"
|
||||
is SndFileCompleteXFTP -> "sndFileCompleteXFTP"
|
||||
is SndFileError -> "sndFileError"
|
||||
is CallInvitations -> "callInvitations"
|
||||
is CallInvitation -> "callInvitation"
|
||||
is CallOffer -> "callOffer"
|
||||
is CallAnswer -> "callAnswer"
|
||||
@ -4173,6 +4235,7 @@ sealed class CR {
|
||||
is SndFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\nsentSize: $sentSize\ntotalSize: $totalSize")
|
||||
is SndFileCompleteXFTP -> withUser(user, json.encodeToString(chatItem))
|
||||
is SndFileError -> withUser(user, json.encodeToString(chatItem))
|
||||
is CallInvitations -> "callInvitations: ${json.encodeToString(callInvitations)}"
|
||||
is CallInvitation -> "contact: ${callInvitation.contact.id}\ncallType: $callInvitation.callType\nsharedKey: ${callInvitation.sharedKey ?: ""}"
|
||||
is CallOffer -> withUser(user, "contact: ${contact.id}\ncallType: $callType\nsharedKey: ${sharedKey ?: ""}\naskConfirmation: $askConfirmation\noffer: ${json.encodeToString(offer)}")
|
||||
is CallAnswer -> withUser(user, "contact: ${contact.id}\nanswer: ${json.encodeToString(answer)}")
|
||||
@ -4447,6 +4510,7 @@ sealed class ChatErrorType {
|
||||
is EmptyUserPassword -> "emptyUserPassword"
|
||||
is UserAlreadyHidden -> "userAlreadyHidden"
|
||||
is UserNotHidden -> "userNotHidden"
|
||||
is InvalidDisplayName -> "invalidDisplayName"
|
||||
is ChatNotStarted -> "chatNotStarted"
|
||||
is ChatNotStopped -> "chatNotStopped"
|
||||
is ChatStoreChanged -> "chatStoreChanged"
|
||||
@ -4524,6 +4588,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("emptyUserPassword") class EmptyUserPassword(val userId: Long): ChatErrorType()
|
||||
@Serializable @SerialName("userAlreadyHidden") class UserAlreadyHidden(val userId: Long): ChatErrorType()
|
||||
@Serializable @SerialName("userNotHidden") class UserNotHidden(val userId: Long): ChatErrorType()
|
||||
@Serializable @SerialName("invalidDisplayName") object InvalidDisplayName: ChatErrorType()
|
||||
@Serializable @SerialName("chatNotStarted") object ChatNotStarted: ChatErrorType()
|
||||
@Serializable @SerialName("chatNotStopped") object ChatNotStopped: ChatErrorType()
|
||||
@Serializable @SerialName("chatStoreChanged") object ChatStoreChanged: ChatErrorType()
|
||||
@ -4973,6 +5038,15 @@ sealed class RemoteHostError {
|
||||
is BadVersion -> "badVersion"
|
||||
is Disconnected -> "disconnected"
|
||||
}
|
||||
fun localizedString(name: String): String = when (this) {
|
||||
is Missing -> generalGetString(MR.strings.remote_host_error_missing)
|
||||
is Inactive -> generalGetString(MR.strings.remote_host_error_inactive)
|
||||
is Busy -> generalGetString(MR.strings.remote_host_error_busy)
|
||||
is Timeout -> generalGetString(MR.strings.remote_host_error_timeout)
|
||||
is BadState -> generalGetString(MR.strings.remote_host_error_bad_state)
|
||||
is BadVersion -> generalGetString(MR.strings.remote_host_error_bad_version)
|
||||
is Disconnected -> generalGetString(MR.strings.remote_host_error_disconnected)
|
||||
}.format(name)
|
||||
@Serializable @SerialName("missing") object Missing: RemoteHostError()
|
||||
@Serializable @SerialName("inactive") object Inactive: RemoteHostError()
|
||||
@Serializable @SerialName("busy") object Busy: RemoteHostError()
|
||||
@ -4993,6 +5067,16 @@ sealed class RemoteCtrlError {
|
||||
is BadInvitation -> "badInvitation"
|
||||
is BadVersion -> "badVersion"
|
||||
}
|
||||
val localizedString: String get() = when (this) {
|
||||
is Inactive -> generalGetString(MR.strings.remote_ctrl_error_inactive)
|
||||
is BadState -> generalGetString(MR.strings.remote_ctrl_error_bad_state)
|
||||
is Busy -> generalGetString(MR.strings.remote_ctrl_error_busy)
|
||||
is Timeout -> generalGetString(MR.strings.remote_ctrl_error_timeout)
|
||||
is Disconnected -> generalGetString(MR.strings.remote_ctrl_error_disconnected)
|
||||
is BadInvitation -> generalGetString(MR.strings.remote_ctrl_error_bad_invitation)
|
||||
is BadVersion -> generalGetString(MR.strings.remote_ctrl_error_bad_version)
|
||||
}
|
||||
|
||||
@Serializable @SerialName("inactive") object Inactive: RemoteCtrlError()
|
||||
@Serializable @SerialName("badState") object BadState: RemoteCtrlError()
|
||||
@Serializable @SerialName("busy") object Busy: RemoteCtrlError()
|
||||
|
@ -93,7 +93,7 @@ abstract class NtfManager {
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun notifyCallInvitation(invitation: RcvCallInvitation)
|
||||
abstract fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean
|
||||
abstract fun hasNotificationsForChat(chatId: String): Boolean
|
||||
abstract fun cancelNotificationsForChat(chatId: String)
|
||||
abstract fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<Pair<NotificationAction, () -> Unit>> = emptyList())
|
||||
|
@ -13,8 +13,8 @@ class CallManager(val chatModel: ChatModel) {
|
||||
callInvitations[invitation.contact.id] = invitation
|
||||
if (invitation.user.showNotifications) {
|
||||
if (Clock.System.now() - invitation.callTs <= 3.minutes) {
|
||||
invitation.sentNotification = ntfManager.notifyCallInvitation(invitation)
|
||||
activeCallInvitation.value = invitation
|
||||
ntfManager.notifyCallInvitation(invitation)
|
||||
} else {
|
||||
val contact = invitation.contact
|
||||
ntfManager.displayNotification(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText)
|
||||
|
@ -15,11 +15,10 @@ import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.ProfileImage
|
||||
import chat.simplex.common.views.usersettings.ProfilePreview
|
||||
import chat.simplex.common.platform.ntfManager
|
||||
import chat.simplex.common.platform.SoundPlayer
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@ -27,7 +26,11 @@ import kotlinx.datetime.Clock
|
||||
fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
|
||||
val cm = chatModel.callManager
|
||||
val scope = rememberCoroutineScope()
|
||||
LaunchedEffect(true) { SoundPlayer.start(scope, sound = !chatModel.showCallView.value) }
|
||||
LaunchedEffect(Unit) {
|
||||
if (chatModel.activeCallInvitation.value?.sentNotification == false || appPlatform.isDesktop) {
|
||||
SoundPlayer.start(scope, sound = !chatModel.showCallView.value)
|
||||
}
|
||||
}
|
||||
DisposableEffect(true) { onDispose { SoundPlayer.stop() } }
|
||||
IncomingCallAlertLayout(
|
||||
invitation,
|
||||
|
@ -112,6 +112,9 @@ sealed class WCallResponse {
|
||||
CallMediaType.Video -> MR.strings.incoming_video_call
|
||||
CallMediaType.Audio -> MR.strings.incoming_audio_call
|
||||
})
|
||||
|
||||
// Shows whether notification was shown or not to prevent playing sound twice in both notification and in-app
|
||||
var sentNotification: Boolean = false
|
||||
}
|
||||
@Serializable data class CallCapabilities(val encryption: Boolean)
|
||||
@Serializable data class ConnectionInfo(private val localCandidate: RTCIceCandidate?, private val remoteCandidate: RTCIceCandidate?) {
|
||||
|
@ -24,6 +24,7 @@ import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.call.*
|
||||
import chat.simplex.common.views.chat.group.*
|
||||
@ -317,12 +318,15 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
},
|
||||
acceptCall = { contact ->
|
||||
hideKeyboard(view)
|
||||
withApi {
|
||||
val invitation = chatModel.callInvitations.remove(contact.id)
|
||||
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
|
||||
if (invitation == null) {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
|
||||
} else {
|
||||
chatModel.callManager.acceptIncomingCall(invitation = invitation)
|
||||
}
|
||||
}
|
||||
},
|
||||
acceptFeature = { contact, feature, param ->
|
||||
withApi {
|
||||
|
@ -90,6 +90,8 @@
|
||||
<string name="failed_to_create_user_title">Error creating profile!</string>
|
||||
<string name="failed_to_create_user_duplicate_title">Duplicate display name!</string>
|
||||
<string name="failed_to_create_user_duplicate_desc">You already have a chat profile with the same display name. Please choose another name.</string>
|
||||
<string name="failed_to_create_user_invalid_title">Invalid display name!</string>
|
||||
<string name="failed_to_create_user_invalid_desc">This display name is invalid. Please choose another name.</string>
|
||||
<string name="failed_to_active_user_title">Error switching profile!</string>
|
||||
|
||||
<!-- API Error Responses - SimpleXAPI.kt -->
|
||||
@ -1694,6 +1696,10 @@
|
||||
<string name="disconnect_remote_host">Disconnect</string>
|
||||
<string name="disconnect_remote_hosts">Disconnect mobiles</string>
|
||||
<string name="remote_host_was_disconnected_toast"><![CDATA[Mobile <b>%s</b> was disconnected]]></string>
|
||||
<string name="remote_host_was_disconnected_title">Connection stopped</string>
|
||||
<string name="remote_ctrl_was_disconnected_title">Connection stopped</string>
|
||||
<string name="remote_host_disconnected_from"><![CDATA[Disconnected from mobile <b>%s</b> with the reason: %s]]></string>
|
||||
<string name="remote_ctrl_disconnected_with_reason">Disconnected with the reason: %s</string>
|
||||
<string name="disconnect_desktop_question">Disconnect desktop?</string>
|
||||
<string name="only_one_device_can_work_at_the_same_time">Only one device can work at the same time</string>
|
||||
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[Open <i>Use from desktop</i> in mobile app and scan QR code.]]></string>
|
||||
@ -1728,6 +1734,20 @@
|
||||
<string name="random_port">Random</string>
|
||||
<string name="open_port_in_firewall_title">Open port in firewall</string>
|
||||
<string name="open_port_in_firewall_desc">To allow a mobile app to connect to the desktop, open this port in your firewall, if you have it enabled</string>
|
||||
<string name="remote_host_error_missing"><![CDATA[Mobile <b>%s</b> is missing]]></string>
|
||||
<string name="remote_host_error_inactive"><![CDATA[Mobile <b>%s</b> is inactive]]></string>
|
||||
<string name="remote_host_error_busy"><![CDATA[Mobile <b>%s</b> is busy]]></string>
|
||||
<string name="remote_host_error_timeout"><![CDATA[Timeout reached while connecting to the mobile <b>%s</b>]]></string>
|
||||
<string name="remote_host_error_bad_state"><![CDATA[Connection to the mobile <b>%s</b> is in a bad state]]></string>
|
||||
<string name="remote_host_error_bad_version"><![CDATA[Mobile <b>%s</b> has an unsupported version. Please, make sure you use the same version on both devices]]></string>
|
||||
<string name="remote_host_error_disconnected"><![CDATA[Mobile <b>%s</b> was disconnected]]></string>
|
||||
<string name="remote_ctrl_error_inactive">Desktop is inactive</string>
|
||||
<string name="remote_ctrl_error_bad_state">Connection to the desktop is in a bad state</string>
|
||||
<string name="remote_ctrl_error_busy">Desktop is busy</string>
|
||||
<string name="remote_ctrl_error_timeout">Timeout reached while connecting to the desktop</string>
|
||||
<string name="remote_ctrl_error_disconnected">Desktop was disconnected</string>
|
||||
<string name="remote_ctrl_error_bad_invitation">Desktop has wrong invitation code</string>
|
||||
<string name="remote_ctrl_error_bad_version">Desktop has an unsupported version. Please, make sure you use the same version on both devices</string>
|
||||
|
||||
<!-- Under development -->
|
||||
<string name="in_developing_title">Coming soon!</string>
|
||||
|
@ -16,8 +16,8 @@ import javax.imageio.ImageIO
|
||||
object NtfManager {
|
||||
private val prevNtfs = arrayListOf<Pair<ChatId, Slice>>()
|
||||
|
||||
fun notifyCallInvitation(invitation: RcvCallInvitation) {
|
||||
if (simplexWindowState.windowFocused.value) return
|
||||
fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean {
|
||||
if (simplexWindowState.windowFocused.value) return false
|
||||
val contactId = invitation.contact.id
|
||||
Log.d(TAG, "notifyCallInvitation $contactId")
|
||||
val image = invitation.contact.image
|
||||
@ -45,6 +45,7 @@ object NtfManager {
|
||||
displayNotificationViaLib(contactId, title, text, prepareIconPath(largeIcon), actions) {
|
||||
ntfManager.openChatAction(invitation.user.userId, contactId)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun showMessage(title: String, text: String) {
|
||||
|
@ -15,7 +15,7 @@ val defaultLocale: Locale = Locale.getDefault()
|
||||
|
||||
fun initApp() {
|
||||
ntfManager = object : NtfManager() {
|
||||
override fun notifyCallInvitation(invitation: RcvCallInvitation) = chat.simplex.common.model.NtfManager.notifyCallInvitation(invitation)
|
||||
override fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean = chat.simplex.common.model.NtfManager.notifyCallInvitation(invitation)
|
||||
override fun hasNotificationsForChat(chatId: String): Boolean = chat.simplex.common.model.NtfManager.hasNotificationsForChat(chatId)
|
||||
override fun cancelNotificationsForChat(chatId: String) = chat.simplex.common.model.NtfManager.cancelNotificationsForChat(chatId)
|
||||
override fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List<Pair<NotificationAction, () -> Unit>>) = chat.simplex.common.model.NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions)
|
||||
|
@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: ca527b4d6cb83d24abdc9cbefcf56c870f694a63
|
||||
tag: ad8cd1d5154617663065652b45c784ad5a0a584d
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.5.0.0
|
||||
version: 5.5.0.1
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
@ -37,12 +37,12 @@ for ((i = 0 ; i < ${#arches[@]}; i++)); do
|
||||
|
||||
mkdir -p "$output_dir" 2> /dev/null
|
||||
|
||||
curl --location -o libsupport.zip $job_repo/$arch-android:lib:support.x86_64-linux/latest/download/1 && \
|
||||
curl --location -o libsupport.zip $job_repo/x86_64-linux."$arch"-android:lib:support/latest/download/1 && \
|
||||
unzip -o libsupport.zip && \
|
||||
mv libsupport.so "$output_dir" && \
|
||||
rm libsupport.zip
|
||||
|
||||
curl --location -o libsimplex.zip "$job_repo"/"$arch"-android:lib:simplex-chat.x86_64-linux/latest/download/1 && \
|
||||
curl --location -o libsimplex.zip "$job_repo"/x86_64-linux."$arch"-android:lib:simplex-chat/latest/download/1 && \
|
||||
unzip -o libsimplex.zip && \
|
||||
mv libsimplex.so "$output_dir" && \
|
||||
rm libsimplex.zip
|
||||
|
@ -35,7 +35,7 @@ for ((i = 0 ; i < ${#arches[@]}; i++)); do
|
||||
output_arch="${output_arches[$i]}"
|
||||
output_dir="$HOME/Downloads"
|
||||
|
||||
curl --location -o "$output_dir"/pkg-ios-"$arch"-swift-json.zip "$job_repo"/"$arch"-darwin-ios:lib:simplex-chat."$arch"-darwin/latest/download/1 && \
|
||||
curl --location -o "$output_dir"/pkg-ios-"$arch"-swift-json.zip "$job_repo"/"$arch"-darwin."$arch"-darwin-ios:lib:simplex-chat/latest/download/1 && \
|
||||
unzip -o "$output_dir"/pkg-ios-"$output_arch"-swift-json.zip -d ~/Downloads/pkg-ios-"$output_arch"-swift-json
|
||||
done
|
||||
sh "$root_dir"/scripts/ios/prepare-x86_64.sh
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."ca527b4d6cb83d24abdc9cbefcf56c870f694a63" = "06547v4n30xbk49c87frnvfbj6pihvxh4nx8rq9idpd8x2kxpyb1";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."ad8cd1d5154617663065652b45c784ad5a0a584d" = "19sinz1gynab776x8h9va7r6ifm9pmgzljsbc7z5cbkcnjl5sfh3";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.5.0.0
|
||||
version: 5.5.0.1
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
@ -460,10 +460,9 @@ createGroupInvitedViaLink
|
||||
"INSERT INTO groups (group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs, created_at, updated_at, chat_ts) VALUES (?,?,?,?,?,?,?,?)"
|
||||
(profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs)
|
||||
insertedRowId db
|
||||
insertHost_ currentTs groupId = ExceptT $ do
|
||||
insertHost_ currentTs groupId = do
|
||||
let fromMemberProfile = profileFromName fromMemberName
|
||||
withLocalDisplayName db userId fromMemberName $ \localDisplayName -> runExceptT $ do
|
||||
(_, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs
|
||||
(localDisplayName, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs
|
||||
let MemberIdRole {memberId, memberRole} = fromMember
|
||||
liftIO $ do
|
||||
DB.execute
|
||||
|
Loading…
Reference in New Issue
Block a user