refactor and simplify

This commit is contained in:
Evgeny @ SimpleX Chat
2026-05-19 19:03:52 +00:00
parent 4a3f7b7b72
commit 13de33c511
60 changed files with 837 additions and 886 deletions
@@ -1,6 +1,5 @@
package chat.simplex.common.views.chatlist
import SectionDivider
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
@@ -9,7 +8,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import chat.simplex.common.platform.onRightClick
import LocalInSectionCard
import chat.simplex.common.views.helpers.*
@Composable
@@ -40,9 +38,5 @@ actual fun ChatListNavLinkLayout(
DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems)
}
}
if (LocalInSectionCard.current) {
SectionDivider()
} else {
Divider(Modifier.padding(horizontal = 8.dp))
}
Divider(Modifier.padding(horizontal = 8.dp))
}
@@ -126,7 +126,7 @@ fun AppearanceScope.AppearanceLayout(
SectionDividerSpaced()
ProfileImageSection()
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.settings_section_title_icon), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) {
LazyRow {
@@ -152,7 +152,7 @@ fun AppearanceScope.AppearanceLayout(
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
FontScaleSection()
SectionBottomSpacer()
@@ -603,13 +603,6 @@ data class ThemeModeOverride (
// cards above. DARK/BLACK: palette bg (cards already raised via founder's
// formula in Section.kt). SIMPLEX: gradient bottom stop (darker), since the
// canvas itself is a gradient drawn by themedBackgroundBrush.
//
// Reads CurrentColors.value directly rather than via MaterialTheme/CompositionLocal
// because this helper is called from two scopes: @Composable bodies (SectionView,
// SectionDivider) and DrawScope (sectionItemDivider's drawWithContent). DrawScope
// can't call @Composable getters, so a snapshot read keeps one helper usable in
// both places. State invalidation still works — CurrentColors is a MutableStateFlow,
// reads are tracked by Compose snapshot system.
fun canvasColorForCurrentTheme(): Color {
val theme = CurrentColors.value
val c = theme.colors
@@ -85,7 +85,7 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
Box(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.surface)
modifier = Modifier.fillMaxSize()
) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING_HALF)
@@ -239,7 +239,6 @@ private fun CreateFirstProfileMobile(chatModel: ChatModel, close: () -> Unit) {
val refocusTrigger = remember { mutableStateOf(0) }
ModalView(
close = { onboardingBackAction(chatModel, close) },
background = MaterialTheme.colors.surface,
endButtons = { MigrateButton(refocusTrigger) }
) {
val displayName = rememberSaveable { mutableStateOf("") }
@@ -318,7 +317,6 @@ private fun CreateFirstProfileDesktop(chatModel: ChatModel, close: () -> Unit) {
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
ModalView(
close = { onboardingBackAction(chatModel, close) },
background = MaterialTheme.colors.surface,
endButtons = { MigrateButton(refocusTrigger) }
) {
ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) {
@@ -576,7 +576,7 @@ fun ChatInfoLayout(
SectionSpacer()
if (customUserProfile != null) {
SectionView(generalGetString(MR.strings.incognito)) {
SectionView(generalGetString(MR.strings.incognito), card = true) {
SectionItemViewSpaceBetween {
Text(generalGetString(MR.strings.incognito_random_profile))
Text(customUserProfile.chatViewName, color = Indigo)
@@ -585,7 +585,7 @@ fun ChatInfoLayout(
SectionDividerSpaced()
}
SectionView {
SectionView(card = true) {
if (contact.ready && contact.active) {
if (connectionCode != null) {
VerifyCodeButton(contact.verified, verifyClicked)
@@ -610,35 +610,35 @@ fun ChatInfoLayout(
}
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems)
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
val conn = contact.activeConn
if (conn != null) {
SectionView {
SectionView(card = true) {
InfoRow("E2E encryption", if (conn.connPQEnabled) "Quantum resistant" else "Standard")
SectionDividerSpaced()
}
SectionDividerSpaced()
}
if (contact.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title)) {
SectionView(stringResource(MR.strings.address_section_title), card = true) {
SimpleXLinkQRCode(contact.contactLink)
val clipboard = LocalClipboardManager.current
ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
}
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
val chatSubStatus = chatModel.chatSubStatus.value
if (contact.ready && contact.active && (chatSubStatus != null || cStats != null)) {
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
if (contact.ready && contact.active) {
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers), card = true) {
val chatSubStatus = chatModel.chatSubStatus.value
if (chatSubStatus != null) {
SectionItemView({
AlertManager.shared.showAlertMsg(
@@ -673,14 +673,14 @@ fun ChatInfoLayout(
SectionDividerSpaced(maxBottomPadding = false)
}
SectionView {
SectionView(card = true) {
ClearChatButton(clearChat)
DeleteContactButton(deleteContact)
}
if (developerTools) {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.section_title_for_console)) {
SectionView(title = stringResource(MR.strings.section_title_for_console), card = true) {
InfoRow(stringResource(MR.strings.info_row_local_name), chat.chatInfo.localDisplayName)
InfoRow(stringResource(MR.strings.info_row_database_id), chat.chatInfo.apiId.toString())
SectionItemView({
@@ -4,6 +4,7 @@ import InfoRow
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -248,7 +249,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
@Composable
fun Details() {
AppBarTitle(stringResource(if (ci.localNote) MR.strings.saved_message_title else if (sent) MR.strings.sent_message else MR.strings.received_message))
Column {
SectionView {
InfoRow(stringResource(if (!ci.localNote) MR.strings.info_row_sent_at else MR.strings.info_row_created_at), localTimestamp(ci.meta.itemTs))
if (!sent) {
InfoRow(stringResource(MR.strings.info_row_received_at), localTimestamp(ci.meta.createdAt))
@@ -285,17 +286,17 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
fun HistoryTab() {
ColumnWithScrollBar {
Details()
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
val versions = ciInfo.itemVersions
if (versions.isNotEmpty()) {
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.edit_history), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
versions.forEachIndexed { i, ciVersion ->
ItemVersionView(ciVersion, current = i == 0)
}
}
} else {
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(MR.strings.no_history), color = MaterialTheme.colors.secondary)
}
@@ -310,8 +311,8 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
fun QuoteTab(qi: CIQuote) {
ColumnWithScrollBar {
Details()
SectionDividerSpaced()
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.in_reply_to), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
QuotedMsgView(qi)
}
@@ -324,8 +325,8 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
fun ForwardedFromTab(forwardedFromItem: AChatItem) {
ColumnWithScrollBar {
Details()
SectionDividerSpaced()
Column {
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
SectionView {
Text(stringResource(if (local) MR.strings.saved_from_chat_item_info_title else MR.strings.forwarded_from_chat_item_info_title),
style = MaterialTheme.typography.h2,
modifier = Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING))
@@ -387,17 +388,17 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) {
ColumnWithScrollBar {
Details()
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = true)
val mss = membersStatuses(chatModel, memberDeliveryStatuses)
if (mss.isNotEmpty()) {
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(stringResource(MR.strings.delivery), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = DEFAULT_PADDING))
mss.forEach { (member, status, sentViaProxy) ->
MemberDeliveryStatusView(member, status, sentViaProxy)
}
}
} else {
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(stringResource(MR.strings.no_info_on_delivery), color = MaterialTheme.colors.secondary)
}
@@ -441,7 +442,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
return numTabs
}
Column(Modifier.fillMaxSize().background(MaterialTheme.colors.surface)) {
Column {
if (numTabs() > 1) {
Box(
Modifier
@@ -90,27 +90,27 @@ private fun ContactPreferencesLayout(
TimedMessagesFeatureSection(featuresAllowed, contact.mergedPreferences.timedMessages, timedMessages, onTTLUpdated) { allowed, ttl ->
applyPrefs(featuresAllowed.copy(timedMessagesAllowed = allowed, timedMessagesTTL = ttl ?: currentFeaturesAllowed.timedMessagesTTL))
}
SectionDividerSpaced()
SectionDividerSpaced(true)
val allowFullDeletion: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) }
FeatureSection(ChatFeature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) {
applyPrefs(featuresAllowed.copy(fullDelete = it))
}
SectionDividerSpaced()
SectionDividerSpaced(true)
val allowReactions: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.reactions) }
FeatureSection(ChatFeature.Reactions, user.fullPreferences.reactions.allow, contact.mergedPreferences.reactions, allowReactions) {
applyPrefs(featuresAllowed.copy(reactions = it))
}
SectionDividerSpaced()
SectionDividerSpaced(true)
val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) }
FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
applyPrefs(featuresAllowed.copy(voice = it))
}
SectionDividerSpaced()
SectionDividerSpaced(true)
val allowCalls: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) }
FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) {
applyPrefs(featuresAllowed.copy(calls = it))
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
reset = reset,
save = savePrefs,
@@ -139,6 +139,7 @@ private fun FeatureSection(
icon = feature.iconFilled(),
iconTint = if (enabled.forUser) SimplexGreen else if (enabled.forContact) WarningYellow else Color.Red,
leadingIcon = true,
card = true,
) {
ExposedDropDownSettingRow(
generalGetString(MR.strings.chat_preferences_you_allow),
@@ -174,6 +175,7 @@ private fun TimedMessagesFeatureSection(
icon = ChatFeature.TimedMessages.iconFilled(),
iconTint = if (enabled.forUser) SimplexGreen else if (enabled.forContact) WarningYellow else Color.Red,
leadingIcon = true,
card = true,
) {
PreferenceToggle(
generalGetString(MR.strings.chat_preferences_you_allow),
@@ -205,7 +207,7 @@ private fun TimedMessagesFeatureSection(
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
SectionView {
SectionView(card = true) {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
@@ -175,7 +175,7 @@ fun AddGroupMembersLayout(
)
}
} else {
SectionView {
SectionView(card = true) {
if (creatingGroup) {
SectionItemView(openMemberAdmission) {
Text(stringResource(MR.strings.set_member_admission))
@@ -195,8 +195,8 @@ fun AddGroupMembersLayout(
SectionCustomFooter {
InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.select_contacts)) {
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.select_contacts), card = true) {
SectionItemView(padding = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF)) {
SearchRowView(searchText)
}
@@ -106,7 +106,7 @@ private fun AddGroupRelayLayout(
CircularProgressIndicator()
}
} else if (availableRelays.isEmpty()) {
SectionView {
SectionView(card = true) {
SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(
generalGetString(MR.strings.no_available_relays),
@@ -115,7 +115,7 @@ private fun AddGroupRelayLayout(
}
}
} else {
SectionView {
SectionView(card = true) {
AddRelaysButton(
onClick = onAddRelays,
disabled = selectedRelayIds.isEmpty() || isAdding
@@ -131,8 +131,8 @@ private fun AddGroupRelayLayout(
fontSize = 14.sp
)
}
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.select_relays)) {
SectionDividerSpaced(maxTopPadding = true)
SectionView(generalGetString(MR.strings.select_relays), card = true) {
availableRelays.forEach { item ->
val selected = item.relayId in selectedRelayIds
SectionItemView(
@@ -44,7 +44,7 @@ fun ChannelMembersView(
if (groupInfo.isOwner) {
val subscriberCount = groupInfo.groupSummary.publicMemberCount ?: (members.size + 1).toLong()
SectionView(title = subscriberCountStr(subscriberCount)) {
SectionView(title = subscriberCountStr(subscriberCount), card = true) {
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
ChannelMemberRow(groupInfo.membership, user = true, showRole = true)
}
@@ -61,7 +61,7 @@ fun ChannelMembersView(
}
} else {
val owners = members.filter { it.memberRole >= GroupMemberRole.Owner }
SectionView(title = generalGetString(MR.strings.channel_members_section_owners)) {
SectionView(title = generalGetString(MR.strings.channel_members_section_owners), card = true) {
owners.forEachIndexed { index, member ->
if (index > 0) {
Divider()
@@ -584,7 +584,7 @@ fun ModalData.GroupChatInfoLayout(
SectionSpacer()
if (groupInfo.useRelays && groupInfo.membership.memberIncognito) {
SectionView(generalGetString(MR.strings.incognito)) {
SectionView(generalGetString(MR.strings.incognito), card = true) {
SectionItemViewSpaceBetween {
Text(generalGetString(MR.strings.incognito_random_profile))
Text(groupInfo.membership.chatViewName, color = Indigo)
@@ -600,7 +600,7 @@ fun ModalData.GroupChatInfoLayout(
|| groupInfo.membership.supportChat != null)
if (groupInfo.useRelays) {
SectionView {
SectionView(card = true) {
if (groupInfo.isOwner && groupLink != null) {
anyTopSectionRowShow = true
ChannelLinkButton(manageGroupLink)
@@ -630,7 +630,7 @@ fun ModalData.GroupChatInfoLayout(
SectionTextFooter(stringResource(MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect))
}
} else {
SectionView {
SectionView(card = true) {
if (groupInfo.canAddMembers && groupInfo.businessChat == null) {
anyTopSectionRowShow = true
if (groupLink == null) {
@@ -658,9 +658,9 @@ fun ModalData.GroupChatInfoLayout(
}
}
if (anyTopSectionRowShow) {
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false)
}
SectionView {
SectionView(card = true) {
if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) {
val editProfileTitleId = if (groupInfo.useRelays) MR.strings.button_edit_channel_profile else MR.strings.button_edit_group_profile
EditGroupProfileButton(editProfileTitleId, editGroupProfile)
@@ -677,9 +677,9 @@ fun ModalData.GroupChatInfoLayout(
else if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs
else MR.strings.only_chat_owners_can_change_prefs
SectionTextFooter(stringResource(footerId))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView {
SectionView(card = true) {
if (!groupInfo.useRelays) {
if (activeSortedMembers.filter { it.memberCurrent }.size <= SMALL_GROUPS_RCPS_MEM_LIMIT) {
SendReceiptsOption(currentUser, sendReceipts, setSendReceipts)
@@ -697,12 +697,12 @@ fun ModalData.GroupChatInfoLayout(
}
}
ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems)
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = true)
if (!groupInfo.nextConnectPrepared && !groupInfo.useRelays) {
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) {
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1), card = true) {
if (groupInfo.canAddMembers) {
val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers
val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
@@ -718,18 +718,16 @@ fun ModalData.GroupChatInfoLayout(
MemberListSearchRowView(searchText)
}
}
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
MemberRow(groupInfo.membership, user = true)
}
}
}
}
if (!groupInfo.nextConnectPrepared && !groupInfo.useRelays) {
item {
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
MemberRow(groupInfo.membership, user = true)
}
}
items(filteredMembers.value, key = { it.groupMemberId }) { member ->
Divider()
val showMenu = remember(member.groupMemberId) { mutableStateOf(false) }
val showMenu = remember { mutableStateOf(false) }
val canBeSelected = groupInfo.membership.memberRole >= member.memberRole && member.memberRole < GroupMemberRole.Moderator
SectionItemViewLongClickable(
click = {
@@ -760,9 +758,9 @@ fun ModalData.GroupChatInfoLayout(
}
item {
if (!groupInfo.nextConnectPrepared && !groupInfo.useRelays) {
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
}
SectionView {
SectionView(card = true) {
if (groupInfo.useRelays && (groupInfo.isOwner || activeSortedMembers.any { it.memberRole == GroupMemberRole.Relay })) {
ChannelRelaysButton(chat.remoteHostId, groupInfo, showMemberInfo)
}
@@ -788,7 +786,7 @@ fun ModalData.GroupChatInfoLayout(
if (developerTools) {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.section_title_for_console)) {
SectionView(title = stringResource(MR.strings.section_title_for_console), card = true) {
InfoRow(stringResource(MR.strings.info_row_local_name), groupInfo.localDisplayName)
InfoRow(stringResource(MR.strings.info_row_database_id), groupInfo.apiId.toString())
}
@@ -3,7 +3,6 @@ package chat.simplex.common.views.chat.group
import SectionBottomSpacer
import SectionItemView
import SectionViewWithButton
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
@@ -191,110 +190,108 @@ fun GroupLinkLayout(
)
}
Box(Modifier.fillMaxSize().background(MaterialTheme.colors.surface)) {
ColumnWithScrollBar {
AppBarTitle(stringResource(if (isChannel) MR.strings.channel_link else MR.strings.group_link))
Text(
stringResource(if (isChannel) MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect else MR.strings.you_can_share_group_link_anybody_will_be_able_to_connect),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = 12.dp),
lineHeight = 22.sp
)
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
if (groupLink == null) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = DEFAULT_PADDING, vertical = 10.dp)
) {
SimpleButton(stringResource(MR.strings.button_create_group_link), icon = painterResource(MR.images.ic_add_link), disabled = creatingLink, click = createLink)
if (creatingGroup && close != null) {
ContinueButton(close)
}
ColumnWithScrollBar {
AppBarTitle(stringResource(if (isChannel) MR.strings.channel_link else MR.strings.group_link))
Text(
stringResource(if (isChannel) MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect else MR.strings.you_can_share_group_link_anybody_will_be_able_to_connect),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = 12.dp),
lineHeight = 22.sp
)
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
if (groupLink == null) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = DEFAULT_PADDING, vertical = 10.dp)
) {
SimpleButton(stringResource(MR.strings.button_create_group_link), icon = painterResource(MR.images.ic_add_link), disabled = creatingLink, click = createLink)
if (creatingGroup && close != null) {
ContinueButton(close)
}
} else {
if (!isChannel) {
RoleSelectionRow(groupInfo, groupLinkMemberRole)
}
} else {
if (!isChannel) {
RoleSelectionRow(groupInfo, groupLinkMemberRole)
}
var initialLaunch by remember { mutableStateOf(true) }
LaunchedEffect(groupLinkMemberRole.value) {
if (!initialLaunch) {
updateLink()
}
var initialLaunch by remember { mutableStateOf(true) }
LaunchedEffect(groupLinkMemberRole.value) {
if (!initialLaunch) {
updateLink()
}
initialLaunch = false
}
val showShortLink = remember { mutableStateOf(true) }
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
SectionViewWithButton(
titleButton =
if (!isChannel && groupLink.connLinkContact.connShortLink != null) {
{ ToggleShortLinkButton(showShortLink) }
} else null) {
SimpleXCreatedLinkQRCode(groupLink.connLinkContact, short = showShortLink.value)
}
if (!isChannel && groupLink.shouldBeUpgraded) {
SettingsActionItem(
painterResource(MR.images.ic_add),
stringResource(MR.strings.upgrade_group_link),
click = { showAddShortLinkAlert(null) },
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
val clipboard = LocalClipboardManager.current
initialLaunch = false
}
val showShortLink = remember { mutableStateOf(true) }
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
SectionViewWithButton(
titleButton =
if (!isChannel && groupLink.connLinkContact.connShortLink != null) {
{ ToggleShortLinkButton(showShortLink) }
} else null) {
SimpleXCreatedLinkQRCode(groupLink.connLinkContact, short = showShortLink.value)
}
if (!isChannel && groupLink.shouldBeUpgraded) {
SettingsActionItem(
painterResource(MR.images.ic_share),
stringResource(MR.strings.share_link),
click = {
if (!isChannel && groupLink.shouldBeUpgraded) {
showAddShortLinkAlert {
clipboard.shareText(groupLink.connLinkContact.simplexChatUri(short = showShortLink.value))
}
} else {
painterResource(MR.images.ic_add),
stringResource(MR.strings.upgrade_group_link),
click = { showAddShortLinkAlert(null) },
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
val clipboard = LocalClipboardManager.current
SettingsActionItem(
painterResource(MR.images.ic_share),
stringResource(MR.strings.share_link),
click = {
if (!isChannel && groupLink.shouldBeUpgraded) {
showAddShortLinkAlert {
clipboard.shareText(groupLink.connLinkContact.simplexChatUri(short = showShortLink.value))
}
} else {
clipboard.shareText(groupLink.connLinkContact.simplexChatUri(short = showShortLink.value))
}
},
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
if (shareGroupInfo != null && isChannel) {
SettingsActionItem(
painterResource(MR.images.ic_forward),
stringResource(MR.strings.share_via_chat),
click = {
chatModel.sharedContent.value = SharedContent.ChatLink(shareGroupInfo)
chatModel.chatId.value = null
ModalManager.closeAllModalsEverywhere()
},
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
if (shareGroupInfo != null && isChannel) {
SettingsActionItem(
painterResource(MR.images.ic_forward),
stringResource(MR.strings.share_via_chat),
click = {
chatModel.sharedContent.value = SharedContent.ChatLink(shareGroupInfo)
chatModel.chatId.value = null
ModalManager.closeAllModalsEverywhere()
},
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
if (!creatingGroup && !isChannel) {
SettingsActionItem(
painterResource(MR.images.ic_delete),
stringResource(MR.strings.delete_link),
click = deleteLink,
iconColor = Color.Red,
textColor = Color.Red,
)
}
if (creatingGroup && close != null) {
SettingsActionItem(
painterResource(MR.images.ic_check),
stringResource(MR.strings.continue_to_next_step),
click = close,
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
}
if (!creatingGroup && !isChannel) {
SettingsActionItem(
painterResource(MR.images.ic_delete),
stringResource(MR.strings.delete_link),
click = deleteLink,
iconColor = Color.Red,
textColor = Color.Red,
)
}
if (creatingGroup && close != null) {
SettingsActionItem(
painterResource(MR.images.ic_check),
stringResource(MR.strings.continue_to_next_step),
click = close,
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
}
SectionBottomSpacer()
}
SectionBottomSpacer()
}
}
@@ -424,7 +424,7 @@ fun GroupMemberInfoLayout(
val canRemove = member.canBeRemoved(groupInfo) && member.memberRole != GroupMemberRole.Relay
if (canBlockForAll || canRemove) {
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
if (canBlockForAll) {
if (member.blockedByAdmin) {
UnblockForAllButton(unblockForAll)
@@ -446,7 +446,7 @@ fun GroupMemberInfoLayout(
@Composable
fun NonAdminBlockSection() {
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
if (member.blockedByAdmin) {
SettingsActionItem(
painterResource(MR.images.ic_back_hand),
@@ -543,7 +543,7 @@ fun GroupMemberInfoLayout(
|| member.supportChat != null)
if (member.memberActive) {
SectionView {
SectionView(card = true) {
if (showMemberSupportChat) {
SupportChatButton()
}
@@ -559,14 +559,14 @@ fun GroupMemberInfoLayout(
}
SectionDividerSpaced()
} else if (groupInfo.useRelays && member.memberCurrent && showMemberSupportChat) {
SectionView {
SectionView(card = true) {
SupportChatButton()
}
SectionDividerSpaced()
}
if (member.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title)) {
SectionView(stringResource(MR.strings.address_section_title), card = true) {
SimpleXLinkQRCode(member.contactLink)
val clipboard = LocalClipboardManager.current
ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) }
@@ -577,8 +577,8 @@ fun GroupMemberInfoLayout(
} else {
ConnectViaAddressButton(onClick = { connectViaAddress(member.contactLink) })
}
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(member.displayName))
}
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(member.displayName))
SectionDividerSpaced()
}
@@ -591,7 +591,7 @@ fun GroupMemberInfoLayout(
} else {
stringResource(MR.strings.member_info_section_title_member)
}
SectionView(title = memberSectionTitle) {
SectionView(title = memberSectionTitle, card = true) {
val titleId = if (groupInfo.useRelays) MR.strings.info_row_channel
else if (groupInfo.businessChat == null) MR.strings.info_row_group
else MR.strings.info_row_chat
@@ -628,7 +628,7 @@ fun GroupMemberInfoLayout(
}
if (cStats != null) {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers), card = true) {
val subStatus = cStats.subStatus
if (subStatus != null) {
SectionItemView({
@@ -664,7 +664,7 @@ fun GroupMemberInfoLayout(
val connFailedErr = member.activeConn?.connFailedErr
if (connFailedErr != null) {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.info_row_connection_failed), icon = painterResource(MR.images.ic_warning), iconTint = Color.Red, leadingIcon = true) {
SectionView(title = stringResource(MR.strings.info_row_connection_failed), icon = painterResource(MR.images.ic_warning), iconTint = Color.Red, leadingIcon = true, card = true) {
SectionItemView {
Text(
connFailedErr,
@@ -682,7 +682,7 @@ fun GroupMemberInfoLayout(
if (developerTools) {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.section_title_for_console)) {
SectionView(title = stringResource(MR.strings.section_title_for_console), card = true) {
InfoRow(stringResource(MR.strings.info_row_local_name), member.localDisplayName)
InfoRow(stringResource(MR.strings.info_row_database_id), member.groupMemberId.toString())
val conn = member.activeConn
@@ -186,33 +186,33 @@ private fun GroupPreferencesLayout(
SectionDividerSpaced(maxBottomPadding = false)
}
TimedMessagesPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
DirectMessagesPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
FullDeletePreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
ReactionsPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
VoicePreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
FilesPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
SimplexLinksPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
ReportsPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
HistoryPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
SupportPreference(disabled = true)
} else {
TimedMessagesPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
FullDeletePreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
ReactionsPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
HistoryPreference()
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
SupportPreference(notice = generalGetString(MR.strings.chat_with_admins_relay_note), onEnable = { revert ->
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.enable_chats_with_admins_question),
@@ -225,7 +225,7 @@ private fun GroupPreferencesLayout(
})
}
if (groupInfo.isOwner) {
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
val saveTextId = if (groupInfo.useRelays) MR.strings.save_and_notify_channel_subscribers
else MR.strings.save_and_notify_group_members
ResetSaveButtons(
@@ -260,7 +260,7 @@ private fun FeatureSection(
notice: String? = null,
onSelected: (GroupFeatureEnabled, GroupMemberRole?) -> Unit
) {
SectionView {
SectionView(card = true) {
val on = enableFeature.value == GroupFeatureEnabled.ON
val icon = if (on) feature.iconFilled() else feature.icon
val iconTint = if (on) SimplexGreen else MaterialTheme.colors.secondary
@@ -325,7 +325,7 @@ private fun FeatureSection(
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean, saveTextId: StringResource) {
SectionView {
SectionView(card = true) {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
@@ -100,7 +100,7 @@ fun GroupProfileLayout(
sheetState = bottomSheetModalState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
ModalView(close = closeWithAlert, background = MaterialTheme.colors.surface) {
ModalView(close = closeWithAlert) {
ColumnWithScrollBar {
Column(
Modifier.fillMaxWidth()
@@ -85,7 +85,7 @@ private fun MemberAdmissionLayout(
}
}
if (groupInfo.isOwner) {
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
reset = reset,
save = saveAdmission,
@@ -109,7 +109,7 @@ private fun AdmissionSection(
groupInfo: GroupInfo,
onSelected: (MemberCriteria?) -> Unit
) {
SectionView {
SectionView(card = true) {
if (groupInfo.isOwner) {
ExposedDropDownSettingRow(
generalGetString(admissionStageStrId),
@@ -131,7 +131,7 @@ private fun AdmissionSection(
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
SectionView {
SectionView(card = true) {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
@@ -4,6 +4,7 @@ import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
import SectionTextFooter
import SectionView
import TextIconSpaced
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.selection.SelectionContainer
@@ -64,7 +65,6 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
else -> showUnsavedChangesAlert({ save(close) }, close)
}
},
background = MaterialTheme.colors.surface,
) {
GroupWelcomeLayout(
welcomeText,
@@ -160,8 +160,10 @@ private fun TextPreview(text: String, linkMode: SimplexLinkMode, markdown: Boole
@Composable
private fun SaveButton(save: () -> Unit, disabled: Boolean) {
SectionItemView(save, disabled = disabled) {
Text(stringResource(MR.strings.save_and_update_group_profile), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
SectionView {
SectionItemView(save, disabled = disabled) {
Text(stringResource(MR.strings.save_and_update_group_profile), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
}
@@ -1,6 +1,5 @@
package chat.simplex.common.views.chatlist
import CARD_ITEM_PADDING
import InfoRow
import InfoRowTwoValues
import SectionBottomSpacer
@@ -30,7 +29,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.AgentSMPServerStatsData
@@ -153,7 +151,7 @@ enum class PresentedServerType {
@Composable
private fun ServerSessionsView(sess: ServerSessions) {
SectionView(generalGetString(MR.strings.servers_info_transport_sessions_section_header)) {
SectionView(generalGetString(MR.strings.servers_info_transport_sessions_section_header), card = true) {
InfoRow(
generalGetString(MR.strings.servers_info_sessions_connected),
numOrDash(sess.ssConnected)
@@ -220,7 +218,7 @@ private fun SMPServersListView(servers: List<SMPServerSummary>, statsStartedAt:
val sortedServers = servers.sortedWith(compareBy<SMPServerSummary> { !it.hasSubs }
.thenBy { serverAddress(it.smpServer) })
SectionView(header) {
SectionView(header, card = true) {
sortedServers.map { svr -> SMPServerView(srvSumm = svr, statsStartedAt = statsStartedAt, rh = rh) }
}
if (footer != null) {
@@ -288,14 +286,14 @@ private fun XFTPServerInProgressIcon(srvSumm: XFTPServerSummary) {
private fun XFTPServersListView(servers: List<XFTPServerSummary>, statsStartedAt: Instant, header: String? = null, rh: RemoteHostInfo?) {
val sortedServers = servers.sortedBy { serverAddress(it.xftpServer) }
SectionView(header) {
SectionView(header, card = true) {
sortedServers.map { svr -> XFTPServerView(svr, statsStartedAt, rh) }
}
}
@Composable
private fun SMPStatsView(stats: AgentSMPServerStatsData, statsStartedAt: Instant, remoteHostInfo: RemoteHostInfo?) {
SectionView(generalGetString(MR.strings.servers_info_statistics_section_header)) {
SectionView(generalGetString(MR.strings.servers_info_statistics_section_header), card = true) {
InfoRow(
generalGetString(MR.strings.servers_info_messages_sent),
numOrDash(stats._sentDirect + stats._sentViaProxy)
@@ -326,20 +324,19 @@ private fun SMPStatsView(stats: AgentSMPServerStatsData, statsStartedAt: Instant
private fun SMPSubscriptionsSection(totals: SMPTotals) {
Column {
Row(
Modifier.padding(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, bottom = 8.dp),
Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON * 2)
) {
Text(
generalGetString(MR.strings.servers_info_subscriptions_section_header),
generalGetString(MR.strings.servers_info_subscriptions_section_header).uppercase(),
color = MaterialTheme.colors.secondary,
style = MaterialTheme.typography.body2,
fontSize = 14.sp,
fontWeight = FontWeight.Medium
fontSize = 12.sp
)
SubscriptionStatusIndicatorView(totals.subs, totals.sessions.hasSess)
}
SectionView {
Column(Modifier.padding(PaddingValues()).fillMaxWidth()) {
InfoRow(
generalGetString(MR.strings.servers_info_subscriptions_connections_subscribed),
numOrDash(totals.subs.ssActive)
@@ -357,20 +354,19 @@ private fun SMPSubscriptionsSection(totals: SMPTotals) {
private fun SMPSubscriptionsSection(subs: SMPServerSubs, summary: SMPServerSummary, rh: RemoteHostInfo?) {
Column {
Row(
Modifier.padding(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, bottom = 8.dp),
Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON * 2)
) {
Text(
generalGetString(MR.strings.servers_info_subscriptions_section_header),
generalGetString(MR.strings.servers_info_subscriptions_section_header).uppercase(),
color = MaterialTheme.colors.secondary,
style = MaterialTheme.typography.body2,
fontSize = 14.sp,
fontWeight = FontWeight.Medium
fontSize = 12.sp
)
SubscriptionStatusIndicatorView(subs, summary.sessionsOrNew.hasSess)
}
SectionView {
Column(Modifier.padding(PaddingValues()).fillMaxWidth()) {
InfoRow(
generalGetString(MR.strings.servers_info_subscriptions_connections_subscribed),
numOrDash(subs.ssActive)
@@ -419,7 +415,7 @@ private fun reconnectServerAlert(rh: RemoteHostInfo?, server: String) {
@Composable
fun XFTPStatsView(stats: AgentXFTPServerStatsData, statsStartedAt: Instant, rh: RemoteHostInfo?) {
SectionView(generalGetString(MR.strings.servers_info_statistics_section_header)) {
SectionView(generalGetString(MR.strings.servers_info_statistics_section_header), card = true) {
InfoRow(
generalGetString(MR.strings.servers_info_uploaded),
prettySize(stats._uploadsSize)
@@ -448,12 +444,12 @@ fun XFTPStatsView(stats: AgentXFTPServerStatsData, statsStartedAt: Instant, rh:
@Composable
private fun IndentedInfoRow(title: String, desc: String) {
InfoRow(title, desc, padding = PaddingValues(start = 24.dp + CARD_ITEM_PADDING, end = CARD_ITEM_PADDING))
InfoRow(title, desc, padding = PaddingValues(start = 24.dp + DEFAULT_PADDING, end = DEFAULT_PADDING))
}
@Composable
fun DetailedSMPStatsLayout(stats: AgentSMPServerStatsData, statsStartedAt: Instant) {
SectionView(generalGetString(MR.strings.servers_info_detailed_statistics_sent_messages_header)) {
SectionView(generalGetString(MR.strings.servers_info_detailed_statistics_sent_messages_header), card = true) {
InfoRow(generalGetString(MR.strings.servers_info_detailed_statistics_sent_messages_total), numOrDash(stats._sentDirect + stats._sentViaProxy))
InfoRowTwoValues(generalGetString(MR.strings.sent_directly), generalGetString(MR.strings.attempts_label), stats._sentDirect, stats._sentDirectAttempts)
InfoRowTwoValues(generalGetString(MR.strings.sent_via_proxy), generalGetString(MR.strings.attempts_label), stats._sentViaProxy, stats._sentViaProxyAttempts)
@@ -469,7 +465,7 @@ fun DetailedSMPStatsLayout(stats: AgentSMPServerStatsData, statsStartedAt: Insta
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.servers_info_detailed_statistics_received_messages_header)) {
SectionView(generalGetString(MR.strings.servers_info_detailed_statistics_received_messages_header), card = true) {
InfoRow(generalGetString(MR.strings.servers_info_detailed_statistics_received_total), numOrDash(stats._recvMsgs))
SectionItemView {
Text(generalGetString(MR.strings.servers_info_detailed_statistics_receive_errors), color = MaterialTheme.colors.onBackground)
@@ -487,7 +483,7 @@ fun DetailedSMPStatsLayout(stats: AgentSMPServerStatsData, statsStartedAt: Insta
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.connections)) {
SectionView(generalGetString(MR.strings.connections), card = true) {
InfoRow(generalGetString(MR.strings.created), numOrDash(stats._connCreated))
InfoRow(generalGetString(MR.strings.secured), numOrDash(stats._connSecured))
InfoRow(generalGetString(MR.strings.completed), numOrDash(stats._connCompleted))
@@ -506,7 +502,7 @@ fun DetailedSMPStatsLayout(stats: AgentSMPServerStatsData, statsStartedAt: Insta
@Composable
fun DetailedXFTPStatsLayout(stats: AgentXFTPServerStatsData, statsStartedAt: Instant) {
SectionView(generalGetString(MR.strings.uploaded_files)) {
SectionView(generalGetString(MR.strings.uploaded_files), card = true) {
InfoRow(generalGetString(MR.strings.size), prettySize(stats._uploadsSize))
InfoRowTwoValues(generalGetString(MR.strings.chunks_uploaded), generalGetString(MR.strings.attempts_label), stats._uploads, stats._uploadAttempts)
InfoRow(generalGetString(MR.strings.upload_errors), numOrDash(stats._uploadErrs))
@@ -514,7 +510,7 @@ fun DetailedXFTPStatsLayout(stats: AgentXFTPServerStatsData, statsStartedAt: Ins
InfoRow(generalGetString(MR.strings.deletion_errors), numOrDash(stats._deleteErrs))
}
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.downloaded_files)) {
SectionView(generalGetString(MR.strings.downloaded_files), card = true) {
InfoRow(generalGetString(MR.strings.size), prettySize(stats._downloadsSize))
InfoRowTwoValues(generalGetString(MR.strings.chunks_downloaded), generalGetString(MR.strings.attempts_label), stats._downloads, stats._downloadAttempts)
SectionItemView {
@@ -532,27 +528,31 @@ fun DetailedXFTPStatsLayout(stats: AgentXFTPServerStatsData, statsStartedAt: Ins
@Composable
fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant, rh: RemoteHostInfo?) {
SectionView(generalGetString(MR.strings.server_address)) {
SectionView(generalGetString(MR.strings.server_address), card = true) {
SelectionContainer {
Text(
summary.xftpServer,
Modifier.padding(start = CARD_ITEM_PADDING, top = 5.dp, end = CARD_ITEM_PADDING, bottom = 10.dp),
Modifier.padding(start = DEFAULT_PADDING, top = 5.dp, end = DEFAULT_PADDING, bottom = 10.dp),
style = TextStyle(
fontFamily = FontFamily.Monospace, fontSize = 16.sp,
color = MaterialTheme.colors.secondary
)
)
}
}
if (summary.stats != null || summary.sessions != null) {
SectionDividerSpaced()
}
if (summary.stats != null) {
SectionDividerSpaced()
XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt)
}
if (summary.stats != null) {
XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt)
if (summary.sessions != null) {
SectionDividerSpaced(maxTopPadding = true)
}
}
if (summary.sessions != null) {
SectionDividerSpaced()
ServerSessionsView(summary.sessions)
if (summary.sessions != null) {
ServerSessionsView(summary.sessions)
}
}
SectionBottomSpacer()
@@ -560,32 +560,38 @@ fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant,
@Composable
fun SMPServerSummaryLayout(summary: SMPServerSummary, statsStartedAt: Instant, rh: RemoteHostInfo?) {
SectionView(generalGetString(MR.strings.server_address)) {
SectionView(generalGetString(MR.strings.server_address), card = true) {
SelectionContainer {
Text(
summary.smpServer,
Modifier.padding(start = CARD_ITEM_PADDING, top = 5.dp, end = CARD_ITEM_PADDING, bottom = 10.dp),
Modifier.padding(start = DEFAULT_PADDING, top = 5.dp, end = DEFAULT_PADDING, bottom = 10.dp),
style = TextStyle(
fontFamily = FontFamily.Monospace, fontSize = 16.sp,
color = MaterialTheme.colors.secondary
)
)
}
}
if (summary.stats != null || summary.subs != null || summary.sessions != null) {
SectionDividerSpaced()
}
if (summary.stats != null) {
SectionDividerSpaced()
SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt)
}
if (summary.stats != null) {
SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt)
if (summary.subs != null || summary.sessions != null) {
SectionDividerSpaced(maxTopPadding = true)
}
}
if (summary.subs != null) {
SectionDividerSpaced()
SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh)
}
if (summary.subs != null) {
SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh)
if (summary.sessions != null) {
SectionDividerSpaced()
}
}
if (summary.sessions != null) {
SectionDividerSpaced()
ServerSessionsView(summary.sessions)
if (summary.sessions != null) {
ServerSessionsView(summary.sessions)
}
}
SectionBottomSpacer()
@@ -809,18 +815,16 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
) {
Spacer(Modifier.height(DEFAULT_PADDING))
if (showUserSelection) {
SectionView {
ExposedDropDownSettingRow(
generalGetString(MR.strings.servers_info_target),
userOptions,
selectedUserCategory,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = {
selectedUserCategory.value = it
}
)
}
ExposedDropDownSettingRow(
generalGetString(MR.strings.servers_info_target),
userOptions,
selectedUserCategory,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = {
selectedUserCategory.value = it
}
)
SectionDividerSpaced()
}
when (index) {
@@ -835,7 +839,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
val statsStartedAt = it.statsStartedAt
SMPStatsView(totals.stats, statsStartedAt, rh)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SMPSubscriptionsSection(totals)
SectionDividerSpaced()
@@ -843,7 +847,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
SMPServersListView(
servers = currentlyUsedSMPServers,
statsStartedAt = statsStartedAt,
header = generalGetString(MR.strings.servers_info_connected_servers_section_header),
header = generalGetString(MR.strings.servers_info_connected_servers_section_header).uppercase(),
rh = rh
)
SectionDividerSpaced()
@@ -853,7 +857,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
SMPServersListView(
servers = previouslyUsedSMPServers,
statsStartedAt = statsStartedAt,
header = generalGetString(MR.strings.servers_info_previously_connected_servers_section_header),
header = generalGetString(MR.strings.servers_info_previously_connected_servers_section_header).uppercase(),
rh = rh
)
SectionDividerSpaced()
@@ -863,11 +867,11 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
SMPServersListView(
servers = proxySMPServers,
statsStartedAt = statsStartedAt,
header = generalGetString(MR.strings.servers_info_proxied_servers_section_header),
header = generalGetString(MR.strings.servers_info_proxied_servers_section_header).uppercase(),
footer = generalGetString(MR.strings.servers_info_proxied_servers_section_footer),
rh = rh
)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
ServerSessionsView(totals.sessions)
@@ -884,13 +888,13 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
val previouslyUsedXFTPServers = xftpSummary.previouslyUsedXFTPServers
XFTPStatsView(totals.stats, statsStartedAt, rh)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
if (currentlyUsedXFTPServers.isNotEmpty()) {
XFTPServersListView(
currentlyUsedXFTPServers,
statsStartedAt,
generalGetString(MR.strings.servers_info_connected_servers_section_header),
generalGetString(MR.strings.servers_info_connected_servers_section_header).uppercase(),
rh
)
SectionDividerSpaced()
@@ -900,7 +904,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
XFTPServersListView(
previouslyUsedXFTPServers,
statsStartedAt,
generalGetString(MR.strings.servers_info_previously_connected_servers_section_header),
generalGetString(MR.strings.servers_info_previously_connected_servers_section_header).uppercase(),
rh
)
SectionDividerSpaced()
@@ -913,7 +917,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
ReconnectAllServersButton(rh)
ResetStatisticsButton(rh, resetStats = { resetStats() })
}
@@ -1,6 +1,5 @@
package chat.simplex.common.views.chatlist
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
@@ -26,7 +25,7 @@ import chat.simplex.res.MR
fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
var searchInList by rememberSaveable { mutableStateOf("") }
val oneHandUI = remember { appPrefs.oneHandUI.state }
Box(Modifier.fillMaxSize().background(MaterialTheme.colors.surface)) {
Box(Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) {
val sharedContent = chatModel.sharedContent.value
var isMediaOrFileAttachment = false
var isVoice = false
@@ -1,7 +1,6 @@
package chat.simplex.common.views.chatlist
import SectionCustomFooter
import SectionDivider
import SectionItemView
import TextIconSpaced
import androidx.compose.animation.core.animateDpAsState
@@ -72,101 +71,99 @@ fun TagListView(rhId: Long?, chat: Chat? = null, close: () -> Unit, reorderMode:
}
val topPaddingToContent = topPaddingToContent(false)
Box(Modifier.fillMaxSize().background(MaterialTheme.colors.surface)) {
LazyColumnWithScrollBar(
modifier = if (reorderMode) Modifier.dragContainer(dragDropState) else Modifier,
state = listState,
contentPadding = PaddingValues(
top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else topPaddingToContent,
bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp
),
verticalArrangement = if (oneHandUI.value) Arrangement.Bottom else Arrangement.Top,
) {
@Composable fun CreateList() {
SectionItemView({
ModalManager.start.showModalCloseable { close ->
TagListEditor(rhId = rhId, close = close, chat = chat)
}
}) {
Icon(painterResource(MR.images.ic_add), stringResource(MR.strings.create_list), tint = MaterialTheme.colors.primary)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(stringResource(MR.strings.create_list), color = MaterialTheme.colors.primary)
LazyColumnWithScrollBar(
modifier = if (reorderMode) Modifier.dragContainer(dragDropState) else Modifier,
state = listState,
contentPadding = PaddingValues(
top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else topPaddingToContent,
bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp
),
verticalArrangement = if (oneHandUI.value) Arrangement.Bottom else Arrangement.Top,
) {
@Composable fun CreateList() {
SectionItemView({
ModalManager.start.showModalCloseable { close ->
TagListEditor(rhId = rhId, close = close, chat = chat)
}
}) {
Icon(painterResource(MR.images.ic_add), stringResource(MR.strings.create_list), tint = MaterialTheme.colors.primary)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(stringResource(MR.strings.create_list), color = MaterialTheme.colors.primary)
}
}
if (oneHandUI.value && !reorderMode) {
item {
CreateList()
}
if (oneHandUI.value && !reorderMode) {
item {
CreateList()
}
itemsIndexed(userTags.value, key = { _, item -> item.chatTagId }) { index, tag ->
DraggableItem(dragDropState, index) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
}
itemsIndexed(userTags.value, key = { _, item -> item.chatTagId }) { index, tag ->
DraggableItem(dragDropState, index) { isDragging ->
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
Card(
elevation = elevation,
backgroundColor = if (isDragging) colors.surface else Color.Unspecified
) {
Column {
val selected = chatTagIds.value.contains(tag.chatTagId)
Card(
elevation = elevation,
backgroundColor = if (isDragging) colors.surface else Color.Unspecified
) {
Column {
val selected = chatTagIds.value.contains(tag.chatTagId)
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.clickable(
enabled = !saving.value && !reorderMode,
onClick = {
if (chat == null) {
ModalManager.start.showModalCloseable { close ->
TagListEditor(
rhId = rhId,
tagId = tag.chatTagId,
close = close,
emoji = tag.chatTagEmoji,
name = tag.chatTagText,
)
}
} else {
saving.value = true
setTag(rhId = rhId, tagId = if (selected) null else tag.chatTagId, chat = chat, close = {
saving.value = false
close()
})
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT)
.clickable(
enabled = !saving.value && !reorderMode,
onClick = {
if (chat == null) {
ModalManager.start.showModalCloseable { close ->
TagListEditor(
rhId = rhId,
tagId = tag.chatTagId,
close = close,
emoji = tag.chatTagEmoji,
name = tag.chatTagText,
)
}
},
)
.padding(PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)),
verticalAlignment = Alignment.CenterVertically
) {
if (tag.chatTagEmoji != null) {
ReactionIcon(tag.chatTagEmoji, fontSize = 14.sp)
} else {
Icon(painterResource(MR.images.ic_label), null, Modifier.size(18.sp.toDp()), tint = MaterialTheme.colors.onBackground)
}
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
tag.chatTagText,
color = MenuTextColor,
fontWeight = if (selected) FontWeight.Medium else FontWeight.Normal
} else {
saving.value = true
setTag(rhId = rhId, tagId = if (selected) null else tag.chatTagId, chat = chat, close = {
saving.value = false
close()
})
}
},
)
if (selected) {
Spacer(Modifier.weight(1f))
Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
} else if (reorderMode) {
Spacer(Modifier.weight(1f))
Icon(painterResource(MR.images.ic_drag_handle), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary)
}
.padding(PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)),
verticalAlignment = Alignment.CenterVertically
) {
if (tag.chatTagEmoji != null) {
ReactionIcon(tag.chatTagEmoji, fontSize = 14.sp)
} else {
Icon(painterResource(MR.images.ic_label), null, Modifier.size(18.sp.toDp()), tint = MaterialTheme.colors.onBackground)
}
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
tag.chatTagText,
color = MenuTextColor,
fontWeight = if (selected) FontWeight.Medium else FontWeight.Normal
)
if (selected) {
Spacer(Modifier.weight(1f))
Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
} else if (reorderMode) {
Spacer(Modifier.weight(1f))
Icon(painterResource(MR.images.ic_drag_handle), null, Modifier.size(20.dp), tint = MaterialTheme.colors.secondary)
}
SectionDivider()
}
Divider(Modifier.padding(horizontal = 8.dp))
}
}
}
if (!oneHandUI.value && !reorderMode) {
item {
CreateList()
}
}
if (!oneHandUI.value && !reorderMode) {
item {
CreateList()
}
}
}
@@ -119,7 +119,7 @@ fun DatabaseEncryptionLayout(
ChatStoppedView()
SectionSpacer()
}
SectionView(if (migration) generalGetString(MR.strings.database_passphrase) else null) {
SectionView(if (migration) generalGetString(MR.strings.database_passphrase) else null, card = true) {
SavePassphraseSetting(
useKeychain.value,
initialRandomDBPassphrase.value,
@@ -170,7 +170,7 @@ fun DatabaseLayout(
AppBarTitle(stringResource(MR.strings.your_chat_database))
if (!chatModel.desktopNoUserNoRemote) {
SectionView(stringResource(MR.strings.messages_section_title)) {
SectionView(stringResource(MR.strings.messages_section_title), card = true) {
TtlOptions(chatItemTTL, enabled = rememberUpdatedState(!stopped && !progressIndicator), onChatItemTTLSelected)
}
SectionTextFooter(
@@ -184,14 +184,14 @@ fun DatabaseLayout(
}
}
)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
val toggleEnabled = remember { chatModel.remoteHosts }.none { it.sessionState is RemoteHostSessionState.Connected }
if (chatModel.localUserCreated.value == true) {
// still show the toggle in case database was stopped when the user opened this screen because it can be in the following situations:
// - database was stopped after migration and the app relaunched
// - something wrong happened with database operations and the database couldn't be launched when it should
SectionView(stringResource(MR.strings.run_chat_section)) {
SectionView(stringResource(MR.strings.run_chat_section), card = true) {
if (!toggleEnabled) {
SectionItemView(disconnectAllHosts) {
Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
@@ -200,10 +200,10 @@ fun DatabaseLayout(
RunChatSetting(stopped, toggleEnabled && !progressIndicator, startChat, stopChatAlert)
}
if (stopped) SectionTextFooter(stringResource(MR.strings.you_must_use_the_most_recent_version_of_database))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
}
SectionView(stringResource(MR.strings.chat_database_section)) {
SectionView(stringResource(MR.strings.chat_database_section), card = true) {
if (chatModel.localUserCreated.value != true && !toggleEnabled) {
SectionItemView(disconnectAllHosts) {
Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
@@ -262,7 +262,7 @@ fun DatabaseLayout(
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.files_and_media_section)) {
SectionView(stringResource(MR.strings.files_and_media_section), card = true) {
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
SectionItemView(
deleteAppFilesAndMedia,
@@ -25,14 +25,7 @@ import dev.icerock.moko.resources.ImageResource
import kotlin.math.max
@Composable
private fun defaultProfileIconColor(): Color =
if (CurrentColors.value.base == DefaultTheme.LIGHT)
MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.91f)
else
MaterialTheme.colors.secondaryVariant
@Composable
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = defaultProfileIconColor(), shadow: Boolean = false) {
fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, shadow: Boolean = false) {
val icon =
when (chatInfo) {
is ChatInfo.Group -> chatInfo.groupInfo.chatIconName
@@ -59,7 +52,7 @@ fun ProfileImage(
size: Dp,
image: String? = null,
icon: ImageResource = MR.images.ic_account_circle_filled,
color: Color = defaultProfileIconColor(),
color: Color = MaterialTheme.colors.secondaryVariant,
backgroundColor: Color? = null,
blurred: Boolean = false,
async: Boolean = false
@@ -6,8 +6,9 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalDensity
@@ -30,41 +31,71 @@ val CARD_PADDING = 16.dp
val ICON_TEXT_SPACING = 5.dp
val CARD_ITEM_PADDING = CARD_PADDING - 1.dp
// Set to true by SectionView around its inner Column. SectionItemView reads it
// to decide whether to draw the 2dp bottom divider. False default keeps
// standalone usage (alerts, pickers, custom contexts) unaffected.
internal val LocalInSectionCard = staticCompositionLocalOf { false }
@Composable
private fun Modifier.sectionItemDivider(): Modifier {
if (!LocalInSectionCard.current) return this
return this.drawWithContent {
drawContent()
drawLine(canvasColorForCurrentTheme(), Offset(0f, size.height), Offset(size.width, size.height), strokeWidth = 2.dp.toPx())
private fun CardColumnLayout(
contentPadding: PaddingValues = PaddingValues(),
content: @Composable () -> Unit
) {
val dividerColor = canvasColorForCurrentTheme()
val dividerPx = with(LocalDensity.current) { 2.dp.toPx() }
val childBottoms = remember { mutableListOf<Float>() }
Layout(
content = content,
modifier = Modifier
.padding(horizontal = CARD_PADDING)
.fillMaxWidth()
.clip(SectionCardShape)
.background(sectionCardColor())
.padding(contentPadding)
.drawBehind {
for (i in 0 until childBottoms.size - 1) {
val y = childBottoms[i]
drawLine(dividerColor, Offset(0f, y), Offset(size.width, y), strokeWidth = dividerPx)
}
}
) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
childBottoms.clear()
var y = 0f
placeables.forEach { p ->
y += p.height
childBottoms.add(y)
}
layout(constraints.maxWidth, y.toInt()) {
var yPos = 0
placeables.forEach { p ->
p.placeRelative(0, yPos)
yPos += p.height
}
}
}
}
@Composable
fun SectionView(title: String? = null, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = 8.dp, content: (@Composable ColumnScope.() -> Unit)) {
val cardColor = sectionCardColor()
private fun CardColumn(
card: Boolean,
contentPadding: PaddingValues = PaddingValues(),
content: @Composable () -> Unit
) {
if (card) {
CardColumnLayout(contentPadding, content)
} else {
Column(Modifier.padding(contentPadding).fillMaxWidth()) { content() }
}
}
@Composable
fun SectionView(title: String? = null, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, card: Boolean = false, content: (@Composable ColumnScope.() -> Unit)) {
Column {
if (title != null) {
Text(
title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2,
modifier = Modifier.padding(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, bottom = headerBottomPadding), fontSize = 14.sp, fontWeight = FontWeight.Medium
modifier = Modifier.padding(start = if (card) DEFAULT_PADDING + DEFAULT_PADDING_HALF else DEFAULT_PADDING, bottom = if (card) 8.dp else headerBottomPadding),
fontSize = if (card) 14.sp else 12.sp,
fontWeight = if (card) FontWeight.Medium else FontWeight.Normal
)
}
CompositionLocalProvider(LocalInSectionCard provides true) {
Column(
Modifier
.padding(horizontal = CARD_PADDING)
.fillMaxWidth()
.clip(SectionCardShape)
.background(cardColor)
.padding(contentPadding)
) { content() }
}
CardColumn(card, contentPadding) { content() }
}
}
@@ -75,37 +106,28 @@ fun SectionView(
iconTint: Color = MaterialTheme.colors.secondary,
leadingIcon: Boolean = false,
padding: PaddingValues = PaddingValues(),
card: Boolean = false,
content: (@Composable ColumnScope.() -> Unit)
) {
val cardColor = sectionCardColor()
Column {
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
Row(Modifier.padding(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
Row(Modifier.padding(start = if (card) DEFAULT_PADDING + DEFAULT_PADDING_HALF else DEFAULT_PADDING, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
if (leadingIcon) Icon(icon, null, Modifier.padding(end = DEFAULT_PADDING_HALF).size(iconSize), tint = iconTint)
Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 14.sp, fontWeight = FontWeight.Medium)
Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = if (card) 14.sp else 12.sp, fontWeight = if (card) FontWeight.Medium else FontWeight.Normal)
if (!leadingIcon) Icon(icon, null, Modifier.padding(start = DEFAULT_PADDING_HALF).size(iconSize), tint = iconTint)
}
CompositionLocalProvider(LocalInSectionCard provides true) {
Column(
Modifier
.padding(horizontal = CARD_PADDING)
.fillMaxWidth()
.clip(SectionCardShape)
.background(cardColor)
.padding(padding)
) { content() }
}
CardColumn(card, padding) { content() }
}
}
@Composable
fun SectionViewWithButton(title: String? = null, titleButton: (@Composable () -> Unit)?, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = 8.dp, content: (@Composable ColumnScope.() -> Unit)) {
val cardColor = sectionCardColor()
fun SectionViewWithButton(title: String? = null, titleButton: (@Composable () -> Unit)?, contentPadding: PaddingValues = PaddingValues(), headerBottomPadding: Dp = DEFAULT_PADDING, card: Boolean = false, content: (@Composable ColumnScope.() -> Unit)) {
Column {
if (title != null || titleButton != null) {
Row(modifier = Modifier.padding(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, end = DEFAULT_PADDING + DEFAULT_PADDING_HALF, bottom = headerBottomPadding).fillMaxWidth()) {
val hPadding = if (card) DEFAULT_PADDING + DEFAULT_PADDING_HALF else DEFAULT_PADDING
Row(modifier = Modifier.padding(start = hPadding, end = hPadding, bottom = if (card) 8.dp else headerBottomPadding).fillMaxWidth()) {
if (title != null) {
Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 14.sp, fontWeight = FontWeight.Medium)
Text(title, color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = if (card) 14.sp else 12.sp, fontWeight = if (card) FontWeight.Medium else FontWeight.Normal)
}
if (titleButton != null) {
Spacer(modifier = Modifier.weight(1f))
@@ -113,16 +135,7 @@ fun SectionViewWithButton(title: String? = null, titleButton: (@Composable () ->
}
}
}
CompositionLocalProvider(LocalInSectionCard provides true) {
Column(
Modifier
.padding(horizontal = CARD_PADDING)
.fillMaxWidth()
.clip(SectionCardShape)
.background(cardColor)
.padding(contentPadding)
) { content() }
}
CardColumn(card, contentPadding) { content() }
}
}
@@ -172,19 +185,18 @@ fun <T> SectionViewSelectableCards(
@Composable
fun SectionItemView(
click: (() -> Unit)? = null,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = CARD_ITEM_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
else
PaddingValues(horizontal = CARD_ITEM_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
.sectionItemDivider()
Row(
if (click == null || disabled) modifier.padding(padding) else modifier.clickable(onClick = click).padding(padding),
verticalAlignment = Alignment.CenterVertically
@@ -196,13 +208,13 @@ fun SectionItemView(
@Composable
fun SectionItemViewWithoutMinPadding(
click: (() -> Unit)? = null,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = CARD_ITEM_PADDING)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING)
else
PaddingValues(horizontal = CARD_ITEM_PADDING),
PaddingValues(horizontal = DEFAULT_PADDING),
content: (@Composable RowScope.() -> Unit)
) {
SectionItemView(click, minHeight, disabled, extraPadding, padding, content)
@@ -212,19 +224,18 @@ fun SectionItemViewWithoutMinPadding(
fun SectionItemViewLongClickable(
click: () -> Unit,
longClick: () -> Unit,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
disabled: Boolean = false,
extraPadding: Boolean = false,
padding: PaddingValues = if (extraPadding)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = CARD_ITEM_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING, top = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL, bottom = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL)
else
PaddingValues(horizontal = CARD_ITEM_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL),
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
.sectionItemDivider()
Row(
if (disabled) {
modifier.padding(padding)
@@ -241,15 +252,14 @@ fun SectionItemViewLongClickable(
fun SectionItemViewSpaceBetween(
click: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp,
padding: PaddingValues = PaddingValues(horizontal = CARD_ITEM_PADDING),
minHeight: Dp = DEFAULT_MIN_SECTION_ITEM_HEIGHT,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING),
disabled: Boolean = false,
content: (@Composable RowScope.() -> Unit)
) {
val modifier = Modifier
.fillMaxWidth()
.sizeIn(minHeight = minHeight)
.sectionItemDivider()
Row(
if (click == null || disabled) modifier.padding(padding).padding(vertical = DEFAULT_MIN_SECTION_ITEM_PADDING_VERTICAL) else modifier
.combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding)
@@ -297,7 +307,7 @@ fun SectionTextFooter(text: String, color: Color = MaterialTheme.colors.secondar
fun SectionTextFooter(text: AnnotatedString, textAlign: TextAlign = TextAlign.Start, color: Color = MaterialTheme.colors.secondary) {
Text(
text,
Modifier.padding(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, end = DEFAULT_PADDING + DEFAULT_PADDING_HALF, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F),
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F),
color = color,
lineHeight = 18.sp,
fontSize = 14.sp,
@@ -306,7 +316,7 @@ fun SectionTextFooter(text: AnnotatedString, textAlign: TextAlign = TextAlign.St
}
@Composable
fun SectionCustomFooter(padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING + DEFAULT_PADDING_HALF, end = DEFAULT_PADDING + DEFAULT_PADDING_HALF, top = 5.dp), content: (@Composable () -> Unit)) {
fun SectionCustomFooter(padding: PaddingValues = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = 5.dp), content: (@Composable () -> Unit)) {
Row(
Modifier.padding(padding)
) {
@@ -314,20 +324,15 @@ fun SectionCustomFooter(padding: PaddingValues = PaddingValues(start = DEFAULT_P
}
}
// Explicit 2dp canvas-color divider for inserting between non-SectionItemView
// composables inside a SectionView card (e.g., between a custom block and a
// SectionItemView). Auto-divider on SectionItemView handles the row-to-row
// case; this one covers manual placement around mixed content. No-op outside
// a SectionView card.
@Composable
fun SectionDivider() {
if (!LocalInSectionCard.current) return
Box(Modifier.fillMaxWidth().height(2.dp).background(canvasColorForCurrentTheme()))
}
@Composable
fun SectionDividerSpaced(maxTopPadding: Boolean = false, maxBottomPadding: Boolean = true) {
Spacer(Modifier.height(if (maxTopPadding || maxBottomPadding) DEFAULT_PADDING else DEFAULT_PADDING_HALF))
Divider(
Modifier.padding(
start = DEFAULT_PADDING_HALF,
top = if (maxTopPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp,
end = DEFAULT_PADDING_HALF,
bottom = if (maxBottomPadding) DEFAULT_PADDING + 18.dp else DEFAULT_PADDING + 2.dp)
)
}
@Composable
@@ -342,11 +347,11 @@ fun SectionBottomSpacer() {
@Composable
fun TextIconSpaced(extraPadding: Boolean = false) {
Spacer(Modifier.padding(horizontal = if (extraPadding) 17.dp else ICON_TEXT_SPACING))
Spacer(Modifier.padding(horizontal = if (extraPadding) 17.dp else DEFAULT_PADDING_HALF))
}
@Composable
fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null, textColor: Color = MaterialTheme.colors.onBackground, padding: PaddingValues = PaddingValues(horizontal = CARD_ITEM_PADDING)) {
fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null, textColor: Color = MaterialTheme.colors.onBackground, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionItemViewSpaceBetween(padding = padding) {
Row {
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
@@ -329,7 +329,6 @@ fun ModalData.ChatWallpaperEditor(
ThemeManager.currentColors(type, if (type?.sameType(themeModeOverride.value.type) == true) themeModeOverride.value else null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
}
SectionView {
WallpaperPresetSelector(
selectedWallpaper = currentTheme.wallpaper.type,
activeBackgroundColor = currentTheme.wallpaper.background,
@@ -368,11 +367,9 @@ fun ModalData.ChatWallpaperEditor(
editColor = editColor,
onTypeChange = onTypeChange,
)
}
SectionSpacer()
SectionView {
if (!globalThemeUsed.value) {
ResetToGlobalThemeButton(remember { chatModel.currentUser }.value?.uiThemes?.preferredMode(isInDarkTheme()) == null) {
themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
@@ -396,7 +393,6 @@ fun ModalData.ChatWallpaperEditor(
save(themeModeOverride.value.mode, themeModeOverride.value)
}
}
}
KeyChangeEffect(theme.mode) {
themeModeOverride.value = theme
@@ -424,7 +420,6 @@ fun ModalData.ChatWallpaperEditor(
)
)
}
SectionView {
ExposedDropDownSettingRow(
generalGetString(MR.strings.chat_theme_apply_to_mode),
values,
@@ -440,7 +435,6 @@ fun ModalData.ChatWallpaperEditor(
}
}
)
}
SectionDividerSpaced()
@@ -454,9 +448,7 @@ fun ModalData.ChatWallpaperEditor(
}
}
} else {
SectionView {
AdvancedSettingsButton { showMore = true }
}
AdvancedSettingsButton { showMore = true }
}
SectionBottomSpacer()
@@ -465,7 +457,7 @@ fun ModalData.ChatWallpaperEditor(
@Composable
private fun ImportExportThemeSection(perChat: ThemeModeOverride?, perUser: ThemeModeOverrides?, save: (ThemeModeOverride) -> Unit) {
SectionView {
SectionView(card = true) {
val theme = remember { mutableStateOf(null as String?) }
val exportThemeLauncher = rememberFileChooserLauncher(false) { to: URI? ->
val themeValue = theme.value
@@ -182,7 +182,7 @@ private fun SectionByState(
@Composable
private fun MutableState<MigrationFromState>.ChatStopInProgressView() {
Box {
SectionView(stringResource(MR.strings.migrate_from_device_stopping_chat)) {}
SectionView(stringResource(MR.strings.migrate_from_device_stopping_chat), card = true) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -192,7 +192,7 @@ private fun MutableState<MigrationFromState>.ChatStopInProgressView() {
@Composable
private fun MutableState<MigrationFromState>.ChatStopFailedView(reason: String) {
SectionView(stringResource(MR.strings.error_stopping_chat)) {
SectionView(stringResource(MR.strings.error_stopping_chat), card = true) {
Text(reason)
SectionSpacer()
SettingsActionItemWithContent(
@@ -201,8 +201,8 @@ private fun MutableState<MigrationFromState>.ChatStopFailedView(reason: String)
textColor = MaterialTheme.colors.error,
click = ::stopChat
){}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_chat_should_be_stopped))
}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_chat_should_be_stopped))
}
@Composable
@@ -226,7 +226,7 @@ private fun MutableState<MigrationFromState>.PassphraseConfirmationView() {
ChatStoppedView()
SectionSpacer()
SectionView(stringResource(MR.strings.migrate_from_device_verify_database_passphrase)) {
SectionView(stringResource(MR.strings.migrate_from_device_verify_database_passphrase), card = true) {
PassphraseField(currentKey, placeholder = stringResource(MR.strings.current_passphrase), Modifier.padding(horizontal = DEFAULT_PADDING), isValid = ::validKey, requestFocus = true)
SettingsActionItemWithContent(
@@ -243,8 +243,8 @@ private fun MutableState<MigrationFromState>.PassphraseConfirmationView() {
}
}
) {}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_confirm_you_remember_passphrase))
}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_confirm_you_remember_passphrase))
}
if (verifyingPassphrase.value) {
ProgressView()
@@ -254,21 +254,21 @@ private fun MutableState<MigrationFromState>.PassphraseConfirmationView() {
@Composable
private fun MutableState<MigrationFromState>.UploadConfirmationView() {
SectionView(stringResource(MR.strings.migrate_from_device_confirm_upload)) {
SectionView(stringResource(MR.strings.migrate_from_device_confirm_upload), card = true) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_ios_share),
text = stringResource(MR.strings.migrate_from_device_archive_and_upload),
textColor = MaterialTheme.colors.primary,
click = { state = MigrationFromState.Archiving }
){}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_all_data_will_be_uploaded))
}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_all_data_will_be_uploaded))
}
@Composable
private fun MutableState<MigrationFromState>.ArchivingView() {
Box {
SectionView(stringResource(MR.strings.migrate_from_device_archiving_database)) {}
SectionView(stringResource(MR.strings.migrate_from_device_archiving_database), card = true) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -279,7 +279,7 @@ private fun MutableState<MigrationFromState>.ArchivingView() {
@Composable
private fun MutableState<MigrationFromState>.DatabaseInitView(tempDatabaseFile: File, totalBytes: Long, archivePath: String) {
Box {
SectionView(stringResource(MR.strings.migrate_from_device_database_init)) {}
SectionView(stringResource(MR.strings.migrate_from_device_database_init), card = true) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -298,7 +298,7 @@ private fun MutableState<MigrationFromState>.UploadProgressView(
archivePath: String,
) {
Box {
SectionView(stringResource(MR.strings.migrate_from_device_uploading_archive)) {
SectionView(stringResource(MR.strings.migrate_from_device_uploading_archive), card = true) {
val ratio = uploadedBytes.toFloat() / max(totalBytes, 1)
LargeProgressView(ratio, "${(ratio * 100).toInt()}%", stringResource(MR.strings.migrate_from_device_bytes_uploaded).format(formatBytes(uploadedBytes)))
}
@@ -310,7 +310,7 @@ private fun MutableState<MigrationFromState>.UploadProgressView(
@Composable
private fun MutableState<MigrationFromState>.UploadFailedView(totalBytes: Long, archivePath: String, chatReceiver: MigrationFromChatReceiver?) {
SectionView(stringResource(MR.strings.migrate_from_device_upload_failed)) {
SectionView(stringResource(MR.strings.migrate_from_device_upload_failed), card = true) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_ios_share),
text = stringResource(MR.strings.migrate_from_device_repeat_upload),
@@ -319,8 +319,8 @@ private fun MutableState<MigrationFromState>.UploadFailedView(totalBytes: Long,
state = MigrationFromState.DatabaseInit(totalBytes, archivePath)
}
) {}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_try_again))
}
SectionTextFooter(stringResource(MR.strings.migrate_from_device_try_again))
LaunchedEffect(Unit) {
chatReceiver?.stopAndCleanUp()
}
@@ -329,14 +329,14 @@ private fun MutableState<MigrationFromState>.UploadFailedView(totalBytes: Long,
@Composable
private fun LinkCreationView() {
Box {
SectionView(stringResource(MR.strings.migrate_from_device_creating_archive_link)) {}
SectionView(stringResource(MR.strings.migrate_from_device_creating_archive_link), card = true) {}
ProgressView()
}
}
@Composable
private fun MutableState<MigrationFromState>.LinkShownView(fileId: Long, link: String, ctrl: ChatCtrl, chatReceiver: MigrationFromChatReceiver?) {
SectionView {
SectionView(card = true) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_close),
text = stringResource(MR.strings.migrate_from_device_cancel_migration),
@@ -361,15 +361,15 @@ private fun MutableState<MigrationFromState>.LinkShownView(fileId: Long, link: S
)
}
) {}
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_archive_will_be_deleted))
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_choose_migrate_from_another_device))
}
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_archive_will_be_deleted))
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_choose_migrate_from_another_device))
SectionSpacer()
SectionView(stringResource(MR.strings.show_QR_code)) {
SectionView(stringResource(MR.strings.show_QR_code), card = true) {
SimpleXLinkQRCode(link, onShare = {})
}
SectionSpacer()
SectionView(stringResource(MR.strings.migrate_from_device_or_share_this_file_link)) {
SectionView(stringResource(MR.strings.migrate_from_device_or_share_this_file_link), card = true) {
LinkTextView(link, true)
}
}
@@ -377,7 +377,7 @@ private fun MutableState<MigrationFromState>.LinkShownView(fileId: Long, link: S
@Composable
private fun MutableState<MigrationFromState>.FinishedView(chatDeletion: Boolean) {
Box {
SectionView(stringResource(MR.strings.migrate_from_device_migration_complete)) {
SectionView(stringResource(MR.strings.migrate_from_device_migration_complete), card = true) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_play_arrow_filled),
text = stringResource(MR.strings.migrate_from_device_start_chat),
@@ -410,9 +410,9 @@ private fun MutableState<MigrationFromState>.FinishedView(chatDeletion: Boolean)
)
}
) {}
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_you_must_not_start_database_on_two_device))
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_using_on_two_device_breaks_encryption))
}
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_you_must_not_start_database_on_two_device))
SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_using_on_two_device_breaks_encryption))
if (chatDeletion) {
ProgressView()
}
@@ -201,7 +201,7 @@ private fun MutableState<MigrationToState?>.PasteOrScanLinkView(close: () -> Uni
val progressIndicator = remember { mutableStateOf(false) }
Column {
if (appPlatform.isAndroid) {
SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' ')) {
SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' '), card = true) {
QRCodeScanner(showQRCodeScanner = remember { mutableStateOf(true) }) { text ->
checkUserLink(text)
}
@@ -209,12 +209,12 @@ private fun MutableState<MigrationToState?>.PasteOrScanLinkView(close: () -> Uni
SectionSpacer()
}
SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link)) {
SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link), card = true) {
PasteLinkView()
}
SectionSpacer()
SectionView(stringResource(MR.strings.chat_archive)) {
SectionView(stringResource(MR.strings.chat_archive), card = true) {
ArchiveImportView(progressIndicator, close)
}
}
@@ -280,7 +280,7 @@ private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, lin
mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, sessionMode = sessionMode.value))
}
SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings)) {
SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings), card = true) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_check),
text = stringResource(MR.strings.migrate_to_device_apply_onion),
@@ -297,15 +297,15 @@ private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, lin
}
}
){}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_confirm_network_settings_footer))
}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_confirm_network_settings_footer))
SectionSpacer()
val networkProxyPref = SharedPreference(get = { networkProxy.value }, set = {
networkProxy.value = it
})
SectionView(stringResource(MR.strings.network_settings_title)) {
SectionView(stringResource(MR.strings.network_settings_title), card = true) {
OnionRelatedLayout(
appPreferences.developerTools.get(),
networkUseSocksProxy,
@@ -325,7 +325,7 @@ private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, lin
@Composable
private fun MutableState<MigrationToState?>.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_database_init)) {}
SectionView(stringResource(MR.strings.migrate_to_device_database_init), card = true) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -345,7 +345,7 @@ private fun MutableState<MigrationToState?>.LinkDownloadingView(
networkProxy: NetworkProxy?
) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_downloading_details)) {}
SectionView(stringResource(MR.strings.migrate_to_device_downloading_details), card = true) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -356,7 +356,7 @@ private fun MutableState<MigrationToState?>.LinkDownloadingView(
@Composable
private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_downloading_archive)) {
SectionView(stringResource(MR.strings.migrate_to_device_downloading_archive), card = true) {
val ratio = downloadedBytes.toFloat() / max(totalBytes, 1)
LargeProgressView(ratio, "${(ratio * 100).toInt()}%", stringResource(MR.strings.migrate_to_device_bytes_downloaded).format(formatBytes(downloadedBytes)))
}
@@ -365,7 +365,7 @@ private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) {
@Composable
private fun MutableState<MigrationToState?>.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
SectionView(stringResource(MR.strings.migrate_to_device_download_failed)) {
SectionView(stringResource(MR.strings.migrate_to_device_download_failed), card = true) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_download),
text = stringResource(MR.strings.migrate_to_device_repeat_download),
@@ -374,8 +374,8 @@ private fun MutableState<MigrationToState?>.DownloadFailedView(link: String, cha
state = MigrationToState.DatabaseInit(link, netCfg, networkProxy)
}
) {}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again))
}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again))
LaunchedEffect(Unit) {
chatReceiver?.stopAndCleanUp()
File(archivePath).delete()
@@ -386,7 +386,7 @@ private fun MutableState<MigrationToState?>.DownloadFailedView(link: String, cha
@Composable
private fun MutableState<MigrationToState?>.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_importing_archive)) {}
SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -396,7 +396,7 @@ private fun MutableState<MigrationToState?>.ArchiveImportView(archivePath: Strin
@Composable
private fun MutableState<MigrationToState?>.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
SectionView(stringResource(MR.strings.migrate_to_device_import_failed)) {
SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_download),
text = stringResource(MR.strings.migrate_to_device_repeat_import),
@@ -405,8 +405,8 @@ private fun MutableState<MigrationToState?>.ArchiveImportFailedView(archivePath:
state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy)
}
) {}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again))
}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again))
}
@Composable
@@ -417,7 +417,7 @@ private fun MutableState<MigrationToState?>.PassphraseEnteringView(currentKey: S
Box {
val view = LocalMultiplatformView()
SectionView(stringResource(MR.strings.migrate_to_device_enter_passphrase)) {
SectionView(stringResource(MR.strings.migrate_to_device_enter_passphrase), card = true) {
SavePassphraseSetting(
useKeychain.value,
false,
@@ -489,7 +489,7 @@ private fun MutableState<MigrationToState?>.MigrationConfirmationView(status: DB
}
else -> Tuple4(generalGetString(MR.strings.error), null, generalGetString(MR.strings.unknown_error), null)
}
SectionView(header) {
SectionView(header, card = true) {
if (button != null && confirmation != null) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_download),
@@ -500,14 +500,14 @@ private fun MutableState<MigrationToState?>.MigrationConfirmationView(status: DB
}
) {}
}
SectionTextFooter(footer)
}
SectionTextFooter(footer)
}
@Composable
private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_migrating)) {}
SectionView(stringResource(MR.strings.migrate_to_device_migrating), card = true) {}
ProgressView()
}
LaunchedEffect(Unit) {
@@ -257,7 +257,7 @@ private fun ProfileStepView(
sheetState = bottomSheetModalState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
ModalView(close = close, background = MaterialTheme.colors.surface) {
ModalView(close = close) {
ColumnWithScrollBar {
AppBarTitle(generalGetString(MR.strings.create_channel_title), bottomPadding = DEFAULT_PADDING_HALF)
Row(
@@ -403,7 +403,6 @@ private fun ProgressStepView(
ModalView(
close = { showCancelAlert() },
background = MaterialTheme.colors.surface,
showClose = false,
) {
ColumnWithScrollBar {
@@ -568,7 +567,7 @@ private fun LinkStepView(
}
}
}
ModalView(close = close, background = MaterialTheme.colors.surface, showClose = false) {
ModalView(close = close, showClose = false) {
GroupLinkView(
chatModel = chatModel,
rhId = null,
@@ -99,7 +99,7 @@ fun AddGroupLayout(
sheetState = bottomSheetModalState,
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
ModalView(close = close, background = MaterialTheme.colors.surface) {
ModalView(close = close) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.create_secret_group_title), hostDevice(rhId), bottomPadding = DEFAULT_PADDING_HALF)
Row(
@@ -131,7 +131,8 @@ private fun ContactConnectionInfoLayout(
Spacer(Modifier.height(DEFAULT_PADDING))
SectionViewWithButton(
stringResource(MR.strings.one_time_link),
titleButton = if (connLink.connShortLink == null) null else {{ ToggleShortLinkButton(showShortLink) }}
titleButton = if (connLink.connShortLink == null) null else {{ ToggleShortLinkButton(showShortLink) }},
card = true
) {
SimpleXCreatedLinkQRCode(connLink, short = showShortLink.value)
incognitoEnabled()
@@ -139,14 +140,14 @@ private fun ContactConnectionInfoLayout(
OneTimeLinkLearnMoreButton(learnMore)
}
} else {
SectionView {
SectionView(card = true) {
incognitoEnabled()
OneTimeLinkLearnMoreButton(learnMore)
}
}
SectionTextFooter(sharedProfileInfo(chatModel, contactConnection.incognito))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
DeleteButton(deleteConnection)
@@ -224,7 +224,7 @@ private fun ModalData.NewChatSheetLayout(
}
}
if (deletedChats.isNotEmpty()) {
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionItemView(
click = {
@@ -322,26 +322,23 @@ private fun ModalData.NewChatSheetLayout(
item {
DeletedChatsItem(actionButtonsOriginal.asReversed())
}
if (filteredContactChats.isNotEmpty()) {
if (searchText.value.text.isEmpty()) {
item {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.contact_list_header_title), headerBottomPadding = DEFAULT_PADDING_HALF) {}
item {
if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) {
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false)
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
}
}
item {
NoFilteredContactsItem()
}
itemsIndexed(filteredContactChats) { index, chat ->
val nextChatSelected = remember(chat.id, filteredContactChats) {
derivedStateOf {
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
}
}
itemsIndexed(filteredContactChats) { index, chat ->
val nextChatSelected = remember(chat.id, filteredContactChats) {
derivedStateOf {
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
}
}
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
}
if (searchText.value.text.isEmpty()) {
item { Spacer(Modifier.height(DEFAULT_PADDING_HALF)) }
}
} else {
item { NoFilteredContactsItem() }
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
}
if (appPlatform.isAndroid) {
item {
@@ -410,23 +407,22 @@ private fun ModalData.NewChatSheetLayout(
item {
DeletedChatsItem(actionButtonsOriginal)
}
if (filteredContactChats.isNotEmpty()) {
if (searchText.value.text.isEmpty()) {
item {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.contact_list_header_title), headerBottomPadding = DEFAULT_PADDING_HALF) {}
item {
if (filteredContactChats.isNotEmpty() && searchText.value.text.isEmpty()) {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.contact_list_header_title).uppercase(), headerBottomPadding = DEFAULT_PADDING_HALF) {}
}
}
item {
NoFilteredContactsItem()
}
itemsIndexed(filteredContactChats) { index, chat ->
val nextChatSelected = remember(chat.id, filteredContactChats) {
derivedStateOf {
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
}
}
itemsIndexed(filteredContactChats) { index, chat ->
val nextChatSelected = remember(chat.id, filteredContactChats) {
derivedStateOf {
chatModel.chatId.value != null && filteredContactChats.getOrNull(index + 1)?.id == chatModel.chatId.value
}
}
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
}
} else {
item { NoFilteredContactsItem() }
ContactListNavLinkView(chat, nextChatSelected, showDeletedChatIcon = true)
}
if (appPlatform.isAndroid) {
item {
@@ -495,14 +495,14 @@ private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contact
)
SimpleXCreatedLinkQRCode(connLinkInvitation, short = showShortLink.value, onShare = { chatModel.markShowingInvitationUsed() })
} else {
SectionView(stringResource(MR.strings.share_this_1_time_link), headerBottomPadding = 5.dp) {
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) {
LinkTextView(connLinkInvitation.simplexChatUri(short = showShortLink.value), true)
}
Spacer(Modifier.height(DEFAULT_PADDING))
SectionViewWithButton(
stringResource(MR.strings.or_show_this_qr_code),
stringResource(MR.strings.or_show_this_qr_code).uppercase(),
titleButton = if (connLinkInvitation.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null
) {
SimpleXCreatedLinkQRCode(connLinkInvitation, short = showShortLink.value, onShare = { chatModel.markShowingInvitationUsed() })
@@ -519,7 +519,7 @@ private fun InviteView(rhId: Long?, connLinkInvitation: CreatedConnLink, contact
val currentUser = remember { chatModel.currentUser }.value
if (currentUser != null) {
SectionView(stringResource(MR.strings.new_chat_share_profile), headerBottomPadding = 5.dp) {
SectionView(stringResource(MR.strings.new_chat_share_profile).uppercase(), headerBottomPadding = 5.dp) {
SectionItemView(
padding = PaddingValues(
top = 0.dp,
@@ -643,14 +643,14 @@ private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState<Boolean>, p
)
}
SectionView(stringResource(MR.strings.paste_the_link_you_received), headerBottomPadding = 5.dp) {
SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase(), headerBottomPadding = 5.dp) {
PasteLinkView(rhId, pastedLink, showQRCodeScanner, close)
}
if (appPlatform.isAndroid) {
Spacer(Modifier.height(10.dp))
SectionView(stringResource(MR.strings.or_scan_qr_code), headerBottomPadding = 5.dp) {
SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase(), headerBottomPadding = 5.dp) {
QRCodeScanner(showQRCodeScanner) { text ->
val linkVerified = verifyOnly(text)
if (!linkVerified) {
@@ -373,7 +373,7 @@ private fun ChooseServerOperatorsInfoView() {
SectionDividerSpaced()
SectionView(title = stringResource(MR.strings.onboarding_network_about_operators)) {
SectionView(title = stringResource(MR.strings.onboarding_network_about_operators), card = true) {
chatModel.conditions.value.serverOperators.forEach { op ->
ServerOperatorRow(op)
}
@@ -23,7 +23,7 @@ import dev.icerock.moko.resources.StringResource
@Composable
fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) {
Column(Modifier.fillMaxSize().background(MaterialTheme.colors.surface).padding(horizontal = if (appPlatform.isDesktop) DEFAULT_PADDING * 2 else DEFAULT_PADDING)) {
Column(Modifier.fillMaxSize().padding(horizontal = if (appPlatform.isDesktop) DEFAULT_PADDING * 2 else DEFAULT_PADDING)) {
Spacer(Modifier.statusBarsPadding().padding(top = AppBarHeight * fontSizeSqrtMultiplier))
val paraPadding = PaddingValues(bottom = if (appPlatform.isDesktop) 10.dp else 12.dp)
Column(Modifier.weight(1f).padding(bottom = DEFAULT_PADDING).verticalScroll(rememberScrollState())) {
@@ -65,7 +65,7 @@ private fun LinkAMobileLayout(
Modifier.weight(0.3f),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
SectionView(generalGetString(MR.strings.this_device_name)) {
SectionView(generalGetString(MR.strings.this_device_name), card = true) {
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile))
PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { ChatModel.controller.appPrefs.offerRemoteMulticast.state }.value) {
@@ -106,8 +106,7 @@ fun SimpleXInfoLayout(
onboardingStage: SharedPreference<OnboardingStage>?
) {
val topBar = onboardingStage == null && !appPrefs.oneHandUI.state.value
val baseModifier = Modifier.fillMaxSize().systemBarsPadding().padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING)
val modifier = if (onboardingStage == null) baseModifier.background(MaterialTheme.colors.surface) else baseModifier
val modifier = Modifier.fillMaxSize().systemBarsPadding().padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING)
Column(if (topBar) modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier) else modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Box(Modifier.padding(top = DEFAULT_PADDING * 2).widthIn(max = if (appPlatform.isAndroid) 185.dp else 160.dp), contentAlignment = Alignment.Center) {
SimpleXLogo()
@@ -138,7 +138,7 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool
val v = versionDescriptions[currentVersion.value]
ModalView(close = close, background = MaterialTheme.colors.surface) {
ModalView(close = close) {
ColumnWithScrollBar(
Modifier
.padding(horizontal = DEFAULT_PADDING),
@@ -128,7 +128,7 @@ private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) {
@Composable
private fun ConnectDesktop(deviceName: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>, sessionAddress: MutableState<String>) {
AppBarTitle(stringResource(MR.strings.connect_to_desktop))
SectionView(stringResource(MR.strings.this_device_name)) {
SectionView(stringResource(MR.strings.this_device_name), card = true) {
DevicesView(deviceName, remoteCtrls) {
if (it != "") {
setDeviceName(it)
@@ -147,7 +147,7 @@ private fun ConnectDesktop(deviceName: String, remoteCtrls: SnapshotStateList<Re
@Composable
private fun ConnectingDesktop(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
AppBarTitle(stringResource(MR.strings.connecting_to_desktop))
SectionView(stringResource(MR.strings.connecting_to_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionView(stringResource(MR.strings.connecting_to_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
CtrlDeviceNameText(session, rc)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
CtrlDeviceVersionText(session)
@@ -155,14 +155,14 @@ private fun ConnectingDesktop(session: RemoteCtrlSession, rc: RemoteCtrlInfo?) {
if (session.sessionCode != null) {
SectionSpacer()
SectionView(stringResource(MR.strings.session_code)) {
SectionView(stringResource(MR.strings.session_code), card = true) {
SessionCodeText(session.sessionCode!!)
}
}
SectionSpacer()
SectionView {
SectionView(card = true) {
DisconnectButton(onClick = ::disconnectDesktop)
}
@@ -188,7 +188,7 @@ private fun ProgressIndicator() {
@Composable
private fun SearchingDesktop(deviceName: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
AppBarTitle(stringResource(MR.strings.connecting_to_desktop))
SectionView(stringResource(MR.strings.this_device_name)) {
SectionView(stringResource(MR.strings.this_device_name), card = true) {
DevicesView(deviceName, remoteCtrls) {
if (it != "") {
setDeviceName(it)
@@ -197,7 +197,7 @@ private fun SearchingDesktop(deviceName: String, remoteCtrls: SnapshotStateList<
}
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.found_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionView(stringResource(MR.strings.found_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
Text(stringResource(MR.strings.waiting_for_desktop), fontStyle = FontStyle.Italic)
}
SectionSpacer()
@@ -215,7 +215,7 @@ private fun FoundDesktop(
sessionAddress: MutableState<String>,
) {
AppBarTitle(stringResource(MR.strings.found_desktop))
SectionView(stringResource(MR.strings.this_device_name)) {
SectionView(stringResource(MR.strings.this_device_name), card = true) {
DevicesView(deviceName, remoteCtrls) {
if (it != "") {
setDeviceName(it)
@@ -224,7 +224,7 @@ private fun FoundDesktop(
}
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.found_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionView(stringResource(MR.strings.found_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
CtrlDeviceNameText(session, rc)
CtrlDeviceVersionText(session)
if (!compatible) {
@@ -256,7 +256,7 @@ private fun FoundDesktop(
@Composable
private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessCode: String, remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
AppBarTitle(stringResource(MR.strings.verify_connection))
SectionView(stringResource(MR.strings.connected_to_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionView(stringResource(MR.strings.connected_to_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
CtrlDeviceNameText(session, rc)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
CtrlDeviceVersionText(session)
@@ -264,7 +264,7 @@ private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessC
SectionSpacer()
SectionView(stringResource(MR.strings.verify_code_with_desktop)) {
SectionView(stringResource(MR.strings.verify_code_with_desktop), card = true) {
SessionCodeText(sessCode)
}
@@ -276,7 +276,7 @@ private fun VerifySession(session: RemoteCtrlSession, rc: RemoteCtrlInfo?, sessC
Text(generalGetString(MR.strings.confirm_verb))
}
SectionView {
SectionView(card = true) {
DisconnectButton(onClick = ::disconnectDesktop)
}
}
@@ -311,7 +311,7 @@ private fun CtrlDeviceVersionText(session: RemoteCtrlSession) {
@Composable
private fun ActiveSession(session: RemoteCtrlSession, rc: RemoteCtrlInfo, close: () -> Unit) {
AppBarTitle(stringResource(MR.strings.connected_to_desktop))
SectionView(stringResource(MR.strings.connected_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionView(stringResource(MR.strings.connected_desktop), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
Text(rc.deviceViewName)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
CtrlDeviceVersionText(session)
@@ -319,14 +319,14 @@ private fun ActiveSession(session: RemoteCtrlSession, rc: RemoteCtrlInfo, close:
if (session.sessionCode != null) {
SectionSpacer()
SectionView(stringResource(MR.strings.session_code)) {
SectionView(stringResource(MR.strings.session_code), card = true) {
SessionCodeText(session.sessionCode!!)
}
}
SectionSpacer()
SectionView {
SectionView(card = true) {
DisconnectButton { disconnectDesktop(close) }
}
}
@@ -355,7 +355,7 @@ private fun DevicesView(deviceName: String, remoteCtrls: SnapshotStateList<Remot
@Composable
private fun ScanDesktopAddressView(sessionAddress: MutableState<String>) {
SectionView(stringResource(MR.strings.scan_qr_code_from_desktop)) {
SectionView(stringResource(MR.strings.scan_qr_code_from_desktop), card = true) {
QRCodeScanner { text ->
sessionAddress.value = text
connectDesktopAddress(sessionAddress, text)
@@ -366,7 +366,7 @@ private fun ScanDesktopAddressView(sessionAddress: MutableState<String>) {
@Composable
private fun DesktopAddressView(sessionAddress: MutableState<String>) {
val clipboard = LocalClipboardManager.current
SectionView(stringResource(MR.strings.desktop_address)) {
SectionView(stringResource(MR.strings.desktop_address), card = true) {
if (sessionAddress.value.isEmpty()) {
SettingsActionItem(
painterResource(MR.images.ic_content_paste),
@@ -410,7 +410,7 @@ private fun DesktopAddressView(sessionAddress: MutableState<String>) {
private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.linked_desktops))
SectionView(stringResource(MR.strings.desktop_devices)) {
SectionView(stringResource(MR.strings.desktop_devices), card = true) {
remoteCtrls.forEach { rc ->
val showMenu = rememberSaveable { mutableStateOf(false) }
SectionItemViewLongClickable(click = {}, longClick = { showMenu.value = true }) {
@@ -427,7 +427,7 @@ private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) {
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.linked_desktop_options)) {
SectionView(stringResource(MR.strings.linked_desktop_options), card = true) {
PreferenceToggle(stringResource(MR.strings.verify_connections), checked = remember { controller.appPrefs.confirmRemoteSessions.state }.value) {
controller.appPrefs.confirmRemoteSessions.set(it)
}
@@ -92,15 +92,15 @@ fun ConnectMobileLayout(
) {
ColumnWithScrollBar {
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
SectionView(generalGetString(MR.strings.this_device_name)) {
SectionView(generalGetString(MR.strings.this_device_name), card = true) {
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile))
PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { controller.appPrefs.offerRemoteMulticast.state }.value) {
controller.appPrefs.offerRemoteMulticast.set(it)
}
SectionDividerSpaced()
}
SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile))
SectionDividerSpaced()
SectionView(stringResource(MR.strings.devices)) {
SectionView(stringResource(MR.strings.devices), card = true) {
if (chatModel.localUserCreated.value == true) {
SettingsActionItemWithContent(text = stringResource(MR.strings.this_device), icon = painterResource(MR.images.ic_desktop), click = connectDesktop) {
if (connectedHost.value == null) {
@@ -189,7 +189,7 @@ private fun ConnectMobileViewLayout(
if (title != null) {
AppBarTitle(title)
}
SectionView {
SectionView(card = true) {
if (invitation != null && sessionCode == null && port != null) {
Box {
QRCode(invitation)
@@ -211,10 +211,11 @@ private fun ConnectMobileViewLayout(
Text(generalGetString(MR.strings.share_link), color = MaterialTheme.colors.primary)
}
}
}
Spacer(Modifier.height(DEFAULT_PADDING))
}
if (deviceName != null || sessionCode != null) {
SectionView(stringResource(MR.strings.connected_mobile)) {
SectionView(stringResource(MR.strings.connected_mobile), card = true) {
SelectionContainer {
Text(
deviceName ?: stringResource(MR.strings.new_mobile_device),
@@ -227,7 +228,7 @@ private fun ConnectMobileViewLayout(
}
if (sessionCode != null) {
SectionView(stringResource(MR.strings.verify_code_on_mobile)) {
SectionView(stringResource(MR.strings.verify_code_on_mobile), card = true) {
SelectionContainer {
Text(
sessionCode.substring(0, 23),
@@ -1,9 +1,6 @@
package chat.simplex.common.views.usersettings
import CARD_ITEM_PADDING
import CARD_PADDING
import SectionBottomSpacer
import SectionDivider
import SectionDividerSpaced
import SectionItemView
import SectionItemViewSpaceBetween
@@ -61,9 +58,9 @@ expect fun AppearanceView(m: ChatModel)
object AppearanceScope {
@Composable
fun ProfileImageSection() {
SectionView(stringResource(MR.strings.settings_section_title_profile_images), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
SectionView(stringResource(MR.strings.settings_section_title_profile_images), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
val image = remember { chatModel.currentUser }.value?.image
Row(Modifier.padding(vertical = 10.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
Row(Modifier.padding(top = 10.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
val size = 60
Box(Modifier.offset(x = -(size / 12).dp)) {
if (!image.isNullOrEmpty()) {
@@ -94,7 +91,7 @@ object AppearanceScope {
@Composable
fun AppToolbarsSection() {
BoxWithConstraints {
SectionView(stringResource(MR.strings.appearance_app_toolbars)) {
SectionView(stringResource(MR.strings.appearance_app_toolbars), card = true) {
SectionItemViewWithoutMinPadding {
Box(Modifier.weight(1f)) {
Text(
@@ -115,7 +112,7 @@ object AppearanceScope {
val diff = it % 0.025f
appPrefs.inAppBarsAlpha.set(1f - (String.format(Locale.US, "%.3f", it + (if (diff >= 0.0125f) -diff + 0.025f else -diff)).toFloatOrNull() ?: 1f))
},
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - (CARD_PADDING + CARD_ITEM_PADDING) * 2) * 0.618f),
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f),
valueRange = 0f..0.5f,
steps = 21,
colors = SliderDefaults.colors(
@@ -161,7 +158,7 @@ object AppearanceScope {
val diff = it % 0.05f
saveBlur(((String.format(Locale.US, "%.2f", it + (if (diff >= 0.025f) -diff + 0.05f else -diff)).toFloatOrNull() ?: 1f) * 100).toInt())
},
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - (CARD_PADDING + CARD_ITEM_PADDING) * 2) * 0.618f),
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f),
valueRange = 0f..1f,
steps = 21,
colors = SliderDefaults.colors(
@@ -178,7 +175,7 @@ object AppearanceScope {
@Composable
fun MessageShapeSection() {
BoxWithConstraints {
SectionView(stringResource(MR.strings.settings_section_title_message_shape)) {
SectionView(stringResource(MR.strings.settings_section_title_message_shape), card = true) {
SectionItemViewWithoutMinPadding {
Text(stringResource(MR.strings.settings_message_shape_corner), Modifier.weight(1f))
Spacer(Modifier.width(10.dp))
@@ -189,7 +186,7 @@ object AppearanceScope {
appPreferences.chatItemRoundness.set(it + (if (diff >= 0.025f) -diff + 0.05f else -diff))
saveThemeToDatabase(null)
},
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - (CARD_PADDING + CARD_ITEM_PADDING) * 2) * 0.618f),
Modifier.widthIn(max = (this@BoxWithConstraints.maxWidth - DEFAULT_PADDING * 2) * 0.618f),
valueRange = 0f..1f,
steps = 20,
colors = SliderDefaults.colors(
@@ -208,8 +205,8 @@ object AppearanceScope {
@Composable
fun FontScaleSection() {
val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) }
SectionView(stringResource(MR.strings.appearance_font_size), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Row(Modifier.padding(vertical = 10.dp), verticalAlignment = Alignment.CenterVertically) {
SectionView(stringResource(MR.strings.appearance_font_size), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING), card = true) {
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(50.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
.clip(RoundedCornerShape(percent = 22))
@@ -523,9 +520,10 @@ object AppearanceScope {
}
}
SectionView(stringResource(MR.strings.settings_section_title_themes)) {
SectionView(stringResource(MR.strings.settings_section_title_themes), card = true) {
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
ThemeDestinationPicker(themeUserDestination)
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? ->
if (to != null) onImport(to)
@@ -539,7 +537,6 @@ object AppearanceScope {
},
onChooseType = { onChooseType(it, importWallpaperLauncher) },
)
SectionDivider()
val type = MaterialTheme.wallpaper.type
if (type is WallpaperType.Image && (themeUserDestination.value == null || perUserTheme.value.wallpaper?.imageFile != null)) {
SectionItemView(disabled = chatModel.remoteHostId != null && themeUserDestination.value != null, click = {
@@ -558,6 +555,7 @@ object AppearanceScope {
color = if (chatModel.remoteHostId != null && themeUserDestination.value != null) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
SectionSpacer()
}
val state: State<DefaultThemeMode?> = remember(appPrefs.currentTheme.get()) {
@@ -586,23 +584,23 @@ object AppearanceScope {
}
saveThemeToDatabase(null)
}
SectionItemView(click = {
val user = themeUserDestination.value
if (user == null) {
ModalManager.start.showModal {
val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? ->
if (to != null) onImport(to)
}
CustomizeThemeView { onChooseType(it, importWallpaperLauncher) }
}
} else {
ModalManager.start.showModalCloseable { close ->
UserWallpaperEditorModal(chatModel.remoteHostId(), user.first, close)
}
SectionItemView(click = {
val user = themeUserDestination.value
if (user == null) {
ModalManager.start.showModal {
val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? ->
if (to != null) onImport(to)
}
CustomizeThemeView { onChooseType(it, importWallpaperLauncher) }
}
} else {
ModalManager.start.showModalCloseable { close ->
UserWallpaperEditorModal(chatModel.remoteHostId(), user.first, close)
}
}) {
Text(stringResource(MR.strings.customize_theme_title))
}
}) {
Text(stringResource(MR.strings.customize_theme_title))
}
}
@@ -628,35 +626,33 @@ object AppearanceScope {
)
}
SectionView {
WallpaperPresetSelector(
selectedWallpaper = wallpaperType,
baseTheme = currentTheme.base,
currentColors = { type ->
ThemeManager.currentColors(type, null, null, appPrefs.themeOverrides.get())
},
onChooseType = onChooseType
)
WallpaperPresetSelector(
selectedWallpaper = wallpaperType,
baseTheme = currentTheme.base,
currentColors = { type ->
ThemeManager.currentColors(type, null, null, appPrefs.themeOverrides.get())
},
onChooseType = onChooseType
)
val type = MaterialTheme.wallpaper.type
if (type is WallpaperType.Image) {
SectionItemView(disabled = chatModel.remoteHostId != null, click = {
val defaultActiveTheme = ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get())
ThemeManager.saveAndApplyWallpaper(baseTheme, null)
ThemeManager.removeTheme(defaultActiveTheme?.themeId)
removeWallpaperFile(type.filename)
saveThemeToDatabase(null)
}) {
Text(
stringResource(MR.strings.theme_remove_image),
color = if (chatModel.remoteHostId == null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
)
}
val type = MaterialTheme.wallpaper.type
if (type is WallpaperType.Image) {
SectionItemView(disabled = chatModel.remoteHostId != null, click = {
val defaultActiveTheme = ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get())
ThemeManager.saveAndApplyWallpaper(baseTheme, null)
ThemeManager.removeTheme(defaultActiveTheme?.themeId)
removeWallpaperFile(type.filename)
saveThemeToDatabase(null)
}) {
Text(
stringResource(MR.strings.theme_remove_image),
color = if (chatModel.remoteHostId == null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
)
}
SectionSpacer()
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_chat_colors)) {
SectionView(stringResource(MR.strings.settings_section_title_chat_colors), card = true) {
WallpaperSetupView(
wallpaperType,
baseTheme,
@@ -685,18 +681,16 @@ object AppearanceScope {
val currentOverrides = remember(currentTheme) { ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) }
val canResetColors = currentTheme.base.hasChangedAnyColor(currentOverrides)
if (canResetColors) {
SectionView {
SectionItemView({
ThemeManager.resetAllThemeColors()
saveThemeToDatabase(null)
}) {
Text(generalGetString(MR.strings.reset_color), color = colors.primary)
}
SectionItemView({
ThemeManager.resetAllThemeColors()
saveThemeToDatabase(null)
}) {
Text(generalGetString(MR.strings.reset_color), color = colors.primary)
}
SectionSpacer()
}
SectionView {
SectionView(card = true) {
val theme = remember { mutableStateOf(null as String?) }
val exportThemeLauncher = rememberFileChooserLauncher(false) { to: URI? ->
val themeValue = theme.value
@@ -759,7 +753,7 @@ object AppearanceScope {
onLongClick = onLongClick
)
.onRightClick(onLongClick)
.size(28.dp),
.size(44.dp),
contentAlignment = Alignment.Center
) {
Icon(painterResource(if (themeMode == DefaultThemeMode.LIGHT) MR.images.ic_light_mode else MR.images.ic_bedtime_moon), stringResource(MR.strings.color_mode_light), tint = MaterialTheme.colors.secondary)
@@ -949,7 +943,7 @@ object AppearanceScope {
@Composable
fun CustomizeThemeColorsSection(currentTheme: ThemeManager.ActiveTheme, editColor: (ThemeColor) -> Unit) {
SectionView(stringResource(MR.strings.theme_colors_section_title)) {
SectionView(stringResource(MR.strings.theme_colors_section_title), card = true) {
SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY) }) {
val title = generalGetString(MR.strings.color_primary)
Text(title)
@@ -1241,6 +1235,7 @@ fun WallpaperSetupView(
Text(title)
Icon(painterResource(MR.images.ic_circle_filled), title, tint = wallpaperTintColor)
}
SectionSpacer()
}
SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE) }) {
@@ -24,19 +24,19 @@ fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) ->
val developerTools = m.controller.appPrefs.developerTools
val devTools = remember { developerTools.state }
val unchangedHints = mutableStateOf(unchangedHintPreferences())
SectionView {
SectionView(card = true) {
InstallTerminalAppItem(uriHandler)
ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.start.showModalCloseable { TerminalView(false) } } }
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) + " " +
generalGetString(MR.strings.developer_options)
)
}
SectionTextFooter(
generalGetString(if (devTools.value) MR.strings.show_dev_options else MR.strings.hide_dev_options) + " " +
generalGetString(MR.strings.developer_options)
)
if (devTools.value) {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.developer_options_section)) {
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.developer_options_section), card = true) {
SettingsActionItemWithContent(painterResource(MR.images.ic_breaking_news), stringResource(MR.strings.debug_logs)) {
DefaultSwitch(
checked = remember { appPrefs.logLevel.state }.value <= LogLevel.DEBUG,
@@ -59,15 +59,15 @@ fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) ->
SettingsPreferenceItem(painterResource(MR.images.ic_avg_pace), stringResource(MR.strings.show_slow_api_calls), appPreferences.showSlowApiCalls)
}
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.deprecated_options_section)) {
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.deprecated_options_section), card = true) {
val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode
SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = {
simplexLinkMode.set(it)
chatModel.simplexLinkMode.value = it
})
SectionBottomSpacer()
}
SectionBottomSpacer()
}
}
@@ -6,7 +6,6 @@ import SectionItemViewWithoutMinPadding
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
@@ -57,32 +56,30 @@ private fun HiddenProfileLayout(
user: User,
saveProfilePassword: (String) -> Unit
) {
Box(Modifier.fillMaxSize().background(MaterialTheme.colors.surface)) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.hide_profile))
SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) {
UserProfileRow(user)
}
SectionSpacer()
val hidePassword = rememberSaveable { mutableStateOf("") }
val confirmHidePassword = rememberSaveable { mutableStateOf("") }
val passwordValid by remember { derivedStateOf { hidePassword.value == hidePassword.value.trim() } }
val confirmValid by remember { derivedStateOf { confirmHidePassword.value == "" || hidePassword.value == confirmHidePassword.value } }
val saveDisabled by remember { derivedStateOf { hidePassword.value == "" || !passwordValid || confirmHidePassword.value == "" || !confirmValid } }
SectionView(stringResource(MR.strings.hidden_profile_password)) {
SectionItemViewWithoutMinPadding {
PassphraseField(hidePassword, generalGetString(MR.strings.password_to_show), isValid = { passwordValid }, showStrength = true)
}
SectionItemViewWithoutMinPadding {
PassphraseField(confirmHidePassword, stringResource(MR.strings.confirm_password), isValid = { confirmValid }, dependsOn = hidePassword)
}
SectionItemViewSpaceBetween({ saveProfilePassword(hidePassword.value) }, disabled = saveDisabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(MR.strings.save_profile_password), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
SectionTextFooter(stringResource(MR.strings.to_reveal_profile_enter_password))
SectionBottomSpacer()
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.hide_profile))
SectionView(contentPadding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) {
UserProfileRow(user)
}
SectionSpacer()
val hidePassword = rememberSaveable { mutableStateOf("") }
val confirmHidePassword = rememberSaveable { mutableStateOf("") }
val passwordValid by remember { derivedStateOf { hidePassword.value == hidePassword.value.trim() } }
val confirmValid by remember { derivedStateOf { confirmHidePassword.value == "" || hidePassword.value == confirmHidePassword.value } }
val saveDisabled by remember { derivedStateOf { hidePassword.value == "" || !passwordValid || confirmHidePassword.value == "" || !confirmValid } }
SectionView(stringResource(MR.strings.hidden_profile_password).uppercase()) {
SectionItemViewWithoutMinPadding {
PassphraseField(hidePassword, generalGetString(MR.strings.password_to_show), isValid = { passwordValid }, showStrength = true)
}
SectionItemViewWithoutMinPadding {
PassphraseField(confirmHidePassword, stringResource(MR.strings.confirm_password), isValid = { confirmValid }, dependsOn = hidePassword)
}
SectionItemViewSpaceBetween({ saveProfilePassword(hidePassword.value) }, disabled = saveDisabled, minHeight = TextFieldDefaults.MinHeight) {
Text(generalGetString(MR.strings.save_profile_password), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
}
SectionTextFooter(stringResource(MR.strings.to_reveal_profile_enter_password))
SectionBottomSpacer()
}
}
@@ -55,7 +55,7 @@ fun NotificationsSettingsLayout(
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.notifications))
SectionView(null) {
SectionView(null, card = true) {
if (appPlatform == AppPlatform.ANDROID) {
SettingsActionItemWithContent(null, stringResource(MR.strings.settings_notifications_mode_title), { showPage(CurrentPage.NOTIFICATIONS_MODE) }) {
Text(
@@ -74,9 +74,9 @@ fun NotificationsSettingsLayout(
color = MaterialTheme.colors.secondary
)
}
}
if (platform.androidIsXiaomiDevice() && (notificationsMode.value == NotificationsMode.PERIODIC || notificationsMode.value == NotificationsMode.SERVICE)) {
SectionTextFooter(annotatedStringResource(MR.strings.xiaomi_ignore_battery_optimization))
if (platform.androidIsXiaomiDevice() && (notificationsMode.value == NotificationsMode.PERIODIC || notificationsMode.value == NotificationsMode.SERVICE)) {
SectionTextFooter(annotatedStringResource(MR.strings.xiaomi_ignore_battery_optimization))
}
}
SectionBottomSpacer()
}
@@ -81,27 +81,27 @@ private fun PreferencesLayout(
onTTLUpdated = onTTLUpdated
)
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.allow) }
FeatureSection(ChatFeature.FullDelete, allowFullDeletion) {
applyPrefs(preferences.copy(fullDelete = SimpleChatPreference(allow = it)))
}
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowReactions = remember(preferences) { mutableStateOf(preferences.reactions.allow) }
FeatureSection(ChatFeature.Reactions, allowReactions) {
applyPrefs(preferences.copy(reactions = SimpleChatPreference(allow = it)))
}
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.allow) }
FeatureSection(ChatFeature.Voice, allowVoice) {
applyPrefs(preferences.copy(voice = SimpleChatPreference(allow = it)))
}
SectionDividerSpaced()
SectionDividerSpaced(true, maxBottomPadding = false)
val allowCalls = remember(preferences) { mutableStateOf(preferences.calls.allow) }
FeatureSection(ChatFeature.Calls, allowCalls) {
applyPrefs(preferences.copy(calls = SimpleChatPreference(allow = it)))
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
reset = reset,
save = savePrefs,
@@ -113,7 +113,7 @@ private fun PreferencesLayout(
@Composable
private fun FeatureSection(feature: ChatFeature, allowFeature: State<FeatureAllowed>, onSelected: (FeatureAllowed) -> Unit) {
SectionView {
SectionView(card = true) {
ExposedDropDownSettingRow(
feature.text,
FeatureAllowed.values().map { it to it.text },
@@ -133,7 +133,7 @@ private fun TimedMessagesFeatureSection(
onTTLUpdated: (Int?) -> Unit
) {
val ttl = rememberSaveable(preferences) { mutableStateOf(preferences.timedMessages.ttl) }
SectionView {
SectionView(card = true) {
PreferenceToggleWithIcon(
ChatFeature.TimedMessages.text,
ChatFeature.TimedMessages.icon,
@@ -163,7 +163,7 @@ private fun TimedMessagesFeatureSection(
@Composable
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
SectionView {
SectionView(card = true) {
SectionItemView(reset, disabled = disabled) {
Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
@@ -60,7 +60,7 @@ fun PrivacySettingsView(
PrivacyDeviceSection(showSettingsModal, setPerformLA)
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_chats)) {
SectionView(stringResource(MR.strings.settings_section_title_chats), card = true) {
SettingsPreferenceItem(
painterResource(MR.images.ic_travel_explore),
stringResource(MR.strings.send_link_previews),
@@ -93,7 +93,7 @@ fun PrivacySettingsView(
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_files)) {
SectionView(stringResource(MR.strings.settings_section_title_files), card = true) {
SettingsPreferenceItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.encrypt_local_files), chatModel.controller.appPrefs.privacyEncryptLocalFiles, onChange = { enable ->
withBGApi { chatModel.controller.apiSetEncryptLocalFiles(enable) }
})
@@ -171,7 +171,7 @@ fun PrivacySettingsView(
}
if (!chatModel.desktopNoUserNoRemote) {
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
ContacRequestsFromGroupsSection(
currentUser = currentUser,
setAutoAcceptGrpDirectInvs = { enable ->
@@ -179,7 +179,7 @@ fun PrivacySettingsView(
}
)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
DeliveryReceiptsSection(
currentUser = currentUser,
setOrAskSendReceiptsContacts = { enable ->
@@ -277,7 +277,7 @@ private fun ContacRequestsFromGroupsSection(
currentUser: User,
setAutoAcceptGrpDirectInvs: (Boolean) -> Unit
) {
SectionView(stringResource(MR.strings.settings_section_title_contact_requests_from_groups)) {
SectionView(stringResource(MR.strings.settings_section_title_contact_requests_from_groups), card = true) {
SettingsActionItemWithContent(painterResource(MR.images.ic_check), stringResource(MR.strings.auto_accept_contact)) {
DefaultSwitch(
checked = currentUser.autoAcceptMemberContacts,
@@ -306,7 +306,7 @@ private fun DeliveryReceiptsSection(
setOrAskSendReceiptsContacts: (Boolean) -> Unit,
setOrAskSendReceiptsGroups: (Boolean) -> Unit,
) {
SectionView(stringResource(MR.strings.settings_section_title_delivery_receipts)) {
SectionView(stringResource(MR.strings.settings_section_title_delivery_receipts), card = true) {
SettingsActionItemWithContent(painterResource(MR.images.ic_person), stringResource(MR.strings.receipts_section_contacts)) {
DefaultSwitch(
checked = currentUser.sendRcptsContacts,
@@ -562,7 +562,7 @@ fun SimplexLockView(
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.chat_lock))
SectionView {
SectionView(card = true) {
EnableLock(remember { appPrefs.performLA.state }) { performLAToggle ->
showAuthScreen.value = performLAToggle
chatModel.controller.appPrefs.laNoticeShown.set(true)
@@ -619,7 +619,7 @@ fun SimplexLockView(
}
if (performLA.value && laMode.value == LAMode.PASSCODE) {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.self_destruct_passcode)) {
SectionView(stringResource(MR.strings.self_destruct_passcode), card = true) {
val openInfo = {
ModalManager.start.showModal {
SelfDestructInfoView()
@@ -2,7 +2,6 @@ package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionItemViewSpaceBetween
import SectionView
import androidx.compose.runtime.Composable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@@ -102,21 +101,20 @@ fun RTCServersLayout(
) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.your_ICE_servers))
SectionView {
SectionItemViewSpaceBetween {
Text(stringResource(MR.strings.configure_ICE_servers), Modifier.padding(end = 24.dp))
DefaultSwitch(
checked = isUserRTCServers,
onCheckedChange = isUserRTCServersOnOff,
)
}
}
Column(
Modifier
.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
SectionItemViewSpaceBetween(padding = PaddingValues()) {
Text(stringResource(MR.strings.configure_ICE_servers), Modifier.padding(end = 24.dp))
DefaultSwitch(
checked = isUserRTCServers,
onCheckedChange = isUserRTCServersOnOff,
)
}
if (!isUserRTCServers) {
Text(stringResource(MR.strings.using_simplex_chat_servers), lineHeight = 22.sp)
} else {
@@ -1,6 +1,5 @@
package chat.simplex.common.views.usersettings
import CARD_ITEM_PADDING
import SectionBottomSpacer
import SectionDividerSpaced
import SectionItemView
@@ -101,7 +100,7 @@ fun SettingsLayout(
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.your_settings))
SectionView(stringResource(MR.strings.settings_section_title_settings)) {
SectionView(stringResource(MR.strings.settings_section_title_settings), card = true) {
SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showCustomModal { _, close -> NetworkAndServersView(close) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
@@ -110,14 +109,14 @@ fun SettingsLayout(
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_chat_database)) {
SectionView(stringResource(MR.strings.settings_section_title_chat_database), card = true) {
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView() }, stopped)
SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } } }, disabled = stopped)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_help)) {
SectionView(stringResource(MR.strings.settings_section_title_help), card = true) {
SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close = close) }, disabled = stopped)
SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) })
@@ -128,7 +127,7 @@ fun SettingsLayout(
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_support)) {
SectionView(stringResource(MR.strings.settings_section_title_support), card = true) {
if (!BuildConfigCommon.ANDROID_BUNDLE) {
ContributeItem(uriHandler)
}
@@ -349,9 +348,9 @@ fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (
click,
extraPadding = extraPadding,
padding = if (extraPadding && icon != null)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = CARD_ITEM_PADDING)
PaddingValues(start = DEFAULT_PADDING * 1.7f, end = DEFAULT_PADDING)
else
PaddingValues(horizontal = CARD_ITEM_PADDING),
PaddingValues(horizontal = DEFAULT_PADDING),
disabled = disabled
) {
if (icon != null) {
@@ -301,17 +301,17 @@ private fun UserAddressLayout(
) {
if (userAddress == null) {
if (!onboarding) {
SectionView(generalGetString(MR.strings.for_social_media)) {
SectionView(generalGetString(MR.strings.for_social_media), card = true) {
CreateAddressButton(createAddress)
}
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.or_to_share_privately)) {
SectionView(generalGetString(MR.strings.or_to_share_privately), card = true) {
CreateOneTimeLinkButton()
}
SectionDividerSpaced()
SectionView {
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView(card = true) {
LearnMoreButton(learnMore)
}
}
@@ -336,7 +336,7 @@ private fun UserAddressLayout(
val savedAddressSettingsState = remember { mutableStateOf(addressSettingsState.value) }
SectionViewWithButton(
stringResource(MR.strings.for_social_media),
stringResource(MR.strings.for_social_media).uppercase(),
titleButton = if (userAddress.connLinkContact.connShortLink != null) {{ ToggleShortLinkButton(showShortLink) }} else null
) {
SimpleXCreatedLinkQRCode(userAddress.connLinkContact, short = showShortLink.value)
@@ -359,20 +359,20 @@ private fun UserAddressLayout(
}
}
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.or_to_share_privately)) {
SectionDividerSpaced(maxTopPadding = addressSettingsState.value.businessAddress)
SectionView(generalGetString(MR.strings.or_to_share_privately), card = true) {
CreateOneTimeLinkButton()
}
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
LearnMoreButton(learnMore)
}
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
DeleteAddressButton(deleteAddress)
SectionTextFooter(stringResource(MR.strings.your_contacts_will_remain_connected))
}
SectionTextFooter(stringResource(MR.strings.your_contacts_will_remain_connected))
}
}
}
@@ -503,7 +503,7 @@ private fun ModalData.UserAddressSettings(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
SectionView {
SectionView(card = true) {
ShareWithContactsButton(shareViaProfile, setProfileAddress)
AutoAcceptToggle(addressSettingsState) { saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) }
if (addressSettingsState.value.autoAccept && !chatModel.addressShortLinkDataSet() && !addressSettingsState.value.businessAddress) {
@@ -512,18 +512,13 @@ private fun ModalData.UserAddressSettings(
}
SectionDividerSpaced()
SectionView(
stringResource(MR.strings.address_welcome_message),
contentPadding = PaddingValues(vertical = DEFAULT_PADDING_HALF),
) {
SectionView(stringResource(MR.strings.address_welcome_message), card = true) {
AutoReplyEditor(addressSettingsState)
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView {
saveAddressSettingsButton(addressSettingsState.value == savedAddressSettingsState.value) {
saveAddressSettings(addressSettingsState.value, savedAddressSettingsState)
}
saveAddressSettingsButton(addressSettingsState.value == savedAddressSettingsState.value) {
saveAddressSettings(addressSettingsState.value, savedAddressSettingsState)
}
}
}
@@ -98,7 +98,7 @@ fun UserProfileLayout(
showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) }, close)
}
}
ModalView(close = closeWithAlert, background = MaterialTheme.colors.surface) {
ModalView(close = closeWithAlert) {
ColumnWithScrollBar(
Modifier
.padding(horizontal = DEFAULT_PADDING),
@@ -1,14 +1,12 @@
package chat.simplex.common.views.usersettings
import SectionBottomSpacer
import SectionDivider
import SectionItemView
import SectionItemViewSpaceBetween
import SectionItemViewWithoutMinPadding
import SectionSpacer
import SectionTextFooter
import SectionView
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
@@ -164,16 +162,18 @@ private fun UserProfilesLayout(
unmuteUser: (User) -> Unit,
showHiddenProfile: (User) -> Unit,
) {
Box(Modifier.fillMaxSize().background(MaterialTheme.colors.surface)) {
ColumnWithScrollBar {
if (profileHidden.value) {
ColumnWithScrollBar {
if (profileHidden.value) {
SectionView {
SettingsActionItem(painterResource(MR.images.ic_lock_open_right), stringResource(MR.strings.enter_password_to_show), click = {
profileHidden.value = false
})
SectionSpacer()
}
AppBarTitle(stringResource(MR.strings.your_chat_profiles), hostDevice(remember { chatModel.remoteHostId() }))
SectionSpacer()
}
AppBarTitle(stringResource(MR.strings.your_chat_profiles), hostDevice(remember { chatModel.remoteHostId() }))
SectionView {
for (user in filteredUsers) {
UserView(user, visibleUsersCount, activateUser, removeUser, unhideUser, muteUser, unmuteUser, showHiddenProfile)
Divider(Modifier.padding(horizontal = 8.dp))
@@ -185,22 +185,22 @@ private fun UserProfilesLayout(
Text(stringResource(MR.strings.users_add), color = MaterialTheme.colors.primary)
}
}
SectionTextFooter(stringResource(MR.strings.tap_to_activate_profile))
LaunchedEffect(Unit) {
if (showHiddenProfilesNotice.state.value && users.size > 1) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.make_profile_private),
text = generalGetString(MR.strings.you_can_hide_or_mute_user_profile),
confirmText = generalGetString(MR.strings.ok),
dismissText = generalGetString(MR.strings.dont_show_again),
onDismiss = {
showHiddenProfilesNotice.set(false)
},
)
}
}
SectionBottomSpacer()
}
SectionTextFooter(stringResource(MR.strings.tap_to_activate_profile))
LaunchedEffect(Unit) {
if (showHiddenProfilesNotice.state.value && users.size > 1) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.make_profile_private),
text = generalGetString(MR.strings.you_can_hide_or_mute_user_profile),
confirmText = generalGetString(MR.strings.ok),
dismissText = generalGetString(MR.strings.dont_show_again),
onDismiss = {
showHiddenProfilesNotice.set(false)
},
)
}
}
SectionBottomSpacer()
}
}
@@ -1,9 +1,6 @@
package chat.simplex.common.views.usersettings
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -19,7 +16,7 @@ import chat.simplex.res.MR
@Composable
fun VersionInfoView(info: CoreVersionInfo) {
ColumnWithScrollBar(
Modifier.fillMaxSize().background(MaterialTheme.colors.surface).padding(horizontal = DEFAULT_PADDING),
Modifier.padding(horizontal = DEFAULT_PADDING),
) {
AppBarTitle(stringResource(MR.strings.app_version_title), withPadding = false)
if (appPlatform.isAndroid) {
@@ -228,19 +228,19 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() -
AppBarTitle(stringResource(MR.strings.network_settings_title))
if (currentRemoteHost == null) {
SectionView(generalGetString(MR.strings.settings_section_title_private_message_routing)) {
SectionView(generalGetString(MR.strings.settings_section_title_private_message_routing), card = true) {
SMPProxyModePicker(smpProxyMode, showModal, updateSMPProxyMode)
SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { derivedStateOf { smpProxyMode.value != SMPProxyMode.Never } })
SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy)
}
SectionTextFooter(stringResource(MR.strings.private_routing_explanation))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.network_session_mode_transport_isolation)) {
SectionView(stringResource(MR.strings.network_session_mode_transport_isolation), card = true) {
SessionModePicker(sessionMode, showModal, updateSessionMode)
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.network_smp_web_port_section_title)) {
SectionView(stringResource(MR.strings.network_smp_web_port_section_title), card = true) {
ExposedDropDownSettingRow(
stringResource(MR.strings.network_smp_web_port_toggle),
SMPWebPortServers.entries.map { it to stringResource(it.text) },
@@ -251,9 +251,9 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() -
if (smpWebPortServers.value == SMPWebPortServers.Preset) stringResource(MR.strings.network_smp_web_port_preset_footer)
else String.format(stringResource(MR.strings.network_smp_web_port_footer), if (smpWebPortServers.value == SMPWebPortServers.All) "443" else "5223")
)
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.network_option_tcp_connection)) {
SectionView(stringResource(MR.strings.network_option_tcp_connection), card = true) {
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeoutInteractive,
@@ -332,7 +332,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() -
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
SectionItemView(reset, disabled = resetDisabled) {
Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
@@ -182,7 +182,7 @@ private fun ChatRelayLayout(
@Composable
private fun PresetRelay(relay: MutableState<UserChatRelay>, testing: MutableState<Boolean>) {
SectionView(stringResource(MR.strings.preset_relay_address)) {
SectionView(stringResource(MR.strings.preset_relay_address).uppercase()) {
SelectionContainer {
Text(
relay.value.address,
@@ -192,7 +192,7 @@ private fun PresetRelay(relay: MutableState<UserChatRelay>, testing: MutableStat
}
}
SectionDividerSpaced()
SectionView(stringResource(MR.strings.preset_relay_name)) {
SectionView(stringResource(MR.strings.preset_relay_name).uppercase()) {
SectionItemView {
Text(relay.value.displayName)
}
@@ -229,7 +229,7 @@ private fun CustomRelay(
}
SectionView(
stringResource(MR.strings.your_relay_address),
stringResource(MR.strings.your_relay_address).uppercase(),
icon = painterResource(MR.images.ic_error),
iconTint = if (!validAddress.value) MaterialTheme.colors.error else Color.Transparent,
) {
@@ -238,13 +238,13 @@ private fun CustomRelay(
Modifier.height(144.dp)
)
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
Column {
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
Row(Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
Text(
stringResource(MR.strings.your_relay_name),
stringResource(MR.strings.your_relay_name).uppercase(),
color = MaterialTheme.colors.secondary, style = MaterialTheme.typography.body2, fontSize = 12.sp
)
IconButton(
@@ -270,7 +270,7 @@ private fun CustomRelay(
if (relay.value.tested != true) {
SectionTextFooter(annotatedStringResource(MR.strings.test_relay_to_retrieve_name))
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
UseRelaySection(relay, validAddress.value, testing)
@@ -291,7 +291,7 @@ private fun UseRelaySection(
testing: MutableState<Boolean>
) {
val scope = rememberCoroutineScope()
SectionView(stringResource(MR.strings.use_relay)) {
SectionView(stringResource(MR.strings.use_relay).uppercase()) {
SectionItemViewSpaceBetween(
click = {
testing.value = true
@@ -377,7 +377,7 @@ fun ModalData.NewChatRelayView(
ModalView(close = {
addChatRelay(relayToEdit.value, userServers, serverErrors, serverWarnings, rhId, close)
}, background = MaterialTheme.colors.surface) {
}) {
NewChatRelayLayout(relayToEdit)
}
}
@@ -22,7 +22,6 @@ import androidx.compose.ui.text.*
import androidx.compose.ui.text.input.*
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
@@ -211,13 +210,13 @@ fun ModalData.NetworkAndServersView(closeNetworkAndServers: () -> Unit) {
AppBarTitle(stringResource(MR.strings.network_and_servers))
// TODO: Review this and socks.
if (!chatModel.desktopNoUserNoRemote) {
SectionView(generalGetString(MR.strings.network_preset_servers_title)) {
SectionView(generalGetString(MR.strings.network_preset_servers_title), card = true) {
userServers.value.forEachIndexed { index, srv ->
srv.operator?.let { ServerOperatorRow(index, it, currUserServers, userServers, serverErrors, serverWarnings, currentRemoteHost?.remoteHostId) }
}
if (conditionsAction != null && anyOperatorEnabled.value) {
ConditionsButton(conditionsAction, rhId = currentRemoteHost?.remoteHostId)
}
}
if (conditionsAction != null && anyOperatorEnabled.value) {
ConditionsButton(conditionsAction, rhId = currentRemoteHost?.remoteHostId)
}
val footerText = if (conditionsAction is UsageConditionsAction.Review && conditionsAction.deadline != null && anyOperatorEnabled.value) {
String.format(generalGetString(MR.strings.operator_conditions_will_be_accepted_on), localDate(conditionsAction.deadline))
@@ -229,7 +228,7 @@ fun ModalData.NetworkAndServersView(closeNetworkAndServers: () -> Unit) {
SectionDividerSpaced()
}
SectionView(generalGetString(MR.strings.settings_section_title_messages)) {
SectionView(generalGetString(MR.strings.settings_section_title_messages), card = true) {
val nullOperatorIndex = userServers.value.indexOfFirst { it.operator == null }
if (nullOperatorIndex != -1) {
@@ -263,25 +262,21 @@ fun ModalData.NetworkAndServersView(closeNetworkAndServers: () -> Unit) {
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) } })
SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } })
}
}
if (currentRemoteHost == null) {
if (networkUseSocksProxy.value) {
SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations))
SectionDividerSpaced()
} else {
SectionDividerSpaced(maxBottomPadding = false)
if (networkUseSocksProxy.value) {
SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations))
SectionDividerSpaced(maxTopPadding = true)
} else {
SectionDividerSpaced(maxBottomPadding = false)
}
}
}
val saveDisabled = !serversCanBeSaved(currUserServers.value, userServers.value, serverErrors.value)
SectionView {
SectionItemView(
{ scope.launch { saveServers(rhId = currentRemoteHost?.remoteHostId, currUserServers, userServers) } },
disabled = saveDisabled,
) {
Text(stringResource(MR.strings.smp_servers_save), color = if (!saveDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary)
}
SectionItemView(
{ scope.launch { saveServers(rhId = currentRemoteHost?.remoteHostId, currUserServers, userServers) } },
disabled = saveDisabled,
) {
Text(stringResource(MR.strings.smp_servers_save), color = if (!saveDisabled) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary)
}
val serversErr = globalServersError(serverErrors.value)
if (serversErr != null) {
@@ -302,13 +297,13 @@ fun ModalData.NetworkAndServersView(closeNetworkAndServers: () -> Unit) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.settings_section_title_calls)) {
SectionView(generalGetString(MR.strings.settings_section_title_calls), card = true) {
SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), { ModalManager.start.showModal { RTCServersView(m) } })
}
if (appPlatform.isAndroid) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.settings_section_title_network_connection)) {
SectionView(generalGetString(MR.strings.settings_section_title_network_connection), card = true) {
val info = remember { chatModel.networkInfo }.value
SettingsActionItemWithContent(icon = null, info.networkType.text) {
Icon(painterResource(MR.images.ic_circle_filled), stringResource(MR.strings.icon_descr_server_status_connected), tint = if (info.online) Color.Green else MaterialTheme.colors.error)
@@ -340,10 +335,23 @@ fun UseSocksProxySwitch(
networkUseSocksProxy: MutableState<Boolean>,
toggleSocksProxy: (Boolean) -> Unit,
) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_settings_ethernet),
text = stringResource(MR.strings.network_socks_toggle_use_socks_proxy),
Row(
Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painterResource(MR.images.ic_settings_ethernet),
stringResource(MR.strings.network_socks_toggle_use_socks_proxy),
tint = MaterialTheme.colors.secondary
)
TextIconSpaced(false)
Text(generalGetString(MR.strings.network_socks_toggle_use_socks_proxy))
}
DefaultSwitch(
checked = networkUseSocksProxy.value,
onCheckedChange = toggleSocksProxy,
@@ -461,7 +469,7 @@ fun SocksProxySettings(
) {
ColumnWithScrollBar {
AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings))
SectionView(stringResource(MR.strings.network_socks_proxy)) {
SectionView(stringResource(MR.strings.network_socks_proxy), card = true) {
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
DefaultConfigurableTextField(
hostUnsaved,
@@ -484,13 +492,12 @@ fun SocksProxySettings(
UseOnionHosts(onionHosts, rememberUpdatedState(networkUseSocksProxy && proxyAuthRandomUnsaved.value)) {
onionHosts.value = it
}
SectionTextFooter(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported))
}
UseOnionHostsDescription(onionHosts)
SectionTextFooter(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported))
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.network_proxy_auth)) {
SectionView(stringResource(MR.strings.network_proxy_auth), card = true) {
PreferenceToggle(
stringResource(MR.strings.network_proxy_random_credentials),
checked = proxyAuthRandomUnsaved.value,
@@ -516,12 +523,12 @@ fun SocksProxySettings(
)
}
}
SectionTextFooter(proxyAuthFooter(usernameUnsaved.value.text, passwordUnsaved.value.text, proxyAuthModeUnsaved.value, sessionMode))
}
SectionTextFooter(proxyAuthFooter(usernameUnsaved.value.text, passwordUnsaved.value.text, proxyAuthModeUnsaved.value, sessionMode))
SectionDividerSpaced()
SectionDividerSpaced(maxBottomPadding = false, maxTopPadding = true)
SectionView {
SectionView(card = true) {
SectionItemView({
hostUnsaved.value = hostUnsaved.value.copy("localhost", TextRange(9))
portUnsaved.value = portUnsaved.value.copy("9050", TextRange(4))
@@ -560,8 +567,13 @@ private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit,
)
}
private val onionHostsValues: List<ValueTitleDesc<OnionHosts>>
@Composable get() = remember {
@Composable
fun UseOnionHosts(
onionHosts: MutableState<OnionHosts>,
enabled: State<Boolean>,
useOnion: (OnionHosts) -> Unit,
) {
val values = remember {
OnionHosts.values().map {
when (it) {
OnionHosts.NEVER -> ValueTitleDesc(OnionHosts.NEVER, generalGetString(MR.strings.network_use_onion_hosts_no), AnnotatedString(generalGetString(MR.strings.network_use_onion_hosts_no_desc)))
@@ -571,39 +583,31 @@ private val onionHostsValues: List<ValueTitleDesc<OnionHosts>>
}
}
@Composable
fun UseOnionHosts(
onionHosts: MutableState<OnionHosts>,
enabled: State<Boolean>,
useOnion: (OnionHosts) -> Unit,
) {
if (enabled.value) {
ExposedDropDownSettingRow(
generalGetString(MR.strings.network_use_onion_hosts),
onionHostsValues.map { it.value to it.title },
onionHosts,
icon = painterResource(MR.images.ic_security),
enabled = enabled,
onSelected = useOnion
)
} else {
// In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before
ExposedDropDownSettingRow(
generalGetString(MR.strings.network_use_onion_hosts),
listOf(OnionHosts.NEVER to generalGetString(MR.strings.network_use_onion_hosts_no)),
remember { mutableStateOf(OnionHosts.NEVER) },
icon = painterResource(MR.images.ic_security),
enabled = enabled,
onSelected = {}
)
Column {
if (enabled.value) {
ExposedDropDownSettingRow(
generalGetString(MR.strings.network_use_onion_hosts),
values.map { it.value to it.title },
onionHosts,
icon = painterResource(MR.images.ic_security),
enabled = enabled,
onSelected = useOnion
)
} else {
// In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before
ExposedDropDownSettingRow(
generalGetString(MR.strings.network_use_onion_hosts),
listOf(OnionHosts.NEVER to generalGetString(MR.strings.network_use_onion_hosts_no)),
remember { mutableStateOf(OnionHosts.NEVER) },
icon = painterResource(MR.images.ic_security),
enabled = enabled,
onSelected = {}
)
}
SectionTextFooter(values.firstOrNull { it.value == onionHosts.value }?.description ?: AnnotatedString(""))
}
}
@Composable
fun UseOnionHostsDescription(onionHosts: MutableState<OnionHosts>) {
SectionTextFooter(onionHostsValues.firstOrNull { it.value == onionHosts.value }?.description ?: AnnotatedString(""))
}
@Composable
fun SessionModePicker(
sessionMode: MutableState<TransportSessionMode>,
@@ -778,7 +782,7 @@ fun UsageConditionsView(
}
}
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.surface).padding(horizontal = DEFAULT_PADDING)) {
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) {
when (val conditionsAction = chatModel.conditions.value.conditionsAction) {
is UsageConditionsAction.Review -> {
AppBarTitle(stringResource(MR.strings.operator_updated_conditions), enableAlphaChanges = false, withPadding = false, bottomPadding = DEFAULT_PADDING)
@@ -2,7 +2,6 @@ package chat.simplex.common.views.usersettings.networkAndServers
import SectionBottomSpacer
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.model.*
@@ -34,7 +33,7 @@ fun ModalData.NewServerView(
rhId,
close = close
)
}, background = MaterialTheme.colors.surface) {
}) {
Box {
NewServerLayout(
newServer,
@@ -181,7 +181,7 @@ fun OperatorViewLayout(
val duplicateHosts = findDuplicateHosts(serverErrors.value)
Column {
SectionView(generalGetString(MR.strings.operator)) {
SectionView(generalGetString(MR.strings.operator), card = true) {
SectionItemView({ ModalManager.start.showModalCloseable { _ -> OperatorInfoView(operator) } }) {
Row(
Modifier.fillMaxWidth(),
@@ -238,7 +238,7 @@ fun OperatorViewLayout(
if (userServers.value[operatorIndex].chatRelays.any { !it.deleted }) {
val duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors.value)
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.chat_relays)) {
SectionView(generalGetString(MR.strings.chat_relays), card = true) {
userServers.value[operatorIndex].chatRelays.forEachIndexed { index, relay ->
if (!relay.deleted) {
ChatRelayViewLink(relay, duplicateRelayAddresses) {
@@ -252,7 +252,7 @@ fun OperatorViewLayout(
if (userServers.value[operatorIndex].smpServers.any { !it.deleted }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.operator_use_for_messages)) {
SectionView(generalGetString(MR.strings.operator_use_for_messages), card = true) {
SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(
stringResource(MR.strings.operator_use_for_messages_receiving),
@@ -306,7 +306,7 @@ fun OperatorViewLayout(
// Preset servers can't be deleted
if (userServers.value[operatorIndex].smpServers.any { it.preset }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.message_servers)) {
SectionView(generalGetString(MR.strings.message_servers), card = true) {
userServers.value[operatorIndex].smpServers.forEachIndexed { i, server ->
if (!server.preset) return@forEachIndexed
SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.SMP) }) {
@@ -340,7 +340,7 @@ fun OperatorViewLayout(
if (userServers.value[operatorIndex].smpServers.any { !it.preset && !it.deleted }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.operator_added_message_servers)) {
SectionView(generalGetString(MR.strings.operator_added_message_servers), card = true) {
userServers.value[operatorIndex].smpServers.forEachIndexed { i, server ->
if (server.deleted || server.preset) return@forEachIndexed
SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.SMP) }) {
@@ -356,7 +356,7 @@ fun OperatorViewLayout(
if (userServers.value[operatorIndex].xftpServers.any { !it.deleted }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.operator_use_for_files)) {
SectionView(generalGetString(MR.strings.operator_use_for_files), card = true) {
SectionItemView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Text(
stringResource(MR.strings.operator_use_for_sending),
@@ -389,7 +389,7 @@ fun OperatorViewLayout(
// Preset servers can't be deleted
if (userServers.value[operatorIndex].xftpServers.any { it.preset }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.media_and_file_servers)) {
SectionView(generalGetString(MR.strings.media_and_file_servers), card = true) {
userServers.value[operatorIndex].xftpServers.forEachIndexed { i, server ->
if (!server.preset) return@forEachIndexed
SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.XFTP) }) {
@@ -423,7 +423,7 @@ fun OperatorViewLayout(
if (userServers.value[operatorIndex].xftpServers.any { !it.preset && !it.deleted}) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.operator_added_xftp_servers)) {
SectionView(generalGetString(MR.strings.operator_added_xftp_servers), card = true) {
userServers.value[operatorIndex].xftpServers.forEachIndexed { i, server ->
if (server.deleted || server.preset) return@forEachIndexed
SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.XFTP) }) {
@@ -438,7 +438,7 @@ fun OperatorViewLayout(
}
SectionDividerSpaced()
SectionView {
SectionView(card = true) {
TestServersButton(
testing = testing,
smpServers = userServers.value[operatorIndex].smpServers,
@@ -479,7 +479,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) {
ColumnWithScrollBar {
AppBarTitle(stringResource(MR.strings.operator_info_title))
SectionView {
SectionView(card = true) {
SectionItemView {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Image(painterResource(serverOperator.largeLogo), null, Modifier.height(48.dp))
@@ -493,7 +493,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) {
SectionDividerSpaced(maxBottomPadding = false)
val uriHandler = LocalUriHandler.current
SectionView {
SectionView(card = true) {
SectionItemView {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
serverOperator.info.description.forEach { d ->
@@ -508,7 +508,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) {
val selfhost = serverOperator.info.selfhost
if (selfhost != null) {
SectionDividerSpaced(maxBottomPadding = false)
SectionView {
SectionView(card = true) {
SectionItemView {
val (text, link) = selfhost
Text(text, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(link) })
@@ -645,7 +645,7 @@ private fun SingleOperatorUsageConditionsView(
)
}
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.surface).padding(horizontal = DEFAULT_PADDING)) {
ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) {
AppBarTitle(String.format(stringResource(MR.strings.use_servers_of_operator_x), operator.tradeName), enableAlphaChanges = false, withPadding = false)
if (operator.conditionsAcceptance is ConditionsAcceptance.Accepted) {
// In current UI implementation this branch doesn't get shown - as conditions can't be opened from inside operator once accepted
@@ -140,7 +140,7 @@ private fun PresetServer(
testing: Boolean,
testServer: () -> Unit
) {
SectionView(stringResource(MR.strings.smp_servers_preset_address)) {
SectionView(stringResource(MR.strings.smp_servers_preset_address), card = true) {
SelectionContainer {
Text(
server.value.server,
@@ -175,7 +175,7 @@ fun CustomServer(
stringResource(MR.strings.smp_servers_your_server_address),
icon = painterResource(MR.images.ic_error),
iconTint = if (!valid.value) MaterialTheme.colors.error else Color.Transparent,
padding = PaddingValues(vertical = DEFAULT_PADDING_HALF),
card = true,
) {
val testedPreviously = remember { mutableMapOf<String, Boolean?>() }
TextEditor(
@@ -191,13 +191,13 @@ fun CustomServer(
}
}
}
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
UseServerSection(server, valid.value, testing, testServer, onDelete)
if (valid.value) {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.smp_servers_add_to_another_device)) {
SectionView(stringResource(MR.strings.smp_servers_add_to_another_device), card = true) {
QRCode(serverAddress.value, small = true)
}
}
@@ -211,7 +211,7 @@ private fun UseServerSection(
testServer: () -> Unit,
onDelete: (() -> Unit)? = null,
) {
SectionView(stringResource(MR.strings.smp_servers_use_server)) {
SectionView(stringResource(MR.strings.smp_servers_use_server), card = true) {
SectionItemViewSpaceBetween(testServer, disabled = !valid || testing) {
Text(stringResource(MR.strings.smp_servers_test_server), color = if (valid && !testing) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary)
ShowTestStatus(server.value)
@@ -86,7 +86,7 @@ fun YourServersViewLayout(
Column {
if (userServers.value[operatorIndex].chatRelays.any { !it.deleted }) {
val duplicateRelayAddresses = findDuplicateRelayAddresses(serverErrors.value)
SectionView(generalGetString(MR.strings.chat_relays)) {
SectionView(generalGetString(MR.strings.chat_relays), card = true) {
userServers.value[operatorIndex].chatRelays.forEachIndexed { i, relay ->
if (relay.deleted) return@forEachIndexed
ChatRelayViewLink(relay, duplicateRelayAddresses) {
@@ -99,7 +99,7 @@ fun YourServersViewLayout(
if (userServers.value[operatorIndex].smpServers.any { !it.deleted }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.message_servers)) {
SectionView(generalGetString(MR.strings.message_servers), card = true) {
userServers.value[operatorIndex].smpServers.forEachIndexed { i, server ->
if (server.deleted) return@forEachIndexed
SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.SMP) }) {
@@ -133,7 +133,7 @@ fun YourServersViewLayout(
if (userServers.value[operatorIndex].xftpServers.any { !it.deleted }) {
SectionDividerSpaced()
SectionView(generalGetString(MR.strings.media_and_file_servers)) {
SectionView(generalGetString(MR.strings.media_and_file_servers), card = true) {
userServers.value[operatorIndex].xftpServers.forEachIndexed { i, server ->
if (server.deleted) return@forEachIndexed
SectionItemView({ navigateToProtocolView(i, server, ServerProtocol.XFTP) }) {
@@ -170,10 +170,10 @@ fun YourServersViewLayout(
userServers.value[operatorIndex].xftpServers.any { !it.deleted } ||
userServers.value[operatorIndex].chatRelays.any { !it.deleted }
) {
SectionDividerSpaced(maxBottomPadding = false)
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false)
}
SectionView {
SectionView(card = true) {
SettingsActionItem(
painterResource(MR.images.ic_add),
stringResource(MR.strings.smp_servers_add),
@@ -195,9 +195,9 @@ fun YourServersViewLayout(
ServersWarningFooter(serversWarn)
}
}
SectionDividerSpaced(maxBottomPadding = false)
SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false)
SectionView {
SectionView(card = true) {
TestServersButton(
testing = testing,
smpServers = userServers.value[operatorIndex].smpServers,
@@ -1,7 +1,5 @@
package chat.simplex.common.views.chatlist
import LocalInSectionCard
import SectionDivider
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.layout.*
@@ -62,8 +60,6 @@ actual fun ChatListNavLinkLayout(
}
if (selectedChat.value || nextChatSelected.value) {
Divider()
} else if (LocalInSectionCard.current) {
SectionDivider()
} else {
Divider(Modifier.padding(horizontal = 8.dp))
}
@@ -82,10 +82,10 @@ fun AppearanceScope.AppearanceLayout(
SectionDividerSpaced()
ProfileImageSection()
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
FontScaleSection()
SectionDividerSpaced()
SectionDividerSpaced(maxTopPadding = true)
DensityScaleSection()
SectionBottomSpacer()
@@ -110,8 +110,8 @@ private fun MinimizeToTraySection() {
@Composable
fun DensityScaleSection() {
val localDensityScale = remember { mutableStateOf(appPrefs.densityScale.get()) }
SectionView(stringResource(MR.strings.appearance_zoom), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Row(Modifier.padding(vertical = 10.dp), verticalAlignment = Alignment.CenterVertically) {
SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.size(50.dp)
.background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22))
.clip(RoundedCornerShape(percent = 22))