Merge branch 'master' into ep/debug-reconnect
This commit is contained in:
@@ -1212,7 +1212,7 @@ private func currentUserId(_ funcName: String) throws -> Int64 {
|
||||
throw RuntimeError("\(funcName): no current user")
|
||||
}
|
||||
|
||||
func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool = true, confirmMigrations: MigrationConfirmation? = nil) throws {
|
||||
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.chatDbEncrypted, m.chatDbStatus) = chatMigrateInit(dbKey, confirmMigrations: confirmMigrations)
|
||||
@@ -1231,7 +1231,37 @@ func initializeChat(start: Bool, dbKey: String? = nil, refreshInvitations: Bool
|
||||
onboardingStageDefault.set(.step1_SimpleXInfo)
|
||||
privacyDeliveryReceiptsSet.set(true)
|
||||
m.onboardingStage = .step1_SimpleXInfo
|
||||
} else if start {
|
||||
} else if confirmStart {
|
||||
showStartChatAfterRestartAlert { start in
|
||||
do {
|
||||
if start { AppChatState.shared.set(.active) }
|
||||
try chatInitialized(start: start, refreshInvitations: refreshInvitations)
|
||||
} catch let error {
|
||||
logger.error("ChatInitialized error: \(error)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try chatInitialized(start: start, refreshInvitations: refreshInvitations)
|
||||
}
|
||||
}
|
||||
|
||||
func showStartChatAfterRestartAlert(result: @escaping (_ start: Bool) -> Void) {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Start chat?"),
|
||||
message: Text("Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
result(true)
|
||||
},
|
||||
secondaryButton: .cancel {
|
||||
result(false)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
private func chatInitialized(start: Bool, refreshInvitations: Bool) throws {
|
||||
let m = ChatModel.shared
|
||||
if m.currentUser == nil { return }
|
||||
if start {
|
||||
try startChat(refreshInvitations: refreshInvitations)
|
||||
} else {
|
||||
m.chatRunning = false
|
||||
|
||||
@@ -105,26 +105,16 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
|
||||
let m = ChatModel.shared
|
||||
if (!m.chatInitialized) {
|
||||
m.v3DBMigration = v3DBMigrationDefault.get()
|
||||
if AppChatState.shared.value == .stopped {
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text("Start chat?"),
|
||||
message: Text("Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
AppChatState.shared.set(.active)
|
||||
initialize(start: true)
|
||||
},
|
||||
secondaryButton: .cancel {
|
||||
initialize(start: false)
|
||||
}
|
||||
))
|
||||
if AppChatState.shared.value == .stopped && storeDBPassphraseGroupDefault.get() && kcDatabasePassword.get() != nil {
|
||||
initialize(start: true, confirmStart: true)
|
||||
} else {
|
||||
initialize(start: true)
|
||||
}
|
||||
}
|
||||
|
||||
func initialize(start: Bool) {
|
||||
func initialize(start: Bool, confirmStart: Bool = false) {
|
||||
do {
|
||||
try initializeChat(start: m.v3DBMigration.startChat && start, refreshInvitations: refreshInvitations)
|
||||
try initializeChat(start: m.v3DBMigration.startChat && start, confirmStart: m.v3DBMigration.startChat && confirmStart, refreshInvitations: refreshInvitations)
|
||||
} catch let error {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: start ? "Error starting chat" : "Error opening chat",
|
||||
|
||||
@@ -149,7 +149,7 @@ struct DatabaseErrorView: View {
|
||||
private func runChatSync(confirmMigrations: MigrationConfirmation? = nil) {
|
||||
do {
|
||||
resetChatCtrl()
|
||||
try initializeChat(start: m.v3DBMigration.startChat, dbKey: useKeychain ? nil : dbKey, confirmMigrations: confirmMigrations)
|
||||
try initializeChat(start: m.v3DBMigration.startChat, confirmStart: m.v3DBMigration.startChat && AppChatState.shared.value == .stopped, dbKey: useKeychain ? nil : dbKey, confirmMigrations: confirmMigrations)
|
||||
if let s = m.chatDbStatus {
|
||||
status = s
|
||||
let am = AlertManager.shared
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.platform.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.*
|
||||
@@ -21,12 +20,13 @@ import chat.simplex.common.AppLock
|
||||
import chat.simplex.common.helpers.requiresIgnoringBattery
|
||||
import chat.simplex.common.model.ChatController
|
||||
import chat.simplex.common.model.NotificationsMode
|
||||
import chat.simplex.common.platform.androidAppContext
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import kotlinx.coroutines.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
// based on:
|
||||
// https://robertohuertas.com/2019/06/29/android_foreground_services/
|
||||
@@ -173,6 +173,11 @@ class SimplexService: Service() {
|
||||
// Just to make sure that after restart of the app the user will need to re-authenticate
|
||||
AppLock.clearAuthState()
|
||||
|
||||
if (appPreferences.chatStopped.get()) {
|
||||
stopSelf()
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
// If notification service isn't enabled or battery optimization isn't disabled, we shouldn't restart the service
|
||||
if (!SimplexApp.context.allowToStartServiceAfterAppExit()) {
|
||||
return
|
||||
|
||||
@@ -97,12 +97,14 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
|
||||
mainActivity.get()?.recreate()
|
||||
} else {
|
||||
mainActivity.get()?.apply {
|
||||
window
|
||||
?.decorView
|
||||
?.findViewById<ViewGroup>(android.R.id.content)
|
||||
?.removeViewAt(0)
|
||||
setContent {
|
||||
AppScreen()
|
||||
runOnUiThread {
|
||||
window
|
||||
?.decorView
|
||||
?.findViewById<ViewGroup>(android.R.id.content)
|
||||
?.removeViewAt(0)
|
||||
setContent {
|
||||
AppScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ class AppPreferences {
|
||||
val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null)
|
||||
val chatArchiveTime = mkDatePreference(SHARED_PREFS_CHAT_ARCHIVE_TIME, null)
|
||||
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
|
||||
val chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, false)
|
||||
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
||||
val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
|
||||
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
||||
@@ -273,6 +274,7 @@ class AppPreferences {
|
||||
private const val SHARED_PREFS_APP_LANGUAGE = "AppLanguage"
|
||||
private const val SHARED_PREFS_ONBOARDING_STAGE = "OnboardingStage"
|
||||
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
|
||||
private const val SHARED_PREFS_CHAT_STOPPED = "ChatStopped"
|
||||
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
||||
private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
|
||||
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
||||
@@ -346,14 +348,8 @@ object ChatController {
|
||||
try {
|
||||
if (chatModel.chatRunning.value == true) return
|
||||
apiSetNetworkConfig(getNetCfg())
|
||||
apiSetTempFolder(coreTmpDir.absolutePath)
|
||||
apiSetFilesFolder(appFilesDir.absolutePath)
|
||||
if (appPlatform.isDesktop) {
|
||||
apiSetRemoteHostsFolder(remoteHostsDir.absolutePath)
|
||||
}
|
||||
apiSetXFTPConfig(getXFTPCfg())
|
||||
apiSetEncryptLocalFiles(appPrefs.privacyEncryptLocalFiles.get())
|
||||
val justStarted = apiStartChat()
|
||||
appPrefs.chatStopped.set(false)
|
||||
val users = listUsers(null)
|
||||
chatModel.users.clear()
|
||||
chatModel.users.addAll(users)
|
||||
@@ -365,6 +361,9 @@ object ChatController {
|
||||
chatModel.chatRunning.value = true
|
||||
startReceiver()
|
||||
setLocalDeviceName(appPrefs.deviceNameForRemoteAccess.get()!!)
|
||||
if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) {
|
||||
chatModel.setDeliveryReceipts.value = true
|
||||
}
|
||||
Log.d(TAG, "startChat: started")
|
||||
} else {
|
||||
updatingChatsMutex.withLock {
|
||||
@@ -383,13 +382,6 @@ object ChatController {
|
||||
Log.d(TAG, "user: null")
|
||||
try {
|
||||
if (chatModel.chatRunning.value == true) return
|
||||
apiSetTempFolder(coreTmpDir.absolutePath)
|
||||
apiSetFilesFolder(appFilesDir.absolutePath)
|
||||
if (appPlatform.isDesktop) {
|
||||
apiSetRemoteHostsFolder(remoteHostsDir.absolutePath)
|
||||
}
|
||||
apiSetXFTPConfig(getXFTPCfg())
|
||||
apiSetEncryptLocalFiles(appPrefs.privacyEncryptLocalFiles.get())
|
||||
chatModel.users.clear()
|
||||
chatModel.currentUser.value = null
|
||||
chatModel.localUserCreated.value = false
|
||||
@@ -596,19 +588,19 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun apiSetTempFolder(tempFolder: String) {
|
||||
suspend fun apiSetTempFolder(tempFolder: String) {
|
||||
val r = sendCmd(null, CC.SetTempFolder(tempFolder))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Error("failed to set temp folder: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
private suspend fun apiSetFilesFolder(filesFolder: String) {
|
||||
suspend fun apiSetFilesFolder(filesFolder: String) {
|
||||
val r = sendCmd(null, CC.SetFilesFolder(filesFolder))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Error("failed to set files folder: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
private suspend fun apiSetRemoteHostsFolder(remoteHostsFolder: String) {
|
||||
suspend fun apiSetRemoteHostsFolder(remoteHostsFolder: String) {
|
||||
val r = sendCmd(null, CC.SetRemoteHostsFolder(remoteHostsFolder))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Error("failed to set remote hosts folder: ${r.responseType} ${r.details}")
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package chat.simplex.common.platform
|
||||
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.ChatModel.currentUser
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.helpers.DatabaseUtils.ksDatabasePassword
|
||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
@@ -39,13 +44,17 @@ val chatController: ChatController = ChatController
|
||||
fun initChatControllerAndRunMigrations(ignoreSelfDestruct: Boolean) {
|
||||
if (ignoreSelfDestruct || DatabaseUtils.ksSelfDestructPassword.get() == null) {
|
||||
withBGApi {
|
||||
initChatController()
|
||||
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
|
||||
initChatController(startChat = ::showStartChatAfterRestartAlert)
|
||||
} else {
|
||||
initChatController()
|
||||
}
|
||||
runMigrations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) {
|
||||
suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: () -> CompletableDeferred<Boolean> = { CompletableDeferred(true) }) {
|
||||
try {
|
||||
chatModel.ctrlInitInProgress.value = true
|
||||
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
|
||||
@@ -62,45 +71,66 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
chatModel.chatDbStatus.value = res
|
||||
if (res != DBMigrationResult.OK) {
|
||||
Log.d(TAG, "Unable to migrate successfully: $res")
|
||||
} else if (startChat) {
|
||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
||||
val user = chatController.apiGetActiveUser(null)
|
||||
if (user == null) {
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
chatModel.currentUser.value = null
|
||||
chatModel.users.clear()
|
||||
if (appPlatform.isDesktop) {
|
||||
/**
|
||||
* Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start
|
||||
* because of default value of [OnboardingStage.OnboardingComplete]
|
||||
* */
|
||||
chatModel.localUserCreated.value = null
|
||||
if (chatController.listRemoteHosts()?.isEmpty() == true) {
|
||||
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
}
|
||||
chatController.startChatWithoutUser()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
controller.apiSetTempFolder(coreTmpDir.absolutePath)
|
||||
controller.apiSetFilesFolder(appFilesDir.absolutePath)
|
||||
if (appPlatform.isDesktop) {
|
||||
controller.apiSetRemoteHostsFolder(remoteHostsDir.absolutePath)
|
||||
}
|
||||
controller.apiSetXFTPConfig(controller.getXFTPCfg())
|
||||
controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get())
|
||||
// If we migrated successfully means previous re-encryption process on database level finished successfully too
|
||||
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
||||
val user = chatController.apiGetActiveUser(null)
|
||||
chatModel.currentUser.value = user
|
||||
if (user == null) {
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
chatModel.currentUser.value = null
|
||||
chatModel.users.clear()
|
||||
if (appPlatform.isDesktop) {
|
||||
/**
|
||||
* Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start
|
||||
* because of default value of [OnboardingStage.OnboardingComplete]
|
||||
* */
|
||||
chatModel.localUserCreated.value = null
|
||||
if (chatController.listRemoteHosts()?.isEmpty() == true) {
|
||||
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
}
|
||||
chatController.startChatWithoutUser()
|
||||
} else {
|
||||
val savedOnboardingStage = appPreferences.onboardingStage.get()
|
||||
val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
|
||||
OnboardingStage.Step3_CreateSimpleXAddress
|
||||
} else {
|
||||
savedOnboardingStage
|
||||
}
|
||||
if (appPreferences.onboardingStage.get() != newStage) {
|
||||
appPreferences.onboardingStage.set(newStage)
|
||||
}
|
||||
if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) {
|
||||
chatModel.setDeliveryReceipts.value = true
|
||||
}
|
||||
chatController.startChat(user)
|
||||
platform.androidChatInitializedAndStarted()
|
||||
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
}
|
||||
} else if (startChat().await()) {
|
||||
val savedOnboardingStage = appPreferences.onboardingStage.get()
|
||||
val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
|
||||
OnboardingStage.Step3_CreateSimpleXAddress
|
||||
} else {
|
||||
savedOnboardingStage
|
||||
}
|
||||
if (appPreferences.onboardingStage.get() != newStage) {
|
||||
appPreferences.onboardingStage.set(newStage)
|
||||
}
|
||||
chatController.startChat(user)
|
||||
platform.androidChatInitializedAndStarted()
|
||||
} else {
|
||||
chatController.getUserChatData(null)
|
||||
chatModel.localUserCreated.value = currentUser.value != null
|
||||
chatModel.chatRunning.value = false
|
||||
}
|
||||
} finally {
|
||||
chatModel.ctrlInitInProgress.value = false
|
||||
}
|
||||
}
|
||||
|
||||
fun showStartChatAfterRestartAlert(): CompletableDeferred<Boolean> {
|
||||
val deferred = CompletableDeferred<Boolean>()
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.start_chat_question),
|
||||
text = generalGetString(MR.strings.chat_is_stopped_you_should_transfer_database),
|
||||
onConfirm = { deferred.complete(true) },
|
||||
onDismiss = { deferred.complete(false) },
|
||||
onDismissRequest = { deferred.complete(false) }
|
||||
)
|
||||
return deferred
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.model.ChatController
|
||||
import chat.simplex.common.platform.isInNightMode
|
||||
@@ -282,8 +283,27 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
|
||||
val theme by CurrentColors.collectAsState()
|
||||
MaterialTheme(
|
||||
colors = theme.colors,
|
||||
typography = Typography,
|
||||
typography = Typography.copy(
|
||||
h1 = Typography.h1.copy(color = theme.colors.onBackground),
|
||||
h2 = Typography.h2.copy(color = theme.colors.onBackground),
|
||||
h3 = Typography.h3.copy(color = theme.colors.onBackground),
|
||||
h4 = Typography.h4.copy(color = theme.colors.onBackground),
|
||||
h5 = Typography.h5.copy(color = theme.colors.onBackground),
|
||||
h6 = Typography.h6.copy(color = theme.colors.onBackground),
|
||||
subtitle1 = Typography.subtitle1.copy(color = theme.colors.onBackground),
|
||||
subtitle2 = Typography.subtitle2.copy(color = theme.colors.onBackground),
|
||||
body1 = Typography.body1.copy(color = theme.colors.onBackground),
|
||||
body2 = Typography.body2.copy(color = theme.colors.onBackground),
|
||||
button = Typography.button.copy(color = theme.colors.onBackground),
|
||||
caption = Typography.caption.copy(color = theme.colors.onBackground),
|
||||
overline = Typography.overline.copy(color = theme.colors.onBackground)
|
||||
),
|
||||
shapes = Shapes,
|
||||
content = content
|
||||
content = {
|
||||
ProvideTextStyle(
|
||||
value = TextStyle(color = theme.colors.onBackground),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1353,6 +1353,8 @@ private fun providerForGallery(
|
||||
fun item(skipInternalIndex: Int, initialChatId: Long): Pair<Int, ChatItem>? {
|
||||
var processedInternalIndex = -skipInternalIndex.sign
|
||||
val indexOfFirst = chatItems.indexOfFirst { it.id == initialChatId }
|
||||
// The first was deleted or moderated
|
||||
if (indexOfFirst == -1) return null
|
||||
for (chatItemsIndex in if (skipInternalIndex >= 0) indexOfFirst downTo 0 else indexOfFirst..chatItems.lastIndex) {
|
||||
val item = chatItems[chatItemsIndex]
|
||||
if (canShowMedia(item)) {
|
||||
@@ -1402,7 +1404,7 @@ private fun providerForGallery(
|
||||
|
||||
override fun scrollToStart() {
|
||||
initialIndex = 0
|
||||
initialChatId = chatItems.first { canShowMedia(it) }.id
|
||||
initialChatId = chatItems.firstOrNull { canShowMedia(it) }?.id ?: return
|
||||
}
|
||||
|
||||
override fun onDismiss(index: Int) {
|
||||
|
||||
@@ -86,6 +86,8 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () ->
|
||||
provider.scrollToStart()
|
||||
pagerState.scrollToPage(0)
|
||||
}
|
||||
// Current media was deleted or moderated, close gallery
|
||||
index -> close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ fun DatabaseErrorView(
|
||||
|
||||
fun callRunChat(confirmMigrations: MigrationConfirmation? = null) {
|
||||
val useKey = if (useKeychain) null else dbKey.value
|
||||
runChat(useKey, confirmMigrations, chatDbStatus, progressIndicator, appPreferences)
|
||||
runChat(useKey, confirmMigrations, chatDbStatus, progressIndicator)
|
||||
}
|
||||
|
||||
fun saveAndRunChatOnClick() {
|
||||
@@ -190,13 +190,14 @@ private fun runChat(
|
||||
confirmMigrations: MigrationConfirmation? = null,
|
||||
chatDbStatus: State<DBMigrationResult?>,
|
||||
progressIndicator: MutableState<Boolean>,
|
||||
prefs: AppPreferences
|
||||
) = CoroutineScope(Dispatchers.Default).launch {
|
||||
// Don't do things concurrently. Shouldn't be here concurrently, just in case
|
||||
if (progressIndicator.value) return@launch
|
||||
progressIndicator.value = true
|
||||
try {
|
||||
initChatController(dbKey, confirmMigrations)
|
||||
initChatController(dbKey, confirmMigrations,
|
||||
startChat = if (appPreferences.chatStopped.get()) ::showStartChatAfterRestartAlert else { { CompletableDeferred(true) } }
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "initializeChat ${e.stackTraceToString()}")
|
||||
}
|
||||
|
||||
@@ -378,12 +378,12 @@ private fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatD
|
||||
ModalManager.closeAllModalsEverywhere()
|
||||
return@withApi
|
||||
}
|
||||
if (m.currentUser.value == null) {
|
||||
val user = m.currentUser.value
|
||||
if (user == null) {
|
||||
ModalManager.closeAllModalsEverywhere()
|
||||
return@withApi
|
||||
} else {
|
||||
m.controller.apiStartChat()
|
||||
m.chatRunning.value = true
|
||||
m.controller.startChat(user)
|
||||
}
|
||||
val ts = Clock.System.now()
|
||||
m.controller.appPrefs.chatLastStart.set(ts)
|
||||
@@ -453,6 +453,7 @@ private fun stopChat(m: ChatModel) {
|
||||
suspend fun stopChatAsync(m: ChatModel) {
|
||||
m.controller.apiStopChat()
|
||||
m.chatRunning.value = false
|
||||
controller.appPrefs.chatStopped.set(true)
|
||||
}
|
||||
|
||||
suspend fun deleteChatAsync(m: ChatModel) {
|
||||
|
||||
@@ -82,7 +82,6 @@ sealed class DBMigrationResult {
|
||||
@Serializable @SerialName("unknown") data class Unknown(val json: String): DBMigrationResult()
|
||||
}
|
||||
|
||||
|
||||
enum class MigrationConfirmation(val value: String) {
|
||||
YesUp("yesUp"),
|
||||
YesUpDown ("yesUpDown"),
|
||||
|
||||
@@ -84,7 +84,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (
|
||||
m.chatDbChanged.value = true
|
||||
m.chatDbStatus.value = null
|
||||
try {
|
||||
initChatController(startChat = true)
|
||||
initChatController()
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "initializeChat ${e.stackTraceToString()}")
|
||||
}
|
||||
|
||||
@@ -1112,6 +1112,8 @@
|
||||
<!-- ChatModel.chatRunning interactions -->
|
||||
<string name="chat_is_stopped_indication">Chat is stopped</string>
|
||||
<string name="you_can_start_chat_via_setting_or_by_restarting_the_app">You can start chat via app Settings / Database or by restarting the app.</string>
|
||||
<string name="start_chat_question">Start chat?</string>
|
||||
<string name="chat_is_stopped_you_should_transfer_database">Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat.</string>
|
||||
|
||||
<!-- ChatArchiveView.kt -->
|
||||
<string name="chat_archive_header">Chat archive</string>
|
||||
|
||||
@@ -7,7 +7,7 @@ revision: 25.11.2023
|
||||
| Updated 25.11.2023 | Languages: EN |
|
||||
# Download SimpleX apps
|
||||
|
||||
The latest stable version is v5.4.1.
|
||||
The latest stable version is v5.4.2.
|
||||
|
||||
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
||||
|
||||
@@ -21,24 +21,24 @@ You can get the latest beta releases from [GitHub](https://github.com/simplex-ch
|
||||
|
||||
Using the same profile as on mobile device is not yet supported – you need to create a separate profile to use desktop apps.
|
||||
|
||||
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-ubuntu-22_04-x86_64.deb).
|
||||
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-ubuntu-22_04-x86_64.deb).
|
||||
|
||||
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
|
||||
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
|
||||
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-desktop-windows-x86_64.msi).
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-desktop-windows-x86_64.msi).
|
||||
|
||||
## Mobile apps
|
||||
|
||||
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084), [TestFlight](https://testflight.apple.com/join/DWuT2LQu).
|
||||
|
||||
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-armv7a.apk).
|
||||
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-armv7a.apk).
|
||||
|
||||
## Terminal (console) app
|
||||
|
||||
See [Using terminal app](/docs/CLI.md).
|
||||
|
||||
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-ubuntu-22_04-x86-64).
|
||||
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-ubuntu-22_04-x86-64).
|
||||
|
||||
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
|
||||
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
|
||||
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.1/simplex-chat-windows-x86-64).
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.2/simplex-chat-windows-x86-64).
|
||||
|
||||
Reference in New Issue
Block a user