mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-27 10:45:54 +00:00
tmp
This commit is contained in:
@@ -292,7 +292,7 @@ fun AndroidWrapInCallLayout(content: @Composable () -> Unit) {
|
||||
@Composable
|
||||
fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||
BoxWithConstraints {
|
||||
val currentChatId = remember { mutableStateOf(chatModel.chatId.value) }
|
||||
val currentChatId = remember { MutableStateFlow(chatModel.chatId.value) }
|
||||
val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) }
|
||||
val cutout = WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||
val direction = LocalLayoutDirection.current
|
||||
|
||||
+154
-106
@@ -48,71 +48,71 @@ import kotlin.time.*
|
||||
@Stable
|
||||
object ChatModel {
|
||||
val controller: ChatController = ChatController
|
||||
val setDeliveryReceipts = mutableStateOf(false)
|
||||
val currentUser = mutableStateOf<User?>(null)
|
||||
val users = mutableStateListOf<UserInfo>()
|
||||
val localUserCreated = mutableStateOf<Boolean?>(null)
|
||||
val chatRunning = mutableStateOf<Boolean?>(null)
|
||||
val chatDbChanged = mutableStateOf<Boolean>(false)
|
||||
val chatDbEncrypted = mutableStateOf<Boolean?>(false)
|
||||
val chatDbStatus = mutableStateOf<DBMigrationResult?>(null)
|
||||
val ctrlInitInProgress = mutableStateOf(false)
|
||||
val dbMigrationInProgress = mutableStateOf(false)
|
||||
val incompleteInitializedDbRemoved = mutableStateOf(false)
|
||||
private val _chats = mutableStateOf(SnapshotStateList<Chat>())
|
||||
val chats: State<List<Chat>> = _chats
|
||||
val setDeliveryReceipts = MutableStateFlow(false)
|
||||
val currentUser = MutableStateFlow<User?>(null)
|
||||
val users = MutableStateFlow<List<UserInfo>>(emptyList())
|
||||
val localUserCreated = MutableStateFlow<Boolean?>(null)
|
||||
val chatRunning = MutableStateFlow<Boolean?>(null)
|
||||
val chatDbChanged = MutableStateFlow<Boolean>(false)
|
||||
val chatDbEncrypted = MutableStateFlow<Boolean?>(false)
|
||||
val chatDbStatus = MutableStateFlow<DBMigrationResult?>(null)
|
||||
val ctrlInitInProgress = MutableStateFlow(false)
|
||||
val dbMigrationInProgress = MutableStateFlow(false)
|
||||
val incompleteInitializedDbRemoved = MutableStateFlow(false)
|
||||
private val _chats = MutableStateFlow(SnapshotStateList<Chat>())
|
||||
val chats: StateFlow<List<Chat>> = _chats
|
||||
private val chatsContext = ChatsContext()
|
||||
// map of connections network statuses, key is agent connection id
|
||||
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
||||
val switchingUsersAndHosts = mutableStateOf(false)
|
||||
val networkStatuses = MutableStateFlow<Map<String, NetworkStatus>>(emptyMap())
|
||||
val switchingUsersAndHosts = MutableStateFlow(false)
|
||||
|
||||
// current chat
|
||||
val chatId = mutableStateOf<String?>(null)
|
||||
val chatId = MutableStateFlow<String?>(null)
|
||||
/** if you modify the items by adding/removing them, use helpers methods like [addAndNotify], [removeLastAndNotify], [removeAllAndNotify], [clearAndNotify] and so on.
|
||||
* If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages].
|
||||
* If you use api call to get the items, use just [add] instead of [addAndNotify].
|
||||
* Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */
|
||||
val chatItems = mutableStateOf(SnapshotStateList<ChatItem>())
|
||||
val chatItems = MutableStateFlow(SnapshotStateList<ChatItem>())
|
||||
// set listener here that will be notified on every add/delete of a chat item
|
||||
var chatItemsChangesListener: ChatItemsChangesListener? = null
|
||||
val chatState = ActiveChatState()
|
||||
// rhId, chatId
|
||||
val deletedChats = mutableStateOf<List<Pair<Long?, String>>>(emptyList())
|
||||
val deletedChats = MutableStateFlow<List<Pair<Long?, String>>>(emptyList())
|
||||
val chatItemStatuses = mutableMapOf<Long, CIStatus>()
|
||||
val groupMembers = mutableStateListOf<GroupMember>()
|
||||
val groupMembersIndexes = mutableStateMapOf<Long, Int>()
|
||||
val groupMembers = MutableStateFlow<List<GroupMember>>(emptyList())
|
||||
val groupMembersIndexes = MutableStateFlow<Map<Long, Int>>(emptyMap())
|
||||
|
||||
// Chat Tags
|
||||
val userTags = mutableStateOf(emptyList<ChatTag>())
|
||||
val activeChatTagFilter = mutableStateOf<ActiveFilter?>(null)
|
||||
val presetTags = mutableStateMapOf<PresetTagKind, Int>()
|
||||
val unreadTags = mutableStateMapOf<Long, Int>()
|
||||
val userTags = MutableStateFlow(emptyList<ChatTag>())
|
||||
val activeChatTagFilter = MutableStateFlow<ActiveFilter?>(null)
|
||||
val presetTags = MutableStateFlow<Map<PresetTagKind, Int>>(emptyMap())
|
||||
val unreadTags = MutableStateFlow<Map<Long, Int>>(emptyMap())
|
||||
|
||||
// false: default placement, true: floating window.
|
||||
// Used for deciding to add terminal items on main thread or not. Floating means appPrefs.terminalAlwaysVisible
|
||||
var terminalsVisible = setOf<Boolean>()
|
||||
val terminalItems = mutableStateOf<List<TerminalItem>>(listOf())
|
||||
val userAddress = mutableStateOf<UserContactLinkRec?>(null)
|
||||
val chatItemTTL = mutableStateOf<ChatItemTTL>(ChatItemTTL.None)
|
||||
val terminalItems = MutableStateFlow<List<TerminalItem>>(emptyList())
|
||||
val userAddress = MutableStateFlow<UserContactLinkRec?>(null)
|
||||
val chatItemTTL = MutableStateFlow<ChatItemTTL>(ChatItemTTL.None)
|
||||
|
||||
// set when app opened from external intent
|
||||
val clearOverlays = mutableStateOf<Boolean>(false)
|
||||
val clearOverlays = MutableStateFlow<Boolean>(false)
|
||||
|
||||
// Only needed during onboarding when user skipped password setup (left as random password)
|
||||
val desktopOnboardingRandomPassword = mutableStateOf(false)
|
||||
val desktopOnboardingRandomPassword = MutableStateFlow(false)
|
||||
|
||||
// set when app is opened via contact or invitation URI (rhId, uri)
|
||||
val appOpenUrl = mutableStateOf<Pair<Long?, String>?>(null)
|
||||
val appOpenUrl = MutableStateFlow<Pair<Long?, String>?>(null)
|
||||
|
||||
// Needed to check for bottom nav bar and to apply or not navigation bar color on Android
|
||||
val newChatSheetVisible = mutableStateOf(false)
|
||||
val newChatSheetVisible = MutableStateFlow(false)
|
||||
|
||||
// Needed to apply black color to left/right cutout area on Android
|
||||
val fullscreenGalleryVisible = mutableStateOf(false)
|
||||
val fullscreenGalleryVisible = MutableStateFlow(false)
|
||||
|
||||
// preferences
|
||||
val notificationPreviewMode by lazy {
|
||||
mutableStateOf(
|
||||
MutableStateFlow(
|
||||
try {
|
||||
NotificationPreviewMode.valueOf(controller.appPrefs.notificationPreviewMode.get()!!)
|
||||
} catch (e: Exception) {
|
||||
@@ -120,41 +120,41 @@ object ChatModel {
|
||||
}
|
||||
)
|
||||
}
|
||||
val showAuthScreen by lazy { mutableStateOf(ChatController.appPrefs.performLA.get()) }
|
||||
val showAdvertiseLAUnavailableAlert = mutableStateOf(false)
|
||||
val showChatPreviews by lazy { mutableStateOf(ChatController.appPrefs.privacyShowChatPreviews.get()) }
|
||||
val showAuthScreen by lazy { MutableStateFlow(ChatController.appPrefs.performLA.get()) }
|
||||
val showAdvertiseLAUnavailableAlert = MutableStateFlow(false)
|
||||
val showChatPreviews by lazy { MutableStateFlow(ChatController.appPrefs.privacyShowChatPreviews.get()) }
|
||||
|
||||
// current WebRTC call
|
||||
val callManager = CallManager(this)
|
||||
val callInvitations = mutableStateMapOf<String, RcvCallInvitation>()
|
||||
val activeCallInvitation = mutableStateOf<RcvCallInvitation?>(null)
|
||||
val activeCall = mutableStateOf<Call?>(null)
|
||||
val activeCallViewIsVisible = mutableStateOf<Boolean>(false)
|
||||
val activeCallViewIsCollapsed = mutableStateOf<Boolean>(false)
|
||||
val callCommand = mutableStateListOf<WCallCommand>()
|
||||
val showCallView = mutableStateOf(false)
|
||||
val switchingCall = mutableStateOf(false)
|
||||
val callInvitations = MutableStateFlow<Map<String, RcvCallInvitation>>(emptyMap())
|
||||
val activeCallInvitation = MutableStateFlow<RcvCallInvitation?>(null)
|
||||
val activeCall = MutableStateFlow<Call?>(null)
|
||||
val activeCallViewIsVisible = MutableStateFlow<Boolean>(false)
|
||||
val activeCallViewIsCollapsed = MutableStateFlow<Boolean>(false)
|
||||
val callCommand = MutableStateFlow<List<WCallCommand>>(emptyList())
|
||||
val showCallView = MutableStateFlow(false)
|
||||
val switchingCall = MutableStateFlow(false)
|
||||
|
||||
// currently showing invitation
|
||||
val showingInvitation = mutableStateOf(null as ShowingInvitation?)
|
||||
val showingInvitation = MutableStateFlow(null as ShowingInvitation?)
|
||||
|
||||
val migrationState: MutableState<MigrationToState?> by lazy { mutableStateOf(MigrationToDeviceState.makeMigrationState()) }
|
||||
val migrationState: MutableStateFlow<MigrationToState?> by lazy { MutableStateFlow(MigrationToDeviceState.makeMigrationState()) }
|
||||
|
||||
var draft = mutableStateOf(null as ComposeState?)
|
||||
var draftChatId = mutableStateOf(null as String?)
|
||||
var draft = MutableStateFlow(null as ComposeState?)
|
||||
var draftChatId = MutableStateFlow(null as String?)
|
||||
|
||||
// working with external intents or internal forwarding of chat items
|
||||
val sharedContent = mutableStateOf(null as SharedContent?)
|
||||
val sharedContent = MutableStateFlow(null as SharedContent?)
|
||||
|
||||
val filesToDelete = mutableSetOf<File>()
|
||||
val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) }
|
||||
val simplexLinkMode by lazy { MutableStateFlow(ChatController.appPrefs.simplexLinkMode.get()) }
|
||||
|
||||
val clipboardHasText = mutableStateOf(false)
|
||||
val networkInfo = mutableStateOf(UserNetworkInfo(networkType = UserNetworkType.OTHER, online = true))
|
||||
val clipboardHasText = MutableStateFlow(false)
|
||||
val networkInfo = MutableStateFlow(UserNetworkInfo(networkType = UserNetworkType.OTHER, online = true))
|
||||
|
||||
val conditions = mutableStateOf(ServerOperatorConditionsDetail.empty)
|
||||
val conditions = MutableStateFlow(ServerOperatorConditionsDetail.empty)
|
||||
|
||||
val updatingProgress = mutableStateOf(null as Float?)
|
||||
val updatingProgress = MutableStateFlow(null as Float?)
|
||||
var updatingRequest: Closeable? = null
|
||||
|
||||
private val updatingChatsMutex: Mutex = Mutex()
|
||||
@@ -164,12 +164,12 @@ object ChatModel {
|
||||
fun desktopNoUserNoRemote(): Boolean = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
|
||||
|
||||
// remote controller
|
||||
val remoteHosts = mutableStateListOf<RemoteHostInfo>()
|
||||
val currentRemoteHost = mutableStateOf<RemoteHostInfo?>(null)
|
||||
val remoteHosts = MutableStateFlow<List<RemoteHostInfo>>(emptyList())
|
||||
val currentRemoteHost = MutableStateFlow<RemoteHostInfo?>(null)
|
||||
val remoteHostId: Long? @Composable get() = remember { currentRemoteHost }.value?.remoteHostId
|
||||
fun remoteHostId(): Long? = currentRemoteHost.value?.remoteHostId
|
||||
val remoteHostPairing = mutableStateOf<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
|
||||
val remoteCtrlSession = mutableStateOf<RemoteCtrlSession?>(null)
|
||||
val remoteHostPairing = MutableStateFlow<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
|
||||
val remoteCtrlSession = MutableStateFlow<RemoteCtrlSession?>(null)
|
||||
|
||||
val processedCriticalError: ProcessedErrors<AgentErrorType.CRITICAL> = ProcessedErrors(60_000)
|
||||
val processedInternalError: ProcessedErrors<AgentErrorType.INTERNAL> = ProcessedErrors(20_000)
|
||||
@@ -180,11 +180,11 @@ object ChatModel {
|
||||
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
|
||||
currentUser.value
|
||||
} else {
|
||||
users.firstOrNull { it.user.userId == userId }?.user
|
||||
users.value.firstOrNull { it.user.userId == userId }?.user
|
||||
}
|
||||
|
||||
private fun getUserIndex(user: User): Int =
|
||||
users.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == user.remoteHostId }
|
||||
users.value.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == user.remoteHostId }
|
||||
|
||||
fun updateUser(user: User) {
|
||||
val i = getUserIndex(user)
|
||||
@@ -230,42 +230,47 @@ object ChatModel {
|
||||
activeChatTagFilter.value = null
|
||||
}
|
||||
|
||||
presetTags.clear()
|
||||
presetTags.putAll(newPresetTags)
|
||||
unreadTags.clear()
|
||||
unreadTags.putAll(newUnreadTags)
|
||||
presetTags.value = newPresetTags
|
||||
unreadTags.value = newUnreadTags
|
||||
}
|
||||
|
||||
fun updateChatFavorite(favorite: Boolean, wasFavorite: Boolean) {
|
||||
val count = presetTags[PresetTagKind.FAVORITES]
|
||||
val pTags = presetTags.value.toMutableMap()
|
||||
val count = pTags[PresetTagKind.FAVORITES]
|
||||
|
||||
if (favorite && !wasFavorite) {
|
||||
presetTags[PresetTagKind.FAVORITES] = (count ?: 0) + 1
|
||||
pTags[PresetTagKind.FAVORITES] = (count ?: 0) + 1
|
||||
presetTags.value = pTags
|
||||
} else if (!favorite && wasFavorite && count != null) {
|
||||
presetTags[PresetTagKind.FAVORITES] = maxOf(0, count - 1)
|
||||
if (activeChatTagFilter.value == ActiveFilter.PresetTag(PresetTagKind.FAVORITES) && (presetTags[PresetTagKind.FAVORITES] ?: 0) == 0) {
|
||||
pTags[PresetTagKind.FAVORITES] = maxOf(0, count - 1)
|
||||
if (activeChatTagFilter.value == ActiveFilter.PresetTag(PresetTagKind.FAVORITES) && (pTags[PresetTagKind.FAVORITES] ?: 0) == 0) {
|
||||
activeChatTagFilter.value = null
|
||||
}
|
||||
presetTags.value = pTags
|
||||
}
|
||||
}
|
||||
|
||||
fun addPresetChatTags(chatInfo: ChatInfo) {
|
||||
val pTags = presetTags.value.toMutableMap()
|
||||
for (tag in PresetTagKind.entries) {
|
||||
if (presetTagMatchesChat(tag, chatInfo)) {
|
||||
presetTags[tag] = (presetTags[tag] ?: 0) + 1
|
||||
pTags[tag] = (pTags[tag] ?: 0) + 1
|
||||
}
|
||||
}
|
||||
presetTags.value = pTags
|
||||
}
|
||||
|
||||
fun removePresetChatTags(chatInfo: ChatInfo) {
|
||||
val pTags = presetTags.value.toMutableMap()
|
||||
for (tag in PresetTagKind.entries) {
|
||||
if (presetTagMatchesChat(tag, chatInfo)) {
|
||||
val count = presetTags[tag]
|
||||
val count = pTags[tag]
|
||||
if (count != null) {
|
||||
presetTags[tag] = maxOf(0, count - 1)
|
||||
pTags[tag] = maxOf(0, count - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
presetTags.value = pTags
|
||||
}
|
||||
|
||||
fun markChatTagRead(chat: Chat) {
|
||||
@@ -281,9 +286,11 @@ object ChatModel {
|
||||
val nowUnread = chat.unreadTag
|
||||
|
||||
if (nowUnread && !wasUnread) {
|
||||
val uTags = unreadTags.value.toMutableMap()
|
||||
tags.forEach { tag ->
|
||||
unreadTags[tag] = (unreadTags[tag] ?: 0) + 1
|
||||
uTags[tag] = (uTags[tag] ?: 0) + 1
|
||||
}
|
||||
unreadTags.value = uTags
|
||||
} else if (!nowUnread && wasUnread) {
|
||||
markChatTagRead_(chat, tags)
|
||||
}
|
||||
@@ -291,26 +298,30 @@ object ChatModel {
|
||||
|
||||
fun moveChatTagUnread(chat: Chat, oldTags: List<Long>?, newTags: List<Long>) {
|
||||
if (chat.unreadTag) {
|
||||
val uTags = unreadTags.value.toMutableMap()
|
||||
oldTags?.forEach { t ->
|
||||
val oldCount = unreadTags[t]
|
||||
val oldCount = uTags[t]
|
||||
if (oldCount != null) {
|
||||
unreadTags[t] = maxOf(0, oldCount - 1)
|
||||
uTags[t] = maxOf(0, oldCount - 1)
|
||||
}
|
||||
}
|
||||
|
||||
newTags.forEach { t ->
|
||||
unreadTags[t] = (unreadTags[t] ?: 0) + 1
|
||||
uTags[t] = (uTags[t] ?: 0) + 1
|
||||
}
|
||||
unreadTags.value = uTags
|
||||
}
|
||||
}
|
||||
|
||||
private fun markChatTagRead_(chat: Chat, tags: List<Long>) {
|
||||
val uTags = unreadTags.value.toMutableMap()
|
||||
for (tag in tags) {
|
||||
val count = unreadTags[tag]
|
||||
val count = uTags[tag]
|
||||
if (count != null) {
|
||||
unreadTags[tag] = maxOf(0, count - 1)
|
||||
uTags[tag] = maxOf(0, count - 1)
|
||||
}
|
||||
}
|
||||
unreadTags.value = uTags
|
||||
}
|
||||
|
||||
// toList() here is to prevent ConcurrentModificationException that is rarely happens but happens
|
||||
@@ -321,16 +332,17 @@ object ChatModel {
|
||||
fun getGroupChat(groupId: Long): Chat? = chats.value.firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId }
|
||||
|
||||
fun populateGroupMembersIndexes() {
|
||||
groupMembersIndexes.clear()
|
||||
groupMembers.forEachIndexed { i, member ->
|
||||
groupMembersIndexes[member.groupMemberId] = i
|
||||
val indexes = mutableMapOf<Long, Int>()
|
||||
groupMembers.value.forEachIndexed { i, member ->
|
||||
indexes[member.groupMemberId] = i
|
||||
}
|
||||
groupMembersIndexes.value = indexes
|
||||
}
|
||||
|
||||
fun getGroupMember(groupMemberId: Long): GroupMember? {
|
||||
val memberIndex = groupMembersIndexes[groupMemberId]
|
||||
val memberIndex = groupMembersIndexes.value[groupMemberId]
|
||||
return if (memberIndex != null) {
|
||||
groupMembers[memberIndex]
|
||||
groupMembers.value[memberIndex]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -694,7 +706,7 @@ object ChatModel {
|
||||
}
|
||||
// update current chat
|
||||
return if (chatId.value == groupInfo.id) {
|
||||
val memberIndex = groupMembersIndexes[member.groupMemberId]
|
||||
val memberIndex = groupMembersIndexes.value[member.groupMemberId]
|
||||
val updated = chatItems.value.map {
|
||||
// Take into account only specific changes, not all. Other member updates are not important and can be skipped
|
||||
if (it.chatDir is CIDirection.GroupRcv && it.chatDir.groupMember.groupMemberId == member.groupMemberId &&
|
||||
@@ -710,11 +722,14 @@ object ChatModel {
|
||||
if (updated != chatItems.value) {
|
||||
chatItems.replaceAll(updated)
|
||||
}
|
||||
val gMembers = groupMembers.value.toMutableList()
|
||||
if (memberIndex != null) {
|
||||
groupMembers[memberIndex] = member
|
||||
gMembers[memberIndex] = member
|
||||
groupMembers.value = gMembers
|
||||
false
|
||||
} else {
|
||||
groupMembers.add(member)
|
||||
gMembers.add(member)
|
||||
groupMembers.value = gMembers
|
||||
groupMembersIndexes[member.groupMemberId] = groupMembers.size - 1
|
||||
true
|
||||
}
|
||||
@@ -741,7 +756,7 @@ object ChatModel {
|
||||
profile = newProfile.toLocalProfile(current.profile.profileId),
|
||||
fullPreferences = preferences ?: current.fullPreferences
|
||||
)
|
||||
val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
|
||||
val i = users.value.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
|
||||
if (i != -1) {
|
||||
users[i] = users[i].copy(user = updated)
|
||||
}
|
||||
@@ -753,7 +768,7 @@ object ChatModel {
|
||||
val updated = current.copy(
|
||||
uiThemes = uiThemes
|
||||
)
|
||||
val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
|
||||
val i = users.value.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId }
|
||||
if (i != -1) {
|
||||
users[i] = users[i].copy(user = updated)
|
||||
}
|
||||
@@ -816,9 +831,11 @@ object ChatModel {
|
||||
}
|
||||
|
||||
private fun changeUnreadCounter(rhId: Long?, user: UserLike, by: Int) {
|
||||
val i = users.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == rhId }
|
||||
val i = users.value.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == rhId }
|
||||
if (i != -1) {
|
||||
users[i] = users[i].copy(unreadCount = users[i].unreadCount + by)
|
||||
val usrs = users.value.toMutableList()
|
||||
usrs[i] = usrs[i].copy(unreadCount = usrs[i].unreadCount + by)
|
||||
users.value = usrs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2488,15 +2505,15 @@ data class ChatItem (
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<Chat>>.add(index: Int, elem: Chat) {
|
||||
fun MutableStateFlow<SnapshotStateList<Chat>>.add(index: Int, elem: Chat) {
|
||||
value = SnapshotStateList<Chat>().apply { addAll(value); add(index, elem) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.addAndNotify(index: Int, elem: ChatItem) {
|
||||
fun MutableStateFlow<SnapshotStateList<ChatItem>>.addAndNotify(index: Int, elem: ChatItem) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(index, elem); chatItemsChangesListener?.added(elem.id to elem.isRcvNew, index) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<Chat>>.add(elem: Chat) {
|
||||
fun MutableStateFlow<SnapshotStateList<Chat>>.add(elem: Chat) {
|
||||
value = SnapshotStateList<Chat>().apply { addAll(value); add(elem) }
|
||||
}
|
||||
|
||||
@@ -2504,24 +2521,24 @@ fun MutableState<SnapshotStateList<Chat>>.add(elem: Chat) {
|
||||
fun <T> MutableList<T>.removeAll(predicate: (T) -> Boolean): Boolean = if (isEmpty()) false else remAll(predicate)
|
||||
|
||||
// Adds item to chatItems and notifies a listener about newly added item
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.addAndNotify(elem: ChatItem) {
|
||||
fun MutableStateFlow<SnapshotStateList<ChatItem>>.addAndNotify(elem: ChatItem) {
|
||||
value = SnapshotStateList<ChatItem>().apply { addAll(value); add(elem); chatItemsChangesListener?.added(elem.id to elem.isRcvNew, lastIndex) }
|
||||
}
|
||||
|
||||
fun <T> MutableState<SnapshotStateList<T>>.addAll(index: Int, elems: List<T>) {
|
||||
fun <T> MutableStateFlow<SnapshotStateList<T>>.addAll(index: Int, elems: List<T>) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); addAll(index, elems) }
|
||||
}
|
||||
|
||||
fun <T> MutableState<SnapshotStateList<T>>.addAll(elems: List<T>) {
|
||||
fun <T> MutableStateFlow<SnapshotStateList<T>>.addAll(elems: List<T>) {
|
||||
value = SnapshotStateList<T>().apply { addAll(value); addAll(elems) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<Chat>>.removeAll(block: (Chat) -> Boolean) {
|
||||
fun MutableStateFlow<SnapshotStateList<Chat>>.removeAll(block: (Chat) -> Boolean) {
|
||||
value = SnapshotStateList<Chat>().apply { addAll(value); removeAll(block) }
|
||||
}
|
||||
|
||||
// Removes item(s) from chatItems and notifies a listener about removed item(s)
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.removeAllAndNotify(block: (ChatItem) -> Boolean) {
|
||||
fun MutableStateFlow<SnapshotStateList<ChatItem>>.removeAllAndNotify(block: (ChatItem) -> Boolean) {
|
||||
val toRemove = ArrayList<Triple<Long, Int, Boolean>>()
|
||||
value = SnapshotStateList<ChatItem>().apply {
|
||||
addAll(value)
|
||||
@@ -2538,7 +2555,7 @@ fun MutableState<SnapshotStateList<ChatItem>>.removeAllAndNotify(block: (ChatIte
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<Chat>>.removeAt(index: Int): Chat {
|
||||
fun MutableStateFlow<SnapshotStateList<Chat>>.removeAt(index: Int): Chat {
|
||||
val new = SnapshotStateList<Chat>()
|
||||
new.addAll(value)
|
||||
val res = new.removeAt(index)
|
||||
@@ -2546,7 +2563,14 @@ fun MutableState<SnapshotStateList<Chat>>.removeAt(index: Int): Chat {
|
||||
return res
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.removeLastAndNotify() {
|
||||
fun <T> MutableStateFlow<List<T>>.removeAt(index: Int): T {
|
||||
val l = value.toMutableList()
|
||||
val removed = l.removeAt(index)
|
||||
value = l
|
||||
return removed
|
||||
}
|
||||
|
||||
fun MutableStateFlow<SnapshotStateList<ChatItem>>.removeLastAndNotify() {
|
||||
val removed: Triple<Long, Int, Boolean>
|
||||
value = SnapshotStateList<ChatItem>().apply {
|
||||
addAll(value)
|
||||
@@ -2557,29 +2581,53 @@ fun MutableState<SnapshotStateList<ChatItem>>.removeLastAndNotify() {
|
||||
chatItemsChangesListener?.removed(listOf(removed), value)
|
||||
}
|
||||
|
||||
fun <T> MutableState<SnapshotStateList<T>>.replaceAll(elems: List<T>) {
|
||||
fun <T> MutableStateFlow<SnapshotStateList<T>>.replaceAll(elems: List<T>) {
|
||||
value = SnapshotStateList<T>().apply { addAll(elems) }
|
||||
}
|
||||
|
||||
fun MutableState<SnapshotStateList<Chat>>.clear() {
|
||||
fun MutableStateFlow<SnapshotStateList<Chat>>.clear() {
|
||||
value = SnapshotStateList()
|
||||
}
|
||||
|
||||
// Removes all chatItems and notifies a listener about it
|
||||
fun MutableState<SnapshotStateList<ChatItem>>.clearAndNotify() {
|
||||
fun MutableStateFlow<SnapshotStateList<ChatItem>>.clearAndNotify() {
|
||||
value = SnapshotStateList()
|
||||
chatItemsChangesListener?.cleared()
|
||||
}
|
||||
|
||||
fun <T> State<SnapshotStateList<T>>.asReversed(): MutableList<T> = value.asReversed()
|
||||
fun <T> StateFlow<SnapshotStateList<T>>.asReversed(): MutableList<T> = value.asReversed()
|
||||
|
||||
fun <T> State<SnapshotStateList<T>>.toList(): List<T> = value.toList()
|
||||
fun <T> StateFlow<SnapshotStateList<T>>.toList(): List<T> = value.toList()
|
||||
|
||||
operator fun <T> State<SnapshotStateList<T>>.get(i: Int): T = value[i]
|
||||
operator fun <K, V> StateFlow<Map<K, V>>.get(key: K): V? = value[key]
|
||||
|
||||
operator fun <T> State<SnapshotStateList<T>>.set(index: Int, elem: T) { value[index] = elem }
|
||||
operator fun <T> StateFlow<List<T>>.get(i: Int): T = value[i]
|
||||
|
||||
operator fun <T> StateFlow<SnapshotStateList<T>>.set(index: Int, elem: T) { value[index] = elem }
|
||||
|
||||
operator fun <T> MutableStateFlow<List<T>>.set(index: Int, elem: T) {
|
||||
val l = value.toMutableList()
|
||||
l[index] = elem
|
||||
value = l
|
||||
}
|
||||
|
||||
fun StateFlow<List<Any>>.isEmpty(): Boolean = value.isEmpty()
|
||||
|
||||
operator fun <K, V> MutableStateFlow<Map<K, V>>.set(key: K, elem: V) {
|
||||
val m = value.toMutableMap()
|
||||
m[key] = elem
|
||||
value = m
|
||||
}
|
||||
|
||||
fun <K, V> MutableStateFlow<Map<K, V>>.remove(key: K): V? {
|
||||
val m = value.toMutableMap()
|
||||
val removed = m.remove(key)
|
||||
value = m
|
||||
return removed
|
||||
}
|
||||
|
||||
val State<List<Any>>.size: Int get() = value.size
|
||||
val StateFlow<List<Any>>.size: Int get() = value.size
|
||||
|
||||
enum class CIMergeCategory {
|
||||
MemberConnected,
|
||||
|
||||
+26
-27
@@ -524,8 +524,7 @@ object ChatController {
|
||||
apiSetNetworkConfig(getNetCfg())
|
||||
val chatRunning = apiCheckChatRunning()
|
||||
val users = listUsers(null)
|
||||
chatModel.users.clear()
|
||||
chatModel.users.addAll(users)
|
||||
chatModel.users.value = users
|
||||
if (!chatRunning) {
|
||||
chatModel.currentUser.value = user
|
||||
chatModel.localUserCreated.value = true
|
||||
@@ -557,7 +556,7 @@ object ChatController {
|
||||
Log.d(TAG, "user: null")
|
||||
try {
|
||||
if (chatModel.chatRunning.value == true) return
|
||||
chatModel.users.clear()
|
||||
chatModel.users.value = emptyList()
|
||||
chatModel.currentUser.value = null
|
||||
chatModel.localUserCreated.value = false
|
||||
appPrefs.chatLastStart.set(Clock.System.now())
|
||||
@@ -609,10 +608,9 @@ object ChatController {
|
||||
ntfManager.cancelNotificationsForUser(prevActiveUser.userId)
|
||||
}
|
||||
val users = listUsers(rhId)
|
||||
chatModel.users.clear()
|
||||
chatModel.users.addAll(users)
|
||||
chatModel.users.value = users
|
||||
getUserChatData(rhId)
|
||||
val invitation = chatModel.callInvitations.values.firstOrNull { inv -> inv.user.userId == toUserId }
|
||||
val invitation = chatModel.callInvitations.value.values.firstOrNull { inv -> inv.user.userId == toUserId }
|
||||
if (invitation != null && currentUser != null) {
|
||||
chatModel.callManager.reportNewIncomingCall(invitation.copy(user = currentUser))
|
||||
}
|
||||
@@ -2086,8 +2084,7 @@ object ChatController {
|
||||
|
||||
suspend fun reloadRemoteHosts() {
|
||||
val hosts = listRemoteHosts() ?: return
|
||||
chatModel.remoteHosts.clear()
|
||||
chatModel.remoteHosts.addAll(hosts)
|
||||
chatModel.remoteHosts.value = hosts
|
||||
}
|
||||
|
||||
suspend fun startRemoteHost(rhId: Long?, multicast: Boolean = true, address: RemoteCtrlAddress?, port: Int?): CR.RemoteHostStarted? {
|
||||
@@ -2446,14 +2443,18 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
is CR.NetworkStatusResp -> {
|
||||
val nStatuses = chatModel.networkStatuses.value.toMutableMap()
|
||||
for (cId in r.connections) {
|
||||
chatModel.networkStatuses[cId] = r.networkStatus
|
||||
nStatuses[cId] = r.networkStatus
|
||||
}
|
||||
chatModel.networkStatuses.value = nStatuses
|
||||
}
|
||||
is CR.NetworkStatuses -> {
|
||||
val nStatuses = chatModel.networkStatuses.value.toMutableMap()
|
||||
for (s in r.networkStatuses) {
|
||||
chatModel.networkStatuses[s.agentConnId] = s.networkStatus
|
||||
nStatuses[s.agentConnId] = s.networkStatus
|
||||
}
|
||||
chatModel.networkStatuses.value = nStatuses
|
||||
}
|
||||
is CR.NewChatItems -> withBGApi {
|
||||
r.chatItems.forEach { chatItem ->
|
||||
@@ -2727,31 +2728,33 @@ object ChatController {
|
||||
val useRelay = appPrefs.webrtcPolicyRelay.get()
|
||||
val iceServers = getIceServers()
|
||||
Log.d(TAG, ".callOffer iceServers $iceServers")
|
||||
chatModel.callCommand.add(WCallCommand.Offer(
|
||||
chatModel.callCommand.value += WCallCommand.Offer(
|
||||
offer = r.offer.rtcSession,
|
||||
iceCandidates = r.offer.rtcIceCandidates,
|
||||
media = r.callType.media,
|
||||
aesKey = r.sharedKey,
|
||||
iceServers = iceServers,
|
||||
relay = useRelay
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
is CR.CallAnswer -> {
|
||||
withCall(r, r.contact) { call ->
|
||||
chatModel.activeCall.value = call.copy(callState = CallState.AnswerReceived)
|
||||
chatModel.callCommand.add(WCallCommand.Answer(answer = r.answer.rtcSession, iceCandidates = r.answer.rtcIceCandidates))
|
||||
chatModel.callCommand.value += WCallCommand.Answer(answer = r.answer.rtcSession, iceCandidates = r.answer.rtcIceCandidates)
|
||||
}
|
||||
}
|
||||
is CR.CallExtraInfo -> {
|
||||
withCall(r, r.contact) { _ ->
|
||||
chatModel.callCommand.add(WCallCommand.Ice(iceCandidates = r.extraInfo.rtcIceCandidates))
|
||||
chatModel.callCommand.value += WCallCommand.Ice(iceCandidates = r.extraInfo.rtcIceCandidates)
|
||||
}
|
||||
}
|
||||
is CR.CallEnded -> {
|
||||
val invitation = chatModel.callInvitations.remove(r.contact.id)
|
||||
val invits = chatModel.callInvitations.value.toMutableMap()
|
||||
val invitation = invits.remove(r.contact.id)
|
||||
if (invitation != null) {
|
||||
chatModel.callManager.reportCallRemoteEnded(invitation = invitation)
|
||||
chatModel.callInvitations.value = invits
|
||||
}
|
||||
withCall(r, r.contact) { call ->
|
||||
withBGApi { chatModel.callManager.endCall(call) }
|
||||
@@ -2797,7 +2800,7 @@ object ChatController {
|
||||
}
|
||||
}
|
||||
is CR.RemoteHostStopped -> {
|
||||
val disconnectedHost = chatModel.remoteHosts.firstOrNull { it.remoteHostId == r.remoteHostId_ }
|
||||
val disconnectedHost = chatModel.remoteHosts.value.firstOrNull { it.remoteHostId == r.remoteHostId_ }
|
||||
chatModel.remoteHostPairing.value = null
|
||||
if (disconnectedHost != null) {
|
||||
val deviceName = disconnectedHost.hostDeviceName.ifEmpty { disconnectedHost.remoteHostId.toString() }
|
||||
@@ -2955,14 +2958,11 @@ object ChatController {
|
||||
val m = chatModel
|
||||
m.remoteCtrlSession.value = null
|
||||
val users = listUsers(null)
|
||||
m.users.clear()
|
||||
m.users.addAll(users)
|
||||
m.users.value = users
|
||||
getUserChatData(null)
|
||||
val statuses = apiGetNetworkStatuses(null)
|
||||
if (statuses != null) {
|
||||
chatModel.networkStatuses.clear()
|
||||
val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
|
||||
chatModel.networkStatuses.putAll(ss)
|
||||
chatModel.networkStatuses.value = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3009,9 +3009,11 @@ object ChatController {
|
||||
}
|
||||
|
||||
private fun updateContactsStatus(contactRefs: List<ContactRef>, status: NetworkStatus) {
|
||||
val nStatuses = chatModel.networkStatuses.value.toMutableMap()
|
||||
for (c in contactRefs) {
|
||||
chatModel.networkStatuses[c.agentConnId] = status
|
||||
nStatuses[c.agentConnId] = status
|
||||
}
|
||||
chatModel.networkStatuses.value = nStatuses
|
||||
}
|
||||
|
||||
private fun processContactSubError(contact: Contact, chatError: ChatError) {
|
||||
@@ -3040,8 +3042,7 @@ object ChatController {
|
||||
reloadRemoteHosts()
|
||||
val user = apiGetActiveUser(rhId)
|
||||
val users = listUsers(rhId)
|
||||
chatModel.users.clear()
|
||||
chatModel.users.addAll(users)
|
||||
chatModel.users.value = users
|
||||
chatModel.currentUser.value = user
|
||||
if (user == null) {
|
||||
chatModel.chatItems.clearAndNotify()
|
||||
@@ -3052,9 +3053,7 @@ object ChatController {
|
||||
}
|
||||
val statuses = apiGetNetworkStatuses(rhId)
|
||||
if (statuses != null) {
|
||||
chatModel.networkStatuses.clear()
|
||||
val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
|
||||
chatModel.networkStatuses.putAll(ss)
|
||||
chatModel.networkStatuses.value = statuses.associate { it.agentConnId to it.networkStatus }.toMap()
|
||||
}
|
||||
getUserChatData(rhId)
|
||||
}
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
if (user == null) {
|
||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
chatModel.currentUser.value = null
|
||||
chatModel.users.clear()
|
||||
chatModel.users.value = emptyList()
|
||||
if (appPlatform.isDesktop) {
|
||||
/**
|
||||
* Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start
|
||||
|
||||
+3
-4
@@ -108,7 +108,7 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||
ModalView({
|
||||
if (chatModel.users.none { !it.user.hidden }) {
|
||||
if (chatModel.users.value.none { !it.user.hidden }) {
|
||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
} else {
|
||||
close()
|
||||
@@ -185,8 +185,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: ()
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode)
|
||||
} else {
|
||||
val users = chatModel.controller.listUsers(rhId)
|
||||
chatModel.users.clear()
|
||||
chatModel.users.addAll(users)
|
||||
chatModel.users.value = users
|
||||
chatModel.controller.getUserChatData(rhId)
|
||||
close()
|
||||
}
|
||||
@@ -201,7 +200,7 @@ fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: ()
|
||||
chatModel.localUserCreated.value = true
|
||||
val onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
||||
// No users or no visible users
|
||||
if (chatModel.users.none { u -> !u.user.hidden }) {
|
||||
if (chatModel.users.value.none { u -> !u.user.hidden }) {
|
||||
onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) {
|
||||
OnboardingStage.Step2_5_SetupDatabasePassphrase
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -58,12 +58,12 @@ class CallManager(val chatModel: ChatModel) {
|
||||
val useRelay = controller.appPrefs.webrtcPolicyRelay.get()
|
||||
val iceServers = getIceServers()
|
||||
Log.d(TAG, "answerIncomingCall iceServers: $iceServers")
|
||||
callCommand.add(WCallCommand.Start(
|
||||
callCommand.value += WCallCommand.Start(
|
||||
media = invitation.callType.media,
|
||||
aesKey = invitation.sharedKey,
|
||||
iceServers = iceServers,
|
||||
relay = useRelay
|
||||
))
|
||||
)
|
||||
callInvitations.remove(invitation.contact.id)
|
||||
if (invitation.contact.id == activeCallInvitation.value?.contact?.id) {
|
||||
activeCallInvitation.value = null
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ fun ChatInfoView(
|
||||
val connStats = remember(contact.id, connectionStats) { mutableStateOf(connectionStats) }
|
||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||
if (chat != null && currentUser != null) {
|
||||
val contactNetworkStatus = remember(chatModel.networkStatuses.toMap(), contact) {
|
||||
val contactNetworkStatus = remember(chatModel.networkStatuses.value, contact) {
|
||||
mutableStateOf(chatModel.contactNetworkStatus(contact))
|
||||
}
|
||||
val chatRh = chat.remoteHostId
|
||||
|
||||
+11
-9
@@ -57,7 +57,7 @@ data class ItemSeparation(val timestamp: Boolean, val largeGap: Boolean, val dat
|
||||
@Composable
|
||||
// staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat
|
||||
// to chat list smooth. Otherwise, chat view will become blank right before the transition starts
|
||||
fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -> Unit) {
|
||||
fun ChatView(staleChatId: StateFlow<String?>, onComposed: suspend (chatId: String) -> Unit) {
|
||||
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
|
||||
val showSearch = rememberSaveable { mutableStateOf(false) }
|
||||
val activeChatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo } }
|
||||
@@ -220,8 +220,8 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
hideKeyboard(view)
|
||||
AudioPlayer.stop()
|
||||
chatModel.chatId.value = null
|
||||
chatModel.groupMembers.clear()
|
||||
chatModel.groupMembersIndexes.clear()
|
||||
chatModel.groupMembers.value = emptyList()
|
||||
chatModel.groupMembersIndexes.value = emptyMap()
|
||||
},
|
||||
info = {
|
||||
if (ModalManager.end.hasModalsOpen()) {
|
||||
@@ -246,7 +246,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
if (chatInfo is ChatInfo.Direct) {
|
||||
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
|
||||
var code: String? by remember { mutableStateOf(preloadedCode) }
|
||||
KeyChangeEffect(chatInfo.id, ChatModel.networkStatuses.toMap()) {
|
||||
KeyChangeEffect(chatInfo.id, ChatModel.networkStatuses.value) {
|
||||
contactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId)
|
||||
preloadedContactInfo = contactInfo
|
||||
code = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second
|
||||
@@ -359,11 +359,13 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
acceptCall = { contact ->
|
||||
hideKeyboard(view)
|
||||
withBGApi {
|
||||
val invitation = chatModel.callInvitations.remove(contact.id)
|
||||
val invts = chatModel.callInvitations.value.toMutableMap()
|
||||
val invitation = invts.remove(contact.id)
|
||||
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
|
||||
if (invitation == null) {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
|
||||
} else {
|
||||
chatModel.callInvitations.value = invts
|
||||
chatModel.callManager.acceptIncomingCall(invitation = invitation)
|
||||
}
|
||||
}
|
||||
@@ -431,7 +433,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
chatModel.getChat(chatId)
|
||||
},
|
||||
findModelMember = { memberId ->
|
||||
chatModel.groupMembers.find { it.id == memberId }
|
||||
chatModel.groupMembers.value.find { it.id == memberId }
|
||||
},
|
||||
setReaction = { cInfo, cItem, add, reaction ->
|
||||
withBGApi {
|
||||
@@ -578,7 +580,7 @@ fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType)
|
||||
activeCall.value?.androidCallState?.close()
|
||||
chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callUUID = null, callState = CallState.WaitCapabilities, initialCallType = media, userProfile = profile, androidCallState = platform.androidCreateActiveCallState())
|
||||
chatModel.showCallView.value = true
|
||||
chatModel.callCommand.add(WCallCommand.Capabilities(media))
|
||||
chatModel.callCommand.value += WCallCommand.Capabilities(media)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -739,7 +741,7 @@ fun BoxScope.ChatInfoToolbar(
|
||||
}
|
||||
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
||||
val menuItems = arrayListOf<@Composable () -> Unit>()
|
||||
val activeCall by remember { chatModel.activeCall }
|
||||
val activeCall by chatModel.activeCall.collectAsState()
|
||||
if (chatInfo is ChatInfo.Local) {
|
||||
barButtons.add {
|
||||
IconButton(
|
||||
@@ -1359,7 +1361,7 @@ private fun LoadLastItems(loadingMoreItems: MutableState<Boolean>, remoteHostId:
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SmallScrollOnNewMessage(listState: State<LazyListState>, chatItems: State<List<ChatItem>>) {
|
||||
private fun SmallScrollOnNewMessage(listState: State<LazyListState>, chatItems: StateFlow<List<ChatItem>>) {
|
||||
val scrollDistance = with(LocalDensity.current) { -39.dp.toPx() }
|
||||
LaunchedEffect(Unit) {
|
||||
var lastTotalItems = listState.value.layoutInfo.totalItemsCount
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
|
||||
|
||||
fun getContactsToAdd(chatModel: ChatModel, search: String): List<Contact> {
|
||||
val s = search.trim().lowercase()
|
||||
val memberContactIds = chatModel.groupMembers
|
||||
val memberContactIds = chatModel.groupMembers.value
|
||||
.filter { it.memberCurrent }
|
||||
.mapNotNull { it.memberContactId }
|
||||
return chatModel.chats.value
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin
|
||||
updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel)
|
||||
sendReceipts.value = sendRcpts
|
||||
},
|
||||
members = chatModel.groupMembers
|
||||
members = chatModel.groupMembers.value
|
||||
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
|
||||
.sortedByDescending { it.memberRole },
|
||||
developerTools,
|
||||
|
||||
+3
-4
@@ -230,7 +230,7 @@ suspend fun apiFindMessages(ch: Chat, search: String) {
|
||||
|
||||
suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) {
|
||||
val groupMembers = chatModel.controller.apiListMembers(rhId, groupInfo.groupId)
|
||||
val currentMembers = chatModel.groupMembers
|
||||
val currentMembers = chatModel.groupMembers.value
|
||||
val newMembers = groupMembers.map { newMember ->
|
||||
val currentMember = currentMembers.find { it.id == newMember.id }
|
||||
val currentMemberStats = currentMember?.activeConn?.connectionStats
|
||||
@@ -241,9 +241,8 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo
|
||||
newMember
|
||||
}
|
||||
}
|
||||
chatModel.groupMembers.clear()
|
||||
chatModel.groupMembersIndexes.clear()
|
||||
chatModel.groupMembers.addAll(newMembers)
|
||||
chatModel.groupMembersIndexes.value = emptyMap()
|
||||
chatModel.groupMembers.value = newMembers
|
||||
chatModel.populateGroupMembersIndexes()
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -448,7 +448,7 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||
val users by remember { derivedStateOf { chatModel.users.value.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||
val allRead = users
|
||||
.filter { u -> !u.user.activeUser && !u.user.hidden }
|
||||
.all { u -> u.unreadCount == 0 }
|
||||
@@ -544,7 +544,7 @@ fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> U
|
||||
}
|
||||
}
|
||||
if (appPlatform.isDesktop) {
|
||||
val h by remember { chatModel.currentRemoteHost }
|
||||
val h by chatModel.currentRemoteHost.collectAsState()
|
||||
if (h != null) {
|
||||
Spacer(Modifier.width(12.dp))
|
||||
HostDisconnectButton {
|
||||
@@ -947,8 +947,8 @@ private fun TagsView() {
|
||||
val rowSizeModifier = Modifier.sizeIn(minHeight = TAG_MIN_HEIGHT * fontSizeSqrtMultiplier)
|
||||
|
||||
TagsRow {
|
||||
if (presetTags.size > 1) {
|
||||
if (presetTags.size + userTags.value.size <= 3) {
|
||||
if (presetTags.value.size > 1) {
|
||||
if (presetTags.value.size + userTags.value.size <= 3) {
|
||||
PresetTagKind.entries.filter { t -> (presetTags[t] ?: 0) > 0 }.forEach { tag ->
|
||||
ExpandedTagFilterView(tag)
|
||||
}
|
||||
|
||||
+1
-1
@@ -709,7 +709,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (chatModel.users.count { u -> u.user.activeUser || !u.user.hidden } == 1
|
||||
if (chatModel.users.value.count { u -> u.user.activeUser || !u.user.hidden } == 1
|
||||
) {
|
||||
selectedUserCategory.value = PresentedUserCategory.CURRENT_USER
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -94,11 +94,11 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal
|
||||
if (showSearch) {
|
||||
BackHandler(onBack = hideSearchOnBack)
|
||||
}
|
||||
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||
val users by remember { derivedStateOf { chatModel.users.value.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||
val navButton: @Composable RowScope.() -> Unit = {
|
||||
when {
|
||||
showSearch -> NavigationButtonBack(hideSearchOnBack)
|
||||
(users.size > 1 || chatModel.remoteHosts.isNotEmpty()) && remember { chatModel.sharedContent }.value !is SharedContent.Forward -> {
|
||||
(users.size > 1 || chatModel.remoteHosts.value.isNotEmpty()) && remember { chatModel.sharedContent }.value !is SharedContent.Forward -> {
|
||||
val allRead = users
|
||||
.filter { u -> !u.user.activeUser && !u.user.hidden }
|
||||
.all { u -> u.unreadCount == 0 }
|
||||
|
||||
+4
-6
@@ -55,14 +55,14 @@ fun UserPicker(
|
||||
}
|
||||
val users by remember {
|
||||
derivedStateOf {
|
||||
chatModel.users
|
||||
chatModel.users.value
|
||||
.filter { u -> u.user.activeUser || !u.user.hidden }
|
||||
.sortedByDescending { it.user.activeOrder }
|
||||
}
|
||||
}
|
||||
val remoteHosts by remember {
|
||||
derivedStateOf {
|
||||
chatModel.remoteHosts
|
||||
chatModel.remoteHosts.value
|
||||
.sortedBy { it.hostDeviceName }
|
||||
}
|
||||
}
|
||||
@@ -111,8 +111,7 @@ fun UserPicker(
|
||||
}
|
||||
}
|
||||
if (!same) {
|
||||
chatModel.users.clear()
|
||||
chatModel.users.addAll(updatedUsers)
|
||||
chatModel.users.value = updatedUsers
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error updating users ${e.stackTraceToString()}")
|
||||
@@ -121,8 +120,7 @@ fun UserPicker(
|
||||
try {
|
||||
val updatedHosts = chatModel.controller.listRemoteHosts()?.sortedBy { it.hostDeviceName } ?: emptyList()
|
||||
if (remoteHosts != updatedHosts) {
|
||||
chatModel.remoteHosts.clear()
|
||||
chatModel.remoteHosts.addAll(updatedHosts)
|
||||
chatModel.remoteHosts.value = updatedHosts
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error updating remote hosts ${e.stackTraceToString()}")
|
||||
|
||||
+5
-3
@@ -26,6 +26,8 @@ import dev.icerock.moko.resources.StringResource
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.datetime.Clock
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
@@ -34,7 +36,7 @@ import kotlin.io.path.Path
|
||||
|
||||
@Composable
|
||||
fun DatabaseErrorView(
|
||||
chatDbStatus: State<DBMigrationResult?>,
|
||||
chatDbStatus: StateFlow<DBMigrationResult?>,
|
||||
appPreferences: AppPreferences,
|
||||
) {
|
||||
val progressIndicator = remember { mutableStateOf(false) }
|
||||
@@ -200,7 +202,7 @@ fun DatabaseErrorView(
|
||||
private fun runChat(
|
||||
dbKey: String? = null,
|
||||
confirmMigrations: MigrationConfirmation? = null,
|
||||
chatDbStatus: State<DBMigrationResult?>,
|
||||
chatDbStatus: StateFlow<DBMigrationResult?>,
|
||||
progressIndicator: MutableState<Boolean>,
|
||||
) = CoroutineScope(Dispatchers.Default).launch {
|
||||
// Don't do things concurrently. Shouldn't be here concurrently, just in case
|
||||
@@ -337,7 +339,7 @@ private fun ColumnScope.RestoreDbButton(onClick: () -> Unit) {
|
||||
fun PreviewChatInfoLayout() {
|
||||
SimpleXTheme {
|
||||
DatabaseErrorView(
|
||||
remember { mutableStateOf(DBMigrationResult.ErrorNotADatabase("simplex_v1_chat.db")) },
|
||||
remember { MutableStateFlow(DBMigrationResult.ErrorNotADatabase("simplex_v1_chat.db")) },
|
||||
AppPreferences()
|
||||
)
|
||||
}
|
||||
|
||||
+6
-5
@@ -26,6 +26,7 @@ import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.datetime.*
|
||||
import java.io.*
|
||||
import java.net.URI
|
||||
@@ -82,7 +83,7 @@ fun DatabaseView() {
|
||||
appFilesCountAndSize,
|
||||
chatItemTTL,
|
||||
user,
|
||||
m.users,
|
||||
m.users.value,
|
||||
startChat = { startChat(m, chatLastStart, m.chatDbChanged, progressIndicator) },
|
||||
stopChatAlert = { stopChatAlert(m, progressIndicator) },
|
||||
exportArchive = {
|
||||
@@ -116,7 +117,7 @@ fun DatabaseView() {
|
||||
}
|
||||
},
|
||||
disconnectAllHosts = {
|
||||
val connected = chatModel.remoteHosts.filter { it.sessionState is RemoteHostSessionState.Connected }
|
||||
val connected = chatModel.remoteHosts.value.filter { it.sessionState is RemoteHostSessionState.Connected }
|
||||
connected.forEachIndexed { index, h ->
|
||||
controller.stopRemoteHostAndReloadHosts(h, index == connected.lastIndex && chatModel.connectedToRemote())
|
||||
}
|
||||
@@ -182,7 +183,7 @@ fun DatabaseLayout(
|
||||
)
|
||||
SectionDividerSpaced(maxTopPadding = true)
|
||||
}
|
||||
val toggleEnabled = remember { chatModel.remoteHosts }.none { it.sessionState is RemoteHostSessionState.Connected }
|
||||
val toggleEnabled = chatModel.remoteHosts.collectAsState().value.none { it.sessionState is RemoteHostSessionState.Connected }
|
||||
if (chatModel.localUserCreated.value == true) {
|
||||
// still show the toggle in case database was stopped when the user opened this screen because it can be in the following situations:
|
||||
// - database was stopped after migration and the app relaunched
|
||||
@@ -355,7 +356,7 @@ fun RunChatSetting(
|
||||
fun startChat(
|
||||
m: ChatModel,
|
||||
chatLastStart: MutableState<Instant?>,
|
||||
chatDbChanged: MutableState<Boolean>,
|
||||
chatDbChanged: MutableStateFlow<Boolean>,
|
||||
progressIndicator: MutableState<Boolean>? = null
|
||||
) {
|
||||
withLongRunningApi {
|
||||
@@ -535,7 +536,7 @@ fun deleteChatDatabaseFilesAndState() {
|
||||
popChatCollector.clear()
|
||||
}
|
||||
}
|
||||
chatModel.users.clear()
|
||||
chatModel.users.value = emptyList()
|
||||
ntfManager.cancelAllNotifications()
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -277,7 +277,7 @@ class AlertManager {
|
||||
}
|
||||
}
|
||||
|
||||
private fun alertTitle(title: String): (@Composable () -> Unit)? {
|
||||
private fun alertTitle(title: String): (@Composable () -> Unit) {
|
||||
return {
|
||||
Text(
|
||||
title,
|
||||
@@ -368,12 +368,12 @@ private fun AlertContent(text: AnnotatedString?, hostDevice: Pair<Long?, String>
|
||||
}
|
||||
}
|
||||
|
||||
fun hostDevice(rhId: Long?): Pair<Long?, String>? = if (rhId == null && chatModel.remoteHosts.isNotEmpty()) {
|
||||
fun hostDevice(rhId: Long?): Pair<Long?, String>? = if (rhId == null && chatModel.remoteHosts.value.isNotEmpty()) {
|
||||
null to ChatModel.controller.appPrefs.deviceNameForRemoteAccess.get()!!
|
||||
} else if (rhId == null) {
|
||||
null
|
||||
} else {
|
||||
rhId to (chatModel.remoteHosts.firstOrNull { it.remoteHostId == rhId }?.hostDeviceName?.ifEmpty { rhId.toString() } ?: rhId.toString())
|
||||
rhId to (chatModel.remoteHosts.value.firstOrNull { it.remoteHostId == rhId }?.hostDeviceName?.ifEmpty { rhId.toString() } ?: rhId.toString())
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.serialization.*
|
||||
import java.io.File
|
||||
|
||||
+19
-18
@@ -31,6 +31,7 @@ import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.toJavaInstant
|
||||
import kotlinx.serialization.*
|
||||
@@ -112,7 +113,7 @@ sealed class MigrationToState {
|
||||
@Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
|
||||
}
|
||||
|
||||
private var MutableState<MigrationToState?>.state: MigrationToState?
|
||||
private var MutableStateFlow<MigrationToState?>.state: MigrationToState?
|
||||
get() = value
|
||||
set(v) { value = v }
|
||||
|
||||
@@ -159,7 +160,7 @@ fun ModalData.MigrateToDeviceView(close: () -> Unit) {
|
||||
|
||||
@Composable
|
||||
private fun ModalData.MigrateToDeviceLayout(
|
||||
migrationState: MutableState<MigrationToState?>,
|
||||
migrationState: MutableStateFlow<MigrationToState?>,
|
||||
chatReceiver: MutableState<MigrationToChatReceiver?>,
|
||||
close: () -> Unit,
|
||||
) {
|
||||
@@ -174,7 +175,7 @@ private fun ModalData.MigrateToDeviceLayout(
|
||||
|
||||
@Composable
|
||||
private fun ModalData.SectionByState(
|
||||
migrationState: MutableState<MigrationToState?>,
|
||||
migrationState: MutableStateFlow<MigrationToState?>,
|
||||
tempDatabaseFile: File,
|
||||
chatReceiver: MutableState<MigrationToChatReceiver?>,
|
||||
close: () -> Unit
|
||||
@@ -196,7 +197,7 @@ private fun ModalData.SectionByState(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.PasteOrScanLinkView(close: () -> Unit) {
|
||||
private fun MutableStateFlow<MigrationToState?>.PasteOrScanLinkView(close: () -> Unit) {
|
||||
Box {
|
||||
val progressIndicator = remember { mutableStateOf(false) }
|
||||
Column {
|
||||
@@ -224,7 +225,7 @@ private fun MutableState<MigrationToState?>.PasteOrScanLinkView(close: () -> Uni
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.PasteLinkView() {
|
||||
private fun MutableStateFlow<MigrationToState?>.PasteLinkView() {
|
||||
val clipboard = LocalClipboardManager.current
|
||||
SectionItemView({
|
||||
val str = clipboard.getText()?.text ?: return@SectionItemView
|
||||
@@ -260,7 +261,7 @@ private fun ArchiveImportView(progressIndicator: MutableState<Boolean>, close: (
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState<MigrationToState?>) {
|
||||
private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableStateFlow<MigrationToState?>) {
|
||||
val onionHosts = remember { stateGetOrPut("onionHosts") {
|
||||
getNetCfg().copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts
|
||||
} }
|
||||
@@ -323,7 +324,7 @@ private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, lin
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
Box {
|
||||
SectionView(stringResource(MR.strings.migrate_to_device_database_init).uppercase()) {}
|
||||
ProgressView()
|
||||
@@ -334,7 +335,7 @@ private fun MutableState<MigrationToState?>.DatabaseInitView(link: String, tempD
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.LinkDownloadingView(
|
||||
private fun MutableStateFlow<MigrationToState?>.LinkDownloadingView(
|
||||
link: String,
|
||||
ctrl: ChatCtrl,
|
||||
user: User,
|
||||
@@ -364,7 +365,7 @@ private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
SectionView(stringResource(MR.strings.migrate_to_device_download_failed).uppercase()) {
|
||||
SettingsActionItemWithContent(
|
||||
icon = painterResource(MR.images.ic_download),
|
||||
@@ -384,7 +385,7 @@ private fun MutableState<MigrationToState?>.DownloadFailedView(link: String, cha
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
Box {
|
||||
SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {}
|
||||
ProgressView()
|
||||
@@ -395,7 +396,7 @@ private fun MutableState<MigrationToState?>.ArchiveImportView(archivePath: Strin
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) {
|
||||
SettingsActionItemWithContent(
|
||||
icon = painterResource(MR.images.ic_download),
|
||||
@@ -410,7 +411,7 @@ private fun MutableState<MigrationToState?>.ArchiveImportFailedView(archivePath:
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.PassphraseEnteringView(currentKey: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.PassphraseEnteringView(currentKey: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
val currentKey = rememberSaveable { mutableStateOf(currentKey) }
|
||||
val verifyingPassphrase = rememberSaveable { mutableStateOf(false) }
|
||||
val useKeychain = rememberSaveable { mutableStateOf(appPreferences.storeDBPassphrase.get()) }
|
||||
@@ -459,7 +460,7 @@ private fun MutableState<MigrationToState?>.PassphraseEnteringView(currentKey: S
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MutableState<MigrationToState?>.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
data class Tuple4<A,B,C,D>(val a: A, val b: B, val c: C, val d: D)
|
||||
val (header: String, button: String?, footer: String, confirmation: MigrationConfirmation?) = when (status) {
|
||||
is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) {
|
||||
@@ -518,7 +519,7 @@ private fun ProgressView() {
|
||||
DefaultProgressView(null)
|
||||
}
|
||||
|
||||
private suspend fun MutableState<MigrationToState?>.checkUserLink(link: String): Boolean {
|
||||
private suspend fun MutableStateFlow<MigrationToState?>.checkUserLink(link: String): Boolean {
|
||||
return if (strHasSimplexFileLink(link.trim())) {
|
||||
val data = MigrationFileLinkData.readFromLink(link)
|
||||
val hasProxyConfigured = data?.networkConfig?.hasProxyConfigured() ?: false
|
||||
@@ -547,7 +548,7 @@ private suspend fun MutableState<MigrationToState?>.checkUserLink(link: String):
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableState<MigrationToState?>.prepareDatabase(
|
||||
private fun MutableStateFlow<MigrationToState?>.prepareDatabase(
|
||||
link: String,
|
||||
tempDatabaseFile: File,
|
||||
netCfg: NetCfg,
|
||||
@@ -567,7 +568,7 @@ private fun MutableState<MigrationToState?>.prepareDatabase(
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableState<MigrationToState?>.startDownloading(
|
||||
private fun MutableStateFlow<MigrationToState?>.startDownloading(
|
||||
totalBytes: Long,
|
||||
ctrl: ChatCtrl,
|
||||
user: User,
|
||||
@@ -629,7 +630,7 @@ private fun MutableState<MigrationToState?>.startDownloading(
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableState<MigrationToState?>.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
private fun MutableStateFlow<MigrationToState?>.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
|
||||
withLongRunningApi {
|
||||
try {
|
||||
if (ChatController.ctrl == null || ChatController.ctrl == -1L) {
|
||||
@@ -706,7 +707,7 @@ private fun hideView(close: () -> Unit) {
|
||||
close()
|
||||
}
|
||||
|
||||
private suspend fun MutableState<MigrationToState?>.cleanUpOnBack(chatReceiver: MigrationToChatReceiver?) {
|
||||
private suspend fun MutableStateFlow<MigrationToState?>.cleanUpOnBack(chatReceiver: MigrationToChatReceiver?) {
|
||||
val state = state
|
||||
if (state is MigrationToState.ArchiveImportFailed) {
|
||||
// Original database is not exist, nothing is set up correctly for showing to a user yet. Return to clean state
|
||||
|
||||
+2
-2
@@ -279,11 +279,11 @@ fun ActiveProfilePicker(
|
||||
val incognito = remember {
|
||||
chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get()
|
||||
}
|
||||
val selectedProfile by remember { chatModel.currentUser }
|
||||
val selectedProfile by chatModel.currentUser.collectAsState()
|
||||
val searchTextOrPassword = rememberSaveable { search }
|
||||
// Intentionally don't use derivedStateOf in order to NOT change an order after user was selected
|
||||
val filteredProfiles = remember(searchTextOrPassword.value) {
|
||||
filteredProfiles(chatModel.users.map { it.user }.sortedBy { !it.activeUser }, searchTextOrPassword.value)
|
||||
filteredProfiles(chatModel.users.value.map { it.user }.sortedBy { !it.activeUser }, searchTextOrPassword.value)
|
||||
}
|
||||
|
||||
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ private fun LinkAMobileLayout(
|
||||
ModalView({ appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }) {
|
||||
Column(Modifier.fillMaxSize().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) {
|
||||
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
||||
AppBarTitle(stringResource(if (chatModel.remoteHosts.collectAsState().value.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
||||
}
|
||||
Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(
|
||||
@@ -75,7 +75,7 @@ private fun LinkAMobileLayout(
|
||||
Box(Modifier.weight(0.7f)) {
|
||||
AddingMobileDevice(false, staleQrCode, connecting) {
|
||||
// currentRemoteHost will be set instantly but remoteHosts may be delayed
|
||||
if (chatModel.remoteHosts.isEmpty() && chatModel.currentRemoteHost.value == null) {
|
||||
if (chatModel.remoteHosts.value.isEmpty() && chatModel.currentRemoteHost.value == null) {
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
} else {
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete)
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@ private fun NotificationBatteryUsageInfo() {
|
||||
|
||||
fun prepareChatBeforeFinishingOnboarding() {
|
||||
// No visible users but may have hidden. In this case chat should be started anyway because it's stopped on this stage with hidden users
|
||||
if (chatModel.users.any { u -> !u.user.hidden }) return
|
||||
if (chatModel.users.value.any { u -> !u.user.hidden }) return
|
||||
withBGApi {
|
||||
val user = chatModel.controller.apiGetActiveUser(null) ?: return@withBGApi
|
||||
chatModel.currentUser.value = user
|
||||
|
||||
+5
-3
@@ -53,9 +53,9 @@ fun ConnectMobileView() {
|
||||
}
|
||||
ConnectMobileLayout(
|
||||
deviceName = remember { deviceName.state },
|
||||
remoteHosts = remoteHosts,
|
||||
remoteHosts = remoteHosts.value,
|
||||
connecting,
|
||||
connectedHost = remember { chatModel.currentRemoteHost },
|
||||
connectedHost = chatModel.currentRemoteHost.collectAsState(),
|
||||
updateDeviceName = {
|
||||
withBGApi {
|
||||
if (it != "") {
|
||||
@@ -71,7 +71,9 @@ fun ConnectMobileView() {
|
||||
withBGApi {
|
||||
val success = controller.deleteRemoteHost(host.remoteHostId)
|
||||
if (success) {
|
||||
chatModel.remoteHosts.removeAll { it.remoteHostId == host.remoteHostId }
|
||||
val rHosts = chatModel.remoteHosts.value.toMutableList()
|
||||
rHosts.removeAll { it.remoteHostId == host.remoteHostId }
|
||||
chatModel.remoteHosts.value = rHosts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -890,7 +890,7 @@ object AppearanceScope {
|
||||
// Otherwise, it would be needed to make global variable and to use it everywhere for making a decision to include these overrides into active theme constructing or not
|
||||
chatModel.currentUser.value = chatModel.currentUser.value?.copy(uiThemes = null)
|
||||
} else {
|
||||
chatModel.updateCurrentUserUiThemes(chatModel.remoteHostId(), chatModel.users.firstOrNull { it.user.userId == chatModel.currentUser.value?.userId }?.user?.uiThemes)
|
||||
chatModel.updateCurrentUserUiThemes(chatModel.remoteHostId(), chatModel.users.value.firstOrNull { it.user.userId == chatModel.currentUser.value?.userId }?.user?.uiThemes)
|
||||
}
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
@@ -898,15 +898,15 @@ object AppearanceScope {
|
||||
// Skip when Appearance screen is not hidden yet
|
||||
if (ModalManager.start.hasModalsOpen()) return@onDispose
|
||||
// Restore user overrides from stored list of users
|
||||
chatModel.updateCurrentUserUiThemes(chatModel.remoteHostId(), chatModel.users.firstOrNull { it.user.userId == chatModel.currentUser.value?.userId }?.user?.uiThemes)
|
||||
chatModel.updateCurrentUserUiThemes(chatModel.remoteHostId(), chatModel.users.value.firstOrNull { it.user.userId == chatModel.currentUser.value?.userId }?.user?.uiThemes)
|
||||
themeUserDestination.value = if (chatModel.currentUser.value?.uiThemes == null) null else chatModel.currentUser.value?.userId!! to chatModel.currentUser.value?.uiThemes
|
||||
}
|
||||
}
|
||||
|
||||
val values by remember(chatModel.users.toList()) { mutableStateOf(
|
||||
val values by remember(chatModel.users.value) { mutableStateOf(
|
||||
listOf(null as Long? to generalGetString(MR.strings.theme_destination_app_theme))
|
||||
+
|
||||
chatModel.users.filter { it.user.activeUser }.map {
|
||||
chatModel.users.value.filter { it.user.activeUser }.map {
|
||||
it.user.userId to it.user.chatViewName
|
||||
},
|
||||
)
|
||||
@@ -921,7 +921,7 @@ object AppearanceScope {
|
||||
onSelected = { userId ->
|
||||
themeUserDest.value = userId
|
||||
if (userId != null) {
|
||||
themeUserDestination.value = userId to chatModel.users.firstOrNull { it.user.userId == userId }?.user?.uiThemes
|
||||
themeUserDestination.value = userId to chatModel.users.value.firstOrNull { it.user.userId == userId }?.user?.uiThemes
|
||||
} else {
|
||||
themeUserDestination.value = null
|
||||
}
|
||||
|
||||
+2
-2
@@ -31,12 +31,12 @@ fun NotificationsSettingsView(
|
||||
|
||||
NotificationsSettingsLayout(
|
||||
notificationsMode = remember { chatModel.controller.appPrefs.notificationsMode.state },
|
||||
notificationPreviewMode = chatModel.notificationPreviewMode,
|
||||
notificationPreviewMode = chatModel.notificationPreviewMode.collectAsState(),
|
||||
showPage = { page ->
|
||||
ModalManager.start.showModalCloseable(true) {
|
||||
when (page) {
|
||||
CurrentPage.NOTIFICATIONS_MODE -> NotificationsModeView(chatModel.controller.appPrefs.notificationsMode.state) { changeNotificationsMode(it, chatModel) }
|
||||
CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode, onNotificationPreviewModeSelected)
|
||||
CurrentPage.NOTIFICATION_PREVIEW_MODE -> NotificationPreviewView(chatModel.notificationPreviewMode.collectAsState(), onNotificationPreviewModeSelected)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ fun PrivacySettingsView(
|
||||
chatModel.draftChatId.value = null
|
||||
}
|
||||
})
|
||||
SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = {
|
||||
SimpleXLinkOptions(chatModel.simplexLinkMode.collectAsState(), onSelected = {
|
||||
simplexLinkMode.set(it)
|
||||
chatModel.simplexLinkMode.value = it
|
||||
})
|
||||
|
||||
+2
-2
@@ -13,6 +13,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.model.size
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.res.MR
|
||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||
@@ -32,8 +33,7 @@ fun SetDeliveryReceiptsView(m: ChatModel) {
|
||||
m.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||
try {
|
||||
val users = m.controller.listUsers(currentUser.remoteHostId)
|
||||
m.users.clear()
|
||||
m.users.addAll(users)
|
||||
m.users.value = users
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "listUsers error: ${e.stackTraceToString()}")
|
||||
}
|
||||
|
||||
+3
-3
@@ -38,7 +38,7 @@ import kotlinx.coroutines.*
|
||||
@Composable
|
||||
fun UserProfilesView(m: ChatModel, search: MutableState<String>, profileHidden: MutableState<Boolean>, withAuth: (block: () -> Unit) -> Unit) {
|
||||
val searchTextOrPassword = rememberSaveable { search }
|
||||
val users by remember { derivedStateOf { m.users.map { it.user } } }
|
||||
val users by remember { derivedStateOf { m.users.value.map { it.user } } }
|
||||
val filteredUsers by remember { derivedStateOf { filteredUsers(m, searchTextOrPassword.value) } }
|
||||
UserProfilesLayout(
|
||||
users = users,
|
||||
@@ -306,7 +306,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: (
|
||||
fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List<UserInfo> {
|
||||
val s = searchTextOrPassword.trim()
|
||||
val lower = s.lowercase()
|
||||
return m.users.filter { u ->
|
||||
return m.users.value.filter { u ->
|
||||
if ((u.user.activeUser || !u.user.hidden) && (s == "" || u.user.anyNameContains(lower))) {
|
||||
true
|
||||
} else {
|
||||
@@ -315,7 +315,7 @@ fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List<UserInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun visibleUsersCount(m: ChatModel): Int = m.users.filter { u -> !u.user.hidden }.size
|
||||
private fun visibleUsersCount(m: ChatModel): Int = m.users.value.filter { u -> !u.user.hidden }.size
|
||||
|
||||
fun correctPassword(user: User, pwd: String): Boolean {
|
||||
val ph = user.viewPwdHash
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import java.text.DecimalFormat
|
||||
|
||||
@Composable
|
||||
fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> Unit, close: () -> Unit) {
|
||||
val currentRemoteHost by remember { chatModel.currentRemoteHost }
|
||||
val currentRemoteHost by chatModel.currentRemoteHost.collectAsState()
|
||||
val developerTools = remember { appPrefs.developerTools.get() }
|
||||
|
||||
// Will be actual once the screen is re-opened
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ import kotlinx.coroutines.*
|
||||
|
||||
@Composable
|
||||
fun ModalData.NetworkAndServersView(closeNetworkAndServers: () -> Unit) {
|
||||
val currentRemoteHost by remember { chatModel.currentRemoteHost }
|
||||
val currentRemoteHost by chatModel.currentRemoteHost.collectAsState()
|
||||
// It's not a state, just a one-time value. Shouldn't be used in any state-related situations
|
||||
val netCfg = remember { chatModel.controller.getNetCfg() }
|
||||
val networkUseSocksProxy: MutableState<Boolean> = remember { mutableStateOf(netCfg.useSocksProxy) }
|
||||
|
||||
+14
-10
@@ -97,7 +97,7 @@ actual fun ActiveCallView() {
|
||||
is WCallCommand.Camera -> {
|
||||
chatModel.activeCall.value = call.copy(localCamera = cmd.camera)
|
||||
if (!call.localMediaSources.mic) {
|
||||
chatModel.callCommand.add(WCallCommand.Media(CallMediaSource.Mic, enable = false))
|
||||
chatModel.callCommand.value += WCallCommand.Media(CallMediaSource.Mic, enable = false)
|
||||
}
|
||||
}
|
||||
is WCallCommand.End ->
|
||||
@@ -106,11 +106,11 @@ actual fun ActiveCallView() {
|
||||
}
|
||||
is WCallResponse.Error -> {
|
||||
when (apiMsg.command) {
|
||||
is WCallCommand.Capabilities -> chatModel.callCommand.add(WCallCommand.Permission(
|
||||
is WCallCommand.Capabilities -> chatModel.callCommand.value += WCallCommand.Permission(
|
||||
title = generalGetString(MR.strings.call_desktop_permission_denied_title),
|
||||
chrome = generalGetString(MR.strings.call_desktop_permission_denied_chrome),
|
||||
safari = generalGetString(MR.strings.call_desktop_permission_denied_safari)
|
||||
))
|
||||
)
|
||||
else -> {}
|
||||
}
|
||||
Log.e(TAG, "ActiveCallView: command error ${r.message}")
|
||||
@@ -123,11 +123,13 @@ actual fun ActiveCallView() {
|
||||
DisposableEffect(Unit) {
|
||||
chatModel.activeCallViewIsVisible.value = true
|
||||
// After the first call, End command gets added to the list which prevents making another calls
|
||||
chatModel.callCommand.removeAll { it is WCallCommand.End }
|
||||
val cmds = chatModel.callCommand.value.toMutableList()
|
||||
cmds.removeAll { it is WCallCommand.End }
|
||||
chatModel.callCommand.value = cmds
|
||||
onDispose {
|
||||
CallSoundsPlayer.stop()
|
||||
chatModel.activeCallViewIsVisible.value = false
|
||||
chatModel.callCommand.clear()
|
||||
chatModel.callCommand.value = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,13 +145,13 @@ private fun SendStateUpdates() {
|
||||
val connInfo = call.connectionInfo
|
||||
val connInfoText = if (connInfo == null) "" else " (${connInfo.text})"
|
||||
val description = call.encryptionStatus + connInfoText
|
||||
chatModel.callCommand.add(WCallCommand.Description(state, description))
|
||||
chatModel.callCommand.value += WCallCommand.Description(state, description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WebRTCController(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) {
|
||||
fun WebRTCController(callCommand: MutableStateFlow<List<WCallCommand>>, onResponse: (WVAPIMessage) -> Unit) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val endCall = {
|
||||
val call = chatModel.activeCall.value
|
||||
@@ -187,15 +189,17 @@ fun WebRTCController(callCommand: SnapshotStateList<WCallCommand>, onResponse: (
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { callCommand.firstOrNull() }
|
||||
snapshotFlow { callCommand.value.firstOrNull() }
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.collect {
|
||||
while (connections.isEmpty()) {
|
||||
delay(100)
|
||||
}
|
||||
while (callCommand.isNotEmpty()) {
|
||||
val cmd = callCommand.removeFirst()
|
||||
while (callCommand.value.isNotEmpty()) {
|
||||
val cmds = callCommand.value.toMutableList()
|
||||
val cmd = cmds.removeFirst()
|
||||
callCommand.value = cmds
|
||||
Log.d(TAG, "WebRTCController LaunchedEffect executing $cmd")
|
||||
processCommand(cmd)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user