mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
android: delivery receipts (#2705)
* android: delivery receipts * receipts toggle in privacy and in contact info (crashes), double tick in chat * double tick image * SetDeliveryReceiptsView * small changes * update users * remove import * fixing crash * prevent ConcurrentModificationException * fix enable all users * android enable all users --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1bd8f66730
commit
d3b5bbe566
@@ -37,6 +37,7 @@ import chat.simplex.app.views.localauth.SetAppPasscodeView
|
||||
import chat.simplex.app.views.newchat.*
|
||||
import chat.simplex.app.views.onboarding.*
|
||||
import chat.simplex.app.views.usersettings.LAMode
|
||||
import chat.simplex.app.views.usersettings.SetDeliveryReceiptsView
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
@@ -469,11 +470,15 @@ fun MainPage(
|
||||
translationX = -offset.value.dp.toPx()
|
||||
}
|
||||
) {
|
||||
if (chatModel.setDeliveryReceipts.value) {
|
||||
SetDeliveryReceiptsView(chatModel)
|
||||
} else {
|
||||
val stopped = chatModel.chatRunning.value == false
|
||||
if (chatModel.sharedContent.value == null)
|
||||
ChatListView(chatModel, setPerformLA, stopped)
|
||||
else
|
||||
ShareListView(chatModel, stopped)
|
||||
}
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
val onComposed: () -> Unit = {
|
||||
|
||||
@@ -71,6 +71,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
val user = chatController.apiGetActiveUser()
|
||||
if (user == null) {
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo
|
||||
chatModel.currentUser.value = null
|
||||
chatModel.users.clear()
|
||||
@@ -81,6 +82,9 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
} else {
|
||||
savedOnboardingStage
|
||||
}
|
||||
if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) {
|
||||
chatModel.setDeliveryReceipts.value = true
|
||||
}
|
||||
chatController.startChat(user)
|
||||
// Prevents from showing "Enable notifications" alert when onboarding wasn't complete yet
|
||||
if (chatModel.onboardingStage.value == OnboardingStage.OnboardingComplete) {
|
||||
|
||||
@@ -39,6 +39,7 @@ import kotlin.time.*
|
||||
object ChatModel {
|
||||
val controller: ChatController = ChatController
|
||||
val onboardingStage = mutableStateOf<OnboardingStage?>(null)
|
||||
val setDeliveryReceipts = mutableStateOf(false)
|
||||
val currentUser = mutableStateOf<User?>(null)
|
||||
val users = mutableStateListOf<UserInfo>()
|
||||
val userCreated = mutableStateOf<Boolean?>(null)
|
||||
@@ -498,6 +499,8 @@ data class User(
|
||||
val fullPreferences: FullChatPreferences,
|
||||
val activeUser: Boolean,
|
||||
val showNtfs: Boolean,
|
||||
val sendRcptsContacts: Boolean,
|
||||
val sendRcptsSmallGroups: Boolean,
|
||||
val viewPwdHash: UserPwdHash?
|
||||
): NamedChat {
|
||||
override val displayName: String get() = profile.displayName
|
||||
@@ -520,6 +523,8 @@ data class User(
|
||||
fullPreferences = FullChatPreferences.sampleData,
|
||||
activeUser = true,
|
||||
showNtfs = true,
|
||||
sendRcptsContacts = true,
|
||||
sendRcptsSmallGroups = false,
|
||||
viewPwdHash = null,
|
||||
)
|
||||
}
|
||||
@@ -825,7 +830,7 @@ data class Contact(
|
||||
profile = LocalProfile.sampleData,
|
||||
activeConn = Connection.sampleData,
|
||||
contactUsed = true,
|
||||
chatSettings = ChatSettings(true, false),
|
||||
chatSettings = ChatSettings(enableNtfs = true, sendRcpts = null, favorite = false),
|
||||
userPreferences = ChatPreferences.sampleData,
|
||||
mergedPreferences = ContactUserPreferences.sampleData,
|
||||
createdAt = Clock.System.now(),
|
||||
@@ -974,7 +979,7 @@ data class GroupInfo (
|
||||
fullGroupPreferences = FullGroupPreferences.sampleData,
|
||||
membership = GroupMember.sampleData,
|
||||
hostConnCustomUserProfileId = null,
|
||||
chatSettings = ChatSettings(true, false),
|
||||
chatSettings = ChatSettings(enableNtfs = true, sendRcpts = null, favorite = false),
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
@@ -1614,6 +1619,10 @@ data class CIMeta (
|
||||
fun statusIcon(primaryColor: Color, metaColor: Color = CurrentColors.value.colors.secondary): Pair<ImageResource, Color>? =
|
||||
when (itemStatus) {
|
||||
is CIStatus.SndSent -> MR.images.ic_check_filled to metaColor
|
||||
is CIStatus.SndRcvd -> when(itemStatus.msgRcptStatus) {
|
||||
MsgReceiptStatus.Ok -> MR.images.ic_double_check to metaColor
|
||||
MsgReceiptStatus.BadMsgHash -> MR.images.ic_double_check to Color.Red
|
||||
}
|
||||
is CIStatus.SndErrorAuth -> MR.images.ic_close to Color.Red
|
||||
is CIStatus.SndError -> MR.images.ic_warning_filled to WarningYellow
|
||||
is CIStatus.RcvNew -> MR.images.ic_circle_filled to primaryColor
|
||||
@@ -1698,12 +1707,19 @@ fun localTimestamp(t: Instant): String {
|
||||
sealed class CIStatus {
|
||||
@Serializable @SerialName("sndNew") class SndNew: CIStatus()
|
||||
@Serializable @SerialName("sndSent") class SndSent: CIStatus()
|
||||
@Serializable @SerialName("sndRcvd") class SndRcvd(val msgRcptStatus: MsgReceiptStatus): CIStatus()
|
||||
@Serializable @SerialName("sndErrorAuth") class SndErrorAuth: CIStatus()
|
||||
@Serializable @SerialName("sndError") class SndError(val agentError: String): CIStatus()
|
||||
@Serializable @SerialName("rcvNew") class RcvNew: CIStatus()
|
||||
@Serializable @SerialName("rcvRead") class RcvRead: CIStatus()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class MsgReceiptStatus {
|
||||
@SerialName("ok") Ok,
|
||||
@SerialName("badMsgHash") BadMsgHash;
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class CIDeleted {
|
||||
@Serializable @SerialName("deleted") class Deleted(val deletedTs: Instant?): CIDeleted()
|
||||
|
||||
@@ -93,6 +93,7 @@ class AppPreferences {
|
||||
},
|
||||
set = fun(mode: SimplexLinkMode) { _simplexLinkMode.set(mode.name) }
|
||||
)
|
||||
val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false)
|
||||
val privacyFullBackup = mkBoolPreference(SHARED_PREFS_PRIVACY_FULL_BACKUP, false)
|
||||
val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false)
|
||||
val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false)
|
||||
@@ -238,6 +239,7 @@ class AppPreferences {
|
||||
private const val SHARED_PREFS_PRIVACY_TRANSFER_IMAGES_INLINE = "PrivacyTransferImagesInline"
|
||||
private const val SHARED_PREFS_PRIVACY_LINK_PREVIEWS = "PrivacyLinkPreviews"
|
||||
private const val SHARED_PREFS_PRIVACY_SIMPLEX_LINK_MODE = "PrivacySimplexLinkMode"
|
||||
private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet"
|
||||
internal const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup"
|
||||
private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls"
|
||||
private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites"
|
||||
@@ -461,6 +463,18 @@ object ChatController {
|
||||
throw Exception("failed to set the user as active ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiSetAllContactReceipts(enable: Boolean) {
|
||||
val r = sendCmd(CC.SetAllContactReceipts(enable))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Exception("failed to enable receipts for all users ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiSetUserContactReceipts(userId: Long, userMsgReceiptSettings: UserMsgReceiptSettings) {
|
||||
val r = sendCmd(CC.ApiSetUserContactReceipts(userId, userMsgReceiptSettings))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Exception("failed to enable receipts for user contacts ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiHideUser(userId: Long, viewPwd: String): User =
|
||||
setUserPrivacy(CC.ApiHideUser(userId, viewPwd))
|
||||
|
||||
@@ -699,7 +713,7 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiSetSettings(type: ChatType,id: Long, settings: ChatSettings): Boolean {
|
||||
suspend fun apiSetSettings(type: ChatType, id: Long, settings: ChatSettings): Boolean {
|
||||
val r = sendCmd(CC.APISetChatSettings(type, id, settings))
|
||||
return when (r) {
|
||||
is CR.CmdOk -> true
|
||||
@@ -1762,6 +1776,8 @@ sealed class CC {
|
||||
class CreateActiveUser(val profile: Profile?, val sameServers: Boolean, val pastTimestamp: Boolean): CC()
|
||||
class ListUsers: CC()
|
||||
class ApiSetActiveUser(val userId: Long, val viewPwd: String?): CC()
|
||||
class SetAllContactReceipts(val enable: Boolean): CC()
|
||||
class ApiSetUserContactReceipts(val userId: Long, val userMsgReceiptSettings: UserMsgReceiptSettings): CC()
|
||||
class ApiHideUser(val userId: Long, val viewPwd: String): CC()
|
||||
class ApiUnhideUser(val userId: Long, val viewPwd: String): CC()
|
||||
class ApiMuteUser(val userId: Long): CC()
|
||||
@@ -1856,6 +1872,11 @@ sealed class CC {
|
||||
}
|
||||
is ListUsers -> "/users"
|
||||
is ApiSetActiveUser -> "/_user $userId${maybePwd(viewPwd)}"
|
||||
is SetAllContactReceipts -> "/set receipts all ${onOff(enable)}"
|
||||
is ApiSetUserContactReceipts -> {
|
||||
val mrs = userMsgReceiptSettings
|
||||
"/_set receipts $userId ${onOff(mrs.enable)} clear_overrides=${onOff(mrs.clearOverrides)}"
|
||||
}
|
||||
is ApiHideUser -> "/_hide user $userId ${json.encodeToString(viewPwd)}"
|
||||
is ApiUnhideUser -> "/_unhide user $userId ${json.encodeToString(viewPwd)}"
|
||||
is ApiMuteUser -> "/_mute user $userId"
|
||||
@@ -1951,6 +1972,8 @@ sealed class CC {
|
||||
is CreateActiveUser -> "createActiveUser"
|
||||
is ListUsers -> "listUsers"
|
||||
is ApiSetActiveUser -> "apiSetActiveUser"
|
||||
is SetAllContactReceipts -> "setAllContactReceipts"
|
||||
is ApiSetUserContactReceipts -> "apiSetUserContactReceipts"
|
||||
is ApiHideUser -> "apiHideUser"
|
||||
is ApiUnhideUser -> "apiUnhideUser"
|
||||
is ApiMuteUser -> "apiMuteUser"
|
||||
@@ -2389,13 +2412,17 @@ data class KeepAliveOpts(
|
||||
@Serializable
|
||||
data class ChatSettings(
|
||||
val enableNtfs: Boolean,
|
||||
val sendRcpts: Boolean?,
|
||||
val favorite: Boolean
|
||||
) {
|
||||
companion object {
|
||||
val defaults: ChatSettings = ChatSettings(enableNtfs = true, favorite = false)
|
||||
val defaults: ChatSettings = ChatSettings(enableNtfs = true, sendRcpts = null, favorite = false)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class UserMsgReceiptSettings(val enable: Boolean, val clearOverrides: Boolean)
|
||||
|
||||
@Serializable
|
||||
data class FullChatPreferences(
|
||||
val timedMessages: TimedMessagesPreference,
|
||||
|
||||
+63
-1
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.SimplexApp
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.chatlist.updateChatSettings
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.QRCode
|
||||
import chat.simplex.app.views.usersettings.*
|
||||
@@ -52,15 +53,26 @@ fun ChatInfoView(
|
||||
) {
|
||||
BackHandler(onBack = close)
|
||||
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
||||
val currentUser = chatModel.currentUser.value
|
||||
val connStats = remember { mutableStateOf(connectionStats) }
|
||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||
if (chat != null) {
|
||||
if (chat != null && currentUser != null) {
|
||||
val contactNetworkStatus = remember(chatModel.networkStatuses.toMap()) {
|
||||
mutableStateOf(chatModel.contactNetworkStatus(contact))
|
||||
}
|
||||
val sendReceipts = remember { mutableStateOf(SendReceipts.fromBool(contact.chatSettings.sendRcpts, currentUser.sendRcptsContacts)) }
|
||||
ChatInfoLayout(
|
||||
chat,
|
||||
contact,
|
||||
currentUser,
|
||||
sendReceipts = sendReceipts,
|
||||
setSendReceipts = { sendRcpts ->
|
||||
withApi {
|
||||
val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool)
|
||||
updateChatSettings(chat, chatSettings, chatModel)
|
||||
sendReceipts.value = sendRcpts
|
||||
}
|
||||
},
|
||||
connStats = connStats,
|
||||
contactNetworkStatus.value,
|
||||
customUserProfile,
|
||||
@@ -154,6 +166,34 @@ fun ChatInfoView(
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SendReceipts {
|
||||
object Yes: SendReceipts()
|
||||
object No: SendReceipts()
|
||||
data class UserDefault(val enable: Boolean): SendReceipts()
|
||||
|
||||
val text: String get() = when (this) {
|
||||
is Yes -> generalGetString(MR.strings.chat_preferences_yes)
|
||||
is No -> generalGetString(MR.strings.chat_preferences_no)
|
||||
is UserDefault -> String.format(
|
||||
generalGetString(MR.strings.chat_preferences_default),
|
||||
generalGetString(if (enable) MR.strings.chat_preferences_yes else MR.strings.chat_preferences_no)
|
||||
)
|
||||
}
|
||||
|
||||
val bool: Boolean? get() = when (this) {
|
||||
is Yes -> true
|
||||
is No -> false
|
||||
is UserDefault -> null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromBool(enable: Boolean?, userDefault: Boolean): SendReceipts {
|
||||
return if (enable == null) UserDefault(userDefault)
|
||||
else if (enable) Yes else No
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.delete_contact_question),
|
||||
@@ -197,6 +237,9 @@ fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit
|
||||
fun ChatInfoLayout(
|
||||
chat: Chat,
|
||||
contact: Contact,
|
||||
currentUser: User,
|
||||
sendReceipts: State<SendReceipts>,
|
||||
setSendReceipts: (SendReceipts) -> Unit,
|
||||
connStats: MutableState<ConnectionStats?>,
|
||||
contactNetworkStatus: NetworkStatus,
|
||||
customUserProfile: Profile?,
|
||||
@@ -240,6 +283,7 @@ fun ChatInfoLayout(
|
||||
VerifyCodeButton(contact.verified, verifyClicked)
|
||||
}
|
||||
ContactPreferencesButton(openPreferences)
|
||||
SendReceiptsOption(currentUser, sendReceipts, setSendReceipts)
|
||||
if (cStats != null && cStats.ratchetSyncAllowed) {
|
||||
SynchronizeConnectionButton(syncContactConnection)
|
||||
} else if (developerTools) {
|
||||
@@ -499,6 +543,21 @@ private fun ContactPreferencesButton(onClick: () -> Unit) {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SendReceiptsOption(currentUser: User, state: State<SendReceipts>, onSelected: (SendReceipts) -> Unit) {
|
||||
val values = remember {
|
||||
mutableListOf(SendReceipts.Yes, SendReceipts.No, SendReceipts.UserDefault(currentUser.sendRcptsContacts)).map { it to it.text }
|
||||
}
|
||||
ExposedDropDownSettingRow(
|
||||
generalGetString(MR.strings.send_receipts),
|
||||
values,
|
||||
state,
|
||||
icon = painterResource(MR.images.ic_double_check),
|
||||
enabled = remember { mutableStateOf(true) },
|
||||
onSelected = onSelected
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClearChatButton(onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
@@ -577,6 +636,9 @@ fun PreviewChatInfoLayout() {
|
||||
chatItems = arrayListOf()
|
||||
),
|
||||
Contact.sampleData,
|
||||
User.sampleData,
|
||||
sendReceipts = remember { mutableStateOf(SendReceipts.Yes) },
|
||||
setSendReceipts = {},
|
||||
localAlias = "",
|
||||
connectionCode = "123",
|
||||
developerTools = false,
|
||||
|
||||
+6
-3
@@ -10,8 +10,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.CurrentColors
|
||||
import chat.simplex.res.MR
|
||||
@@ -51,7 +50,11 @@ private fun CIMetaText(meta: CIMeta, chatTTL: Int?, color: Color) {
|
||||
val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color)
|
||||
if (statusIcon != null) {
|
||||
val (icon, statusColor) = statusIcon
|
||||
StatusIconText(painterResource(icon), statusColor)
|
||||
if (meta.itemStatus is CIStatus.SndSent || meta.itemStatus is CIStatus.SndRcvd) {
|
||||
Icon(painterResource(icon), null, Modifier.height(17.dp), tint = statusColor)
|
||||
} else {
|
||||
StatusIconText(painterResource(icon), statusColor)
|
||||
}
|
||||
Spacer(Modifier.width(4.dp))
|
||||
} else if (!meta.disappearing) {
|
||||
StatusIconText(painterResource(MR.images.ic_circle_filled), Color.Transparent)
|
||||
|
||||
+115
@@ -12,7 +12,11 @@ import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -29,6 +33,7 @@ import chat.simplex.app.views.isValidDisplayName
|
||||
import chat.simplex.app.views.localauth.SetAppPasscodeView
|
||||
import chat.simplex.app.views.onboarding.ReadableText
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
enum class LAMode {
|
||||
SYSTEM,
|
||||
@@ -79,6 +84,52 @@ fun PrivacySettingsView(
|
||||
if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) {
|
||||
SectionTextFooter(stringResource(MR.strings.simplex_link_mode_browser_warning))
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
|
||||
val currentUser = chatModel.currentUser.value
|
||||
if (currentUser != null) {
|
||||
fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) {
|
||||
withApi {
|
||||
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
||||
chatModel.controller.apiSetUserContactReceipts(currentUser.userId, mrs)
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
chatModel.currentUser.value = currentUser.copy(sendRcptsContacts = enable)
|
||||
if (clearOverrides) {
|
||||
// For loop here is to prevent ConcurrentModificationException that happens with forEach
|
||||
for (i in 0 until chatModel.chats.size) {
|
||||
val chat = chatModel.chats[i]
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
var contact = chat.chatInfo.contact
|
||||
val sendRcpts = contact.chatSettings.sendRcpts
|
||||
if (sendRcpts != null && sendRcpts != enable) {
|
||||
contact = contact.copy(chatSettings = contact.chatSettings.copy(sendRcpts = null))
|
||||
chatModel.updateContact(contact)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeliveryReceiptsSection(
|
||||
currentUser = currentUser,
|
||||
setOrAskSendReceiptsContacts = { enable ->
|
||||
val contactReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
val sendRcpts = chat.chatInfo.contact.chatSettings.sendRcpts
|
||||
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
||||
} else {
|
||||
count
|
||||
}
|
||||
}
|
||||
if (contactReceiptsOverrides == 0) {
|
||||
setSendReceiptsContacts(enable, clearOverrides = false)
|
||||
} else {
|
||||
showUserContactsReceiptsAlert(enable, contactReceiptsOverrides, ::setSendReceiptsContacts)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
@@ -104,6 +155,70 @@ private fun SimpleXLinkOptions(simplexLinkModeState: State<SimplexLinkMode>, onS
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeliveryReceiptsSection(
|
||||
currentUser: User,
|
||||
setOrAskSendReceiptsContacts: (Boolean) -> Unit,
|
||||
) {
|
||||
SectionView(stringResource(MR.strings.settings_section_title_delivery_receipts)) {
|
||||
SettingsActionItemWithContent(painterResource(MR.images.ic_person), stringResource(MR.strings.receipts_section_contacts)) {
|
||||
DefaultSwitch(
|
||||
checked = currentUser.sendRcptsContacts ?: false,
|
||||
onCheckedChange = { enable ->
|
||||
setOrAskSendReceiptsContacts(enable)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionTextFooter(
|
||||
remember(currentUser.displayName) {
|
||||
buildAnnotatedString {
|
||||
append(generalGetString(MR.strings.receipts_section_description) + " ")
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
append(currentUser.displayName)
|
||||
}
|
||||
append(".\n")
|
||||
append(generalGetString(MR.strings.receipts_section_description_1))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun showUserContactsReceiptsAlert(
|
||||
enable: Boolean,
|
||||
contactReceiptsOverrides: Int,
|
||||
setSendReceiptsContacts: (Boolean, Boolean) -> Unit
|
||||
) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(if (enable) MR.strings.receipts_contacts_title_enable else MR.strings.receipts_contacts_title_disable),
|
||||
text = AnnotatedString(String.format(generalGetString(if (enable) MR.strings.receipts_contacts_override_disabled else MR.strings.receipts_contacts_override_enabled), contactReceiptsOverrides)),
|
||||
buttons = {
|
||||
Column {
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
setSendReceiptsContacts(enable, false)
|
||||
}) {
|
||||
val t = stringResource(if (enable) MR.strings.receipts_contacts_enable_keep_overrides else MR.strings.receipts_contacts_disable_keep_overrides)
|
||||
Text(t, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
setSendReceiptsContacts(enable, true)
|
||||
}
|
||||
) {
|
||||
val t = stringResource(if (enable) MR.strings.receipts_contacts_enable_for_all else MR.strings.receipts_contacts_disable_for_all)
|
||||
Text(t, Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
}) {
|
||||
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.onBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val laDelays = listOf(10, 30, 60, 180, 0)
|
||||
|
||||
@Composable
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
import SectionBottomSpacer
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import chat.simplex.app.TAG
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
|
||||
@Composable
|
||||
fun SetDeliveryReceiptsView(m: ChatModel) {
|
||||
SetDeliveryReceiptsLayout(
|
||||
enableReceipts = {
|
||||
val currentUser = m.currentUser.value
|
||||
if (currentUser != null) {
|
||||
withApi {
|
||||
try {
|
||||
m.controller.apiSetAllContactReceipts(enable = true)
|
||||
m.currentUser.value = currentUser.copy(sendRcptsContacts = true)
|
||||
m.setDeliveryReceipts.value = false
|
||||
m.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
try {
|
||||
val users = m.controller.listUsers()
|
||||
m.users.clear()
|
||||
m.users.addAll(users)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "listUsers error: ${e.stackTraceToString()}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.error_enabling_delivery_receipts),
|
||||
text = e.stackTraceToString()
|
||||
)
|
||||
Log.e(TAG, "${generalGetString(MR.strings.error_enabling_delivery_receipts)}: ${e.stackTraceToString()}")
|
||||
m.setDeliveryReceipts.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
skip = {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.delivery_receipts_are_disabled),
|
||||
text = generalGetString(MR.strings.you_can_enable_delivery_receipts_later_alert),
|
||||
confirmText = generalGetString(MR.strings.ok),
|
||||
dismissText = generalGetString(MR.strings.dont_show_again),
|
||||
onConfirm = {
|
||||
m.setDeliveryReceipts.value = false
|
||||
},
|
||||
onDismiss = {
|
||||
m.setDeliveryReceipts.value = false
|
||||
m.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
}
|
||||
)
|
||||
},
|
||||
userCount = m.users.size
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SetDeliveryReceiptsLayout(
|
||||
enableReceipts: () -> Unit,
|
||||
skip: () -> Unit,
|
||||
userCount: Int,
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AppBarTitle(stringResource(MR.strings.delivery_receipts_title))
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
EnableReceiptsButton(enableReceipts)
|
||||
if (userCount > 1) {
|
||||
TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled_all_profiles))
|
||||
} else {
|
||||
TextBelowButton(stringResource(MR.strings.sending_delivery_receipts_will_be_enabled))
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
SkipButton(skip)
|
||||
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EnableReceiptsButton(onClick: () -> Unit) {
|
||||
TextButton(onClick) {
|
||||
Text(stringResource(MR.strings.enable_receipts_all), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SkipButton(onClick: () -> Unit) {
|
||||
SimpleButtonIconEnded(stringResource(MR.strings.dont_enable_receipts), painterResource(MR.images.ic_chevron_right), click = onClick)
|
||||
TextBelowButton(stringResource(MR.strings.you_can_enable_delivery_receipts_later))
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextBelowButton(text: String) {
|
||||
Text(
|
||||
text,
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = DEFAULT_PADDING * 3),
|
||||
style = MaterialTheme.typography.subtitle1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user