android: create contacts with group members (#3078)
This commit is contained in:
parent
648a9761f9
commit
ae6996b2ee
@ -44,6 +44,7 @@ import java.net.URI
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun PlatformTextField(
|
actual fun PlatformTextField(
|
||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
|
sendMsgEnabled: Boolean,
|
||||||
textStyle: MutableState<TextStyle>,
|
textStyle: MutableState<TextStyle>,
|
||||||
showDeleteTextButton: MutableState<Boolean>,
|
showDeleteTextButton: MutableState<Boolean>,
|
||||||
userIsObserver: Boolean,
|
userIsObserver: Boolean,
|
||||||
@ -60,6 +61,7 @@ actual fun PlatformTextField(
|
|||||||
val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() }
|
val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() }
|
||||||
val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() }
|
val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() }
|
||||||
var showKeyboard by remember { mutableStateOf(false) }
|
var showKeyboard by remember { mutableStateOf(false) }
|
||||||
|
var freeFocus by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(cs.contextItem) {
|
LaunchedEffect(cs.contextItem) {
|
||||||
if (cs.contextItem is ComposeContextItem.QuotedItem) {
|
if (cs.contextItem is ComposeContextItem.QuotedItem) {
|
||||||
delay(100)
|
delay(100)
|
||||||
@ -70,6 +72,11 @@ actual fun PlatformTextField(
|
|||||||
showKeyboard = true
|
showKeyboard = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(sendMsgEnabled) {
|
||||||
|
if (!sendMsgEnabled) {
|
||||||
|
freeFocus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AndroidView(modifier = Modifier, factory = {
|
AndroidView(modifier = Modifier, factory = {
|
||||||
val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) {
|
val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) {
|
||||||
@ -142,6 +149,11 @@ actual fun PlatformTextField(
|
|||||||
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
|
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
|
||||||
showKeyboard = false
|
showKeyboard = false
|
||||||
}
|
}
|
||||||
|
if (freeFocus) {
|
||||||
|
it.clearFocus()
|
||||||
|
hideKeyboard(it)
|
||||||
|
freeFocus = false
|
||||||
|
}
|
||||||
showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress
|
showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress
|
||||||
}
|
}
|
||||||
if (composeState.value.preview is ComposePreview.VoicePreview) {
|
if (composeState.value.preview is ComposePreview.VoicePreview) {
|
||||||
|
@ -606,10 +606,13 @@ data class Chat (
|
|||||||
val userCanSend: Boolean
|
val userCanSend: Boolean
|
||||||
get() = when (chatInfo) {
|
get() = when (chatInfo) {
|
||||||
is ChatInfo.Direct -> true
|
is ChatInfo.Direct -> true
|
||||||
is ChatInfo.Group -> {
|
is ChatInfo.Group -> chatInfo.groupInfo.membership.memberRole >= GroupMemberRole.Member
|
||||||
val m = chatInfo.groupInfo.membership
|
else -> false
|
||||||
m.memberActive && m.memberRole >= GroupMemberRole.Member
|
}
|
||||||
}
|
|
||||||
|
val nextSendGrpInv: Boolean
|
||||||
|
get() = when (chatInfo) {
|
||||||
|
is ChatInfo.Direct -> chatInfo.contact.nextSendGrpInv
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -799,13 +802,18 @@ data class Contact(
|
|||||||
val userPreferences: ChatPreferences,
|
val userPreferences: ChatPreferences,
|
||||||
val mergedPreferences: ContactUserPreferences,
|
val mergedPreferences: ContactUserPreferences,
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant,
|
||||||
override val updatedAt: Instant
|
override val updatedAt: Instant,
|
||||||
|
val contactGroupMemberId: Long? = null,
|
||||||
|
val contactGrpInvSent: Boolean
|
||||||
): SomeChat, NamedChat {
|
): SomeChat, NamedChat {
|
||||||
override val chatType get() = ChatType.Direct
|
override val chatType get() = ChatType.Direct
|
||||||
override val id get() = "@$contactId"
|
override val id get() = "@$contactId"
|
||||||
override val apiId get() = contactId
|
override val apiId get() = contactId
|
||||||
override val ready get() = activeConn.connStatus == ConnStatus.Ready
|
override val ready get() = activeConn.connStatus == ConnStatus.Ready
|
||||||
override val sendMsgEnabled get() = !(activeConn.connectionStats?.ratchetSyncSendProhibited ?: false)
|
override val sendMsgEnabled get() =
|
||||||
|
(ready && !(activeConn.connectionStats?.ratchetSyncSendProhibited ?: false))
|
||||||
|
|| nextSendGrpInv
|
||||||
|
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
|
||||||
override val ntfsEnabled get() = chatSettings.enableNtfs
|
override val ntfsEnabled get() = chatSettings.enableNtfs
|
||||||
override val incognito get() = contactConnIncognito
|
override val incognito get() = contactConnIncognito
|
||||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||||
@ -856,7 +864,8 @@ data class Contact(
|
|||||||
userPreferences = ChatPreferences.sampleData,
|
userPreferences = ChatPreferences.sampleData,
|
||||||
mergedPreferences = ContactUserPreferences.sampleData,
|
mergedPreferences = ContactUserPreferences.sampleData,
|
||||||
createdAt = Clock.System.now(),
|
createdAt = Clock.System.now(),
|
||||||
updatedAt = Clock.System.now()
|
updatedAt = Clock.System.now(),
|
||||||
|
contactGrpInvSent = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -881,6 +890,7 @@ class ContactSubStatus(
|
|||||||
data class Connection(
|
data class Connection(
|
||||||
val connId: Long,
|
val connId: Long,
|
||||||
val agentConnId: String,
|
val agentConnId: String,
|
||||||
|
val peerChatVRange: VersionRange,
|
||||||
val connStatus: ConnStatus,
|
val connStatus: ConnStatus,
|
||||||
val connLevel: Int,
|
val connLevel: Int,
|
||||||
val viaGroupLink: Boolean,
|
val viaGroupLink: Boolean,
|
||||||
@ -890,10 +900,17 @@ data class Connection(
|
|||||||
) {
|
) {
|
||||||
val id: ChatId get() = ":$connId"
|
val id: ChatId get() = ":$connId"
|
||||||
companion object {
|
companion object {
|
||||||
val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, customUserProfileId = null)
|
val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VersionRange(val minVersion: Int, val maxVersion: Int) {
|
||||||
|
|
||||||
|
fun isCompatibleRange(vRange: VersionRange): Boolean =
|
||||||
|
this.minVersion <= vRange.maxVersion && vRange.minVersion <= this.maxVersion
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SecurityCode(val securityCode: String, val verifiedAt: Instant)
|
data class SecurityCode(val securityCode: String, val verifiedAt: Instant)
|
||||||
|
|
||||||
@ -1224,6 +1241,7 @@ class MemberSubError (
|
|||||||
@Serializable
|
@Serializable
|
||||||
class UserContactRequest (
|
class UserContactRequest (
|
||||||
val contactRequestId: Long,
|
val contactRequestId: Long,
|
||||||
|
val cReqChatVRange: VersionRange,
|
||||||
override val localDisplayName: String,
|
override val localDisplayName: String,
|
||||||
val profile: Profile,
|
val profile: Profile,
|
||||||
override val createdAt: Instant,
|
override val createdAt: Instant,
|
||||||
@ -1246,6 +1264,7 @@ class UserContactRequest (
|
|||||||
companion object {
|
companion object {
|
||||||
val sampleData = UserContactRequest(
|
val sampleData = UserContactRequest(
|
||||||
contactRequestId = 1,
|
contactRequestId = 1,
|
||||||
|
cReqChatVRange = VersionRange(1, 1),
|
||||||
localDisplayName = "alice",
|
localDisplayName = "alice",
|
||||||
profile = Profile.sampleData,
|
profile = Profile.sampleData,
|
||||||
createdAt = Clock.System.now(),
|
createdAt = Clock.System.now(),
|
||||||
@ -1465,6 +1484,7 @@ data class ChatItem (
|
|||||||
is RcvGroupEvent.GroupDeleted -> showNtfDir
|
is RcvGroupEvent.GroupDeleted -> showNtfDir
|
||||||
is RcvGroupEvent.GroupUpdated -> false
|
is RcvGroupEvent.GroupUpdated -> false
|
||||||
is RcvGroupEvent.InvitedViaGroupLink -> false
|
is RcvGroupEvent.InvitedViaGroupLink -> false
|
||||||
|
is RcvGroupEvent.MemberCreatedContact -> false
|
||||||
}
|
}
|
||||||
is CIContent.SndGroupEventContent -> showNtfDir
|
is CIContent.SndGroupEventContent -> showNtfDir
|
||||||
is CIContent.RcvConnEventContent -> false
|
is CIContent.RcvConnEventContent -> false
|
||||||
@ -2464,6 +2484,7 @@ sealed class RcvGroupEvent() {
|
|||||||
@Serializable @SerialName("groupDeleted") class GroupDeleted(): RcvGroupEvent()
|
@Serializable @SerialName("groupDeleted") class GroupDeleted(): RcvGroupEvent()
|
||||||
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): RcvGroupEvent()
|
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): RcvGroupEvent()
|
||||||
@Serializable @SerialName("invitedViaGroupLink") class InvitedViaGroupLink(): RcvGroupEvent()
|
@Serializable @SerialName("invitedViaGroupLink") class InvitedViaGroupLink(): RcvGroupEvent()
|
||||||
|
@Serializable @SerialName("memberCreatedContact") class MemberCreatedContact(): RcvGroupEvent()
|
||||||
|
|
||||||
val text: String get() = when (this) {
|
val text: String get() = when (this) {
|
||||||
is MemberAdded -> String.format(generalGetString(MR.strings.rcv_group_event_member_added), profile.profileViewName)
|
is MemberAdded -> String.format(generalGetString(MR.strings.rcv_group_event_member_added), profile.profileViewName)
|
||||||
@ -2476,6 +2497,7 @@ sealed class RcvGroupEvent() {
|
|||||||
is GroupDeleted -> generalGetString(MR.strings.rcv_group_event_group_deleted)
|
is GroupDeleted -> generalGetString(MR.strings.rcv_group_event_group_deleted)
|
||||||
is GroupUpdated -> generalGetString(MR.strings.rcv_group_event_updated_group_profile)
|
is GroupUpdated -> generalGetString(MR.strings.rcv_group_event_updated_group_profile)
|
||||||
is InvitedViaGroupLink -> generalGetString(MR.strings.rcv_group_event_invited_via_your_group_link)
|
is InvitedViaGroupLink -> generalGetString(MR.strings.rcv_group_event_invited_via_your_group_link)
|
||||||
|
is MemberCreatedContact -> generalGetString(MR.strings.rcv_group_event_member_created_contact)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,12 @@ import java.util.Date
|
|||||||
|
|
||||||
typealias ChatCtrl = Long
|
typealias ChatCtrl = Long
|
||||||
|
|
||||||
|
// currentChatVersion in core
|
||||||
|
const val CURRENT_CHAT_VERSION: Int = 2
|
||||||
|
|
||||||
|
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
|
||||||
|
val CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion = 2, maxVersion = CURRENT_CHAT_VERSION)
|
||||||
|
|
||||||
enum class CallOnLockScreen {
|
enum class CallOnLockScreen {
|
||||||
DISABLE,
|
DISABLE,
|
||||||
SHOW,
|
SHOW,
|
||||||
@ -784,16 +790,18 @@ object ChatController {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiGetContactCode(contactId: Long): Pair<Contact, String> {
|
suspend fun apiGetContactCode(contactId: Long): Pair<Contact, String>? {
|
||||||
val r = sendCmd(CC.APIGetContactCode(contactId))
|
val r = sendCmd(CC.APIGetContactCode(contactId))
|
||||||
if (r is CR.ContactCode) return r.contact to r.connectionCode
|
if (r is CR.ContactCode) return r.contact to r.connectionCode
|
||||||
throw Exception("failed to get contact code: ${r.responseType} ${r.details}")
|
Log.e(TAG,"failed to get contact code: ${r.responseType} ${r.details}")
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiGetGroupMemberCode(groupId: Long, groupMemberId: Long): Pair<GroupMember, String> {
|
suspend fun apiGetGroupMemberCode(groupId: Long, groupMemberId: Long): Pair<GroupMember, String>? {
|
||||||
val r = sendCmd(CC.APIGetGroupMemberCode(groupId, groupMemberId))
|
val r = sendCmd(CC.APIGetGroupMemberCode(groupId, groupMemberId))
|
||||||
if (r is CR.GroupMemberCode) return r.member to r.connectionCode
|
if (r is CR.GroupMemberCode) return r.member to r.connectionCode
|
||||||
throw Exception("failed to get group member code: ${r.responseType} ${r.details}")
|
Log.e(TAG,"failed to get group member code: ${r.responseType} ${r.details}")
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiVerifyContact(contactId: Long, connectionCode: String?): Pair<Boolean, String>? {
|
suspend fun apiVerifyContact(contactId: Long, connectionCode: String?): Pair<Boolean, String>? {
|
||||||
@ -1272,6 +1280,30 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun apiCreateMemberContact(groupId: Long, groupMemberId: Long): Contact? {
|
||||||
|
return when (val r = sendCmd(CC.APICreateMemberContact(groupId, groupMemberId))) {
|
||||||
|
is CR.NewMemberContact -> r.contact
|
||||||
|
else -> {
|
||||||
|
if (!(networkErrorAlert(r))) {
|
||||||
|
apiErrorAlert("apiCreateMemberContact", generalGetString(MR.strings.error_creating_member_contact), r)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun apiSendMemberContactInvitation(contactId: Long, mc: MsgContent): Contact? {
|
||||||
|
return when (val r = sendCmd(CC.APISendMemberContactInvitation(contactId, mc))) {
|
||||||
|
is CR.NewMemberContactSentInv -> r.contact
|
||||||
|
else -> {
|
||||||
|
if (!(networkErrorAlert(r))) {
|
||||||
|
apiErrorAlert("apiSendMemberContactInvitation", generalGetString(MR.strings.error_sending_message_contact_invitation), r)
|
||||||
|
}
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature, param: Int? = null) {
|
suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature, param: Int? = null) {
|
||||||
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param)
|
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param)
|
||||||
val toContact = apiSetContactPrefs(contact.contactId, prefs)
|
val toContact = apiSetContactPrefs(contact.contactId, prefs)
|
||||||
@ -1527,6 +1559,10 @@ object ChatController {
|
|||||||
if (active(r.user)) {
|
if (active(r.user)) {
|
||||||
chatModel.updateGroup(r.toGroup)
|
chatModel.updateGroup(r.toGroup)
|
||||||
}
|
}
|
||||||
|
is CR.NewMemberContactReceivedInv ->
|
||||||
|
if (active(r.user)) {
|
||||||
|
chatModel.updateContact(r.contact)
|
||||||
|
}
|
||||||
is CR.RcvFileStart ->
|
is CR.RcvFileStart ->
|
||||||
chatItemSimpleUpdate(r.user, r.chatItem)
|
chatItemSimpleUpdate(r.user, r.chatItem)
|
||||||
is CR.RcvFileComplete ->
|
is CR.RcvFileComplete ->
|
||||||
@ -1822,6 +1858,8 @@ sealed class CC {
|
|||||||
class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC()
|
class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC()
|
||||||
class APIDeleteGroupLink(val groupId: Long): CC()
|
class APIDeleteGroupLink(val groupId: Long): CC()
|
||||||
class APIGetGroupLink(val groupId: Long): CC()
|
class APIGetGroupLink(val groupId: Long): CC()
|
||||||
|
class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC()
|
||||||
|
class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC()
|
||||||
class APIGetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol): CC()
|
class APIGetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol): CC()
|
||||||
class APISetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol, val servers: List<ServerCfg>): CC()
|
class APISetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol, val servers: List<ServerCfg>): CC()
|
||||||
class APITestProtoServer(val userId: Long, val server: String): CC()
|
class APITestProtoServer(val userId: Long, val server: String): CC()
|
||||||
@ -1927,6 +1965,8 @@ sealed class CC {
|
|||||||
is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}"
|
is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}"
|
||||||
is APIDeleteGroupLink -> "/_delete link #$groupId"
|
is APIDeleteGroupLink -> "/_delete link #$groupId"
|
||||||
is APIGetGroupLink -> "/_get link #$groupId"
|
is APIGetGroupLink -> "/_get link #$groupId"
|
||||||
|
is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId"
|
||||||
|
is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}"
|
||||||
is APIGetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()}"
|
is APIGetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()}"
|
||||||
is APISetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()} ${protoServersStr(servers)}"
|
is APISetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()} ${protoServersStr(servers)}"
|
||||||
is APITestProtoServer -> "/_server test $userId $server"
|
is APITestProtoServer -> "/_server test $userId $server"
|
||||||
@ -2021,6 +2061,8 @@ sealed class CC {
|
|||||||
is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole"
|
is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole"
|
||||||
is APIDeleteGroupLink -> "apiDeleteGroupLink"
|
is APIDeleteGroupLink -> "apiDeleteGroupLink"
|
||||||
is APIGetGroupLink -> "apiGetGroupLink"
|
is APIGetGroupLink -> "apiGetGroupLink"
|
||||||
|
is APICreateMemberContact -> "apiCreateMemberContact"
|
||||||
|
is APISendMemberContactInvitation -> "apiSendMemberContactInvitation"
|
||||||
is APIGetUserProtoServers -> "apiGetUserProtoServers"
|
is APIGetUserProtoServers -> "apiGetUserProtoServers"
|
||||||
is APISetUserProtoServers -> "apiSetUserProtoServers"
|
is APISetUserProtoServers -> "apiSetUserProtoServers"
|
||||||
is APITestProtoServer -> "testProtoServer"
|
is APITestProtoServer -> "testProtoServer"
|
||||||
@ -3311,6 +3353,9 @@ sealed class CR {
|
|||||||
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
|
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
|
||||||
@Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
|
@Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
|
||||||
@Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR()
|
@Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR()
|
||||||
|
@Serializable @SerialName("newMemberContact") class NewMemberContact(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||||
|
@Serializable @SerialName("newMemberContactSentInv") class NewMemberContactSentInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||||
|
@Serializable @SerialName("newMemberContactReceivedInv") class NewMemberContactReceivedInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
|
||||||
// receiving file events
|
// receiving file events
|
||||||
@Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted(val user: UserRef, val chatItem: AChatItem): CR()
|
@Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted(val user: UserRef, val chatItem: AChatItem): CR()
|
||||||
@Serializable @SerialName("rcvFileAcceptedSndCancelled") class RcvFileAcceptedSndCancelled(val user: UserRef, val rcvFileTransfer: RcvFileTransfer): CR()
|
@Serializable @SerialName("rcvFileAcceptedSndCancelled") class RcvFileAcceptedSndCancelled(val user: UserRef, val rcvFileTransfer: RcvFileTransfer): CR()
|
||||||
@ -3438,6 +3483,9 @@ sealed class CR {
|
|||||||
is GroupLinkCreated -> "groupLinkCreated"
|
is GroupLinkCreated -> "groupLinkCreated"
|
||||||
is GroupLink -> "groupLink"
|
is GroupLink -> "groupLink"
|
||||||
is GroupLinkDeleted -> "groupLinkDeleted"
|
is GroupLinkDeleted -> "groupLinkDeleted"
|
||||||
|
is NewMemberContact -> "newMemberContact"
|
||||||
|
is NewMemberContactSentInv -> "newMemberContactSentInv"
|
||||||
|
is NewMemberContactReceivedInv -> "newMemberContactReceivedInv"
|
||||||
is RcvFileAcceptedSndCancelled -> "rcvFileAcceptedSndCancelled"
|
is RcvFileAcceptedSndCancelled -> "rcvFileAcceptedSndCancelled"
|
||||||
is RcvFileAccepted -> "rcvFileAccepted"
|
is RcvFileAccepted -> "rcvFileAccepted"
|
||||||
is RcvFileStart -> "rcvFileStart"
|
is RcvFileStart -> "rcvFileStart"
|
||||||
@ -3563,6 +3611,9 @@ sealed class CR {
|
|||||||
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
|
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
|
||||||
is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
|
is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
|
||||||
is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo))
|
is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo))
|
||||||
|
is NewMemberContact -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||||
|
is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||||
|
is NewMemberContactReceivedInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
|
||||||
is RcvFileAcceptedSndCancelled -> withUser(user, noDetails())
|
is RcvFileAcceptedSndCancelled -> withUser(user, noDetails())
|
||||||
is RcvFileAccepted -> withUser(user, json.encodeToString(chatItem))
|
is RcvFileAccepted -> withUser(user, json.encodeToString(chatItem))
|
||||||
is RcvFileStart -> withUser(user, json.encodeToString(chatItem))
|
is RcvFileStart -> withUser(user, json.encodeToString(chatItem))
|
||||||
@ -3820,6 +3871,7 @@ sealed class ChatErrorType {
|
|||||||
is AgentCommandError -> "agentCommandError"
|
is AgentCommandError -> "agentCommandError"
|
||||||
is InvalidFileDescription -> "invalidFileDescription"
|
is InvalidFileDescription -> "invalidFileDescription"
|
||||||
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
|
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
|
||||||
|
is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible"
|
||||||
is InternalError -> "internalError"
|
is InternalError -> "internalError"
|
||||||
is CEException -> "exception $message"
|
is CEException -> "exception $message"
|
||||||
}
|
}
|
||||||
@ -3894,6 +3946,7 @@ sealed class ChatErrorType {
|
|||||||
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
|
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
|
||||||
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
|
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
|
||||||
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
|
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
|
||||||
|
@Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType()
|
||||||
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
|
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
|
||||||
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
|
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
|
||||||
}
|
}
|
||||||
@ -3922,6 +3975,7 @@ sealed class StoreError {
|
|||||||
is GroupMemberNameNotFound -> "groupMemberNameNotFound"
|
is GroupMemberNameNotFound -> "groupMemberNameNotFound"
|
||||||
is GroupMemberNotFound -> "groupMemberNotFound"
|
is GroupMemberNotFound -> "groupMemberNotFound"
|
||||||
is GroupMemberNotFoundByMemberId -> "groupMemberNotFoundByMemberId"
|
is GroupMemberNotFoundByMemberId -> "groupMemberNotFoundByMemberId"
|
||||||
|
is MemberContactGroupMemberNotFound -> "memberContactGroupMemberNotFound"
|
||||||
is GroupWithoutUser -> "groupWithoutUser"
|
is GroupWithoutUser -> "groupWithoutUser"
|
||||||
is DuplicateGroupMember -> "duplicateGroupMember"
|
is DuplicateGroupMember -> "duplicateGroupMember"
|
||||||
is GroupAlreadyJoined -> "groupAlreadyJoined"
|
is GroupAlreadyJoined -> "groupAlreadyJoined"
|
||||||
@ -3979,6 +4033,7 @@ sealed class StoreError {
|
|||||||
@Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError()
|
@Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError()
|
||||||
@Serializable @SerialName("groupMemberNotFound") class GroupMemberNotFound(val groupMemberId: Long): StoreError()
|
@Serializable @SerialName("groupMemberNotFound") class GroupMemberNotFound(val groupMemberId: Long): StoreError()
|
||||||
@Serializable @SerialName("groupMemberNotFoundByMemberId") class GroupMemberNotFoundByMemberId(val memberId: String): StoreError()
|
@Serializable @SerialName("groupMemberNotFoundByMemberId") class GroupMemberNotFoundByMemberId(val memberId: String): StoreError()
|
||||||
|
@Serializable @SerialName("memberContactGroupMemberNotFound") class MemberContactGroupMemberNotFound(val contactId: Long): StoreError()
|
||||||
@Serializable @SerialName("groupWithoutUser") object GroupWithoutUser: StoreError()
|
@Serializable @SerialName("groupWithoutUser") object GroupWithoutUser: StoreError()
|
||||||
@Serializable @SerialName("duplicateGroupMember") object DuplicateGroupMember: StoreError()
|
@Serializable @SerialName("duplicateGroupMember") object DuplicateGroupMember: StoreError()
|
||||||
@Serializable @SerialName("groupAlreadyJoined") object GroupAlreadyJoined: StoreError()
|
@Serializable @SerialName("groupAlreadyJoined") object GroupAlreadyJoined: StoreError()
|
||||||
|
@ -8,6 +8,7 @@ import chat.simplex.common.views.chat.ComposeState
|
|||||||
@Composable
|
@Composable
|
||||||
expect fun PlatformTextField(
|
expect fun PlatformTextField(
|
||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
|
sendMsgEnabled: Boolean,
|
||||||
textStyle: MutableState<TextStyle>,
|
textStyle: MutableState<TextStyle>,
|
||||||
showDeleteTextButton: MutableState<Boolean>,
|
showDeleteTextButton: MutableState<Boolean>,
|
||||||
userIsObserver: Boolean,
|
userIsObserver: Boolean,
|
||||||
|
@ -85,6 +85,8 @@ fun TerminalLayout(
|
|||||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||||
isDirectChat = false,
|
isDirectChat = false,
|
||||||
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
|
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
|
||||||
|
sendMsgEnabled = true,
|
||||||
|
nextSendGrpInv = false,
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = false,
|
allowedVoiceByPrefs = false,
|
||||||
userIsObserver = false,
|
userIsObserver = false,
|
||||||
|
@ -291,21 +291,23 @@ fun ChatInfoLayout(
|
|||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionView {
|
if (contact.ready) {
|
||||||
if (connectionCode != null) {
|
SectionView {
|
||||||
VerifyCodeButton(contact.verified, verifyClicked)
|
if (connectionCode != null) {
|
||||||
|
VerifyCodeButton(contact.verified, verifyClicked)
|
||||||
|
}
|
||||||
|
ContactPreferencesButton(openPreferences)
|
||||||
|
SendReceiptsOption(currentUser, sendReceipts, setSendReceipts)
|
||||||
|
if (cStats != null && cStats.ratchetSyncAllowed) {
|
||||||
|
SynchronizeConnectionButton(syncContactConnection)
|
||||||
|
}
|
||||||
|
// } else if (developerTools) {
|
||||||
|
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
ContactPreferencesButton(openPreferences)
|
SectionDividerSpaced()
|
||||||
SendReceiptsOption(currentUser, sendReceipts, setSendReceipts)
|
|
||||||
if (cStats != null && cStats.ratchetSyncAllowed) {
|
|
||||||
SynchronizeConnectionButton(syncContactConnection)
|
|
||||||
}
|
|
||||||
// } else if (developerTools) {
|
|
||||||
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionDividerSpaced()
|
|
||||||
if (contact.contactLink != null) {
|
if (contact.contactLink != null) {
|
||||||
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
|
||||||
QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
|
||||||
@ -316,36 +318,40 @@ fun ChatInfoLayout(
|
|||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
|
if (contact.ready) {
|
||||||
SectionItemView({
|
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
|
||||||
AlertManager.shared.showAlertMsg(
|
SectionItemView({
|
||||||
generalGetString(MR.strings.network_status),
|
AlertManager.shared.showAlertMsg(
|
||||||
contactNetworkStatus.statusExplanation
|
generalGetString(MR.strings.network_status),
|
||||||
)}) {
|
contactNetworkStatus.statusExplanation
|
||||||
NetworkStatusRow(contactNetworkStatus)
|
|
||||||
}
|
|
||||||
if (cStats != null) {
|
|
||||||
SwitchAddressButton(
|
|
||||||
disabled = cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null } || cStats.ratchetSyncSendProhibited,
|
|
||||||
switchAddress = switchContactAddress
|
|
||||||
)
|
|
||||||
if (cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null }) {
|
|
||||||
AbortSwitchAddressButton(
|
|
||||||
disabled = cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null && !it.canAbortSwitch } || cStats.ratchetSyncSendProhibited,
|
|
||||||
abortSwitchAddress = abortSwitchContactAddress
|
|
||||||
)
|
)
|
||||||
|
}) {
|
||||||
|
NetworkStatusRow(contactNetworkStatus)
|
||||||
}
|
}
|
||||||
val rcvServers = cStats.rcvQueuesInfo.map { it.rcvServer }
|
if (cStats != null) {
|
||||||
if (rcvServers.isNotEmpty()) {
|
SwitchAddressButton(
|
||||||
SimplexServers(stringResource(MR.strings.receiving_via), rcvServers)
|
disabled = cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null } || cStats.ratchetSyncSendProhibited,
|
||||||
}
|
switchAddress = switchContactAddress
|
||||||
val sndServers = cStats.sndQueuesInfo.map { it.sndServer }
|
)
|
||||||
if (sndServers.isNotEmpty()) {
|
if (cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null }) {
|
||||||
SimplexServers(stringResource(MR.strings.sending_via), sndServers)
|
AbortSwitchAddressButton(
|
||||||
|
disabled = cStats.rcvQueuesInfo.any { it.rcvSwitchStatus != null && !it.canAbortSwitch } || cStats.ratchetSyncSendProhibited,
|
||||||
|
abortSwitchAddress = abortSwitchContactAddress
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val rcvServers = cStats.rcvQueuesInfo.map { it.rcvServer }
|
||||||
|
if (rcvServers.isNotEmpty()) {
|
||||||
|
SimplexServers(stringResource(MR.strings.receiving_via), rcvServers)
|
||||||
|
}
|
||||||
|
val sndServers = cStats.sndQueuesInfo.map { it.sndServer }
|
||||||
|
if (sndServers.isNotEmpty()) {
|
||||||
|
SimplexServers(stringResource(MR.strings.sending_via), sndServers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SectionDividerSpaced()
|
||||||
}
|
}
|
||||||
SectionDividerSpaced()
|
|
||||||
SectionView {
|
SectionView {
|
||||||
ClearChatButton(clearChat)
|
ClearChatButton(clearChat)
|
||||||
DeleteContactButton(deleteContact)
|
DeleteContactButton(deleteContact)
|
||||||
|
@ -114,7 +114,18 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
unreadCount,
|
unreadCount,
|
||||||
composeState,
|
composeState,
|
||||||
composeView = {
|
composeView = {
|
||||||
if (chat.chatInfo.sendMsgEnabled) {
|
Column(
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
if (chat.chatInfo is ChatInfo.Direct && !chat.chatInfo.contact.ready && !chat.chatInfo.contact.nextSendGrpInv) {
|
||||||
|
Text(
|
||||||
|
generalGetString(MR.strings.contact_connection_pending),
|
||||||
|
Modifier.padding(top = 4.dp),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
ComposeView(
|
ComposeView(
|
||||||
chatModel, chat, composeState, attachmentOption,
|
chatModel, chat, composeState, attachmentOption,
|
||||||
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
|
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
|
||||||
@ -145,7 +156,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
var preloadedLink: Pair<String, GroupMemberRole>? = null
|
var preloadedLink: Pair<String, GroupMemberRole>? = null
|
||||||
if (chat.chatInfo is ChatInfo.Direct) {
|
if (chat.chatInfo is ChatInfo.Direct) {
|
||||||
preloadedContactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
preloadedContactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
||||||
preloadedCode = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId).second
|
preloadedCode = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId)?.second
|
||||||
} else if (chat.chatInfo is ChatInfo.Group) {
|
} else if (chat.chatInfo is ChatInfo.Group) {
|
||||||
setGroupMembers(chat.chatInfo.groupInfo, chatModel)
|
setGroupMembers(chat.chatInfo.groupInfo, chatModel)
|
||||||
preloadedLink = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId)
|
preloadedLink = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId)
|
||||||
@ -158,7 +169,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
|
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
|
||||||
contactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
contactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
||||||
preloadedContactInfo = contactInfo
|
preloadedContactInfo = contactInfo
|
||||||
code = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId).second
|
code = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId)?.second
|
||||||
preloadedCode = code
|
preloadedCode = code
|
||||||
}
|
}
|
||||||
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
|
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
|
||||||
@ -183,12 +194,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
||||||
val stats = r?.second
|
val stats = r?.second
|
||||||
val (_, code) = if (member.memberActive) {
|
val (_, code) = if (member.memberActive) {
|
||||||
try {
|
val memCode = chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
|
||||||
chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
|
member to memCode?.second
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.stackTraceToString())
|
|
||||||
member to null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
member to null
|
member to null
|
||||||
}
|
}
|
||||||
@ -280,6 +287,11 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
chatModel.controller.allowFeatureToContact(contact, feature, param)
|
chatModel.controller.allowFeatureToContact(contact, feature, param)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openDirectChat = { contactId ->
|
||||||
|
withApi {
|
||||||
|
openDirectChat(contactId, chatModel)
|
||||||
|
}
|
||||||
|
},
|
||||||
updateContactStats = { contact ->
|
updateContactStats = { contact ->
|
||||||
withApi {
|
withApi {
|
||||||
val r = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
val r = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
|
||||||
@ -409,6 +421,7 @@ fun ChatLayout(
|
|||||||
startCall: (CallMediaType) -> Unit,
|
startCall: (CallMediaType) -> Unit,
|
||||||
acceptCall: (Contact) -> Unit,
|
acceptCall: (Contact) -> Unit,
|
||||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
||||||
|
openDirectChat: (Long) -> Unit,
|
||||||
updateContactStats: (Contact) -> Unit,
|
updateContactStats: (Contact) -> Unit,
|
||||||
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
|
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
|
||||||
syncContactConnection: (Contact) -> Unit,
|
syncContactConnection: (Contact) -> Unit,
|
||||||
@ -485,7 +498,7 @@ fun ChatLayout(
|
|||||||
ChatItemsList(
|
ChatItemsList(
|
||||||
chat, unreadCount, composeState, chatItems, searchValue,
|
chat, unreadCount, composeState, chatItems, searchValue,
|
||||||
useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage,
|
useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage,
|
||||||
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature,
|
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat,
|
||||||
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
||||||
setReaction, showItemDetails, markRead, setFloatingButton, onComposed,
|
setReaction, showItemDetails, markRead, setFloatingButton, onComposed,
|
||||||
)
|
)
|
||||||
@ -534,15 +547,22 @@ fun ChatInfoToolbar(
|
|||||||
IconButton({
|
IconButton({
|
||||||
showMenu.value = false
|
showMenu.value = false
|
||||||
startCall(CallMediaType.Audio)
|
startCall(CallMediaType.Audio)
|
||||||
}) {
|
},
|
||||||
Icon(painterResource(MR.images.ic_call_500), stringResource(MR.strings.icon_descr_more_button), tint = MaterialTheme.colors.primary)
|
enabled = chat.chatInfo.contact.ready) {
|
||||||
|
Icon(
|
||||||
|
painterResource(MR.images.ic_call_500),
|
||||||
|
stringResource(MR.strings.icon_descr_more_button),
|
||||||
|
tint = if (chat.chatInfo.contact.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
menuItems.add {
|
if (chat.chatInfo.contact.ready) {
|
||||||
ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = {
|
menuItems.add {
|
||||||
showMenu.value = false
|
ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = {
|
||||||
startCall(CallMediaType.Video)
|
showMenu.value = false
|
||||||
})
|
startCall(CallMediaType.Video)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.canAddMembers && !chat.chatInfo.incognito) {
|
} else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.canAddMembers && !chat.chatInfo.incognito) {
|
||||||
barButtons.add {
|
barButtons.add {
|
||||||
@ -554,20 +574,22 @@ fun ChatInfoToolbar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val ntfsEnabled = remember { mutableStateOf(chat.chatInfo.ntfsEnabled) }
|
if ((chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.ready) || chat.chatInfo is ChatInfo.Group) {
|
||||||
menuItems.add {
|
val ntfsEnabled = remember { mutableStateOf(chat.chatInfo.ntfsEnabled) }
|
||||||
ItemAction(
|
menuItems.add {
|
||||||
if (ntfsEnabled.value) stringResource(MR.strings.mute_chat) else stringResource(MR.strings.unmute_chat),
|
ItemAction(
|
||||||
if (ntfsEnabled.value) painterResource(MR.images.ic_notifications_off) else painterResource(MR.images.ic_notifications),
|
if (ntfsEnabled.value) stringResource(MR.strings.mute_chat) else stringResource(MR.strings.unmute_chat),
|
||||||
onClick = {
|
if (ntfsEnabled.value) painterResource(MR.images.ic_notifications_off) else painterResource(MR.images.ic_notifications),
|
||||||
showMenu.value = false
|
onClick = {
|
||||||
// Just to make a delay before changing state of ntfsEnabled, otherwise it will redraw menu item with new value before closing the menu
|
showMenu.value = false
|
||||||
scope.launch {
|
// Just to make a delay before changing state of ntfsEnabled, otherwise it will redraw menu item with new value before closing the menu
|
||||||
delay(200)
|
scope.launch {
|
||||||
changeNtfsState(!ntfsEnabled.value, ntfsEnabled)
|
delay(200)
|
||||||
|
changeNtfsState(!ntfsEnabled.value, ntfsEnabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
barButtons.add {
|
barButtons.add {
|
||||||
@ -661,6 +683,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
joinGroup: (Long) -> Unit,
|
joinGroup: (Long) -> Unit,
|
||||||
acceptCall: (Contact) -> Unit,
|
acceptCall: (Contact) -> Unit,
|
||||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
||||||
|
openDirectChat: (Long) -> Unit,
|
||||||
updateContactStats: (Contact) -> Unit,
|
updateContactStats: (Contact) -> Unit,
|
||||||
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
|
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
|
||||||
syncContactConnection: (Contact) -> Unit,
|
syncContactConnection: (Contact) -> Unit,
|
||||||
@ -808,7 +831,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
) {
|
) {
|
||||||
MemberImage(member)
|
MemberImage(member)
|
||||||
}
|
}
|
||||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
|
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -817,7 +840,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
.padding(start = 8.dp + MEMBER_IMAGE_SIZE + 4.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp)
|
.padding(start = 8.dp + MEMBER_IMAGE_SIZE + 4.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp)
|
||||||
.then(swipeableModifier)
|
.then(swipeableModifier)
|
||||||
) {
|
) {
|
||||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
|
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -827,7 +850,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
.padding(start = if (voiceWithTransparentBack) 12.dp else 104.dp, end = 12.dp)
|
.padding(start = if (voiceWithTransparentBack) 12.dp else 104.dp, end = 12.dp)
|
||||||
.then(swipeableModifier)
|
.then(swipeableModifier)
|
||||||
) {
|
) {
|
||||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
|
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // direct message
|
} else { // direct message
|
||||||
@ -838,7 +861,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
|||||||
end = if (sent || voiceWithTransparentBack) 12.dp else 76.dp,
|
end = if (sent || voiceWithTransparentBack) 12.dp else 76.dp,
|
||||||
).then(swipeableModifier)
|
).then(swipeableModifier)
|
||||||
) {
|
) {
|
||||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
|
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1263,6 +1286,7 @@ fun PreviewChatLayout() {
|
|||||||
startCall = {},
|
startCall = {},
|
||||||
acceptCall = { _ -> },
|
acceptCall = { _ -> },
|
||||||
acceptFeature = { _, _, _ -> },
|
acceptFeature = { _, _, _ -> },
|
||||||
|
openDirectChat = { _ -> },
|
||||||
updateContactStats = { },
|
updateContactStats = { },
|
||||||
updateMemberStats = { _, _ -> },
|
updateMemberStats = { _, _ -> },
|
||||||
syncContactConnection = { },
|
syncContactConnection = { },
|
||||||
@ -1330,6 +1354,7 @@ fun PreviewGroupChatLayout() {
|
|||||||
startCall = {},
|
startCall = {},
|
||||||
acceptCall = { _ -> },
|
acceptCall = { _ -> },
|
||||||
acceptFeature = { _, _, _ -> },
|
acceptFeature = { _, _, _ -> },
|
||||||
|
openDirectChat = { _ -> },
|
||||||
updateContactStats = { },
|
updateContactStats = { },
|
||||||
updateMemberStats = { _, _ -> },
|
updateMemberStats = { _, _ -> },
|
||||||
syncContactConnection = { },
|
syncContactConnection = { },
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package chat.simplex.common.views.chat
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.helpers.generalGetString
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ComposeContextInvitingContactMemberView() {
|
||||||
|
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.height(60.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.background(sentColor),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(MR.images.ic_chat),
|
||||||
|
stringResource(MR.strings.button_send_direct_message),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 12.dp, end = 8.dp)
|
||||||
|
.height(20.dp)
|
||||||
|
.width(20.dp),
|
||||||
|
tint = MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
Text(generalGetString(MR.strings.compose_send_direct_message_to_connect))
|
||||||
|
}
|
||||||
|
}
|
@ -335,8 +335,6 @@ fun ComposeView(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? {
|
suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? {
|
||||||
val cInfo = chat.chatInfo
|
val cInfo = chat.chatInfo
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
@ -358,6 +356,7 @@ fun ComposeView(
|
|||||||
MsgContent.MCText(msgText)
|
MsgContent.MCText(msgText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> MsgContent.MCText(msgText)
|
else -> MsgContent.MCText(msgText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,6 +373,14 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun sendMemberContactInvitation() {
|
||||||
|
val mc = checkLinkPreview()
|
||||||
|
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.chatInfo.apiId, mc)
|
||||||
|
if (contact != null) {
|
||||||
|
chatModel.updateContact(contact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? {
|
suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? {
|
||||||
val oldMsgContent = ei.content.msgContent
|
val oldMsgContent = ei.content.msgContent
|
||||||
if (oldMsgContent != null) {
|
if (oldMsgContent != null) {
|
||||||
@ -397,7 +404,10 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
clearCurrentDraft()
|
clearCurrentDraft()
|
||||||
|
|
||||||
if (cs.contextItem is ComposeContextItem.EditingItem) {
|
if (chat.nextSendGrpInv) {
|
||||||
|
sendMemberContactInvitation()
|
||||||
|
sent = null
|
||||||
|
} else if (cs.contextItem is ComposeContextItem.EditingItem) {
|
||||||
val ei = cs.contextItem.chatItem
|
val ei = cs.contextItem.chatItem
|
||||||
sent = updateMessage(ei, cInfo, live)
|
sent = updateMessage(ei, cInfo, live)
|
||||||
} else if (liveMessage != null && liveMessage.sent) {
|
} else if (liveMessage != null && liveMessage.sent) {
|
||||||
@ -655,9 +665,14 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val userCanSend = rememberUpdatedState(chat.userCanSend)
|
val userCanSend = rememberUpdatedState(chat.userCanSend)
|
||||||
|
val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled)
|
||||||
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
|
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
|
||||||
|
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
if (nextSendGrpInv.value) {
|
||||||
|
ComposeContextInvitingContactMemberView()
|
||||||
|
}
|
||||||
if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) {
|
if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) {
|
||||||
contextItemView()
|
contextItemView()
|
||||||
when {
|
when {
|
||||||
@ -690,15 +705,21 @@ fun ComposeView(
|
|||||||
} else {
|
} else {
|
||||||
showChooseAttachment
|
showChooseAttachment
|
||||||
}
|
}
|
||||||
|
val attachmentEnabled =
|
||||||
|
!composeState.value.attachmentDisabled
|
||||||
|
&& sendMsgEnabled.value
|
||||||
|
&& userCanSend.value
|
||||||
|
&& !isGroupAndProhibitedFiles
|
||||||
|
&& !nextSendGrpInv.value
|
||||||
IconButton(
|
IconButton(
|
||||||
attachmentClicked,
|
attachmentClicked,
|
||||||
Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else 7.dp),
|
Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else 7.dp),
|
||||||
enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value
|
enabled = attachmentEnabled
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(MR.images.ic_attach_file_filled_500),
|
painterResource(MR.images.ic_attach_file_filled_500),
|
||||||
contentDescription = stringResource(MR.strings.attach),
|
contentDescription = stringResource(MR.strings.attach),
|
||||||
tint = if (!composeState.value.attachmentDisabled && userCanSend.value && !isGroupAndProhibitedFiles) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
tint = if (attachmentEnabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(28.dp)
|
.size(28.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
@ -774,6 +795,8 @@ fun ComposeView(
|
|||||||
recState,
|
recState,
|
||||||
chat.chatInfo is ChatInfo.Direct,
|
chat.chatInfo is ChatInfo.Direct,
|
||||||
liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown,
|
liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown,
|
||||||
|
sendMsgEnabled = sendMsgEnabled.value,
|
||||||
|
nextSendGrpInv = nextSendGrpInv.value,
|
||||||
needToAllowVoiceToContact,
|
needToAllowVoiceToContact,
|
||||||
allowedVoiceByPrefs,
|
allowedVoiceByPrefs,
|
||||||
allowVoiceToContact = ::allowVoiceToContact,
|
allowVoiceToContact = ::allowVoiceToContact,
|
||||||
|
@ -37,6 +37,8 @@ fun SendMsgView(
|
|||||||
recState: MutableState<RecordingState>,
|
recState: MutableState<RecordingState>,
|
||||||
isDirectChat: Boolean,
|
isDirectChat: Boolean,
|
||||||
liveMessageAlertShown: SharedPreference<Boolean>,
|
liveMessageAlertShown: SharedPreference<Boolean>,
|
||||||
|
sendMsgEnabled: Boolean,
|
||||||
|
nextSendGrpInv: Boolean,
|
||||||
needToAllowVoiceToContact: Boolean,
|
needToAllowVoiceToContact: Boolean,
|
||||||
allowedVoiceByPrefs: Boolean,
|
allowedVoiceByPrefs: Boolean,
|
||||||
userIsObserver: Boolean,
|
userIsObserver: Boolean,
|
||||||
@ -74,16 +76,16 @@ fun SendMsgView(
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
|
val showVoiceButton = !nextSendGrpInv && cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
|
||||||
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
|
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
|
||||||
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
|
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
|
||||||
PlatformTextField(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange, editPrevMessage) {
|
PlatformTextField(composeState, sendMsgEnabled, textStyle, showDeleteTextButton, userIsObserver, onMessageChange, editPrevMessage) {
|
||||||
if (!cs.inProgress) {
|
if (!cs.inProgress) {
|
||||||
sendMessage(null)
|
sendMessage(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Disable clicks on text field
|
// Disable clicks on text field
|
||||||
if (cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) {
|
if (!sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.matchParentSize()
|
.matchParentSize()
|
||||||
@ -110,7 +112,7 @@ fun SendMsgView(
|
|||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
progressByTimeout -> ProgressIndicator()
|
progressByTimeout -> ProgressIndicator()
|
||||||
showVoiceButton -> {
|
showVoiceButton && sendMsgEnabled -> {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val stopRecOnNextClick = remember { mutableStateOf(false) }
|
val stopRecOnNextClick = remember { mutableStateOf(false) }
|
||||||
when {
|
when {
|
||||||
@ -150,7 +152,7 @@ fun SendMsgView(
|
|||||||
else -> {
|
else -> {
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
val icon = if (cs.editing || cs.liveMessage != null) painterResource(MR.images.ic_check_filled) else painterResource(MR.images.ic_arrow_upward)
|
val icon = if (cs.editing || cs.liveMessage != null) painterResource(MR.images.ic_check_filled) else painterResource(MR.images.ic_arrow_upward)
|
||||||
val disabled = !cs.sendEnabled() ||
|
val disabled = !sendMsgEnabled || !cs.sendEnabled() ||
|
||||||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
|
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
|
||||||
cs.endLiveDisabled
|
cs.endLiveDisabled
|
||||||
val showDropdown = rememberSaveable { mutableStateOf(false) }
|
val showDropdown = rememberSaveable { mutableStateOf(false) }
|
||||||
@ -159,7 +161,7 @@ fun SendMsgView(
|
|||||||
fun MenuItems(): List<@Composable () -> Unit> {
|
fun MenuItems(): List<@Composable () -> Unit> {
|
||||||
val menuItems = mutableListOf<@Composable () -> Unit>()
|
val menuItems = mutableListOf<@Composable () -> Unit>()
|
||||||
|
|
||||||
if (cs.liveMessage == null && !cs.editing) {
|
if (cs.liveMessage == null && !cs.editing && !nextSendGrpInv || sendMsgEnabled) {
|
||||||
if (
|
if (
|
||||||
cs.preview !is ComposePreview.VoicePreview &&
|
cs.preview !is ComposePreview.VoicePreview &&
|
||||||
cs.contextItem is ComposeContextItem.NoContextItem &&
|
cs.contextItem is ComposeContextItem.NoContextItem &&
|
||||||
@ -599,6 +601,8 @@ fun PreviewSendMsgView() {
|
|||||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||||
isDirectChat = true,
|
isDirectChat = true,
|
||||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||||
|
sendMsgEnabled = true,
|
||||||
|
nextSendGrpInv = false,
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = true,
|
allowedVoiceByPrefs = true,
|
||||||
userIsObserver = false,
|
userIsObserver = false,
|
||||||
@ -630,6 +634,8 @@ fun PreviewSendMsgViewEditing() {
|
|||||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||||
isDirectChat = true,
|
isDirectChat = true,
|
||||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||||
|
sendMsgEnabled = true,
|
||||||
|
nextSendGrpInv = false,
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = true,
|
allowedVoiceByPrefs = true,
|
||||||
userIsObserver = false,
|
userIsObserver = false,
|
||||||
@ -661,6 +667,8 @@ fun PreviewSendMsgViewInProgress() {
|
|||||||
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
recState = remember { mutableStateOf(RecordingState.NotStarted) },
|
||||||
isDirectChat = true,
|
isDirectChat = true,
|
||||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||||
|
sendMsgEnabled = true,
|
||||||
|
nextSendGrpInv = false,
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = true,
|
allowedVoiceByPrefs = true,
|
||||||
userIsObserver = false,
|
userIsObserver = false,
|
||||||
|
@ -76,12 +76,8 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR
|
|||||||
val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
||||||
val stats = r?.second
|
val stats = r?.second
|
||||||
val (_, code) = if (member.memberActive) {
|
val (_, code) = if (member.memberActive) {
|
||||||
try {
|
val memCode = chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
|
||||||
chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
|
member to memCode?.second
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.stackTraceToString())
|
|
||||||
member to null
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
member to null
|
member to null
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import chat.simplex.common.views.newchat.*
|
|||||||
import chat.simplex.common.views.usersettings.SettingsActionItem
|
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||||
import chat.simplex.common.model.GroupInfo
|
import chat.simplex.common.model.GroupInfo
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.chatlist.openChat
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
@ -52,6 +53,8 @@ fun GroupMemberInfoView(
|
|||||||
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
||||||
val connStats = remember { mutableStateOf(connectionStats) }
|
val connStats = remember { mutableStateOf(connectionStats) }
|
||||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||||
|
var progressIndicator by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
val newRole = remember { mutableStateOf(member.memberRole) }
|
val newRole = remember { mutableStateOf(member.memberRole) }
|
||||||
GroupMemberInfoLayout(
|
GroupMemberInfoLayout(
|
||||||
@ -76,6 +79,20 @@ fun GroupMemberInfoView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
createMemberContact = {
|
||||||
|
withApi {
|
||||||
|
progressIndicator = true
|
||||||
|
val memberContact = chatModel.controller.apiCreateMemberContact(groupInfo.apiId, member.groupMemberId)
|
||||||
|
if (memberContact != null) {
|
||||||
|
val memberChat = Chat(ChatInfo.Direct(memberContact), chatItems = arrayListOf())
|
||||||
|
chatModel.addChat(memberChat)
|
||||||
|
openChat(memberChat, chatModel)
|
||||||
|
closeAll()
|
||||||
|
chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected())
|
||||||
|
}
|
||||||
|
progressIndicator = false
|
||||||
|
}
|
||||||
|
},
|
||||||
connectViaAddress = { connReqUri ->
|
connectViaAddress = { connReqUri ->
|
||||||
connectViaMemberAddressAlert(connReqUri)
|
connectViaMemberAddressAlert(connReqUri)
|
||||||
},
|
},
|
||||||
@ -170,6 +187,10 @@ fun GroupMemberInfoView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (progressIndicator) {
|
||||||
|
ProgressIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,6 +222,7 @@ fun GroupMemberInfoLayout(
|
|||||||
connectionCode: String?,
|
connectionCode: String?,
|
||||||
getContactChat: (Long) -> Chat?,
|
getContactChat: (Long) -> Chat?,
|
||||||
openDirectChat: (Long) -> Unit,
|
openDirectChat: (Long) -> Unit,
|
||||||
|
createMemberContact: () -> Unit,
|
||||||
connectViaAddress: (String) -> Unit,
|
connectViaAddress: (String) -> Unit,
|
||||||
removeMember: () -> Unit,
|
removeMember: () -> Unit,
|
||||||
onRoleSelected: (GroupMemberRole) -> Unit,
|
onRoleSelected: (GroupMemberRole) -> Unit,
|
||||||
@ -237,9 +259,13 @@ fun GroupMemberInfoLayout(
|
|||||||
|
|
||||||
if (member.memberActive) {
|
if (member.memberActive) {
|
||||||
SectionView {
|
SectionView {
|
||||||
if (contactId != null) {
|
if (contactId != null && knownDirectChat(contactId) != null) {
|
||||||
if (knownDirectChat(contactId) != null || groupInfo.fullGroupPreferences.directMessages.on) {
|
OpenChatButton(onClick = { openDirectChat(contactId) })
|
||||||
|
} else if (groupInfo.fullGroupPreferences.directMessages.on) {
|
||||||
|
if (contactId != null) {
|
||||||
OpenChatButton(onClick = { openDirectChat(contactId) })
|
OpenChatButton(onClick = { openDirectChat(contactId) })
|
||||||
|
} else if (member.activeConn?.peerChatVRange?.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) == true) {
|
||||||
|
OpenChatButton(onClick = { createMemberContact() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (connectionCode != null) {
|
if (connectionCode != null) {
|
||||||
@ -498,6 +524,7 @@ fun PreviewGroupMemberInfoLayout() {
|
|||||||
connectionCode = "123",
|
connectionCode = "123",
|
||||||
getContactChat = { Chat.sampleData },
|
getContactChat = { Chat.sampleData },
|
||||||
openDirectChat = {},
|
openDirectChat = {},
|
||||||
|
createMemberContact = {},
|
||||||
connectViaAddress = {},
|
connectViaAddress = {},
|
||||||
removeMember = {},
|
removeMember = {},
|
||||||
onRoleSelected = {},
|
onRoleSelected = {},
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package chat.simplex.common.views.chat.item
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.*
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.simplex.common.views.helpers.generalGetString
|
||||||
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CIMemberCreatedContactView(
|
||||||
|
chatItem: ChatItem,
|
||||||
|
openDirectChat: (Long) -> Unit
|
||||||
|
) {
|
||||||
|
fun eventText(): AnnotatedString {
|
||||||
|
val memberDisplayName = chatItem.memberDisplayName
|
||||||
|
return if (memberDisplayName != null) {
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(chatEventStyle) { append(memberDisplayName) }
|
||||||
|
append(" ")
|
||||||
|
withStyle(chatEventStyle) { append(chatItem.content.text) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(chatEventStyle) { append(chatItem.content.text) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
if (chatItem.chatDir is CIDirection.GroupRcv && chatItem.chatDir.groupMember.memberContactId != null) {
|
||||||
|
val openChatStyle = SpanStyle(color = MaterialTheme.colors.primary, fontSize = 12.sp)
|
||||||
|
val annotatedText = buildAnnotatedString {
|
||||||
|
append(eventText())
|
||||||
|
append(" ")
|
||||||
|
withAnnotation(tag = "Open", annotation = "Open") {
|
||||||
|
withStyle(openChatStyle) { append(generalGetString(MR.strings.rcv_group_event_open_chat) + " ") }
|
||||||
|
}
|
||||||
|
withStyle(chatEventStyle) { append(chatItem.timestampText) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun open(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Open", start = offset, end = offset).isNotEmpty()
|
||||||
|
ClickableText(
|
||||||
|
annotatedText,
|
||||||
|
onClick = {
|
||||||
|
if (open(it)) {
|
||||||
|
openDirectChat(chatItem.chatDir.groupMember.memberContactId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shouldConsumeEvent = ::open
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val annotatedText = buildAnnotatedString {
|
||||||
|
append(eventText())
|
||||||
|
append(" ")
|
||||||
|
withStyle(chatEventStyle) { append(chatItem.timestampText) }
|
||||||
|
}
|
||||||
|
Text(annotatedText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,7 @@ fun ChatItemView(
|
|||||||
acceptCall: (Contact) -> Unit,
|
acceptCall: (Contact) -> Unit,
|
||||||
scrollToItem: (Long) -> Unit,
|
scrollToItem: (Long) -> Unit,
|
||||||
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
|
||||||
|
openDirectChat: (Long) -> Unit,
|
||||||
updateContactStats: (Contact) -> Unit,
|
updateContactStats: (Contact) -> Unit,
|
||||||
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
|
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
|
||||||
syncContactConnection: (Contact) -> Unit,
|
syncContactConnection: (Contact) -> Unit,
|
||||||
@ -348,6 +349,7 @@ fun ChatItemView(
|
|||||||
is CIContent.SndGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito)
|
is CIContent.SndGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito)
|
||||||
is CIContent.RcvGroupEventContent -> when (c.rcvGroupEvent) {
|
is CIContent.RcvGroupEventContent -> when (c.rcvGroupEvent) {
|
||||||
is RcvGroupEvent.MemberConnected -> CIEventView(membersConnectedItemText())
|
is RcvGroupEvent.MemberConnected -> CIEventView(membersConnectedItemText())
|
||||||
|
is RcvGroupEvent.MemberCreatedContact -> CIMemberCreatedContactView(cItem, openDirectChat)
|
||||||
else -> EventItemView()
|
else -> EventItemView()
|
||||||
}
|
}
|
||||||
is CIContent.SndGroupEventContent -> EventItemView()
|
is CIContent.SndGroupEventContent -> EventItemView()
|
||||||
@ -572,6 +574,7 @@ fun PreviewChatItemView() {
|
|||||||
acceptCall = { _ -> },
|
acceptCall = { _ -> },
|
||||||
scrollToItem = {},
|
scrollToItem = {},
|
||||||
acceptFeature = { _, _, _ -> },
|
acceptFeature = { _, _, _ -> },
|
||||||
|
openDirectChat = { _ -> },
|
||||||
updateContactStats = { },
|
updateContactStats = { },
|
||||||
updateMemberStats = { _, _ -> },
|
updateMemberStats = { _, _ -> },
|
||||||
syncContactConnection = { },
|
syncContactConnection = { },
|
||||||
@ -601,6 +604,7 @@ fun PreviewChatItemViewDeletedContent() {
|
|||||||
acceptCall = { _ -> },
|
acceptCall = { _ -> },
|
||||||
scrollToItem = {},
|
scrollToItem = {},
|
||||||
acceptFeature = { _, _, _ -> },
|
acceptFeature = { _, _, _ -> },
|
||||||
|
openDirectChat = { _ -> },
|
||||||
updateContactStats = { },
|
updateContactStats = { },
|
||||||
updateMemberStats = { _, _ -> },
|
updateMemberStats = { _, _ -> },
|
||||||
syncContactConnection = { },
|
syncContactConnection = { },
|
||||||
|
@ -103,11 +103,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun directChatAction(chatInfo: ChatInfo, chatModel: ChatModel) {
|
fun directChatAction(chatInfo: ChatInfo, chatModel: ChatModel) {
|
||||||
if (chatInfo.ready) {
|
withBGApi { openChat(chatInfo, chatModel) }
|
||||||
withBGApi { openChat(chatInfo, chatModel) }
|
|
||||||
} else {
|
|
||||||
pendingContactAlertDialog(chatInfo, chatModel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
|
fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
|
||||||
@ -118,15 +114,28 @@ fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
|
||||||
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
|
val chat = chatModel.controller.apiGetChat(ChatType.Direct, contactId)
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
chatModel.chatItems.clear()
|
chatModel.chatItems.clear()
|
||||||
chatModel.chatItems.addAll(chat.chatItems)
|
chatModel.chatItems.addAll(chat.chatItems)
|
||||||
chatModel.chatId.value = chatInfo.id
|
chatModel.chatId.value = "@$contactId"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
||||||
|
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
|
||||||
|
if (chat != null) {
|
||||||
|
openChat(chat, chatModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun openChat(chat: Chat, chatModel: ChatModel) {
|
||||||
|
chatModel.chatItems.clear()
|
||||||
|
chatModel.chatItems.addAll(chat.chatItems)
|
||||||
|
chatModel.chatId.value = chat.chatInfo.id
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun apiLoadPrevMessages(chatInfo: ChatInfo, chatModel: ChatModel, beforeChatItemId: Long, search: String) {
|
suspend fun apiLoadPrevMessages(chatInfo: ChatInfo, chatModel: ChatModel, beforeChatItemId: Long, search: String) {
|
||||||
val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT)
|
val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT)
|
||||||
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return
|
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return
|
||||||
|
@ -172,7 +172,9 @@ fun ChatPreviewView(
|
|||||||
} else {
|
} else {
|
||||||
when (cInfo) {
|
when (cInfo) {
|
||||||
is ChatInfo.Direct ->
|
is ChatInfo.Direct ->
|
||||||
if (!cInfo.ready) {
|
if (cInfo.contact.nextSendGrpInv) {
|
||||||
|
Text(stringResource(MR.strings.member_contact_send_direct_message), color = MaterialTheme.colors.secondary)
|
||||||
|
} else if (!cInfo.ready) {
|
||||||
Text(stringResource(MR.strings.contact_connection_pending), color = MaterialTheme.colors.secondary)
|
Text(stringResource(MR.strings.contact_connection_pending), color = MaterialTheme.colors.secondary)
|
||||||
}
|
}
|
||||||
is ChatInfo.Group ->
|
is ChatInfo.Group ->
|
||||||
|
@ -272,6 +272,7 @@
|
|||||||
<string name="this_text_is_available_in_settings">This text is available in settings</string>
|
<string name="this_text_is_available_in_settings">This text is available in settings</string>
|
||||||
<string name="your_chats">Chats</string>
|
<string name="your_chats">Chats</string>
|
||||||
<string name="contact_connection_pending">connecting…</string>
|
<string name="contact_connection_pending">connecting…</string>
|
||||||
|
<string name="member_contact_send_direct_message">send direct message</string>
|
||||||
<string name="group_preview_you_are_invited">you are invited to group</string>
|
<string name="group_preview_you_are_invited">you are invited to group</string>
|
||||||
<string name="group_preview_join_as">join as %s</string>
|
<string name="group_preview_join_as">join as %s</string>
|
||||||
<string name="group_connection_pending">connecting…</string>
|
<string name="group_connection_pending">connecting…</string>
|
||||||
@ -304,6 +305,7 @@
|
|||||||
<string name="observer_cant_send_message_desc">Please contact group admin.</string>
|
<string name="observer_cant_send_message_desc">Please contact group admin.</string>
|
||||||
<string name="files_and_media_prohibited">Files and media prohibited!</string>
|
<string name="files_and_media_prohibited">Files and media prohibited!</string>
|
||||||
<string name="only_owners_can_enable_files_and_media">Only group owners can enable files and media.</string>
|
<string name="only_owners_can_enable_files_and_media">Only group owners can enable files and media.</string>
|
||||||
|
<string name="compose_send_direct_message_to_connect">Send direct message to connect</string>
|
||||||
|
|
||||||
<!-- Images - chat.simplex.app.views.chat.item.CIImageView.kt -->
|
<!-- Images - chat.simplex.app.views.chat.item.CIImageView.kt -->
|
||||||
<string name="image_descr">Image</string>
|
<string name="image_descr">Image</string>
|
||||||
@ -1114,6 +1116,7 @@
|
|||||||
<string name="rcv_group_event_group_deleted">deleted group</string>
|
<string name="rcv_group_event_group_deleted">deleted group</string>
|
||||||
<string name="rcv_group_event_updated_group_profile">updated group profile</string>
|
<string name="rcv_group_event_updated_group_profile">updated group profile</string>
|
||||||
<string name="rcv_group_event_invited_via_your_group_link">invited via your group link</string>
|
<string name="rcv_group_event_invited_via_your_group_link">invited via your group link</string>
|
||||||
|
<string name="rcv_group_event_member_created_contact">connected directly</string>
|
||||||
<string name="snd_group_event_changed_member_role">you changed role of %s to %s</string>
|
<string name="snd_group_event_changed_member_role">you changed role of %s to %s</string>
|
||||||
<string name="snd_group_event_changed_role_for_yourself">you changed role for yourself to %s</string>
|
<string name="snd_group_event_changed_role_for_yourself">you changed role for yourself to %s</string>
|
||||||
<string name="snd_group_event_member_deleted">you removed %1$s</string>
|
<string name="snd_group_event_member_deleted">you removed %1$s</string>
|
||||||
@ -1124,6 +1127,8 @@
|
|||||||
<string name="rcv_group_event_3_members_connected">%s, %s and %s connected</string>
|
<string name="rcv_group_event_3_members_connected">%s, %s and %s connected</string>
|
||||||
<string name="rcv_group_event_n_members_connected">%s, %s and %d other members connected</string>
|
<string name="rcv_group_event_n_members_connected">%s, %s and %d other members connected</string>
|
||||||
|
|
||||||
|
<string name="rcv_group_event_open_chat">Open</string>
|
||||||
|
|
||||||
<!-- Conn event chat items -->
|
<!-- Conn event chat items -->
|
||||||
<string name="rcv_conn_event_switch_queue_phase_completed">changed address for you</string>
|
<string name="rcv_conn_event_switch_queue_phase_completed">changed address for you</string>
|
||||||
<string name="rcv_conn_event_switch_queue_phase_changing">changing address…</string>
|
<string name="rcv_conn_event_switch_queue_phase_changing">changing address…</string>
|
||||||
@ -1201,6 +1206,8 @@
|
|||||||
<string name="error_creating_link_for_group">Error creating group link</string>
|
<string name="error_creating_link_for_group">Error creating group link</string>
|
||||||
<string name="error_updating_link_for_group">Error updating group link</string>
|
<string name="error_updating_link_for_group">Error updating group link</string>
|
||||||
<string name="error_deleting_link_for_group">Error deleting group link</string>
|
<string name="error_deleting_link_for_group">Error deleting group link</string>
|
||||||
|
<string name="error_creating_member_contact">Error creating member contact</string>
|
||||||
|
<string name="error_sending_message_contact_invitation">Sending message contact invitation</string>
|
||||||
<string name="only_group_owners_can_change_prefs">Only group owners can change group preferences.</string>
|
<string name="only_group_owners_can_change_prefs">Only group owners can change group preferences.</string>
|
||||||
<string name="address_section_title">Address</string>
|
<string name="address_section_title">Address</string>
|
||||||
<string name="share_address">Share address</string>
|
<string name="share_address">Share address</string>
|
||||||
|
@ -33,6 +33,7 @@ import kotlin.text.substring
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun PlatformTextField(
|
actual fun PlatformTextField(
|
||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
|
sendMsgEnabled: Boolean,
|
||||||
textStyle: MutableState<TextStyle>,
|
textStyle: MutableState<TextStyle>,
|
||||||
showDeleteTextButton: MutableState<Boolean>,
|
showDeleteTextButton: MutableState<Boolean>,
|
||||||
userIsObserver: Boolean,
|
userIsObserver: Boolean,
|
||||||
@ -42,6 +43,7 @@ actual fun PlatformTextField(
|
|||||||
) {
|
) {
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
val keyboard = LocalSoftwareKeyboardController.current
|
val keyboard = LocalSoftwareKeyboardController.current
|
||||||
val padding = PaddingValues(12.dp, 12.dp, 45.dp, 0.dp)
|
val padding = PaddingValues(12.dp, 12.dp, 45.dp, 0.dp)
|
||||||
LaunchedEffect(cs.contextItem) {
|
LaunchedEffect(cs.contextItem) {
|
||||||
@ -51,6 +53,13 @@ actual fun PlatformTextField(
|
|||||||
delay(50)
|
delay(50)
|
||||||
keyboard?.show()
|
keyboard?.show()
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(sendMsgEnabled) {
|
||||||
|
if (!sendMsgEnabled) {
|
||||||
|
focusManager.clearFocus()
|
||||||
|
delay(50)
|
||||||
|
keyboard?.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
val isRtl = remember(cs.message) { isRtl(cs.message.subSequence(0, min(50, cs.message.length))) }
|
val isRtl = remember(cs.message) { isRtl(cs.message.subSequence(0, min(50, cs.message.length))) }
|
||||||
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message)) }
|
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message)) }
|
||||||
val textFieldValue = textFieldValueState.copy(text = cs.message)
|
val textFieldValue = textFieldValueState.copy(text = cs.message)
|
||||||
@ -113,7 +122,8 @@ actual fun PlatformTextField(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
)
|
)
|
||||||
showDeleteTextButton.value = cs.message.split("\n").size >= 4 && !cs.inProgress
|
showDeleteTextButton.value = cs.message.split("\n").size >= 4 && !cs.inProgress
|
||||||
if (composeState.value.preview is ComposePreview.VoicePreview) {
|
if (composeState.value.preview is ComposePreview.VoicePreview) {
|
||||||
|
Loading…
Reference in New Issue
Block a user