mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-10 01:21:52 +00:00
android: Global preferences and per contact (#1290)
* android: Global preferences and per contact * Fixes * Changes in UI * Fix * Strings and more descriptions * Spelling error * No dots at the end * Adapting changes from core * Adapting changes from core * Change * Simplified user's choice with toggle * Changes after merge * Updated preferences to the latest changes in core * Strings * Changes * Small changes * Contact will be updated in UI too * bigger icons in section headers * Icons and colors Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a6e4e68bc5
commit
fa2f303547
@@ -352,6 +352,7 @@ data class User(
|
||||
val userContactId: Long,
|
||||
val localDisplayName: String,
|
||||
val profile: LocalProfile,
|
||||
val fullPreferences: FullChatPreferences,
|
||||
val activeUser: Boolean
|
||||
): NamedChat {
|
||||
override val displayName: String get() = profile.displayName
|
||||
@@ -365,6 +366,7 @@ data class User(
|
||||
userContactId = 1,
|
||||
localDisplayName = "alice",
|
||||
profile = LocalProfile.sampleData,
|
||||
fullPreferences = FullChatPreferences.sampleData,
|
||||
activeUser = true
|
||||
)
|
||||
}
|
||||
@@ -539,8 +541,8 @@ data class Contact(
|
||||
val activeConn: Connection,
|
||||
val viaGroup: Long? = null,
|
||||
val chatSettings: ChatSettings,
|
||||
// User applies his preferences for the contact here. Named user_preferences on the contact in DB
|
||||
val userPreferences: ChatPreferences,
|
||||
val mergedPreferences: ContactUserPreferences,
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant
|
||||
): SomeChat, NamedChat {
|
||||
@@ -571,7 +573,8 @@ data class Contact(
|
||||
profile = LocalProfile.sampleData,
|
||||
activeConn = Connection.sampleData,
|
||||
chatSettings = ChatSettings(true),
|
||||
userPreferences = ChatPreferences(),
|
||||
userPreferences = ChatPreferences.sampleData,
|
||||
mergedPreferences = ContactUserPreferences.sampleData,
|
||||
createdAt = Clock.System.now(),
|
||||
updatedAt = Clock.System.now()
|
||||
)
|
||||
@@ -601,12 +604,11 @@ class Connection(val connId: Long, val connStatus: ConnStatus, val connLevel: In
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class Profile(
|
||||
data class Profile(
|
||||
override val displayName: String,
|
||||
override val fullName: String,
|
||||
override val image: String? = null,
|
||||
override val localAlias : String = "",
|
||||
// Contact applies his preferences here
|
||||
val preferences: ChatPreferences? = null
|
||||
): NamedChat {
|
||||
val profileViewName: String
|
||||
@@ -614,7 +616,7 @@ class Profile(
|
||||
return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)"
|
||||
}
|
||||
|
||||
fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, image, localAlias)
|
||||
fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, image, localAlias, preferences)
|
||||
|
||||
companion object {
|
||||
val sampleData = Profile(
|
||||
@@ -631,18 +633,18 @@ class LocalProfile(
|
||||
override val fullName: String,
|
||||
override val image: String? = null,
|
||||
override val localAlias: String,
|
||||
// Contact applies his preferences here
|
||||
val preferences: ChatPreferences? = null
|
||||
): NamedChat {
|
||||
val profileViewName: String = localAlias.ifEmpty { if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" }
|
||||
|
||||
fun toProfile(): Profile = Profile(displayName, fullName, image, localAlias)
|
||||
fun toProfile(): Profile = Profile(displayName, fullName, image, localAlias, preferences)
|
||||
|
||||
companion object {
|
||||
val sampleData = LocalProfile(
|
||||
profileId = 1L,
|
||||
displayName = "alice",
|
||||
fullName = "Alice",
|
||||
preferences = ChatPreferences.sampleData,
|
||||
localAlias = ""
|
||||
)
|
||||
}
|
||||
@@ -659,10 +661,10 @@ data class GroupInfo (
|
||||
val groupId: Long,
|
||||
override val localDisplayName: String,
|
||||
val groupProfile: GroupProfile,
|
||||
val fullGroupPreferences: FullGroupPreferences,
|
||||
val membership: GroupMember,
|
||||
val hostConnCustomUserProfileId: Long? = null,
|
||||
val chatSettings: ChatSettings,
|
||||
// val groupPreferences: GroupPreferences? = null,
|
||||
override val createdAt: Instant,
|
||||
override val updatedAt: Instant
|
||||
): SomeChat, NamedChat {
|
||||
@@ -691,6 +693,7 @@ data class GroupInfo (
|
||||
groupId = 1,
|
||||
localDisplayName = "team",
|
||||
groupProfile = GroupProfile.sampleData,
|
||||
fullGroupPreferences = FullGroupPreferences.sampleData,
|
||||
membership = GroupMember.sampleData,
|
||||
hostConnCustomUserProfileId = null,
|
||||
chatSettings = ChatSettings(true),
|
||||
@@ -701,11 +704,12 @@ data class GroupInfo (
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class GroupProfile (
|
||||
data class GroupProfile (
|
||||
override val displayName: String,
|
||||
override val fullName: String,
|
||||
override val image: String? = null,
|
||||
override val localAlias: String = "",
|
||||
val groupPreferences: GroupPreferences? = null
|
||||
): NamedChat {
|
||||
companion object {
|
||||
val sampleData = GroupProfile(
|
||||
@@ -1568,7 +1572,7 @@ enum class FormatColor(val color: String) {
|
||||
red -> Color.Red
|
||||
green -> SimplexGreen
|
||||
blue -> SimplexBlue
|
||||
yellow -> Color.Yellow
|
||||
yellow -> WarningYellow
|
||||
cyan -> Color.Cyan
|
||||
magenta -> Color.Magenta
|
||||
black -> MaterialTheme.colors.onBackground
|
||||
|
||||
@@ -12,7 +12,9 @@ import android.util.Log
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Bolt
|
||||
import androidx.compose.material.icons.filled.DeleteForever
|
||||
import androidx.compose.material.icons.filled.KeyboardVoice
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -84,7 +86,7 @@ class AppPreferences(val context: Context) {
|
||||
val autoRestartWorkerVersion = mkIntPreference(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, 0)
|
||||
val webrtcPolicyRelay = mkBoolPreference(SHARED_PREFS_WEBRTC_POLICY_RELAY, true)
|
||||
private val _callOnLockScreen = mkStrPreference(SHARED_PREFS_WEBRTC_CALLS_ON_LOCK_SCREEN, CallOnLockScreen.default.name)
|
||||
val callOnLockScreen: Preference<CallOnLockScreen> = Preference(
|
||||
val callOnLockScreen: SharedPreference<CallOnLockScreen> = SharedPreference(
|
||||
get = fun(): CallOnLockScreen {
|
||||
val value = _callOnLockScreen.get() ?: return CallOnLockScreen.default
|
||||
return try {
|
||||
@@ -141,33 +143,33 @@ class AppPreferences(val context: Context) {
|
||||
val primaryColor = mkIntPreference(SHARED_PREFS_PRIMARY_COLOR, LightColorPalette.primary.toArgb())
|
||||
|
||||
private fun mkIntPreference(prefName: String, default: Int) =
|
||||
Preference(
|
||||
SharedPreference(
|
||||
get = fun() = sharedPreferences.getInt(prefName, default),
|
||||
set = fun(value) = sharedPreferences.edit().putInt(prefName, value).apply()
|
||||
)
|
||||
|
||||
private fun mkLongPreference(prefName: String, default: Long) =
|
||||
Preference(
|
||||
SharedPreference(
|
||||
get = fun() = sharedPreferences.getLong(prefName, default),
|
||||
set = fun(value) = sharedPreferences.edit().putLong(prefName, value).apply()
|
||||
)
|
||||
|
||||
private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): Preference<Long> {
|
||||
private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): SharedPreference<Long> {
|
||||
val d = if (networkUseSocksProxy.get()) proxyDefault else default
|
||||
return Preference(
|
||||
return SharedPreference(
|
||||
get = fun() = sharedPreferences.getLong(prefName, d),
|
||||
set = fun(value) = sharedPreferences.edit().putLong(prefName, value).apply()
|
||||
)
|
||||
}
|
||||
|
||||
private fun mkBoolPreference(prefName: String, default: Boolean) =
|
||||
Preference(
|
||||
SharedPreference(
|
||||
get = fun() = sharedPreferences.getBoolean(prefName, default),
|
||||
set = fun(value) = sharedPreferences.edit().putBoolean(prefName, value).apply()
|
||||
)
|
||||
|
||||
private fun mkStrPreference(prefName: String, default: String?): Preference<String?> =
|
||||
Preference(
|
||||
private fun mkStrPreference(prefName: String, default: String?): SharedPreference<String?> =
|
||||
SharedPreference(
|
||||
get = fun() = sharedPreferences.getString(prefName, default),
|
||||
set = fun(value) = sharedPreferences.edit().putString(prefName, value).apply()
|
||||
)
|
||||
@@ -176,8 +178,8 @@ class AppPreferences(val context: Context) {
|
||||
* Provide `[commit] = true` to save preferences right now, not after some unknown period of time.
|
||||
* So in case of a crash this value will be saved 100%
|
||||
* */
|
||||
private fun mkDatePreference(prefName: String, default: Instant?, commit: Boolean = false): Preference<Instant?> =
|
||||
Preference(
|
||||
private fun mkDatePreference(prefName: String, default: Instant?, commit: Boolean = false): SharedPreference<Instant?> =
|
||||
SharedPreference(
|
||||
get = {
|
||||
val pref = sharedPreferences.getString(prefName, default?.toEpochMilliseconds()?.toString())
|
||||
pref?.let { Instant.fromEpochMilliseconds(pref.toLong()) }
|
||||
@@ -658,6 +660,13 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiSetContactPrefs(contactId: Long, prefs: ChatPreferences): Contact? {
|
||||
val r = sendCmd(CC.ApiSetContactPrefs(contactId, prefs))
|
||||
if (r is CR.ContactPrefsUpdated) return r.toContact
|
||||
Log.e(TAG, "apiSetContactPrefs bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiSetContactAlias(contactId: Long, localAlias: String): Contact? {
|
||||
val r = sendCmd(CC.ApiSetContactAlias(contactId, localAlias))
|
||||
if (r is CR.ContactAliasUpdated) return r.toContact
|
||||
@@ -672,13 +681,6 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiSetContactPrefs(contactId: Long, prefs: ChatPreferences): Contact? {
|
||||
val r = sendCmd(CC.ApiSetContactPrefs(contactId, prefs))
|
||||
if (r is CR.ContactPrefsUpdated) return r.toContact
|
||||
Log.e(TAG, "apiSetContactPrefs bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiCreateUserAddress(): String? {
|
||||
val r = sendCmd(CC.CreateMyAddress())
|
||||
return when (r) {
|
||||
@@ -1469,7 +1471,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
class Preference<T>(val get: () -> T, val set: (T) -> Unit)
|
||||
class SharedPreference<T>(val get: () -> T, val set: (T) -> Unit)
|
||||
|
||||
// ChatCommand
|
||||
sealed class CC {
|
||||
@@ -1518,10 +1520,10 @@ sealed class CC {
|
||||
class ApiClearChat(val type: ChatType, val id: Long): CC()
|
||||
class ListContacts: CC()
|
||||
class ApiUpdateProfile(val profile: Profile): CC()
|
||||
class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC()
|
||||
class ApiParseMarkdown(val text: String): CC()
|
||||
class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC()
|
||||
class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC()
|
||||
class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC()
|
||||
class CreateMyAddress: CC()
|
||||
class DeleteMyAddress: CC()
|
||||
class ShowMyAddress: CC()
|
||||
@@ -1585,10 +1587,10 @@ sealed class CC {
|
||||
is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
|
||||
is ListContacts -> "/contacts"
|
||||
is ApiUpdateProfile -> "/_profile ${json.encodeToString(profile)}"
|
||||
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
|
||||
is ApiParseMarkdown -> "/_parse $text"
|
||||
is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}"
|
||||
is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}"
|
||||
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
|
||||
is CreateMyAddress -> "/address"
|
||||
is DeleteMyAddress -> "/delete_address"
|
||||
is ShowMyAddress -> "/show_address"
|
||||
@@ -1653,10 +1655,10 @@ sealed class CC {
|
||||
is ApiClearChat -> "apiClearChat"
|
||||
is ListContacts -> "listContacts"
|
||||
is ApiUpdateProfile -> "updateProfile"
|
||||
is ApiSetContactPrefs -> "apiSetContactPrefs"
|
||||
is ApiParseMarkdown -> "apiParseMarkdown"
|
||||
is ApiSetContactAlias -> "apiSetContactAlias"
|
||||
is ApiSetConnectionAlias -> "apiSetConnectionAlias"
|
||||
is ApiSetContactPrefs -> "apiSetContactPrefs"
|
||||
is CreateMyAddress -> "createMyAddress"
|
||||
is DeleteMyAddress -> "deleteMyAddress"
|
||||
is ShowMyAddress -> "showMyAddress"
|
||||
@@ -1946,29 +1948,280 @@ data class ChatSettings(
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ChatPreferences(
|
||||
val voice: ChatPreference? = null
|
||||
data class FullChatPreferences(
|
||||
val fullDelete: ChatPreference,
|
||||
val voice: ChatPreference,
|
||||
) {
|
||||
|
||||
fun toPreferences(): ChatPreferences = ChatPreferences(fullDelete = fullDelete, voice = voice)
|
||||
|
||||
companion object {
|
||||
val default = ChatPreferences(
|
||||
voice = ChatPreference(allow = PrefAllowed.NO)
|
||||
)
|
||||
val empty = ChatPreferences(
|
||||
voice = null
|
||||
)
|
||||
val sampleData = FullChatPreferences(fullDelete = ChatPreference(allow = FeatureAllowed.NO), voice = ChatPreference(allow = FeatureAllowed.YES))
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ChatPreferences(
|
||||
val fullDelete: ChatPreference? = null,
|
||||
val voice: ChatPreference? = null,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val sampleData = ChatPreferences(fullDelete = ChatPreference(allow = FeatureAllowed.NO), voice = ChatPreference(allow = FeatureAllowed.YES))
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ChatPreference(
|
||||
val allow: PrefAllowed
|
||||
val allow: FeatureAllowed
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class PrefAllowed {
|
||||
@SerialName("always") ALWAYS,
|
||||
data class ContactUserPreferences(
|
||||
val fullDelete: ContactUserPreference,
|
||||
val voice: ContactUserPreference,
|
||||
) {
|
||||
companion object {
|
||||
val sampleData = ContactUserPreferences(
|
||||
fullDelete = ContactUserPreference(
|
||||
enabled = FeatureEnabled(forUser = false, forContact = false),
|
||||
userPreference = ContactUserPref.User(preference = ChatPreference(allow = FeatureAllowed.NO)),
|
||||
contactPreference = ChatPreference(allow = FeatureAllowed.NO)
|
||||
),
|
||||
voice = ContactUserPreference(
|
||||
enabled = FeatureEnabled(forUser = true, forContact = true),
|
||||
userPreference = ContactUserPref.User(preference = ChatPreference(allow = FeatureAllowed.YES)),
|
||||
contactPreference = ChatPreference(allow = FeatureAllowed.YES)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ContactUserPreference(
|
||||
val enabled: FeatureEnabled,
|
||||
val userPreference: ContactUserPref,
|
||||
val contactPreference: ChatPreference,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FeatureEnabled(
|
||||
val forUser: Boolean,
|
||||
val forContact: Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun enabled(user: ChatPreference, contact: ChatPreference): FeatureEnabled =
|
||||
when {
|
||||
user.allow == FeatureAllowed.ALWAYS && contact.allow == FeatureAllowed.NO -> FeatureEnabled(forUser = false, forContact = true)
|
||||
user.allow == FeatureAllowed.NO && contact.allow == FeatureAllowed.ALWAYS -> FeatureEnabled(forUser = true, forContact = false)
|
||||
contact.allow == FeatureAllowed.NO -> FeatureEnabled(forUser = false, forContact = false)
|
||||
user.allow == FeatureAllowed.NO -> FeatureEnabled(forUser = false, forContact = false)
|
||||
else -> FeatureEnabled(forUser = true, forContact = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ContactUserPref {
|
||||
@Serializable @SerialName("contact") data class Contact(val preference: ChatPreference): ContactUserPref() // contact override is set
|
||||
@Serializable @SerialName("user") data class User(val preference: ChatPreference): ContactUserPref() // global user default is used
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class Feature {
|
||||
@SerialName("fullDelete") FullDelete,
|
||||
@SerialName("voice") Voice;
|
||||
|
||||
fun text() =
|
||||
when(this) {
|
||||
FullDelete -> generalGetString(R.string.full_deletion)
|
||||
Voice -> generalGetString(R.string.voice_messages)
|
||||
}
|
||||
|
||||
fun icon(filled: Boolean) =
|
||||
when(this) {
|
||||
FullDelete -> if (filled) Icons.Filled.DeleteForever else Icons.Outlined.DeleteForever
|
||||
Voice -> if (filled) Icons.Filled.KeyboardVoice else Icons.Outlined.KeyboardVoice
|
||||
}
|
||||
|
||||
fun allowDescription(allowed: FeatureAllowed): String =
|
||||
when (this) {
|
||||
FullDelete -> when (allowed) {
|
||||
FeatureAllowed.ALWAYS -> generalGetString(R.string.allow_your_contacts_irreversibly_delete)
|
||||
FeatureAllowed.YES -> generalGetString(R.string.allow_irreversible_message_deletion_only_if)
|
||||
FeatureAllowed.NO -> generalGetString(R.string.contacts_can_mark_messages_for_deletion)
|
||||
}
|
||||
Voice -> when (allowed) {
|
||||
FeatureAllowed.ALWAYS -> generalGetString(R.string.allow_your_contacts_to_send_voice_messages)
|
||||
FeatureAllowed.YES -> generalGetString(R.string.allow_voice_messages_only_if)
|
||||
FeatureAllowed.NO -> generalGetString(R.string.prohibit_sending_voice_messages)
|
||||
}
|
||||
}
|
||||
|
||||
fun enabledDescription(enabled: FeatureEnabled): String =
|
||||
when (this) {
|
||||
FullDelete -> when {
|
||||
enabled.forUser && enabled.forContact -> generalGetString(R.string.both_you_and_your_contacts_can_delete)
|
||||
enabled.forUser -> generalGetString(R.string.only_you_can_delete_messages)
|
||||
enabled.forContact -> generalGetString(R.string.only_your_contact_can_delete)
|
||||
else -> generalGetString(R.string.message_deletion_prohibited)
|
||||
}
|
||||
Voice -> when {
|
||||
enabled.forUser && enabled.forContact -> generalGetString(R.string.both_you_and_your_contact_can_send_voice)
|
||||
enabled.forUser -> generalGetString(R.string.only_you_can_send_voice)
|
||||
enabled.forContact -> generalGetString(R.string.only_your_contact_can_send_voice)
|
||||
else -> generalGetString(R.string.voice_prohibited_in_this_chat)
|
||||
}
|
||||
}
|
||||
|
||||
fun enableGroupPrefDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String =
|
||||
if (canEdit) {
|
||||
when(this) {
|
||||
FullDelete -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_to_delete_messages)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_message_deletion)
|
||||
}
|
||||
Voice -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_to_send_voice)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_sending_voice)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when(this) {
|
||||
FullDelete -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_delete)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(R.string.message_deletion_prohibited_in_chat)
|
||||
}
|
||||
Voice -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_send_voice)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(R.string.voice_messages_are_prohibited)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ContactFeatureAllowed {
|
||||
@Serializable @SerialName("userDefault") data class UserDefault(val default: FeatureAllowed): ContactFeatureAllowed()
|
||||
@Serializable @SerialName("always") object Always: ContactFeatureAllowed()
|
||||
@Serializable @SerialName("yes") object Yes: ContactFeatureAllowed()
|
||||
@Serializable @SerialName("no") object No: ContactFeatureAllowed()
|
||||
|
||||
companion object {
|
||||
fun values(def: FeatureAllowed): List<ContactFeatureAllowed> = listOf(UserDefault(def), Always, Yes, No)
|
||||
}
|
||||
|
||||
val allowed: FeatureAllowed
|
||||
get() = when (this) {
|
||||
is UserDefault -> this.default
|
||||
is Always -> FeatureAllowed.ALWAYS
|
||||
is Yes -> FeatureAllowed.YES
|
||||
is No -> FeatureAllowed.NO
|
||||
}
|
||||
val text: String
|
||||
get() = when (this) {
|
||||
is UserDefault -> String.format(generalGetString(R.string.chat_preferences_default), default.text)
|
||||
is Always -> generalGetString(R.string.chat_preferences_always)
|
||||
is Yes -> generalGetString(R.string.chat_preferences_yes)
|
||||
is No -> generalGetString(R.string.chat_preferences_no)
|
||||
}
|
||||
}
|
||||
|
||||
data class ContactFeaturesAllowed(
|
||||
val fullDelete: ContactFeatureAllowed,
|
||||
val voice: ContactFeatureAllowed
|
||||
) {
|
||||
companion object {
|
||||
val sampleData = ContactFeaturesAllowed(
|
||||
fullDelete = ContactFeatureAllowed.UserDefault(FeatureAllowed.NO),
|
||||
voice = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun contactUserPrefsToFeaturesAllowed(contactUserPreferences: ContactUserPreferences): ContactFeaturesAllowed =
|
||||
ContactFeaturesAllowed(
|
||||
fullDelete = contactUserPrefToFeatureAllowed(contactUserPreferences.fullDelete),
|
||||
voice = contactUserPrefToFeatureAllowed(contactUserPreferences.voice)
|
||||
)
|
||||
|
||||
fun contactUserPrefToFeatureAllowed(contactUserPreference: ContactUserPreference): ContactFeatureAllowed =
|
||||
when (val pref = contactUserPreference.userPreference) {
|
||||
is ContactUserPref.User -> ContactFeatureAllowed.UserDefault(pref.preference.allow)
|
||||
is ContactUserPref.Contact -> when (pref.preference.allow) {
|
||||
FeatureAllowed.ALWAYS -> ContactFeatureAllowed.Always
|
||||
FeatureAllowed.YES -> ContactFeatureAllowed.Yes
|
||||
FeatureAllowed.NO -> ContactFeatureAllowed.No
|
||||
}
|
||||
}
|
||||
|
||||
fun contactFeaturesAllowedToPrefs(contactFeaturesAllowed: ContactFeaturesAllowed): ChatPreferences =
|
||||
ChatPreferences(
|
||||
fullDelete = contactFeatureAllowedToPref(contactFeaturesAllowed.fullDelete),
|
||||
voice = contactFeatureAllowedToPref(contactFeaturesAllowed.voice)
|
||||
)
|
||||
|
||||
fun contactFeatureAllowedToPref(contactFeatureAllowed: ContactFeatureAllowed): ChatPreference? =
|
||||
when(contactFeatureAllowed) {
|
||||
is ContactFeatureAllowed.UserDefault -> null
|
||||
is ContactFeatureAllowed.Always -> ChatPreference(allow = FeatureAllowed.ALWAYS)
|
||||
is ContactFeatureAllowed.Yes -> ChatPreference(allow = FeatureAllowed.YES)
|
||||
is ContactFeatureAllowed.No -> ChatPreference(allow = FeatureAllowed.NO)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class FeatureAllowed {
|
||||
@SerialName("yes") YES,
|
||||
@SerialName("no") NO
|
||||
@SerialName("no") NO,
|
||||
@SerialName("always") ALWAYS;
|
||||
|
||||
val text: String
|
||||
get() = when(this) {
|
||||
ALWAYS -> generalGetString(R.string.chat_preferences_always)
|
||||
YES -> generalGetString(R.string.chat_preferences_yes)
|
||||
NO -> generalGetString(R.string.chat_preferences_no)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class FullGroupPreferences(
|
||||
val fullDelete: GroupPreference,
|
||||
val voice: GroupPreference
|
||||
) {
|
||||
fun toGroupPreferences(): GroupPreferences =
|
||||
GroupPreferences(fullDelete = fullDelete, voice = voice)
|
||||
|
||||
companion object {
|
||||
val sampleData = FullGroupPreferences(fullDelete = GroupPreference(enable = GroupFeatureEnabled.OFF), voice = GroupPreference(enable = GroupFeatureEnabled.ON))
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class GroupPreferences(
|
||||
val fullDelete: GroupPreference?,
|
||||
val voice: GroupPreference?
|
||||
) {
|
||||
companion object {
|
||||
val sampleData = GroupPreferences(fullDelete = GroupPreference(enable = GroupFeatureEnabled.OFF), voice = GroupPreference(enable = GroupFeatureEnabled.ON))
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class GroupPreference(
|
||||
val enable: GroupFeatureEnabled
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class GroupFeatureEnabled {
|
||||
@SerialName("on") ON,
|
||||
@SerialName("off") OFF;
|
||||
|
||||
val text: String
|
||||
get() = when (this) {
|
||||
ON -> generalGetString(R.string.chat_preferences_on)
|
||||
OFF -> generalGetString(R.string.chat_preferences_off)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val json = Json {
|
||||
@@ -2024,7 +2277,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val fromProfile: Profile, val toProfile: Profile): CR()
|
||||
@Serializable @SerialName("contactAliasUpdated") class ContactAliasUpdated(val toContact: Contact): CR()
|
||||
@Serializable @SerialName("connectionAliasUpdated") class ConnectionAliasUpdated(val toConnection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("contactPrefsUpdated") class ContactPrefsUpdated(val toContact: Contact): CR()
|
||||
@Serializable @SerialName("contactPrefsUpdated") class ContactPrefsUpdated(val fromContact: Contact, val toContact: Contact): CR()
|
||||
@Serializable @SerialName("apiParsedMarkdown") class ParsedMarkdown(val formattedText: List<FormattedText>? = null): CR()
|
||||
@Serializable @SerialName("userContactLink") class UserContactLink(val contactLink: UserContactLinkRec): CR()
|
||||
@Serializable @SerialName("userContactLinkUpdated") class UserContactLinkUpdated(val contactLink: UserContactLinkRec): CR()
|
||||
@@ -2218,7 +2471,7 @@ sealed class CR {
|
||||
is UserProfileUpdated -> json.encodeToString(toProfile)
|
||||
is ContactAliasUpdated -> json.encodeToString(toContact)
|
||||
is ConnectionAliasUpdated -> json.encodeToString(toConnection)
|
||||
is ContactPrefsUpdated -> json.encodeToString(toContact)
|
||||
is ContactPrefsUpdated -> "fromContact: $fromContact\ntoContact: \n${json.encodeToString(toContact)}"
|
||||
is ParsedMarkdown -> json.encodeToString(formattedText)
|
||||
is UserContactLink -> contactLink.responseDetails
|
||||
is UserContactLinkUpdated -> contactLink.responseDetails
|
||||
|
||||
@@ -35,7 +35,7 @@ import chat.simplex.app.SimplexApp
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.usersettings.SettingsActionItem
|
||||
import chat.simplex.app.views.usersettings.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@@ -62,6 +62,11 @@ fun ChatInfoView(
|
||||
onLocalAliasChanged = {
|
||||
setContactAlias(chat.chatInfo.apiId, it, chatModel)
|
||||
},
|
||||
openPreferences = {
|
||||
ModalManager.shared.showModal(true) {
|
||||
ContactPreferencesView(chatModel, chatModel.currentUser.value ?: return@showModal, contact)
|
||||
}
|
||||
},
|
||||
deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) },
|
||||
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) },
|
||||
switchContactAddress = {
|
||||
@@ -117,6 +122,7 @@ fun ChatInfoLayout(
|
||||
localAlias: String,
|
||||
developerTools: Boolean,
|
||||
onLocalAliasChanged: (String) -> Unit,
|
||||
openPreferences: () -> Unit,
|
||||
deleteContact: () -> Unit,
|
||||
clearChat: () -> Unit,
|
||||
switchContactAddress: () -> Unit,
|
||||
@@ -143,6 +149,11 @@ fun ChatInfoLayout(
|
||||
}
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
SectionView {
|
||||
ContactPreferencesButton(openPreferences)
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
|
||||
SectionView(title = stringResource(R.string.conn_stats_section_title_servers)) {
|
||||
@@ -327,6 +338,15 @@ fun SwitchAddressButton(onClick: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContactPreferencesButton(onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
Icons.Outlined.ToggleOn,
|
||||
stringResource(R.string.contact_preferences),
|
||||
click = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClearChatButton(onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
@@ -386,6 +406,7 @@ fun PreviewChatInfoLayout() {
|
||||
connStats = null,
|
||||
onLocalAliasChanged = {},
|
||||
customUserProfile = null,
|
||||
openPreferences = {},
|
||||
deleteContact = {},
|
||||
clearChat = {},
|
||||
switchContactAddress = {},
|
||||
|
||||
@@ -128,14 +128,16 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
|
||||
info = {
|
||||
hideKeyboard(view)
|
||||
withApi {
|
||||
val cInfo = chat.chatInfo
|
||||
if (cInfo is ChatInfo.Direct) {
|
||||
val contactInfo = chatModel.controller.apiContactInfo(cInfo.apiId)
|
||||
if (chat.chatInfo is ChatInfo.Direct) {
|
||||
val contactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
||||
ModalManager.shared.showModalCloseable(true) { close ->
|
||||
ChatInfoView(chatModel, cInfo.contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, close)
|
||||
val contact = remember { derivedStateOf { (chatModel.getContactChat(chat.chatInfo.contact.contactId)?.chatInfo as? ChatInfo.Direct)?.contact } }
|
||||
contact.value?.let { ct ->
|
||||
ChatInfoView(chatModel, ct, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, close)
|
||||
}
|
||||
}
|
||||
} else if (cInfo is ChatInfo.Group) {
|
||||
setGroupMembers(cInfo.groupInfo, chatModel)
|
||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||
setGroupMembers(chat.chatInfo.groupInfo, chatModel)
|
||||
ModalManager.shared.showModalCloseable(true) { close ->
|
||||
GroupChatInfoView(chatModel, close)
|
||||
}
|
||||
|
||||
+35
-9
@@ -4,6 +4,7 @@ import InfoRow
|
||||
import SectionDivider
|
||||
import SectionItemView
|
||||
import SectionSpacer
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.*
|
||||
@@ -28,6 +29,7 @@ import chat.simplex.app.views.chat.*
|
||||
import chat.simplex.app.views.chatlist.cantInviteIncognitoAlert
|
||||
import chat.simplex.app.views.chatlist.setGroupMembers
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.usersettings.*
|
||||
|
||||
@Composable
|
||||
fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) {
|
||||
@@ -62,6 +64,14 @@ fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) {
|
||||
editGroupProfile = {
|
||||
ModalManager.shared.showCustomModal { close -> GroupProfileView(groupInfo, chatModel, close) }
|
||||
},
|
||||
openPreferences = {
|
||||
ModalManager.shared.showModal(true) {
|
||||
GroupPreferencesView(
|
||||
chatModel,
|
||||
groupInfo
|
||||
)
|
||||
}
|
||||
},
|
||||
deleteGroup = { deleteGroupDialog(chat.chatInfo, groupInfo, chatModel, close) },
|
||||
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) },
|
||||
leaveGroup = { leaveGroupDialog(groupInfo, chatModel, close) },
|
||||
@@ -120,6 +130,7 @@ fun GroupChatInfoLayout(
|
||||
addMembers: () -> Unit,
|
||||
showMemberInfo: (GroupMember) -> Unit,
|
||||
editGroupProfile: () -> Unit,
|
||||
openPreferences: () -> Unit,
|
||||
deleteGroup: () -> Unit,
|
||||
clearChat: () -> Unit,
|
||||
leaveGroup: () -> Unit,
|
||||
@@ -139,6 +150,16 @@ fun GroupChatInfoLayout(
|
||||
}
|
||||
SectionSpacer()
|
||||
|
||||
SectionView {
|
||||
if (groupInfo.canEdit) {
|
||||
SectionItemView(editGroupProfile) { EditGroupProfileButton() }
|
||||
SectionDivider()
|
||||
}
|
||||
GroupPreferencesButton(openPreferences)
|
||||
}
|
||||
SectionTextFooter(stringResource(R.string.only_group_owners_can_change_prefs))
|
||||
SectionSpacer()
|
||||
|
||||
SectionView(title = String.format(generalGetString(R.string.group_info_section_title_num_members), members.count() + 1)) {
|
||||
if (groupInfo.canAddMembers) {
|
||||
SectionItemView(manageGroupLink) { GroupLinkButton() }
|
||||
@@ -160,10 +181,6 @@ fun GroupChatInfoLayout(
|
||||
}
|
||||
SectionSpacer()
|
||||
SectionView {
|
||||
if (groupInfo.canEdit) {
|
||||
SectionItemView(editGroupProfile) { EditGroupProfileButton() }
|
||||
SectionDivider()
|
||||
}
|
||||
ClearChatButton(clearChat)
|
||||
if (groupInfo.canDelete) {
|
||||
SectionDivider()
|
||||
@@ -211,6 +228,15 @@ fun GroupChatInfoHeader(cInfo: ChatInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupPreferencesButton(onClick: () -> Unit) {
|
||||
SettingsActionItem(
|
||||
Icons.Outlined.ToggleOn,
|
||||
stringResource(R.string.group_preferences),
|
||||
click = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddMembersButton(tint: Color = MaterialTheme.colors.primary) {
|
||||
Row(
|
||||
@@ -286,10 +312,10 @@ fun GroupLinkButton() {
|
||||
Icon(
|
||||
Icons.Outlined.Link,
|
||||
stringResource(R.string.group_link),
|
||||
tint = MaterialTheme.colors.primary
|
||||
tint = HighOrLowlight
|
||||
)
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text(stringResource(R.string.group_link), color = MaterialTheme.colors.primary)
|
||||
Text(stringResource(R.string.group_link))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,10 +329,10 @@ fun EditGroupProfileButton() {
|
||||
Icon(
|
||||
Icons.Outlined.Edit,
|
||||
stringResource(R.string.button_edit_group_profile),
|
||||
tint = MaterialTheme.colors.primary
|
||||
tint = HighOrLowlight
|
||||
)
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text(stringResource(R.string.button_edit_group_profile), color = MaterialTheme.colors.primary)
|
||||
Text(stringResource(R.string.button_edit_group_profile))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +381,7 @@ fun PreviewGroupChatInfoLayout() {
|
||||
groupInfo = GroupInfo.sampleData,
|
||||
members = listOf(GroupMember.sampleData, GroupMember.sampleData, GroupMember.sampleData),
|
||||
developerTools = false,
|
||||
addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {},
|
||||
addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ fun CICallItemView(cInfo: ChatInfo, cItem: ChatItem, status: CICallStatus, durat
|
||||
Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.padding(bottom = 8.dp), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
@Composable fun ConnectingCallIcon() = Icon(Icons.Outlined.SettingsPhone, stringResource(R.string.icon_descr_call_connecting), tint = Color.Green)
|
||||
@Composable fun ConnectingCallIcon() = Icon(Icons.Outlined.SettingsPhone, stringResource(R.string.icon_descr_call_connecting), tint = SimplexGreen)
|
||||
when (status) {
|
||||
CICallStatus.Pending -> if (sent) {
|
||||
Icon(Icons.Outlined.Call, stringResource(R.string.icon_descr_call_pending_sent))
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.WarningYellow
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@Composable
|
||||
@@ -51,7 +52,7 @@ fun CIStatusView(status: CIStatus, metaColor: Color = HighOrLowlight) {
|
||||
Icon(Icons.Filled.Close, stringResource(R.string.icon_descr_sent_msg_status_unauthorized_send), Modifier.height(12.dp), tint = Color.Red)
|
||||
}
|
||||
is CIStatus.SndError -> {
|
||||
Icon(Icons.Filled.WarningAmber, stringResource(R.string.icon_descr_sent_msg_status_send_failed), Modifier.height(12.dp), tint = Color.Yellow)
|
||||
Icon(Icons.Filled.WarningAmber, stringResource(R.string.icon_descr_sent_msg_status_send_failed), Modifier.height(12.dp), tint = WarningYellow)
|
||||
}
|
||||
is CIStatus.RcvNew -> {
|
||||
Icon(Icons.Filled.Circle, stringResource(R.string.icon_descr_received_msg_status_unread), Modifier.height(12.dp), tint = MaterialTheme.colors.primary)
|
||||
|
||||
@@ -126,7 +126,7 @@ fun DatabaseLayout(
|
||||
chatDbChanged: Boolean,
|
||||
useKeyChain: Boolean,
|
||||
chatDbEncrypted: Boolean?,
|
||||
initialRandomDBPassphrase: Preference<Boolean>,
|
||||
initialRandomDBPassphrase: SharedPreference<Boolean>,
|
||||
importArchiveLauncher: ManagedActivityResultLauncher<String, Uri?>,
|
||||
chatArchiveName: MutableState<String?>,
|
||||
chatArchiveTime: MutableState<Instant?>,
|
||||
@@ -683,7 +683,7 @@ fun PreviewDatabaseLayout() {
|
||||
chatDbChanged = false,
|
||||
useKeyChain = false,
|
||||
chatDbEncrypted = false,
|
||||
initialRandomDBPassphrase = Preference({ true }, {}),
|
||||
initialRandomDBPassphrase = SharedPreference({ true }, {}),
|
||||
importArchiveLauncher = rememberGetContentLauncher {},
|
||||
chatArchiveName = remember { mutableStateOf("dummy_archive") },
|
||||
chatArchiveTime = remember { mutableStateOf(Clock.System.now()) },
|
||||
|
||||
+7
-4
@@ -10,9 +10,12 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.ui.theme.DEFAULT_PADDING
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
|
||||
@Composable
|
||||
@@ -27,7 +30,7 @@ fun <T> ExposedDropDownSettingRow(
|
||||
onSelected: (T) -> Unit
|
||||
) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
Modifier.fillMaxWidth().padding(vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
@@ -40,9 +43,7 @@ fun <T> ExposedDropDownSettingRow(
|
||||
tint = iconTint
|
||||
)
|
||||
}
|
||||
Text(title, color = if (enabled.value) Color.Unspecified else HighOrLowlight)
|
||||
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
Text(title, Modifier.weight(1f), color = if (enabled.value) Color.Unspecified else HighOrLowlight)
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
@@ -55,8 +56,10 @@ fun <T> ExposedDropDownSettingRow(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
val maxWidth = with(LocalDensity.current){ 180.sp.toDp() }
|
||||
Text(
|
||||
values.first { it.first == selection.value }.second + (if (label != null) " $label" else ""),
|
||||
Modifier.widthIn(max = maxWidth),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = HighOrLowlight
|
||||
|
||||
@@ -42,7 +42,7 @@ fun SectionView(
|
||||
content: (@Composable ColumnScope.() -> Unit)
|
||||
) {
|
||||
Column {
|
||||
val iconSize = with(LocalDensity.current) { 15.sp.toDp() }
|
||||
val iconSize = with(LocalDensity.current) { 21.sp.toDp() }
|
||||
Row(Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
if (leadingIcon) Icon(icon, null, Modifier.padding(end = 4.dp).size(iconSize), tint = iconTint)
|
||||
Text(title, color = HighOrLowlight, style = MaterialTheme.typography.body2, fontSize = 12.sp)
|
||||
|
||||
@@ -30,8 +30,8 @@ fun CallSettingsView(m: ChatModel,
|
||||
|
||||
@Composable
|
||||
fun CallSettingsLayout(
|
||||
webrtcPolicyRelay: Preference<Boolean>,
|
||||
callOnLockScreen: Preference<CallOnLockScreen>,
|
||||
webrtcPolicyRelay: SharedPreference<Boolean>,
|
||||
callOnLockScreen: SharedPreference<CallOnLockScreen>,
|
||||
editIceServers: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
@@ -79,7 +79,7 @@ private fun LockscreenOpts(lockscreenOpts: State<CallOnLockScreen>, enabled: Sta
|
||||
@Composable
|
||||
fun SharedPreferenceToggle(
|
||||
text: String,
|
||||
preference: Preference<Boolean>,
|
||||
preference: SharedPreference<Boolean>,
|
||||
preferenceState: MutableState<Boolean>? = null
|
||||
) {
|
||||
val prefState = preferenceState ?: remember { mutableStateOf(preference.get()) }
|
||||
@@ -106,7 +106,7 @@ fun SharedPreferenceToggleWithIcon(
|
||||
icon: ImageVector,
|
||||
stopped: Boolean = false,
|
||||
onClickInfo: () -> Unit,
|
||||
preference: Preference<Boolean>,
|
||||
preference: SharedPreference<Boolean>,
|
||||
preferenceState: MutableState<Boolean>? = null
|
||||
) {
|
||||
val prefState = preferenceState ?: remember { mutableStateOf(preference.get()) }
|
||||
@@ -135,7 +135,7 @@ fun SharedPreferenceToggleWithIcon(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T>SharedPreferenceRadioButton(text: String, prefState: MutableState<T>, preference: Preference<T>, value: T) {
|
||||
fun <T>SharedPreferenceRadioButton(text: String, prefState: MutableState<T>, preference: SharedPreference<T>, value: T) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text)
|
||||
val colors = RadioButtonDefaults.colors(selectedColor = MaterialTheme.colors.primary)
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
import SectionDivider
|
||||
import SectionItemView
|
||||
import SectionItemWithValue
|
||||
import SectionSpacer
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
|
||||
@Composable
|
||||
fun ContactPreferencesView(
|
||||
m: ChatModel,
|
||||
user: User,
|
||||
contact: Contact,
|
||||
) {
|
||||
var featuresAllowed by remember { mutableStateOf(contactUserPrefsToFeaturesAllowed(contact.mergedPreferences)) }
|
||||
var currentFeaturesAllowed by remember { mutableStateOf(featuresAllowed) }
|
||||
ContactPreferencesLayout(
|
||||
featuresAllowed,
|
||||
currentFeaturesAllowed,
|
||||
user,
|
||||
contact,
|
||||
applyPrefs = { prefs ->
|
||||
featuresAllowed = prefs
|
||||
},
|
||||
reset = {
|
||||
featuresAllowed = currentFeaturesAllowed
|
||||
},
|
||||
savePrefs = {
|
||||
withApi {
|
||||
val prefs = contactFeaturesAllowedToPrefs(featuresAllowed)
|
||||
val toContact = m.controller.apiSetContactPrefs(contact.contactId, prefs)
|
||||
if (toContact != null) {
|
||||
m.updateContact(toContact)
|
||||
currentFeaturesAllowed = featuresAllowed
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContactPreferencesLayout(
|
||||
featuresAllowed: ContactFeaturesAllowed,
|
||||
currentFeaturesAllowed: ContactFeaturesAllowed,
|
||||
user: User,
|
||||
contact: Contact,
|
||||
applyPrefs: (ContactFeaturesAllowed) -> Unit,
|
||||
reset: () -> Unit,
|
||||
savePrefs: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = DEFAULT_PADDING),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.contact_preferences))
|
||||
val allowFullDeletion: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.fullDelete) }
|
||||
FeatureSection(Feature.FullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, allowFullDeletion) {
|
||||
applyPrefs(featuresAllowed.copy(fullDelete = it))
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) }
|
||||
FeatureSection(Feature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
|
||||
applyPrefs(featuresAllowed.copy(voice = it))
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
ResetSaveButtons(
|
||||
reset = reset,
|
||||
save = savePrefs,
|
||||
disabled = featuresAllowed == currentFeaturesAllowed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureSection(
|
||||
feature: Feature,
|
||||
userDefault: FeatureAllowed,
|
||||
pref: ContactUserPreference,
|
||||
allowFeature: State<ContactFeatureAllowed>,
|
||||
onSelected: (ContactFeatureAllowed) -> Unit
|
||||
) {
|
||||
val enabled = FeatureEnabled.enabled(
|
||||
user = ChatPreference(allow = allowFeature.value.allowed),
|
||||
contact = pref.contactPreference
|
||||
)
|
||||
|
||||
SectionView(
|
||||
feature.text().uppercase(),
|
||||
icon = feature.icon(true),
|
||||
iconTint = if (enabled.forUser) SimplexGreen else if (enabled.forContact) WarningYellow else Color.Red,
|
||||
leadingIcon = true,
|
||||
) {
|
||||
SectionItemView {
|
||||
ExposedDropDownSettingRow(
|
||||
generalGetString(R.string.chat_preferences_you_allow),
|
||||
ContactFeatureAllowed.values(userDefault).map { it to it.text },
|
||||
allowFeature,
|
||||
icon = null,
|
||||
onSelected = onSelected
|
||||
)
|
||||
}
|
||||
SectionDivider()
|
||||
SectionItemWithValue(
|
||||
generalGetString(R.string.chat_preferences_contact_allows),
|
||||
remember { mutableStateOf(pref.contactPreference.allow) },
|
||||
listOf(ValueTitleDesc(pref.contactPreference.allow, pref.contactPreference.allow.text, "")),
|
||||
icon = null,
|
||||
enabled = remember { mutableStateOf(true) },
|
||||
onSelected = {}
|
||||
)
|
||||
}
|
||||
SectionTextFooter(feature.enabledDescription(enabled))
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
|
||||
SectionView {
|
||||
SectionItemView(reset) {
|
||||
Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView(save) {
|
||||
Text(stringResource(R.string.save_and_notify_contact), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
import SectionItemView
|
||||
import SectionItemWithValue
|
||||
import SectionSpacer
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.views.helpers.*
|
||||
|
||||
@Composable
|
||||
fun GroupPreferencesView(m: ChatModel, groupInfo: GroupInfo) {
|
||||
var preferences by remember { mutableStateOf(groupInfo.fullGroupPreferences) }
|
||||
var currentPreferences by remember { mutableStateOf(preferences) }
|
||||
GroupPreferencesLayout(
|
||||
preferences,
|
||||
currentPreferences,
|
||||
groupInfo,
|
||||
applyPrefs = { prefs ->
|
||||
preferences = prefs
|
||||
},
|
||||
reset = {
|
||||
preferences = currentPreferences
|
||||
},
|
||||
savePrefs = {
|
||||
withApi {
|
||||
val gp = groupInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
|
||||
val gInfo = m.controller.apiUpdateGroup(groupInfo.groupId, gp)
|
||||
if (gInfo != null) {
|
||||
m.updateGroup(gInfo)
|
||||
currentPreferences = preferences
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupPreferencesLayout(
|
||||
preferences: FullGroupPreferences,
|
||||
currentPreferences: FullGroupPreferences,
|
||||
groupInfo: GroupInfo,
|
||||
applyPrefs: (FullGroupPreferences) -> Unit,
|
||||
reset: () -> Unit,
|
||||
savePrefs: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.group_preferences))
|
||||
val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.enable) }
|
||||
FeatureSection(Feature.FullDelete, allowFullDeletion, groupInfo) {
|
||||
applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = it)))
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.enable) }
|
||||
FeatureSection(Feature.Voice, allowVoice, groupInfo) {
|
||||
applyPrefs(preferences.copy(voice = GroupPreference(enable = it)))
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
ResetSaveButtons(
|
||||
reset = reset,
|
||||
save = savePrefs,
|
||||
disabled = preferences == currentPreferences
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureSection(feature: Feature, enableFeature: State<GroupFeatureEnabled>, groupInfo: GroupInfo, onSelected: (GroupFeatureEnabled) -> Unit) {
|
||||
SectionView {
|
||||
if (groupInfo.canEdit) {
|
||||
SectionItemView {
|
||||
ExposedDropDownSettingRow(
|
||||
feature.text(),
|
||||
GroupFeatureEnabled.values().map { it to it.text },
|
||||
enableFeature,
|
||||
icon = feature.icon(false),
|
||||
onSelected = onSelected
|
||||
)
|
||||
}
|
||||
} else {
|
||||
SectionItemWithValue(
|
||||
feature.text(),
|
||||
remember { mutableStateOf(enableFeature.value) },
|
||||
listOf(ValueTitleDesc(enableFeature.value, enableFeature.value.text, "")),
|
||||
icon = null,
|
||||
enabled = remember { mutableStateOf(true) },
|
||||
onSelected = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionTextFooter(feature.enableGroupPrefDescription(enableFeature.value, groupInfo.canEdit))
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
|
||||
SectionView {
|
||||
SectionItemView(reset) {
|
||||
Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView(save) {
|
||||
Text(stringResource(R.string.save_and_notify_group_members), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
import SectionItemView
|
||||
import SectionSpacer
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
|
||||
@Composable
|
||||
fun PreferencesView(m: ChatModel, user: User) {
|
||||
var preferences by remember { mutableStateOf(user.fullPreferences) }
|
||||
var currentPreferences by remember { mutableStateOf(preferences) }
|
||||
PreferencesLayout(
|
||||
preferences,
|
||||
currentPreferences,
|
||||
applyPrefs = { prefs ->
|
||||
preferences = prefs
|
||||
},
|
||||
reset = {
|
||||
preferences = currentPreferences
|
||||
},
|
||||
savePrefs = {
|
||||
withApi {
|
||||
val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences())
|
||||
val updatedProfile = m.controller.apiUpdateProfile(newProfile)
|
||||
if (updatedProfile != null) {
|
||||
val updatedUser = user.copy(
|
||||
profile = updatedProfile.toLocalProfile(user.profile.profileId),
|
||||
fullPreferences = preferences
|
||||
)
|
||||
currentPreferences = preferences
|
||||
m.currentUser.value = updatedUser
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreferencesLayout(
|
||||
preferences: FullChatPreferences,
|
||||
currentPreferences: FullChatPreferences,
|
||||
applyPrefs: (FullChatPreferences) -> Unit,
|
||||
reset: () -> Unit,
|
||||
savePrefs: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
AppBarTitle(stringResource(R.string.your_preferences))
|
||||
val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.allow) }
|
||||
FeatureSection(Feature.FullDelete, allowFullDeletion) {
|
||||
applyPrefs(preferences.copy(fullDelete = ChatPreference(allow = it)))
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.allow) }
|
||||
FeatureSection(Feature.Voice, allowVoice) {
|
||||
applyPrefs(preferences.copy(voice = ChatPreference(allow = it)))
|
||||
}
|
||||
|
||||
SectionSpacer()
|
||||
ResetSaveButtons(
|
||||
reset = reset,
|
||||
save = savePrefs,
|
||||
disabled = preferences == currentPreferences
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureSection(feature: Feature, allowFeature: State<FeatureAllowed>, onSelected: (FeatureAllowed) -> Unit) {
|
||||
SectionView {
|
||||
SectionItemView {
|
||||
ExposedDropDownSettingRow(
|
||||
feature.text(),
|
||||
FeatureAllowed.values().map { it to it.text },
|
||||
allowFeature,
|
||||
icon = feature.icon(false),
|
||||
onSelected = onSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
SectionTextFooter(feature.allowDescription(allowFeature.value))
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
|
||||
SectionView {
|
||||
SectionItemView(reset) {
|
||||
Text(stringResource(R.string.reset_verb), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView(save) {
|
||||
Text(stringResource(R.string.save_and_notify_contacts), color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -15,7 +15,10 @@ import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.views.helpers.AppBarTitle
|
||||
|
||||
@Composable
|
||||
fun PrivacySettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
|
||||
fun PrivacySettingsView(
|
||||
chatModel: ChatModel,
|
||||
setPerformLA: (Boolean) -> Unit
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.Start
|
||||
@@ -34,6 +37,7 @@ fun PrivacySettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
|
||||
SectionDivider()
|
||||
}
|
||||
SettingsPreferenceItem(Icons.Outlined.TravelExplore, stringResource(R.string.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews)
|
||||
SectionDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ private fun UseServerSection(
|
||||
@Composable
|
||||
fun ShowTestStatus(server: ServerCfg, modifier: Modifier = Modifier) =
|
||||
when (server.tested) {
|
||||
true -> Icon(Icons.Outlined.Check, null, modifier, tint = Color.Green)
|
||||
true -> Icon(Icons.Outlined.Check, null, modifier, tint = SimplexGreen)
|
||||
false -> Icon(Icons.Outlined.Clear, null, modifier, tint = MaterialTheme.colors.error)
|
||||
else -> Icon(Icons.Outlined.Check, null, modifier, tint = Color.Transparent)
|
||||
}
|
||||
|
||||
+36
-18
@@ -80,8 +80,8 @@ fun SettingsLayout(
|
||||
stopped: Boolean,
|
||||
encrypted: Boolean,
|
||||
incognito: MutableState<Boolean>,
|
||||
incognitoPref: Preference<Boolean>,
|
||||
developerTools: Preference<Boolean>,
|
||||
incognitoPref: SharedPreference<Boolean>,
|
||||
developerTools: SharedPreference<Boolean>,
|
||||
userDisplayName: String,
|
||||
setPerformLA: (Boolean) -> Unit,
|
||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||
@@ -116,20 +116,22 @@ fun SettingsLayout(
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.QrCode, stringResource(R.string.your_simplex_contact_address), showModal { CreateLinkView(it, CreateLinkTab.LONG_TERM) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
|
||||
ChatPreferencesItem(showSettingsModal)
|
||||
}
|
||||
SectionSpacer()
|
||||
|
||||
SectionView(stringResource(R.string.settings_section_title_settings)) {
|
||||
SettingsActionItem(Icons.Outlined.Bolt, stringResource(R.string.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.Videocam, stringResource(R.string.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.Lock, stringResource(R.string.privacy_and_security), showSettingsModal { PrivacySettingsView(it, setPerformLA) }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showSettingsModal { AppearanceView() }, disabled = stopped)
|
||||
SectionDivider()
|
||||
SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal) }, disabled = stopped)
|
||||
DatabaseItem(encrypted, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
|
||||
}
|
||||
SectionSpacer()
|
||||
|
||||
@@ -175,7 +177,7 @@ fun SettingsLayout(
|
||||
|
||||
@Composable
|
||||
fun SettingsIncognitoActionItem(
|
||||
incognitoPref: Preference<Boolean>,
|
||||
incognitoPref: SharedPreference<Boolean>,
|
||||
incognito: MutableState<Boolean>,
|
||||
stopped: Boolean,
|
||||
onClickInfo: () -> Unit,
|
||||
@@ -237,6 +239,20 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable fun ChatPreferencesItem(showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) {
|
||||
SettingsActionItem(
|
||||
Icons.Outlined.ToggleOn,
|
||||
stringResource(R.string.chat_preferences),
|
||||
click = {
|
||||
withApi {
|
||||
showSettingsModal {
|
||||
PreferencesView(it, it.currentUser.value ?: return@showSettingsModal)
|
||||
}()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable fun ChatLockItem(performLA: MutableState<Boolean>, setPerformLA: (Boolean) -> Unit) {
|
||||
SectionItemView() {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
@@ -365,8 +381,8 @@ fun SettingsActionItem(icon: ImageVector, text: String, click: (() -> Unit)? = n
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsPreferenceItem(icon: ImageVector, text: String, pref: Preference<Boolean>, prefState: MutableState<Boolean>? = null) {
|
||||
SectionItemView() {
|
||||
fun SettingsPreferenceItem(icon: ImageVector, text: String, pref: SharedPreference<Boolean>, prefState: MutableState<Boolean>? = null) {
|
||||
SectionItemView {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(icon, text, tint = HighOrLowlight)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
@@ -382,7 +398,7 @@ fun SettingsPreferenceItemWithInfo(
|
||||
text: String,
|
||||
stopped: Boolean,
|
||||
onClickInfo: () -> Unit,
|
||||
pref: Preference<Boolean>,
|
||||
pref: SharedPreference<Boolean>,
|
||||
prefState: MutableState<Boolean>? = null
|
||||
) {
|
||||
SectionItemView(onClickInfo) {
|
||||
@@ -417,18 +433,20 @@ fun PreferenceToggle(
|
||||
@Composable
|
||||
fun PreferenceToggleWithIcon(
|
||||
text: String,
|
||||
icon: ImageVector,
|
||||
iconColor: Color = HighOrLowlight,
|
||||
icon: ImageVector? = null,
|
||||
iconColor: Color? = HighOrLowlight,
|
||||
checked: Boolean,
|
||||
onChange: (Boolean) -> Unit = {},
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
icon,
|
||||
null,
|
||||
tint = iconColor
|
||||
)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
if (icon != null) {
|
||||
Icon(
|
||||
icon,
|
||||
null,
|
||||
tint = iconColor ?: HighOrLowlight
|
||||
)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
}
|
||||
Text(text)
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
Switch(
|
||||
@@ -458,8 +476,8 @@ fun PreviewSettingsLayout() {
|
||||
stopped = false,
|
||||
encrypted = false,
|
||||
incognito = remember { mutableStateOf(false) },
|
||||
incognitoPref = Preference({ false }, {}),
|
||||
developerTools = Preference({ false }, {}),
|
||||
incognitoPref = SharedPreference({ false }, {}),
|
||||
developerTools = SharedPreference({ false }, {}),
|
||||
userDisplayName = "Alice",
|
||||
setPerformLA = {},
|
||||
showModal = { {} },
|
||||
|
||||
@@ -460,7 +460,9 @@
|
||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.\n\n<xliff:g id="appName">SimpleX</xliff:g>-Server können Ihr Profil nicht sehen.</string>
|
||||
<string name="edit_image">Bild bearbeiten</string>
|
||||
<string name="delete_image">Bild löschen</string>
|
||||
<string name="save_and_notify_contact">***Save (and notify contacts)</string>
|
||||
<string name="save_and_notify_contacts">Speichern (und Kontakte benachrichtigen)</string>
|
||||
<string name="save_and_notify_group_members">***Save (and notify group members)</string>
|
||||
|
||||
<!-- Welcome Prompts - WelcomeView.kt -->
|
||||
<string name="you_control_your_chat">Sie haben volle Kontrolle über Ihren Chat!</string>
|
||||
@@ -837,6 +839,7 @@
|
||||
<string name="all_group_members_will_remain_connected">Alle Gruppenmitglieder bleiben verbunden.</string>
|
||||
<string name="error_creating_link_for_group">Fehler beim Erzeugen des Gruppen-Links</string>
|
||||
<string name="error_deleting_link_for_group">Fehler beim Löschen des Gruppen-Links</string>
|
||||
<string name="only_group_owners_can_change_prefs">***Only group owners can change group preferences.</string>
|
||||
|
||||
<!-- For Console chat info section -->
|
||||
<string name="section_title_for_console">FÜR KONSOLE</string>
|
||||
@@ -918,4 +921,43 @@
|
||||
<string name="save_color">Farbe speichern</string>
|
||||
<string name="reset_color">Farben zurücksetzen</string>
|
||||
<string name="color_primary">Akzent</string>
|
||||
|
||||
<!-- Preferences.kt -->
|
||||
<string name="chat_preferences_you_allow">***You allow</string>
|
||||
<string name="chat_preferences_contact_allows">***Contact allows</string>
|
||||
<string name="chat_preferences_default">***default (%s)</string>
|
||||
<string name="chat_preferences_yes">***yes</string>
|
||||
<string name="chat_preferences_no">***no</string>
|
||||
<string name="chat_preferences_always">***always</string>
|
||||
<string name="chat_preferences_on">***on</string>
|
||||
<string name="chat_preferences_off">***off</string>
|
||||
<string name="chat_preferences">***Chat preferences</string>
|
||||
<string name="contact_preferences">***Contact preferences</string>
|
||||
<string name="group_preferences">***Group preferences</string>
|
||||
<string name="your_preferences">***Your preferences</string>
|
||||
<string name="full_deletion">***Full deletion</string>
|
||||
<string name="voice_messages">***Voice messages</string>
|
||||
<string name="allow_your_contacts_irreversibly_delete">***Allow your contacts to irreversibly delete sent messages.</string>
|
||||
<string name="allow_irreversible_message_deletion_only_if">***Allow irreversible message deletion only if your contact allows it to you.</string>
|
||||
<string name="contacts_can_mark_messages_for_deletion">***Contacts can mark messages for deletion; you will be able to view them.</string>
|
||||
<string name="allow_your_contacts_to_send_voice_messages">***Allow your contacts to send voice messages.</string>
|
||||
<string name="allow_voice_messages_only_if">***Allow voice messages only if your contact allows them.</string>
|
||||
<string name="prohibit_sending_voice_messages">***Prohibit sending voice messages.</string>
|
||||
<string name="both_you_and_your_contacts_can_delete">***Both you and your contact can irreversibly delete sent messages.</string>
|
||||
<string name="only_you_can_delete_messages">***Only you can irreversibly delete messages (your contact can mark them for deletion).</string>
|
||||
<string name="only_your_contact_can_delete">***Only your contact can irreversibly delete messages (you can mark them for deletion).</string>
|
||||
<string name="message_deletion_prohibited">***Irreversible message deletion is prohibited in this chat.</string>
|
||||
<string name="both_you_and_your_contact_can_send_voice">***Both you and your contact can send voice messages.</string>
|
||||
<string name="only_you_can_send_voice">***Only you can send voice messages.</string>
|
||||
<string name="only_your_contact_can_send_voice">***Only your contact can send voice messages.</string>
|
||||
<string name="voice_prohibited_in_this_chat">***Voice messages are prohibited in this chat.</string>
|
||||
<string name="allow_to_delete_messages">***Allow to irreversibly delete sent messages.</string>
|
||||
<string name="prohibit_message_deletion">***Prohibit irreversible message deletion.</string>
|
||||
<string name="allow_to_send_voice">***Allow to send voice messages.</string>
|
||||
<string name="prohibit_sending_voice">***Prohibit sending voice messages.</string>
|
||||
<string name="group_members_can_delete">***Group members can irreversibly delete sent messages.</string>
|
||||
<string name="message_deletion_prohibited_in_chat">***Irreversible message deletion is prohibited in this chat.</string>
|
||||
<string name="group_members_can_send_voice">***Group members can send voice messages.</string>
|
||||
<string name="voice_messages_are_prohibited">***Voice messages are prohibited in this chat.</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -457,7 +457,9 @@
|
||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам.\n\n<xliff:g id="appName">SimpleX</xliff:g> серверы не могут получить доступ к вашему профилю.</string>
|
||||
<string name="edit_image">Поменять аватар</string>
|
||||
<string name="delete_image">Удалить аватар</string>
|
||||
<string name="save_and_notify_contact">Сохранить (и уведомить контакт)</string>
|
||||
<string name="save_and_notify_contacts">Сохранить (и послать обновление контактам)</string>
|
||||
<string name="save_and_notify_group_members">Сохранить (и уведомить членов группы)</string>
|
||||
|
||||
<!-- Welcome Prompts - WelcomeView.kt -->
|
||||
<string name="you_control_your_chat">Вы котролируете ваш чат!</string>
|
||||
@@ -837,6 +839,7 @@
|
||||
<string name="all_group_members_will_remain_connected">Все члены группы, которые соединились через эту ссылку, останутся в группе.</string>
|
||||
<string name="error_creating_link_for_group">Ошибка при создании ссылки группы</string>
|
||||
<string name="error_deleting_link_for_group">Ошибка при удалении ссылки группы</string>
|
||||
<string name="only_group_owners_can_change_prefs">Только владельцы группы могут изменять предпочтения группы.</string>
|
||||
|
||||
<!-- For Console chat info section -->
|
||||
<string name="section_title_for_console">ДЛЯ КОНСОЛИ</string>
|
||||
@@ -917,4 +920,43 @@
|
||||
<string name="save_color">Сохранить цвет</string>
|
||||
<string name="reset_color">Сбросить цвета</string>
|
||||
<string name="color_primary">Акцент</string>
|
||||
|
||||
<!-- Preferences.kt -->
|
||||
<string name="chat_preferences_you_allow">Вы разрешаете</string>
|
||||
<string name="chat_preferences_contact_allows">Контакт разрешает</string>
|
||||
<string name="chat_preferences_default">по умолчанию (%s)</string>
|
||||
<string name="chat_preferences_yes">да</string>
|
||||
<string name="chat_preferences_no">нет</string>
|
||||
<string name="chat_preferences_always">всегда</string>
|
||||
<string name="chat_preferences_on">да</string>
|
||||
<string name="chat_preferences_off">нет</string>
|
||||
<string name="chat_preferences">Предпочтения</string>
|
||||
<string name="contact_preferences">Предпочтения контакта</string>
|
||||
<string name="group_preferences">Предпочтения группы</string>
|
||||
<string name="your_preferences">Ваши предпочтения</string>
|
||||
<string name="full_deletion">Полное удаление</string>
|
||||
<string name="voice_messages">Голосовые сообщения</string>
|
||||
<string name="allow_your_contacts_irreversibly_delete">Разрешить вашим контактам необратимо удалять отправленные сообщения.</string>
|
||||
<string name="allow_irreversible_message_deletion_only_if">Разрешить необратимое удаление сообщений, только если ваш контакт разрешает это вам.</string>
|
||||
<string name="contacts_can_mark_messages_for_deletion">Контакты могут помечать сообщения для удаления; вы сможете просмотреть их.</string>
|
||||
<string name="allow_your_contacts_to_send_voice_messages">Разрешить вашим контактам отправлять голосовые сообщения.</string>
|
||||
<string name="allow_voice_messages_only_if">Разрешить голосовые сообщения, только если их разрешает ваш контакт.</string>
|
||||
<string name="prohibit_sending_voice_messages">Запретить отправлять голосовые сообщений.</string>
|
||||
<string name="both_you_and_your_contacts_can_delete">Вы и ваш контакт можете необратимо удалять отправленные сообщения.</string>
|
||||
<string name="only_you_can_delete_messages">Только вы можете необратимо удалять сообщения (ваш контакт может помечать их на удаление).</string>
|
||||
<string name="only_your_contact_can_delete">Только ваш контакт может необратимо удалять сообщения (вы можете помечать их на удаление).</string>
|
||||
<string name="message_deletion_prohibited">Необратимое удаление сообщений запрещено в этом чате.</string>
|
||||
<string name="both_you_and_your_contact_can_send_voice">Вы и ваш контакт можете отправлять голосовые сообщения.</string>
|
||||
<string name="only_you_can_send_voice">Только вы можете отправлять голосовые сообщения.</string>
|
||||
<string name="only_your_contact_can_send_voice">Только ваш контакт может отправлять голосовые сообщения.</string>
|
||||
<string name="voice_prohibited_in_this_chat">Голосовые сообщения запрещены в этом чате.</string>
|
||||
<string name="allow_to_delete_messages">Разрешить необратимо удалять отправленные сообщения.</string>
|
||||
<string name="prohibit_message_deletion">Запретить необратимое удаление сообщений.</string>
|
||||
<string name="allow_to_send_voice">Разрешить отправлять голосовые сообщения.</string>
|
||||
<string name="prohibit_sending_voice">Запретить отправлять голосовые сообщений.</string>
|
||||
<string name="group_members_can_delete">Члены группы могут необратимо удалять отправленные сообщения.</string>
|
||||
<string name="message_deletion_prohibited_in_chat">Необратимое удаление сообщений запрещено в этом чате.</string>
|
||||
<string name="group_members_can_send_voice">Члены группы могут отправлять голосовые сообщения.</string>
|
||||
<string name="voice_messages_are_prohibited">Голосовые сообщения запрещены в этом чате.</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -460,7 +460,9 @@
|
||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Your profile is stored on your device and shared only with your contacts.\n\n<xliff:g id="appName">SimpleX</xliff:g> servers cannot see your profile.</string>
|
||||
<string name="edit_image">Edit image</string>
|
||||
<string name="delete_image">Delete image</string>
|
||||
<string name="save_and_notify_contact">Save (and notify contact)</string>
|
||||
<string name="save_and_notify_contacts">Save (and notify contacts)</string>
|
||||
<string name="save_and_notify_group_members">Save (and notify group members)</string>
|
||||
|
||||
<!-- Welcome Prompts - WelcomeView.kt -->
|
||||
<string name="you_control_your_chat">You control your chat!</string>
|
||||
@@ -837,6 +839,7 @@
|
||||
<string name="all_group_members_will_remain_connected">All group members will remain connected.</string>
|
||||
<string name="error_creating_link_for_group">Error creating group link</string>
|
||||
<string name="error_deleting_link_for_group">Error deleting group link</string>
|
||||
<string name="only_group_owners_can_change_prefs">Only group owners can change group preferences.</string>
|
||||
|
||||
<!-- For Console chat info section -->
|
||||
<string name="section_title_for_console">FOR CONSOLE</string>
|
||||
@@ -918,4 +921,43 @@
|
||||
<string name="save_color">Save color</string>
|
||||
<string name="reset_color">Reset colors</string>
|
||||
<string name="color_primary">Accent</string>
|
||||
|
||||
<!-- Preferences.kt -->
|
||||
<string name="chat_preferences_you_allow">You allow</string>
|
||||
<string name="chat_preferences_contact_allows">Contact allows</string>
|
||||
<string name="chat_preferences_default">default (%s)</string>
|
||||
<string name="chat_preferences_yes">yes</string>
|
||||
<string name="chat_preferences_no">no</string>
|
||||
<string name="chat_preferences_always">always</string>
|
||||
<string name="chat_preferences_on">on</string>
|
||||
<string name="chat_preferences_off">off</string>
|
||||
<string name="chat_preferences">Chat preferences</string>
|
||||
<string name="contact_preferences">Contact preferences</string>
|
||||
<string name="group_preferences">Group preferences</string>
|
||||
<string name="your_preferences">Your preferences</string>
|
||||
<string name="full_deletion">Full deletion</string>
|
||||
<string name="voice_messages">Voice messages</string>
|
||||
<string name="allow_your_contacts_irreversibly_delete">Allow your contacts to irreversibly delete sent messages.</string>
|
||||
<string name="allow_irreversible_message_deletion_only_if">Allow irreversible message deletion only if your contact allows it to you.</string>
|
||||
<string name="contacts_can_mark_messages_for_deletion">Contacts can mark messages for deletion; you will be able to view them.</string>
|
||||
<string name="allow_your_contacts_to_send_voice_messages">Allow your contacts to send voice messages.</string>
|
||||
<string name="allow_voice_messages_only_if">Allow voice messages only if your contact allows them.</string>
|
||||
<string name="prohibit_sending_voice_messages">Prohibit sending voice messages.</string>
|
||||
<string name="both_you_and_your_contacts_can_delete">Both you and your contact can irreversibly delete sent messages.</string>
|
||||
<string name="only_you_can_delete_messages">Only you can irreversibly delete messages (your contact can mark them for deletion).</string>
|
||||
<string name="only_your_contact_can_delete">Only your contact can irreversibly delete messages (you can mark them for deletion).</string>
|
||||
<string name="message_deletion_prohibited">Irreversible message deletion is prohibited in this chat.</string>
|
||||
<string name="both_you_and_your_contact_can_send_voice">Both you and your contact can send voice messages.</string>
|
||||
<string name="only_you_can_send_voice">Only you can send voice messages.</string>
|
||||
<string name="only_your_contact_can_send_voice">Only your contact can send voice messages.</string>
|
||||
<string name="voice_prohibited_in_this_chat">Voice messages are prohibited in this chat.</string>
|
||||
<string name="allow_to_delete_messages">Allow to irreversibly delete sent messages.</string>
|
||||
<string name="prohibit_message_deletion">Prohibit irreversible message deletion.</string>
|
||||
<string name="allow_to_send_voice">Allow to send voice messages.</string>
|
||||
<string name="prohibit_sending_voice">Prohibit sending voice messages.</string>
|
||||
<string name="group_members_can_delete">Group members can irreversibly delete sent messages.</string>
|
||||
<string name="message_deletion_prohibited_in_chat">Irreversible message deletion is prohibited in this chat.</string>
|
||||
<string name="group_members_can_send_voice">Group members can send voice messages.</string>
|
||||
<string name="voice_messages_are_prohibited">Voice messages are prohibited in this chat.</string>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user