mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Merge branch 'master' into master-android
This commit is contained in:
+34
-3
@@ -19,6 +19,8 @@ import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.ImageResource
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.internal.ChannelFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.datetime.*
|
||||
@@ -301,9 +303,7 @@ object ChatModel {
|
||||
else
|
||||
chat.chatStats
|
||||
)
|
||||
if (i > 0) {
|
||||
chats.add(index = 0, chats.removeAt(i))
|
||||
}
|
||||
popChatCollector.addChat(chat.remoteHostId, chat.id)
|
||||
} else {
|
||||
addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||
}
|
||||
@@ -421,6 +421,32 @@ object ChatModel {
|
||||
}
|
||||
}
|
||||
|
||||
private val popChatCollector = PopChatCollector()
|
||||
|
||||
class PopChatCollector {
|
||||
private val subject = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
withLongRunningApi {
|
||||
subject
|
||||
.throttleLatest(2000)
|
||||
.collect {
|
||||
withChats {
|
||||
chats.replaceAll(chats.value.sortedByDescending { it.popTs })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addChat(remoteHostId: Long?, chatId: ChatId) {
|
||||
val index = getChatIndex(remoteHostId, chatId)
|
||||
if (index >= 0) {
|
||||
chats.value[index].popTs = Clock.System.now()
|
||||
subject.emit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) {
|
||||
val cInfo = chatInfo
|
||||
val markedRead = markItemsReadInCurrentChat(chatInfo, range)
|
||||
@@ -671,6 +697,7 @@ object ChatModel {
|
||||
showingInvitation.value = null
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatId.value = withId
|
||||
ModalManager.start.closeModals()
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
}
|
||||
@@ -681,6 +708,7 @@ object ChatModel {
|
||||
chatModel.chatItems.clear()
|
||||
chatModel.chatId.value = null
|
||||
// Close NewChatView
|
||||
ModalManager.start.closeModals()
|
||||
ModalManager.center.closeModals()
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
@@ -845,6 +873,9 @@ data class Chat(
|
||||
val chatItems: List<ChatItem>,
|
||||
val chatStats: ChatStats = ChatStats()
|
||||
) {
|
||||
@Transient
|
||||
var popTs: Instant? = null
|
||||
|
||||
val nextSendGrpInv: Boolean
|
||||
get() = when (chatInfo) {
|
||||
is ChatInfo.Direct -> chatInfo.contact.nextSendGrpInv
|
||||
|
||||
+17
-2
@@ -159,6 +159,7 @@ class AppPreferences {
|
||||
val incognito = mkBoolPreference(SHARED_PREFS_INCOGNITO, false)
|
||||
val liveMessageAlertShown = mkBoolPreference(SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN, false)
|
||||
val showHiddenProfilesNotice = mkBoolPreference(SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE, true)
|
||||
val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false)
|
||||
val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true)
|
||||
val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null)
|
||||
val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } }
|
||||
@@ -227,6 +228,16 @@ class AppPreferences {
|
||||
|
||||
val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, appPlatform.isAndroid)
|
||||
|
||||
val hintPreferences: List<Pair<SharedPreference<Boolean>, Boolean>> = listOf(
|
||||
laNoticeShown to false,
|
||||
oneHandUICardShown to false,
|
||||
liveMessageAlertShown to false,
|
||||
showHiddenProfilesNotice to true,
|
||||
showMuteProfileAlert to true,
|
||||
showDeleteConversationNotice to true,
|
||||
showDeleteContactNotice to true,
|
||||
)
|
||||
|
||||
private fun mkIntPreference(prefName: String, default: Int) =
|
||||
SharedPreference(
|
||||
get = fun() = settings.getInt(prefName, default),
|
||||
@@ -372,6 +383,7 @@ class AppPreferences {
|
||||
private const val SHARED_PREFS_INCOGNITO = "Incognito"
|
||||
private const val SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN = "LiveMessageAlertShown"
|
||||
private const val SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE = "ShowHiddenProfilesNotice"
|
||||
private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown"
|
||||
private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert"
|
||||
private const val SHARED_PREFS_STORE_DB_PASSPHRASE = "StoreDBPassphrase"
|
||||
private const val SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE = "InitialRandomDBPassphrase"
|
||||
@@ -519,7 +531,10 @@ object ChatController {
|
||||
suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? {
|
||||
Log.d(TAG, "startChatWithTemporaryDatabase")
|
||||
val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl)
|
||||
apiSetNetworkConfig(netCfg, ctrl)
|
||||
if (!apiSetNetworkConfig(netCfg, ctrl)) {
|
||||
Log.e(TAG, "Error setting network config, stopping migration")
|
||||
return null
|
||||
}
|
||||
apiSetAppFilePaths(
|
||||
getMigrationTempFilesDirectory().absolutePath,
|
||||
getMigrationTempFilesDirectory().absolutePath,
|
||||
@@ -5759,7 +5774,7 @@ sealed class DatabaseError {
|
||||
@Serializable
|
||||
sealed class SQLiteError {
|
||||
@Serializable @SerialName("errorNotADatabase") object ErrorNotADatabase: SQLiteError()
|
||||
@Serializable @SerialName("error") class Error(val error: String): SQLiteError()
|
||||
@Serializable @SerialName("error") class Error(val dbError: String): SQLiteError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
+92
-51
@@ -31,8 +31,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
@@ -545,20 +544,22 @@ fun ChatInfoLayout(
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Box(
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
SearchButton(chat, contact, close, onSearchClicked)
|
||||
Spacer(Modifier.weight(1f))
|
||||
AudioCallButton(chat, contact)
|
||||
Spacer(Modifier.weight(1f))
|
||||
VideoButton(chat, contact)
|
||||
Spacer(Modifier.weight(1f))
|
||||
MuteButton(chat, contact)
|
||||
Row(
|
||||
Modifier
|
||||
.widthIn(max = 460.dp)
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
SearchButton(modifier = Modifier.fillMaxWidth(0.25f), chat, contact, close, onSearchClicked)
|
||||
AudioCallButton(modifier = Modifier.fillMaxWidth(0.33f), chat, contact)
|
||||
VideoButton(modifier = Modifier.fillMaxWidth(0.5f), chat, contact)
|
||||
MuteButton(modifier = Modifier.fillMaxWidth(1f), chat, contact)
|
||||
}
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
@@ -779,9 +780,16 @@ fun LocalAliasEditor(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchButton(chat: Chat, contact: Contact, close: () -> Unit, onSearchClicked: () -> Unit) {
|
||||
fun SearchButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
contact: Contact,
|
||||
close: () -> Unit,
|
||||
onSearchClicked: () -> Unit
|
||||
) {
|
||||
val disabled = !contact.ready || chat.chatItems.isEmpty()
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = painterResource(MR.images.ic_search),
|
||||
title = generalGetString(MR.strings.info_view_search_button),
|
||||
disabled = disabled,
|
||||
@@ -796,11 +804,16 @@ fun SearchButton(chat: Chat, contact: Contact, close: () -> Unit, onSearchClicke
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MuteButton(chat: Chat, contact: Contact) {
|
||||
fun MuteButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
contact: Contact
|
||||
) {
|
||||
val ntfsEnabled = remember { mutableStateOf(chat.chatInfo.ntfsEnabled) }
|
||||
val disabled = !contact.ready || !contact.active
|
||||
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = if (ntfsEnabled.value) painterResource(MR.images.ic_notifications_off) else painterResource(MR.images.ic_notifications),
|
||||
title = if (ntfsEnabled.value) stringResource(MR.strings.mute_chat) else stringResource(MR.strings.unmute_chat),
|
||||
disabled = disabled,
|
||||
@@ -812,8 +825,13 @@ fun MuteButton(chat: Chat, contact: Contact) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AudioCallButton(chat: Chat, contact: Contact) {
|
||||
fun AudioCallButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
contact: Contact
|
||||
) {
|
||||
CallButton(
|
||||
modifier = modifier,
|
||||
chat,
|
||||
contact,
|
||||
icon = painterResource(MR.images.ic_call),
|
||||
@@ -823,8 +841,13 @@ fun AudioCallButton(chat: Chat, contact: Contact) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VideoButton(chat: Chat, contact: Contact) {
|
||||
fun VideoButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
contact: Contact
|
||||
) {
|
||||
CallButton(
|
||||
modifier = modifier,
|
||||
chat,
|
||||
contact,
|
||||
icon = painterResource(MR.images.ic_videocam),
|
||||
@@ -834,7 +857,14 @@ fun VideoButton(chat: Chat, contact: Contact) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CallButton(chat: Chat, contact: Contact, icon: Painter, title: String, mediaType: CallMediaType) {
|
||||
fun CallButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
contact: Contact,
|
||||
icon: Painter,
|
||||
title: String,
|
||||
mediaType: CallMediaType
|
||||
) {
|
||||
val canCall = contact.ready && contact.active && contact.mergedPreferences.calls.enabled.forUser && chatModel.activeCall.value == null
|
||||
val needToAllowCallsToContact = remember(chat.chatInfo) {
|
||||
chat.chatInfo is ChatInfo.Direct && with(chat.chatInfo.contact.mergedPreferences.calls) {
|
||||
@@ -845,6 +875,7 @@ fun CallButton(chat: Chat, contact: Contact, icon: Painter, title: String, media
|
||||
val allowedCallsByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Calls) }
|
||||
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = icon,
|
||||
title = title,
|
||||
disabled = chatModel.activeCall.value != null,
|
||||
@@ -907,42 +938,52 @@ private fun showCallsProhibitedAlert() {
|
||||
)
|
||||
}
|
||||
|
||||
// for ChatInfoView (it has most buttons - 4) we use Spacer(Modifier.weight(1f)) to fit,
|
||||
// for GroupChat And GroupMemberInfoViews (2 to 3 buttons) we use this as approximately equal to spacing in ChatInfoView
|
||||
val INFO_VIEW_BUTTONS_PADDING = 36.dp
|
||||
|
||||
@Composable
|
||||
fun InfoViewActionButton(icon: Painter, title: String, disabled: Boolean, disabledLook: Boolean, onClick: () -> Unit) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
enabled = !disabled
|
||||
fun InfoViewActionButton(
|
||||
modifier: Modifier,
|
||||
icon: Painter,
|
||||
title: String,
|
||||
disabled: Boolean,
|
||||
disabledLook: Boolean,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(modifier) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
if (disabledLook) MaterialTheme.colors.secondaryVariant else MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
)
|
||||
.padding(16.dp)
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
enabled = !disabled
|
||||
) {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
Modifier.size(24.dp * fontSizeSqrtMultiplier),
|
||||
tint = if (disabledLook) MaterialTheme.colors.secondary else MaterialTheme.colors.onPrimary
|
||||
)
|
||||
Box(
|
||||
Modifier
|
||||
.size(56.dp)
|
||||
.background(
|
||||
if (disabledLook) MaterialTheme.colors.secondaryVariant else MaterialTheme.colors.primary,
|
||||
shape = CircleShape
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
Modifier.size(24.dp * fontSizeSqrtMultiplier),
|
||||
tint = if (disabledLook) MaterialTheme.colors.secondary else MaterialTheme.colors.onPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
title.capitalize(Locale.current),
|
||||
Modifier.padding(top = DEFAULT_SPACE_AFTER_ICON),
|
||||
style = MaterialTheme.typography.subtitle2.copy(fontWeight = FontWeight.Normal, fontSize = 12.sp),
|
||||
color = MaterialTheme.colors.secondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Text(
|
||||
title.capitalize(Locale.current),
|
||||
style = MaterialTheme.typography.subtitle2.copy(fontWeight = FontWeight.Normal),
|
||||
color = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier.padding(top = DEFAULT_SPACE_AFTER_ICON)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+39
-17
@@ -23,8 +23,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatModel.withChats
|
||||
import chat.simplex.common.ui.theme.*
|
||||
@@ -184,10 +183,17 @@ private fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMembe
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SearchButton(chat: Chat, group: GroupInfo, close: () -> Unit, onSearchClicked: () -> Unit) {
|
||||
fun SearchButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
group: GroupInfo,
|
||||
close: () -> Unit,
|
||||
onSearchClicked: () -> Unit
|
||||
) {
|
||||
val disabled = !group.ready || chat.chatItems.isEmpty()
|
||||
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = painterResource(MR.images.ic_search),
|
||||
title = generalGetString(MR.strings.info_view_search_button),
|
||||
disabled = disabled,
|
||||
@@ -202,10 +208,15 @@ fun SearchButton(chat: Chat, group: GroupInfo, close: () -> Unit, onSearchClicke
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MuteButton(chat: Chat, groupInfo: GroupInfo) {
|
||||
fun MuteButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
groupInfo: GroupInfo
|
||||
) {
|
||||
val ntfsEnabled = remember { mutableStateOf(chat.chatInfo.ntfsEnabled) }
|
||||
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = if (ntfsEnabled.value) painterResource(MR.images.ic_notifications_off) else painterResource(MR.images.ic_notifications),
|
||||
title = if (ntfsEnabled.value) stringResource(MR.strings.mute_chat) else stringResource(MR.strings.unmute_chat),
|
||||
disabled = !groupInfo.ready,
|
||||
@@ -217,8 +228,13 @@ fun MuteButton(chat: Chat, groupInfo: GroupInfo) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddGroupMembersButton(chat: Chat, groupInfo: GroupInfo) {
|
||||
fun AddGroupMembersButton(
|
||||
modifier: Modifier,
|
||||
chat: Chat,
|
||||
groupInfo: GroupInfo
|
||||
) {
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = if (groupInfo.incognito) painterResource(MR.images.ic_add_link) else painterResource(MR.images.ic_person_add_500),
|
||||
title = stringResource(MR.strings.action_button_add_members),
|
||||
disabled = !groupInfo.ready,
|
||||
@@ -278,20 +294,26 @@ fun GroupChatInfoLayout(
|
||||
}
|
||||
SectionSpacer()
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Box(
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
SearchButton(chat, groupInfo, close, onSearchClicked)
|
||||
if (groupInfo.canAddMembers) {
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
AddGroupMembersButton(chat, groupInfo)
|
||||
Row(
|
||||
Modifier
|
||||
.widthIn(max = if (groupInfo.canAddMembers) 320.dp else 230.dp)
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (groupInfo.canAddMembers) {
|
||||
SearchButton(modifier = Modifier.fillMaxWidth(0.33f), chat, groupInfo, close, onSearchClicked)
|
||||
AddGroupMembersButton(modifier = Modifier.fillMaxWidth(0.5f), chat, groupInfo)
|
||||
MuteButton(modifier = Modifier.fillMaxWidth(1f), chat, groupInfo)
|
||||
} else {
|
||||
SearchButton(modifier = Modifier.fillMaxWidth(0.5f), chat, groupInfo, close, onSearchClicked)
|
||||
MuteButton(modifier = Modifier.fillMaxWidth(1f), chat, groupInfo)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
MuteButton(chat, groupInfo)
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
+44
-40
@@ -327,49 +327,49 @@ fun GroupMemberInfoLayout(
|
||||
|
||||
val contactId = member.memberContactId
|
||||
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Box(
|
||||
Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val knownChat = if (contactId != null) knownDirectChat(contactId) else null
|
||||
if (knownChat != null) {
|
||||
val (chat, contact) = knownChat
|
||||
OpenChatButton(onClick = { openDirectChat(contact.contactId) })
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
AudioCallButton(chat, contact)
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
VideoButton(chat, contact)
|
||||
} else if (groupInfo.fullGroupPreferences.directMessages.on(groupInfo.membership)) {
|
||||
if (contactId != null) {
|
||||
OpenChatButton(onClick = { openDirectChat(contactId) }) // legacy - only relevant for direct contacts created when joining group
|
||||
} else {
|
||||
OpenChatButton(onClick = { createMemberContact() })
|
||||
Row(
|
||||
Modifier
|
||||
.widthIn(max = 320.dp)
|
||||
.padding(horizontal = DEFAULT_PADDING),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val knownChat = if (contactId != null) knownDirectChat(contactId) else null
|
||||
if (knownChat != null) {
|
||||
val (chat, contact) = knownChat
|
||||
OpenChatButton(modifier = Modifier.fillMaxWidth(0.33f), onClick = { openDirectChat(contact.contactId) })
|
||||
AudioCallButton(modifier = Modifier.fillMaxWidth(0.5f), chat, contact)
|
||||
VideoButton(modifier = Modifier.fillMaxWidth(1f), chat, contact)
|
||||
} else if (groupInfo.fullGroupPreferences.directMessages.on(groupInfo.membership)) {
|
||||
if (contactId != null) {
|
||||
OpenChatButton(modifier = Modifier.fillMaxWidth(0.33f), onClick = { openDirectChat(contactId) }) // legacy - only relevant for direct contacts created when joining group
|
||||
} else {
|
||||
OpenChatButton(modifier = Modifier.fillMaxWidth(0.33f), onClick = { createMemberContact() })
|
||||
}
|
||||
InfoViewActionButton(modifier = Modifier.fillMaxWidth(0.5f), painterResource(MR.images.ic_call), generalGetString(MR.strings.info_view_call_button), disabled = false, disabledLook = true, onClick = {
|
||||
showSendMessageToEnableCallsAlert()
|
||||
})
|
||||
InfoViewActionButton(modifier = Modifier.fillMaxWidth(1f), painterResource(MR.images.ic_videocam), generalGetString(MR.strings.info_view_video_button), disabled = false, disabledLook = true, onClick = {
|
||||
showSendMessageToEnableCallsAlert()
|
||||
})
|
||||
} else { // no known contact chat && directMessages are off
|
||||
InfoViewActionButton(modifier = Modifier.fillMaxWidth(0.33f), painterResource(MR.images.ic_chat_bubble), generalGetString(MR.strings.info_view_message_button), disabled = false, disabledLook = true, onClick = {
|
||||
showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_send_message_to_member_alert_title))
|
||||
})
|
||||
InfoViewActionButton(modifier = Modifier.fillMaxWidth(0.5f), painterResource(MR.images.ic_call), generalGetString(MR.strings.info_view_call_button), disabled = false, disabledLook = true, onClick = {
|
||||
showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title))
|
||||
})
|
||||
InfoViewActionButton(modifier = Modifier.fillMaxWidth(1f), painterResource(MR.images.ic_videocam), generalGetString(MR.strings.info_view_video_button), disabled = false, disabledLook = true, onClick = {
|
||||
showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title))
|
||||
})
|
||||
}
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
InfoViewActionButton(painterResource(MR.images.ic_call), generalGetString(MR.strings.info_view_call_button), disabled = false, disabledLook = true, onClick = {
|
||||
showSendMessageToEnableCallsAlert()
|
||||
})
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
InfoViewActionButton(painterResource(MR.images.ic_videocam), generalGetString(MR.strings.info_view_video_button), disabled = false, disabledLook = true, onClick = {
|
||||
showSendMessageToEnableCallsAlert()
|
||||
})
|
||||
} else { // no known contact chat && directMessages are off
|
||||
InfoViewActionButton(painterResource(MR.images.ic_chat_bubble), generalGetString(MR.strings.info_view_message_button), disabled = false, disabledLook = true, onClick = {
|
||||
showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_send_message_to_member_alert_title))
|
||||
})
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
InfoViewActionButton(painterResource(MR.images.ic_call), generalGetString(MR.strings.info_view_call_button), disabled = false, disabledLook = true, onClick = {
|
||||
showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title))
|
||||
})
|
||||
Spacer(Modifier.width(INFO_VIEW_BUTTONS_PADDING))
|
||||
InfoViewActionButton(painterResource(MR.images.ic_videocam), generalGetString(MR.strings.info_view_video_button), disabled = false, disabledLook = true, onClick = {
|
||||
showDirectMessagesProhibitedAlert(generalGetString(MR.strings.cant_call_member_alert_title))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
if (member.memberActive) {
|
||||
@@ -580,8 +580,12 @@ fun RemoveMemberButton(onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OpenChatButton(onClick: () -> Unit) {
|
||||
fun OpenChatButton(
|
||||
modifier: Modifier,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
InfoViewActionButton(
|
||||
modifier = modifier,
|
||||
icon = painterResource(MR.images.ic_chat_bubble),
|
||||
title = generalGetString(MR.strings.info_view_message_button),
|
||||
disabled = false,
|
||||
|
||||
+72
-3
@@ -12,6 +12,7 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.*
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
@@ -30,11 +31,11 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.onboarding.WhatsNewView
|
||||
import chat.simplex.common.views.onboarding.shouldShowWhatsNew
|
||||
import chat.simplex.common.views.usersettings.SettingsView
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.call.Call
|
||||
import chat.simplex.common.views.chat.item.CIFileViewScope
|
||||
import chat.simplex.common.views.newchat.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -70,10 +71,72 @@ private fun showNewChatSheet(oneHandUI: State<Boolean>) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ToggleChatListCard() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.clip(RoundedCornerShape(18.dp))
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.appColors.sentMessage)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().matchParentSize().padding(5.dp),
|
||||
contentAlignment = Alignment.TopEnd
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
appPrefs.oneHandUICardShown.set(true)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.one_hand_ui),
|
||||
text = generalGetString(MR.strings.one_hand_ui_change_instruction),
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_close), stringResource(MR.strings.back), tint = MaterialTheme.colors.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = DEFAULT_PADDING)
|
||||
.padding(top = DEFAULT_PADDING)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(MR.strings.one_hand_ui_card_title), style = MaterialTheme.typography.h3)
|
||||
}
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(top = 6.dp, bottom = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(stringResource(MR.strings.one_hand_ui), Modifier.weight(10f), style = MaterialTheme.typography.body1)
|
||||
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
|
||||
SharedPreferenceToggle(
|
||||
appPrefs.oneHandUI,
|
||||
enabled = true,
|
||||
onChange = {
|
||||
val c = CurrentColors.value.colors
|
||||
platform.androidSetStatusAndNavBarColors(c.isLight, c.background, !appPrefs.oneHandUI.get(), appPrefs.oneHandUI.get())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerformLA: (Boolean) -> Unit, stopped: Boolean) {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (shouldShowWhatsNew(chatModel)) {
|
||||
delay(1000L)
|
||||
@@ -337,7 +400,7 @@ fun SubscriptionStatusIndicator(click: (() -> Unit)) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
suspend fun setSubsTotal() {
|
||||
if (chatModel.currentUser.value != null) {
|
||||
if (chatModel.currentUser.value != null && chatModel.controller.hasChatCtrl() && chatModel.chatRunning.value == true) {
|
||||
val r = chatModel.controller.getAgentSubsTotal(chatModel.remoteHostId())
|
||||
if (r != null) {
|
||||
subs = r.first
|
||||
@@ -547,6 +610,7 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
|
||||
var previousScrollOffset by remember { mutableStateOf(0) }
|
||||
val keyboardState by getKeyboardState()
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state }
|
||||
|
||||
LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) {
|
||||
val currentIndex = listState.firstVisibleItemIndex
|
||||
@@ -610,6 +674,11 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal
|
||||
}
|
||||
}
|
||||
}
|
||||
if (appPlatform.isAndroid && !oneHandUICardShown.value) {
|
||||
item {
|
||||
ToggleChatListCard()
|
||||
}
|
||||
}
|
||||
itemsIndexed(chats, key = { _, chat -> chat.remoteHostId to chat.id }) { index, chat ->
|
||||
val nextChatSelected = remember(chat.id, chats) { derivedStateOf {
|
||||
chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value
|
||||
|
||||
+8
@@ -16,6 +16,7 @@ import chat.simplex.res.MR
|
||||
import com.charleskorn.kaml.decodeFromStream
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.io.*
|
||||
import java.net.URI
|
||||
@@ -535,6 +536,13 @@ fun TextUnit.toDp(): Dp {
|
||||
return Dp(value * LocalDensity.current.fontScale)
|
||||
}
|
||||
|
||||
fun <T> Flow<T>.throttleLatest(delayMillis: Long): Flow<T> = this
|
||||
.conflate()
|
||||
.transform {
|
||||
emit(it)
|
||||
delay(delayMillis)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) {
|
||||
DisposableEffect(Unit) {
|
||||
|
||||
+4
-1
@@ -516,8 +516,11 @@ suspend fun initTemporaryDatabase(tempDatabaseFile: File, netCfg: NetCfg): Pair<
|
||||
try {
|
||||
if (ctrl != null) {
|
||||
val user = startChatWithTemporaryDatabase(ctrl, netCfg)
|
||||
return if (user != null) ctrl to user else null
|
||||
if (user != null) return ctrl to user
|
||||
chatCloseStore(ctrl)
|
||||
}
|
||||
File(tempDatabaseFile.absolutePath + "_chat.db").delete()
|
||||
File(tempDatabaseFile.absolutePath + "_agent.db").delete()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, "Error while starting chat in temporary database: ${e.stackTraceToString()}")
|
||||
}
|
||||
|
||||
+3
-1
@@ -515,7 +515,9 @@ private fun MutableState<MigrationToState?>.prepareDatabase(
|
||||
withLongRunningApi {
|
||||
val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, netCfg)
|
||||
if (ctrlAndUser == null) {
|
||||
state = MigrationToState.DownloadFailed(0, link, archivePath(), netCfg)
|
||||
// Probably, something wrong with network config or database initialization, let's start from scratch
|
||||
state = MigrationToState.PasteOrScanLink
|
||||
MigrationToDeviceState.save(null)
|
||||
return@withLongRunningApi
|
||||
}
|
||||
|
||||
|
||||
+4
-6
@@ -250,7 +250,6 @@ private fun NewChatSheetLayout(
|
||||
icon = it.first,
|
||||
text = it.second,
|
||||
click = it.third,
|
||||
extraPadding = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -283,7 +282,7 @@ private fun NewChatSheetLayout(
|
||||
contentDescription = stringResource(MR.strings.deleted_chats),
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
)
|
||||
TextIconSpaced(extraPadding = true)
|
||||
TextIconSpaced(false)
|
||||
Text(text = stringResource(MR.strings.deleted_chats), color = MaterialTheme.colors.onBackground)
|
||||
}
|
||||
}
|
||||
@@ -330,14 +329,13 @@ private fun NewChatButton(
|
||||
text: String,
|
||||
click: () -> Unit,
|
||||
textColor: Color = Color.Unspecified,
|
||||
iconColor: Color = MaterialTheme.colors.secondary,
|
||||
disabled: Boolean = false,
|
||||
extraPadding: Boolean = false,
|
||||
iconColor: Color = MaterialTheme.colors.primary,
|
||||
disabled: Boolean = false
|
||||
) {
|
||||
SectionItemView(click, disabled = disabled) {
|
||||
Row {
|
||||
Icon(icon, text, tint = if (disabled) MaterialTheme.colors.secondary else iconColor)
|
||||
TextIconSpaced(extraPadding)
|
||||
TextIconSpaced(false)
|
||||
Text(text, color = if (disabled) MaterialTheme.colors.secondary else textColor)
|
||||
}
|
||||
}
|
||||
|
||||
+43
-21
@@ -30,7 +30,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
|
||||
val currentVersion = remember { mutableStateOf(versionDescriptions.lastIndex) }
|
||||
|
||||
@Composable
|
||||
fun featureDescription(icon: Painter, titleId: StringResource, descrId: StringResource, link: String?) {
|
||||
fun featureDescription(icon: ImageResource?, titleId: StringResource, descrId: StringResource?, link: String?, subfeatures: List<Pair<ImageResource, StringResource>>) {
|
||||
@Composable
|
||||
fun linkButton(link: String) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
@@ -47,7 +47,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
) {
|
||||
Icon(icon, stringResource(titleId), tint = MaterialTheme.colors.secondary)
|
||||
if (icon != null) Icon(painterResource(icon), stringResource(titleId), tint = MaterialTheme.colors.secondary)
|
||||
Text(
|
||||
generalGetString(titleId),
|
||||
maxLines = 2,
|
||||
@@ -59,7 +59,17 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
|
||||
linkButton(link)
|
||||
}
|
||||
}
|
||||
Text(generalGetString(descrId), fontSize = 15.sp)
|
||||
if (descrId != null) Text(generalGetString(descrId), fontSize = 15.sp)
|
||||
for ((si, sd) in subfeatures) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
) {
|
||||
Icon(painterResource(si), stringResource(sd), tint = MaterialTheme.colors.secondary)
|
||||
Text(generalGetString(sd), fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +126,7 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) {
|
||||
|
||||
v.features.forEach { feature ->
|
||||
if (feature.show) {
|
||||
featureDescription(painterResource(feature.icon), feature.titleId, feature.descrId, feature.link)
|
||||
featureDescription(feature.icon, feature.titleId, feature.descrId, feature.link, feature.subfeatures)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,9 +167,10 @@ fun ReadMoreButton(url: String) {
|
||||
}
|
||||
|
||||
private data class FeatureDescription(
|
||||
val icon: ImageResource,
|
||||
val icon: ImageResource?,
|
||||
val titleId: StringResource,
|
||||
val descrId: StringResource,
|
||||
val descrId: StringResource?,
|
||||
var subfeatures: List<Pair<ImageResource, StringResource>> = listOf(),
|
||||
val link: String? = null,
|
||||
val show: Boolean = true
|
||||
)
|
||||
@@ -595,14 +606,24 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
post = "https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html",
|
||||
features = listOf(
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_settings_ethernet,
|
||||
titleId = MR.strings.v5_8_private_routing,
|
||||
descrId = MR.strings.v6_0_private_routing_descr
|
||||
icon = null,
|
||||
titleId = MR.strings.v6_0_new_chat_experience,
|
||||
descrId = null,
|
||||
subfeatures = listOf(
|
||||
MR.images.ic_add_link to MR.strings.v6_0_connect_faster_descr,
|
||||
MR.images.ic_inventory_2 to MR.strings.v6_0_your_contacts_descr,
|
||||
MR.images.ic_delete to MR.strings.v6_0_delete_many_messages_descr,
|
||||
MR.images.ic_match_case to MR.strings.v6_0_increase_font_size
|
||||
)
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_id_card,
|
||||
titleId = MR.strings.v6_0_your_contacts,
|
||||
descrId = MR.strings.v6_0_your_contacts_descr
|
||||
icon = null,
|
||||
titleId = MR.strings.v6_0_new_media_options,
|
||||
descrId = null,
|
||||
subfeatures = listOf(
|
||||
MR.images.ic_play_arrow_filled to MR.strings.v6_0_chat_list_media,
|
||||
MR.images.ic_blur_on to MR.strings.v6_0_privacy_blur,
|
||||
)
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_toast,
|
||||
@@ -611,20 +632,21 @@ private val versionDescriptions: List<VersionDescription> = listOf(
|
||||
show = appPlatform.isAndroid
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_link,
|
||||
titleId = MR.strings.v6_0_connect_faster,
|
||||
descrId = MR.strings.v6_0_connect_faster_descr
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_delete,
|
||||
titleId = MR.strings.v6_0_delete_many_messages,
|
||||
descrId = MR.strings.v6_0_delete_many_messages_descr
|
||||
icon = MR.images.ic_settings_ethernet,
|
||||
titleId = MR.strings.v5_8_private_routing,
|
||||
descrId = MR.strings.v6_0_private_routing_descr
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_wifi_tethering,
|
||||
titleId = MR.strings.v6_0_connection_servers_status,
|
||||
descrId = MR.strings.v6_0_connection_servers_status_descr
|
||||
)
|
||||
),
|
||||
FeatureDescription(
|
||||
icon = MR.images.ic_upgrade,
|
||||
titleId = MR.strings.v6_0_upgrade_app,
|
||||
descrId = MR.strings.v6_0_upgrade_app_descr,
|
||||
show = appPlatform.isDesktop
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
+3
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import chat.simplex.common.model.*
|
||||
@@ -30,9 +31,11 @@ fun DeveloperView(
|
||||
AppBarTitle(stringResource(MR.strings.settings_developer_tools))
|
||||
val developerTools = m.controller.appPrefs.developerTools
|
||||
val devTools = remember { developerTools.state }
|
||||
val unchangedHints = mutableStateOf(unchangedHintPreferences())
|
||||
SectionView {
|
||||
InstallTerminalAppItem(uriHandler)
|
||||
ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential), showCustomModal { it, close -> TerminalView(it, close) }) }
|
||||
ResetHintsItem(unchangedHints)
|
||||
SettingsPreferenceItem(painterResource(MR.images.ic_code), stringResource(MR.strings.show_developer_options), developerTools)
|
||||
SectionTextFooter(
|
||||
generalGetString(if (devTools.value) MR.strings.show_dev_options else MR.strings.hide_dev_options) + " " +
|
||||
|
||||
+25
@@ -330,6 +330,31 @@ fun ChatLockItem(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable fun ResetHintsItem(unchangedHints: MutableState<Boolean>) {
|
||||
SectionItemView({
|
||||
resetHintPreferences()
|
||||
unchangedHints.value = true
|
||||
}, disabled = unchangedHints.value) {
|
||||
Icon(
|
||||
painter = painterResource(MR.images.ic_lightbulb),
|
||||
contentDescription = "Lightbulb",
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
)
|
||||
TextIconSpaced()
|
||||
Text(generalGetString(MR.strings.reset_all_hints), color = if (unchangedHints.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetHintPreferences() {
|
||||
for ((pref, def) in appPreferences.hintPreferences) {
|
||||
pref.set(def)
|
||||
}
|
||||
}
|
||||
|
||||
fun unchangedHintPreferences(): Boolean = appPreferences.hintPreferences.all { (pref, def) ->
|
||||
pref.state.value == def
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppVersionItem(showVersion: () -> Unit) {
|
||||
SectionItemViewWithIcon(showVersion) { AppVersionText() }
|
||||
|
||||
@@ -739,6 +739,7 @@
|
||||
<string name="xftp_servers_other">Other XFTP servers</string>
|
||||
<string name="subscription_percentage">Show percentage</string>
|
||||
<string name="install_simplex_chat_for_terminal">Install SimpleX Chat for terminal</string>
|
||||
<string name="reset_all_hints">Reset all hints</string>
|
||||
<string name="star_on_github">Star on GitHub</string>
|
||||
<string name="contribute">Contribute</string>
|
||||
<string name="rate_the_app">Rate the app</string>
|
||||
@@ -1294,6 +1295,8 @@
|
||||
<string name="incompatible_database_version">Incompatible database version</string>
|
||||
<string name="confirm_database_upgrades">Confirm database upgrades</string>
|
||||
<string name="one_hand_ui">Reachable chat toolbar</string>
|
||||
<string name="one_hand_ui_card_title">Toggle chat list:</string>
|
||||
<string name="one_hand_ui_change_instruction">You can change it in Appearance settings.</string>
|
||||
<string name="terminal_always_visible">Show console in new window</string>
|
||||
<string name="chat_list_always_visible">Show chat list in new window</string>
|
||||
<string name="invalid_migration_confirmation">Invalid migration confirmation</string>
|
||||
@@ -1974,15 +1977,19 @@
|
||||
<string name="v5_8_message_delivery">Improved message delivery</string>
|
||||
<string name="v5_8_message_delivery_descr">With reduced battery usage.</string>
|
||||
<string name="v5_8_persian_ui">Persian UI</string>
|
||||
<string name="v6_0_new_chat_experience">New chat experience 🎉</string>
|
||||
<string name="v6_0_new_media_options">New media options</string>
|
||||
<string name="v6_0_private_routing_descr">It protects your IP address and connections.</string>
|
||||
<string name="v6_0_your_contacts">Your contacts your way</string>
|
||||
<string name="v6_0_your_contacts_descr">- Search contacts when starting chat.\n- Archive contacts to chat later.</string>
|
||||
<string name="v6_0_reachable_chat_toolbar">Reachable chat toolbar 👋</string>
|
||||
<string name="v6_0_your_contacts_descr">Archive contacts to chat later.</string>
|
||||
<string name="v6_0_reachable_chat_toolbar">Reachable chat toolbar</string>
|
||||
<string name="v6_0_reachable_chat_toolbar_descr">Use the app with one hand.</string>
|
||||
<string name="v6_0_connect_faster">Connect to your friends faster</string>
|
||||
<string name="v6_0_connect_faster_descr">Even when they are offline.</string>
|
||||
<string name="v6_0_delete_many_messages">Moderate like a pro ✋</string>
|
||||
<string name="v6_0_connect_faster_descr">Connect to your friends faster.</string>
|
||||
<string name="v6_0_delete_many_messages_descr">Delete up to 20 messages at once.</string>
|
||||
<string name="v6_0_chat_list_media">Play from the chat list.</string>
|
||||
<string name="v6_0_privacy_blur">Blur for better privacy.</string>
|
||||
<string name="v6_0_increase_font_size">Increase font size.</string>
|
||||
<string name="v6_0_upgrade_app">Upgrade app automatically</string>
|
||||
<string name="v6_0_upgrade_app_descr">Download new versions from GitHub.</string>
|
||||
<string name="v6_0_connection_servers_status">Control your network</string>
|
||||
<string name="v6_0_connection_servers_status_descr">Connection and servers status.</string>
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368">
|
||||
<path
|
||||
d="M480-84.5q-33.5 0-56.75-23.25T400-164.5h160q0 33.5-23.25 56.75T480-84.5Zm-160-143V-285h320v57.5H320Zm4.5-119.5q-65-42.5-102.75-106T184-595.87q0-120.39 87.76-208.26 87.77-87.87 208-87.87Q600-892 688-804.13q88 87.87 88 208.26 0 79.37-37.25 142.87T635.5-347h-311Zm21.32-57.5h269.3q47.88-32 75.63-82.51 27.75-50.51 27.75-108.95 0-98.04-70.04-168.29-70.04-70.25-168.37-70.25-98.34 0-168.46 70.12-70.13 70.12-70.13 168.57 0 58.49 28 108.9 28 50.41 76.32 82.41Zm134.18 0Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 604 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000">
|
||||
<path
|
||||
d="M246-382.5 209-277q-2 6.5-8.25 10.75T187-262q-12 0-18.5-9.25t-2-20.75L308-681.5q3-7.5 8.75-11.5t13.25-4h18q7.5 0 13.25 4t8.75 11.5l142.5 389q4.5 11.5-2.5 21t-19.5 9.5q-7.5 0-13.75-3.75t-8.25-11.75l-37-105H246Zm15.5-42h155l-77-217.5H338l-76.5 217.5Zm403 172.5q-50 0-82.5-26t-32.5-69.5q0-47.5 35-75.75t93-28.25q24.5 0 47 4.5t39 13v-20q0-38-20.25-58.25T684-532.5q-17.5 0-34.5 5.25T618-511q-8 5.5-15.75 3T590-517.5q-4.5-6.5-3.25-15T597-547q20-13 42.25-18.75T685-571.5q59.5 0 90.75 30.75T807-450v166.5q0 9-6.25 15.25T786-262q-9 0-15.25-6.25T764.5-283v-21H761q-16 26.5-43.5 39.25t-53 12.75Zm-.5-36.5q36.5 0 68.5-30.25t32-77.75q-16-8-38-13t-43.5-5q-42 0-65.5 17.5T594-348q0 27 20.5 43.25T664-288.5Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 831 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000">
|
||||
<path
|
||||
d="M310-165q-12.5 0-20.5-8.25t-8-20.75q0-12 8-20.25t20.5-8.25h340q12.5 0 20.75 8.25T679-194q0 12.5-8.25 20.75T650-165H310Zm170-165q-12.5 0-20.5-8.25t-8-20.75v-327L349-583.5q-8.5 8.5-20 8.5t-20-8.5q-9-9-9-20.25t9-20.25l151-151q4.5-4.5 9.25-6.75T480-784q6 0 10.75 2.25t9.75 6.75l151 151q8.5 9 8.5 20.25T651.5-584q-9 9-20.25 9T611-584L509-686v327q0 12.5-8.25 20.75T480-330Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -26,11 +26,11 @@ android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
android.version_name=6.0-beta.2
|
||||
android.version_code=227
|
||||
android.version_name=6.0-beta.3
|
||||
android.version_code=228
|
||||
|
||||
desktop.version_name=6.0-beta.2
|
||||
desktop.version_code=58
|
||||
desktop.version_name=6.0-beta.3
|
||||
desktop.version_code=59
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
Reference in New Issue
Block a user