Merge branch 'master' into master-ghc8107

This commit is contained in:
Evgeny Poberezkin
2024-01-09 11:07:32 +00:00
87 changed files with 955 additions and 470 deletions
@@ -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()
}
}
}
}
@@ -0,0 +1,7 @@
package chat.simplex.common.views.database
import chat.simplex.common.views.usersettings.restartApp
actual fun restartChatOrApp() {
restartApp()
}
@@ -28,7 +28,7 @@ actual fun SettingsSectionApp(
}
private fun restartApp() {
fun restartApp() {
ProcessPhoenix.triggerRebirth(androidAppContext)
shutdownApp()
}
@@ -44,7 +44,7 @@ data class SettingsViewState(
fun AppScreen() {
SimpleXTheme {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
Surface(color = MaterialTheme.colors.background) {
Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
MainScreen()
}
}
@@ -85,7 +85,7 @@ fun MainScreen() {
@Composable
fun AuthView() {
Surface(color = MaterialTheme.colors.background) {
Surface(color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
Box(
Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
@@ -1,8 +1,7 @@
package chat.simplex.common
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.*
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import chat.simplex.common.model.*
@@ -107,7 +106,7 @@ object AppLock {
private fun setPasscode() {
val appPrefs = ChatController.appPrefs
ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView(
submit = {
ChatModel.performLA.value = true
@@ -125,6 +125,9 @@ object ChatModel {
val remoteHostPairing = mutableStateOf<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
val remoteCtrlSession = mutableStateOf<RemoteCtrlSession?>(null)
val processedCriticalError: ProcessedErrors<AgentErrorType.CRITICAL> = ProcessedErrors(60_000)
val processedInternalError: ProcessedErrors<AgentErrorType.INTERNAL> = ProcessedErrors(20_000)
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
currentUser.value
} else {
@@ -1151,7 +1154,6 @@ data class LocalProfile(
@Serializable
data class UserProfileUpdateSummary(
val notChanged: Int,
val updateSuccesses: Int,
val updateFailures: Int,
val changedContacts: List<Contact>
@@ -106,7 +106,9 @@ 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 showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, false)
val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050")
@@ -273,7 +275,9 @@ 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_SHOW_INTERNAL_ERRORS = "ShowInternalErrors"
private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort"
@@ -346,14 +350,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 +363,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 +384,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
@@ -580,7 +574,7 @@ object ChatController {
}
suspend fun apiStartChat(): Boolean {
val r = sendCmd(null, CC.StartChat(expire = true))
val r = sendCmd(null, CC.StartChat(mainApp = true))
when (r) {
is CR.ChatStarted -> return true
is CR.ChatRunning -> return false
@@ -596,19 +590,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}")
@@ -1924,6 +1918,14 @@ object ChatController {
}
}
}
is CR.ChatCmdError -> when {
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> {
chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart)
}
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.INTERNAL && appPrefs.showInternalErrors.get() -> {
chatModel.processedInternalError.newError(r.chatError.agentError, false)
}
}
else ->
Log.d(TAG , "unsupported event: ${r.responseType}")
}
@@ -2173,7 +2175,7 @@ sealed class CC {
class ApiMuteUser(val userId: Long): CC()
class ApiUnmuteUser(val userId: Long): CC()
class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC()
class StartChat(val expire: Boolean): CC()
class StartChat(val mainApp: Boolean): CC()
class ApiStopChat: CC()
class SetTempFolder(val tempFolder: String): CC()
class SetFilesFolder(val filesFolder: String): CC()
@@ -2300,7 +2302,7 @@ sealed class CC {
is ApiMuteUser -> "/_mute user $userId"
is ApiUnmuteUser -> "/_unmute user $userId"
is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}"
is StartChat -> "/_start subscribe=on expire=${onOff(expire)} xftp=on"
is StartChat -> "/_start main=${onOff(mainApp)}"
is ApiStopChat -> "/_stop"
is SetTempFolder -> "/_temp_folder $tempFolder"
is SetFilesFolder -> "/_files_folder $filesFolder"
@@ -4739,6 +4741,7 @@ sealed class AgentErrorType {
is AGENT -> "AGENT ${agentErr.string}"
is INTERNAL -> "INTERNAL $internalErr"
is INACTIVE -> "INACTIVE"
is CRITICAL -> "CRITICAL $offerRestart $criticalErr"
}
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType()
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
@@ -4750,6 +4753,7 @@ sealed class AgentErrorType {
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
@Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType()
@Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType()
}
@Serializable
@@ -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
@@ -36,17 +41,20 @@ val appPreferences: AppPreferences
val chatController: ChatController = ChatController
fun initChatControllerAndRunMigrations(ignoreSelfDestruct: Boolean) {
if (ignoreSelfDestruct || DatabaseUtils.ksSelfDestructPassword.get() == null) {
withBGApi {
fun initChatControllerAndRunMigrations() {
withBGApi {
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
initChatController(startChat = ::showStartChatAfterRestartAlert)
} else {
initChatController()
runMigrations()
}
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 {
if (chatModel.ctrlInitInProgress.value) return
chatModel.ctrlInitInProgress.value = true
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp
@@ -62,45 +70,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
}
@@ -99,6 +99,7 @@ abstract class NtfManager {
abstract fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<Pair<NotificationAction, () -> Unit>> = emptyList())
abstract fun cancelCallNotification()
abstract fun cancelAllNotifications()
abstract fun showMessage(title: String, text: String)
// Android only
abstract fun androidCreateNtfChannelsMaybeShowAlert()
@@ -1,5 +1,7 @@
package chat.simplex.common.ui.theme
import androidx.compose.material.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
@@ -25,4 +27,5 @@ val WarningOrange = Color(255, 127, 0, 255)
val WarningYellow = Color(255, 192, 0, 255)
val FileLight = Color(183, 190, 199, 255)
val FileDark = Color(101, 101, 106, 255)
val MenuTextColorDark = Color.White.copy(alpha = 0.8f)
val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black
@@ -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
@@ -284,6 +285,8 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) {
colors = theme.colors,
typography = Typography,
shapes = Shapes,
content = content
content = {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colors.onBackground, content = content)
}
)
}
@@ -1,8 +1,7 @@
package chat.simplex.common.views
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -11,7 +10,8 @@ fun SplashView() {
Surface(
Modifier
.fillMaxSize(),
color = MaterialTheme.colors.background
color = MaterialTheme.colors.background,
contentColor = LocalContentColor.current
) {
// Image(
// painter = painterResource(MR.images.logo),
@@ -101,13 +101,16 @@ fun TerminalLayout(
)
}
},
contentColor = LocalContentColor.current,
drawerContentColor = LocalContentColor.current,
modifier = Modifier.navigationBarsWithImePadding()
) { contentPadding ->
Surface(
modifier = Modifier
.padding(contentPadding)
.fillMaxWidth(),
color = MaterialTheme.colors.background
color = MaterialTheme.colors.background,
contentColor = LocalContentColor.current
) {
TerminalLog()
}
@@ -239,7 +239,7 @@ fun OnboardingButtons(displayName: MutableState<String>, close: () -> Unit) {
val enabled = canCreateProfile(displayName.value)
val createModifier: Modifier = Modifier.clickable(enabled) { createProfileOnboarding(chatModel, displayName.value, close) }.padding(8.dp)
val createColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
Surface(shape = RoundedCornerShape(20.dp), color = Color.Transparent) {
Surface(shape = RoundedCornerShape(20.dp), color = Color.Transparent, contentColor = LocalContentColor.current) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) {
Text(stringResource(MR.strings.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium)
Icon(painterResource(MR.images.ic_arrow_forward_ios), stringResource(MR.strings.create_profile_button), tint = createColor)
@@ -85,7 +85,8 @@ fun IncomingCallInfo(invitation: RcvCallInvitation, chatModel: ChatModel) {
private fun CallButton(text: String, icon: Painter, color: Color, action: () -> Unit) {
Surface(
shape = RoundedCornerShape(10.dp),
color = Color.Transparent
color = Color.Transparent,
contentColor = LocalContentColor.current
) {
Column(
Modifier
@@ -570,6 +570,8 @@ fun ChatLayout(
bottomBar = composeView,
modifier = Modifier.navigationBarsWithImePadding(),
floatingActionButton = { floatingButton.value() },
contentColor = LocalContentColor.current,
drawerContentColor = LocalContentColor.current,
) { contentPadding ->
BoxWithConstraints(Modifier
.fillMaxHeight()
@@ -1353,6 +1355,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 +1406,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) {
@@ -258,7 +258,8 @@ private fun CustomDisappearingMessageDialog(
DefaultDialog(onDismissRequest = { setShowDialog(false) }) {
Surface(
shape = RoundedCornerShape(corner = CornerSize(25.dp))
shape = RoundedCornerShape(corner = CornerSize(25.dp)),
contentColor = LocalContentColor.current
) {
Box(
contentAlignment = Alignment.Center
@@ -131,7 +131,8 @@ fun CIFileView(
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = Color.Transparent,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
contentColor = LocalContentColor.current
) {
Box(Modifier.size(32.dp))
}
@@ -88,6 +88,7 @@ fun CIGroupInvitationView(
}) else Modifier,
shape = RoundedCornerShape(18.dp),
color = if (sent) sentColor else receivedColor,
contentColor = LocalContentColor.current
) {
Box(
Modifier
@@ -142,6 +142,7 @@ fun DecryptionErrorItemFixButton(
Modifier.clickable(onClick = onClick),
shape = RoundedCornerShape(18.dp),
color = receivedColor,
contentColor = LocalContentColor.current
) {
Box(
Modifier.padding(vertical = 6.dp, horizontal = 12.dp),
@@ -188,6 +189,7 @@ fun DecryptionErrorItem(
Modifier.clickable(onClick = onClick),
shape = RoundedCornerShape(18.dp),
color = receivedColor,
contentColor = LocalContentColor.current
) {
Box(
Modifier.padding(vertical = 6.dp, horizontal = 12.dp),
@@ -153,7 +153,8 @@ private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit,
Surface(
Modifier.align(Alignment.Center),
color = Color.Black.copy(alpha = 0.25f),
shape = RoundedCornerShape(percent = 50)
shape = RoundedCornerShape(percent = 50),
contentColor = LocalContentColor.current
) {
Box(
Modifier
@@ -264,7 +265,8 @@ private fun progressCircle(progress: Long, total: Long) {
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = Color.Transparent,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
contentColor = LocalContentColor.current
) {
Box(Modifier.size(16.dp))
}
@@ -225,7 +225,8 @@ private fun PlayPauseButton(
Surface(
Modifier.drawRingModifier(angle, strokeColor, strokeWidth),
color = if (sent) sentColor else receivedColor,
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50))
shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
contentColor = LocalContentColor.current
) {
Box(
Modifier
@@ -613,7 +613,7 @@ private fun ShrinkItemAction(revealed: MutableState<Boolean>, showMenu: MutableS
@Composable
fun ItemAction(text: String, icon: Painter, color: Color = Color.Unspecified, onClick: () -> Unit) {
val finalColor = if (color == Color.Unspecified) {
if (isInDarkTheme()) MenuTextColorDark else Color.Black
MenuTextColor
} else color
DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -633,7 +633,7 @@ fun ItemAction(text: String, icon: Painter, color: Color = Color.Unspecified, on
@Composable
fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Color = Color.Unspecified) {
val finalColor = if (color == Color.Unspecified) {
if (isInDarkTheme()) MenuTextColorDark else Color.Black
MenuTextColor
} else color
DropdownMenuItem(onClick, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -23,6 +23,7 @@ fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?) {
Surface(
shape = RoundedCornerShape(18.dp),
color = if (sent) sentColor else receivedColor,
contentColor = LocalContentColor.current
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
@@ -86,6 +86,8 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () ->
provider.scrollToStart()
pagerState.scrollToPage(0)
}
// Current media was deleted or moderated, close gallery
index -> close()
}
}
}
@@ -56,6 +56,7 @@ fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, onClick: () -> Unit) {
Modifier.clickable(onClick = onClick),
shape = RoundedCornerShape(18.dp),
color = receivedColor,
contentColor = LocalContentColor.current
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
@@ -26,6 +26,7 @@ fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: Mutabl
Surface(
shape = RoundedCornerShape(18.dp),
color = if (ci.chatDir.sent) sentColor else receivedColor,
contentColor = LocalContentColor.current
) {
Row(
Modifier.padding(horizontal = 12.dp, vertical = 6.dp),
@@ -75,6 +75,8 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
SettingsView(chatModel, setPerformLA, scaffoldState.drawerState)
}
},
contentColor = LocalContentColor.current,
drawerContentColor = LocalContentColor.current,
drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f),
drawerGesturesEnabled = appPlatform.isAndroid,
floatingActionButton = {
@@ -30,6 +30,8 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
Scaffold(
Modifier.padding(end = endPadding),
contentColor = LocalContentColor.current,
drawerContentColor = LocalContentColor.current,
scaffoldState = scaffoldState,
topBar = { Column { ShareListToolbar(chatModel, userPickerState, stopped) { searchInList = it.trim() } } },
) {
@@ -31,7 +31,6 @@ import chat.simplex.common.views.remote.*
import chat.simplex.common.views.usersettings.doWithAuth
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@@ -303,7 +302,7 @@ fun UserProfileRow(u: User) {
u.displayName,
modifier = Modifier
.padding(start = 10.dp, end = 8.dp),
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,
color = MenuTextColor,
fontWeight = if (u.activeUser) FontWeight.Medium else FontWeight.Normal
)
}
@@ -346,7 +345,7 @@ fun RemoteHostRow(h: RemoteHostInfo) {
Text(
h.hostDeviceName,
modifier = Modifier.padding(start = 26.dp, end = 8.dp),
color = if (h.activeHost) MaterialTheme.colors.onBackground else if (isInDarkTheme()) MenuTextColorDark else Color.Black,
color = if (h.activeHost) MaterialTheme.colors.onBackground else MenuTextColor,
fontSize = 14.sp,
)
}
@@ -387,7 +386,7 @@ fun LocalDeviceRow(active: Boolean) {
Text(
stringResource(MR.strings.this_device),
modifier = Modifier.padding(start = 26.dp, end = 8.dp),
color = if (active) MaterialTheme.colors.onBackground else if (isInDarkTheme()) MenuTextColorDark else Color.Black,
color = if (active) MaterialTheme.colors.onBackground else MenuTextColor,
fontSize = 14.sp,
)
}
@@ -399,7 +398,7 @@ private fun UseFromDesktopPickerItem(onClick: () -> Unit) {
val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current)
Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black)
Text(text, color = MenuTextColor)
}
}
@@ -409,7 +408,7 @@ private fun LinkAMobilePickerItem(onClick: () -> Unit) {
val text = generalGetString(MR.strings.link_a_mobile)
Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black)
Text(text, color = MenuTextColor)
}
}
@@ -419,7 +418,7 @@ private fun CreateInitialProfile(onClick: () -> Unit) {
val text = generalGetString(MR.strings.create_chat_profile)
Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black)
Text(text, color = MenuTextColor)
}
}
@@ -429,7 +428,7 @@ private fun SettingsPickerItem(onClick: () -> Unit) {
val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current)
Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black)
Text(text, color = MenuTextColor)
}
}
@@ -439,7 +438,7 @@ private fun CancelPickerItem(onClick: () -> Unit) {
val text = generalGetString(MR.strings.cancel_verb)
Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
Text(text, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black)
Text(text, color = MenuTextColor)
}
}
@@ -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()}")
}
@@ -366,7 +366,7 @@ fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String {
return stringResource(if (chatArchiveTime < chatLastStart) MR.strings.old_database_archive else MR.strings.new_database_archive)
}
private fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) {
fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) {
withApi {
try {
if (chatDbChanged.value) {
@@ -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)
@@ -406,6 +406,8 @@ private fun stopChatAlert(m: ChatModel) {
)
}
expect fun restartChatOrApp()
private fun exportProhibitedAlert() {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.set_password_to_export),
@@ -413,7 +415,7 @@ private fun exportProhibitedAlert() {
)
}
private fun authStopChat(m: ChatModel) {
fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
if (m.controller.appPrefs.performLA.get()) {
authenticate(
generalGetString(MR.strings.auth_stop_chat),
@@ -421,7 +423,7 @@ private fun authStopChat(m: ChatModel) {
completed = { laResult ->
when (laResult) {
LAResult.Success, is LAResult.Unavailable -> {
stopChat(m)
stopChat(m, onStop)
}
is LAResult.Error -> {
m.chatRunning.value = true
@@ -434,15 +436,16 @@ private fun authStopChat(m: ChatModel) {
}
)
} else {
stopChat(m)
stopChat(m, onStop)
}
}
private fun stopChat(m: ChatModel) {
private fun stopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
withApi {
try {
stopChatAsync(m)
platform.androidChatStopped()
onStop?.invoke()
} catch (e: Error) {
m.chatRunning.value = true
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString())
@@ -453,16 +456,17 @@ 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) {
m.controller.apiDeleteStorage()
DatabaseUtils.ksDatabasePassword.remove()
m.controller.appPrefs.storeDBPassphrase.set(true)
deleteChatDatabaseFiles()
deleteAppDatabaseAndFiles()
}
fun deleteChatDatabaseFiles() {
fun deleteAppDatabaseAndFiles() {
val chat = File(dataDir, chatDatabaseFileName)
val chatBak = File(dataDir, "$chatDatabaseFileName.bak")
val agent = File(dataDir, agentDatabaseFileName)
@@ -472,6 +476,7 @@ fun deleteChatDatabaseFiles() {
agent.delete()
agentBak.delete()
filesDir.deleteRecursively()
filesDir.mkdir()
remoteHostsDir.deleteRecursively()
tmpDir.deleteRecursively()
tmpDir.mkdir()
@@ -152,7 +152,8 @@ fun CustomTimePickerDialog(
) {
DefaultDialog(onDismissRequest = cancel) {
Surface(
shape = RoundedCornerShape(corner = CornerSize(25.dp))
shape = RoundedCornerShape(corner = CornerSize(25.dp)),
contentColor = LocalContentColor.current
) {
Box(
contentAlignment = Alignment.Center
@@ -17,7 +17,7 @@ object DatabaseUtils {
val ksAppPassword = KeyStoreItem(APP_PASSWORD_ALIAS, appPreferences.encryptedAppPassphrase, appPreferences.initializationVectorAppPassphrase)
val ksSelfDestructPassword = KeyStoreItem(SELF_DESTRUCT_PASSWORD_ALIAS, appPreferences.encryptedSelfDestructPassphrase, appPreferences.initializationVectorSelfDestructPassphrase)
class KeyStoreItem(val alias: String, val passphrase: SharedPreference<String?>, val initVector: SharedPreference<String?>) {
class KeyStoreItem(private val alias: String, val passphrase: SharedPreference<String?>, val initVector: SharedPreference<String?>) {
fun get(): String? {
return cryptor.decryptData(
passphrase.get()?.toByteArrayFromBase64ForPassphrase() ?: return null,
@@ -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"),
@@ -70,7 +70,7 @@ fun <T> ExposedDropDownSetting(
selectionOption.second + (if (label != null) " $label" else ""),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,
color = MenuTextColor,
fontSize = fontSize,
)
}
@@ -1,8 +1,7 @@
package chat.simplex.common.views.helpers
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.*
import androidx.compose.ui.Modifier
import chat.simplex.common.model.ChatController
import chat.simplex.common.model.ChatModel
@@ -50,7 +49,7 @@ fun authenticateWithPasscode(
close()
completed(LAResult.Error(generalGetString(MR.strings.authentication_cancelled)))
}
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
LocalAuthView(ChatModel, LocalAuthRequest(promptTitle, promptSubtitle, password, selfDestruct && ChatController.appPrefs.selfDestruct.get()) {
close()
completed(it)
@@ -26,7 +26,7 @@ fun ModalView(
if (showClose) {
BackHandler(onBack = close)
}
Surface(Modifier.fillMaxSize()) {
Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) {
Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) {
CloseSheetBar(close, showClose, endButtons)
Box(modifier) { content() }
@@ -0,0 +1,64 @@
package chat.simplex.common.views.helpers
import chat.simplex.common.model.AgentErrorType
import chat.simplex.common.platform.Log
import chat.simplex.common.platform.TAG
import chat.simplex.common.platform.ntfManager
import chat.simplex.common.views.database.restartChatOrApp
import chat.simplex.res.MR
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
private var lastShownTimestamp: Long = -1
private var lastShownOfferRestart: Boolean = false
private var timer: Job = Job()
fun newError(error: T, offerRestart: Boolean) {
timer.cancel()
timer = withBGApi {
val delayBeforeNext = (lastShownTimestamp + interval) - System.currentTimeMillis()
if ((lastShownOfferRestart || !offerRestart) && delayBeforeNext >= 0) {
delay(delayBeforeNext)
}
lastShownTimestamp = System.currentTimeMillis()
lastShownOfferRestart = offerRestart
AlertManager.shared.hideAllAlerts()
showMessage(error, offerRestart)
}
}
private fun showMessage(error: T, offerRestart: Boolean) {
when (error) {
is AgentErrorType.CRITICAL -> {
val title = generalGetString(MR.strings.agent_critical_error_title)
val text = generalGetString(MR.strings.agent_critical_error_desc).format(error.criticalErr)
try {
ntfManager.showMessage(title, text)
} catch (e: Throwable) {
Log.e(TAG, e.stackTraceToString())
}
if (offerRestart) {
AlertManager.shared.showAlertDialog(
title = title,
text = text,
confirmText = generalGetString(MR.strings.restart_chat_button),
onConfirm = {
withApi { restartChatOrApp() }
})
} else {
AlertManager.shared.showAlertMsg(
title = title,
text = text,
)
}
}
is AgentErrorType.INTERNAL -> {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.agent_internal_error_title),
text = generalGetString(MR.strings.agent_internal_error_desc).format(error.internalErr),
)
}
}
}
}
@@ -34,7 +34,7 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) {
} else {
val r: LAResult = if (passcode.value == authRequest.password) {
if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) {
initChatControllerAndRunMigrations(true)
initChatControllerAndRunMigrations()
}
LAResult.Success
} else {
@@ -67,8 +67,8 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (
* */
chatCloseStore(ctrl)
}
deleteChatDatabaseFiles()
// Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself
deleteAppDatabaseAndFiles()
// Clear sensitive data on screen just in case ModalManager fails to hide its modals while new database is created
m.chatId.value = null
m.chatItems.clear()
m.chats.clear()
@@ -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()}")
}
@@ -12,6 +12,7 @@ import chat.simplex.res.MR
@Composable
fun SetAppPasscodeView(
passcodeKeychain: DatabaseUtils.KeyStoreItem = ksAppPassword,
prohibitedPasscodeKeychain: DatabaseUtils.KeyStoreItem = ksSelfDestructPassword,
title: String = generalGetString(MR.strings.new_passcode),
reason: String? = null,
submit: () -> Unit,
@@ -51,7 +52,7 @@ fun SetAppPasscodeView(
} else {
SetPasswordView(title, generalGetString(MR.strings.save_verb),
// Do not allow to set app passcode == selfDestruct passcode
submitEnabled = { pwd -> pwd != (if (passcodeKeychain.alias == ksSelfDestructPassword.alias) ksAppPassword else ksSelfDestructPassword).get() }) {
submitEnabled = { pwd -> pwd != prohibitedPasscodeKeychain.get() }) {
enteredPassword = passcode.value
passcode.value = ""
confirming = true
@@ -175,7 +175,7 @@ fun ActionButton(
disabled: Boolean = false,
click: () -> Unit = {}
) {
Surface(shape = RoundedCornerShape(18.dp), color = Color.Transparent) {
Surface(shape = RoundedCornerShape(18.dp), color = Color.Transparent, contentColor = LocalContentColor.current) {
Column(
Modifier
.clickable(onClick = click)
@@ -220,7 +220,7 @@ fun ActionButton(
disabled: Boolean = false,
click: () -> Unit = {}
) {
Surface(modifier, shape = RoundedCornerShape(18.dp)) {
Surface(modifier, shape = RoundedCornerShape(18.dp), contentColor = LocalContentColor.current) {
Column(
Modifier
.fillMaxWidth()
@@ -380,7 +380,8 @@ fun SettingsSectionFooter(revert: () -> Unit, save: () -> Unit, disabled: Boolea
fun FooterButton(icon: Painter, title: String, action: () -> Unit, disabled: Boolean) {
Surface(
shape = RoundedCornerShape(20.dp),
color = Color.Black.copy(alpha = 0f)
color = Color.Black.copy(alpha = 0f),
contentColor = LocalContentColor.current
) {
val modifier = if (disabled) Modifier else Modifier.clickable { action() }
Row(
@@ -10,10 +10,11 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import chat.simplex.common.model.*
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.model.ChatModel
import chat.simplex.common.platform.appPlatform
import chat.simplex.common.platform.appPreferences
import chat.simplex.common.views.TerminalView
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
@@ -44,6 +45,7 @@ fun DeveloperView(
m.controller.appPrefs.terminalAlwaysVisible.set(false)
}
}
SettingsPreferenceItem(painterResource(MR.images.ic_report), stringResource(MR.strings.show_internal_errors), appPreferences.showInternalErrors)
}
}
SectionTextFooter(
@@ -383,7 +383,7 @@ fun SimplexLockView(
}
LAMode.PASSCODE -> {
ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView(
submit = {
laLockDelay.set(30)
@@ -427,7 +427,7 @@ fun SimplexLockView(
when (laResult) {
LAResult.Success -> {
ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView(
reason = generalGetString(MR.strings.la_app_passcode),
submit = {
@@ -451,9 +451,10 @@ fun SimplexLockView(
when (laResult) {
LAResult.Success -> {
ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView(
passcodeKeychain = ksSelfDestructPassword,
prohibitedPasscodeKeychain = ksAppPassword,
reason = generalGetString(MR.strings.self_destruct),
submit = {
selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_changed))
@@ -487,7 +488,7 @@ fun SimplexLockView(
}
LAMode.PASSCODE -> {
ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView(
submit = {
laLockDelay.set(30)
@@ -598,9 +599,9 @@ private fun EnableSelfDestruct(
selfDestruct: SharedPreference<Boolean>,
close: () -> Unit
) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background, contentColor = LocalContentColor.current) {
SetAppPasscodeView(
passcodeKeychain = ksSelfDestructPassword, title = generalGetString(MR.strings.set_passcode), reason = generalGetString(MR.strings.enabled_self_destruct_passcode),
passcodeKeychain = ksSelfDestructPassword, prohibitedPasscodeKeychain = ksAppPassword, title = generalGetString(MR.strings.set_passcode), reason = generalGetString(MR.strings.enabled_self_destruct_passcode),
submit = {
selfDestruct.set(true)
selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_enabled))
@@ -155,7 +155,8 @@ fun RTCServersLayout(
.height(160.dp)
.fillMaxWidth(),
shape = RoundedCornerShape(10.dp),
border = BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant)
border = BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant),
contentColor = LocalContentColor.current
) {
SelectionContainer(
Modifier.verticalScroll(rememberScrollState())
@@ -155,7 +155,7 @@ fun UserAddressView(
contentAlignment = Alignment.Center
) {
if (userAddress.value != null) {
Surface(Modifier.size(50.dp), color = MaterialTheme.colors.background.copy(0.9f), shape = RoundedCornerShape(50)){}
Surface(Modifier.size(50.dp), color = MaterialTheme.colors.background.copy(0.9f), contentColor = LocalContentColor.current, shape = RoundedCornerShape(50)){}
}
CircularProgressIndicator(
Modifier
@@ -684,6 +684,7 @@
<string name="hide_dev_options">Hide:</string>
<string name="show_developer_options">Show developer options</string>
<string name="developer_options">Database IDs and Transport isolation option.</string>
<string name="show_internal_errors">Show internal errors</string>
<string name="shutdown_alert_question">Shutdown?</string>
<string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</string>
@@ -1112,6 +1113,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>
@@ -1749,4 +1752,11 @@
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[You are already in group <b>%1$s</b>.]]></string>
<string name="connect_plan_connect_via_link">Connect via link?</string>
<!-- Errors -->
<string name="agent_critical_error_title">Critical error</string>
<string name="agent_critical_error_desc">Please report it to the developers: \n%s\n\nIt is recommended to restart the app.</string>
<string name="agent_internal_error_title">Internal error</string>
<string name="agent_internal_error_desc">Please report it to the developers: \n%s</string>
<string name="restart_chat_button">Restart chat</string>
</resources>
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M479.895-284Q494-284 504-293.895q10-9.894 10-24Q514-332 504.105-342q-9.894-10-24-10Q466-352 456-342.105q-10 9.894-10 24Q446-304 455.895-294q9.894 10 24 10ZM451.5-425H509v-261h-57.5v261ZM332-124.5 124.5-332.176V-628l207.676-207.5H628l207.5 207.676V-332L627.824-124.5H332Zm24.222-57.5h248.243L778-356.222v-248.243L604.242-778H356L182-604.242V-356l174.222 174ZM480-480Z"/></svg>

After

Width:  |  Height:  |  Size: 472 B

@@ -47,6 +47,10 @@ object NtfManager {
}
}
fun showMessage(title: String, text: String) {
displayNotificationViaLib("MESSAGE", title, text, null, emptyList()) {}
}
fun hasNotificationsForChat(chatId: ChatId) = false//prevNtfs.any { it.first == chatId }
fun cancelNotificationsForChat(chatId: ChatId) {
@@ -22,9 +22,12 @@ fun initApp() {
override fun androidCreateNtfChannelsMaybeShowAlert() {}
override fun cancelCallNotification() {}
override fun cancelAllNotifications() = chat.simplex.common.model.NtfManager.cancelAllNotifications()
override fun showMessage(title: String, text: String) = chat.simplex.common.model.NtfManager.showMessage(title, text)
}
applyAppLocale()
initChatControllerAndRunMigrations(false)
if (DatabaseUtils.ksSelfDestructPassword.get() == null) {
initChatControllerAndRunMigrations()
}
// LALAL
//testCrypto()
}
@@ -167,7 +167,8 @@ actual fun PlatformTextField(
decorationBox = { innerTextField ->
Surface(
shape = RoundedCornerShape(18.dp),
border = BorderStroke(1.dp, MaterialTheme.colors.secondary)
border = BorderStroke(1.dp, MaterialTheme.colors.secondary),
contentColor = LocalContentColor.current
) {
Row(
Modifier.background(MaterialTheme.colors.background),
@@ -0,0 +1,23 @@
package chat.simplex.common.views.database
import androidx.compose.runtime.mutableStateOf
import chat.simplex.common.platform.chatModel
import chat.simplex.common.views.helpers.withApi
import kotlinx.coroutines.delay
import kotlinx.datetime.Instant
actual fun restartChatOrApp() {
if (chatModel.chatRunning.value == false) {
chatModel.chatDbChanged.value = true
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
} else {
authStopChat(chatModel) {
withApi {
// adding delay in order to prevent locked database by previous initialization
delay(1000)
chatModel.chatDbChanged.value = true
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
}
}
}
}
@@ -2,8 +2,7 @@ package chat.simplex.common.views.helpers
import androidx.compose.foundation.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.*
@@ -39,7 +38,8 @@ actual fun DefaultDialog(
) {
Surface(
Modifier
.border(border = BorderStroke(1.dp, MaterialTheme.colors.secondary.copy(alpha = 0.3F)), shape = RoundedCornerShape(8))
.border(border = BorderStroke(1.dp, MaterialTheme.colors.secondary.copy(alpha = 0.3F)), shape = RoundedCornerShape(8)),
contentColor = LocalContentColor.current
) {
content()
}