android: update available group actions on role change; connect via external link when app was closed; other fixes (#1311)

* android: Fixes for tests

* console item bottom padding

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
Stanislav Dmitrenko
2022-11-07 15:46:15 +03:00
committed by GitHub
parent 18677cec63
commit ddecd847e5
8 changed files with 66 additions and 63 deletions

View File

@@ -307,16 +307,17 @@ class ChatModel(val controller: ChatController) {
}
fun upsertGroupMember(groupInfo: GroupInfo, member: GroupMember): Boolean {
if (groupInfo.membership.groupMemberId == member.groupMemberId) {
// Current user was updated (like his role, for example)
updateChatInfo(ChatInfo.Group(groupInfo))
return false
}
// update current chat
return if (chatId.value == groupInfo.id) {
val memberIndex = groupMembers.indexOfFirst { it.id == member.id }
if (memberIndex >= 0) {
groupMembers[memberIndex] = member
false
} else if (groupInfo.membership.groupMemberId == member.groupMemberId) {
// Current user was updated (like his role, for example)
updateChatInfo(ChatInfo.Group(groupInfo))
true
} else {
groupMembers.add(member)
true
@@ -430,7 +431,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
abstract val incognito: Boolean
@Serializable @SerialName("direct")
class Direct(val contact: Contact): ChatInfo() {
data class Direct(val contact: Contact): ChatInfo() {
override val chatType get() = ChatType.Direct
override val localDisplayName get() = contact.localDisplayName
override val id get() = contact.id

View File

@@ -244,6 +244,10 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
chatModel.onboardingStage.value = OnboardingStage.OnboardingComplete
chatModel.controller.appPrefs.chatLastStart.set(Clock.System.now())
chatModel.chatRunning.value = true
chatModel.appOpenUrl.value?.let {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(it, chatModel)
}
startReceiver()
Log.d(TAG, "startChat: started")
} else {
@@ -1222,23 +1226,12 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
// service or periodic mode was chosen and battery optimization is disabled
SimplexApp.context.schedulePeriodicServiceRestartWorker()
SimplexApp.context.schedulePeriodicWakeUp()
chatModel.appOpenUrl.value?.let {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(it, chatModel)
}
}
}
private fun showBGServiceNotice(mode: NotificationsMode) = AlertManager.shared.showAlert {
val hideAlert: () -> Unit = {
AlertManager.shared.hideAlert()
chatModel.appOpenUrl.value?.let {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(it, chatModel)
}
}
AlertDialog(
onDismissRequest = hideAlert,
onDismissRequest = AlertManager.shared::hideAlert,
title = {
Row {
Icon(
@@ -1264,7 +1257,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
},
confirmButton = {
TextButton(onClick = hideAlert) { Text(stringResource(R.string.ok)) }
TextButton(onClick = AlertManager.shared::hideAlert) { Text(stringResource(R.string.ok)) }
}
)
}
@@ -1305,15 +1298,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
private fun showDisablingServiceNotice(mode: NotificationsMode) = AlertManager.shared.showAlert {
val hideAlert: () -> Unit = {
AlertManager.shared.hideAlert()
chatModel.appOpenUrl.value?.let {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(it, chatModel)
}
}
AlertDialog(
onDismissRequest = hideAlert,
onDismissRequest = AlertManager.shared::hideAlert,
title = {
Row {
Icon(
@@ -1336,7 +1322,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
},
confirmButton = {
TextButton(onClick = hideAlert) { Text(stringResource(R.string.ok)) }
TextButton(onClick = AlertManager.shared::hideAlert) { Text(stringResource(R.string.ok)) }
}
)
}

View File

@@ -25,8 +25,7 @@ import androidx.compose.ui.unit.sp
import androidx.fragment.app.FragmentActivity
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.*
import chat.simplex.app.views.helpers.*
import com.google.accompanist.insets.ProvideWindowInsets
@@ -164,7 +163,8 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed() } }
LazyColumn(state = listState, reverseLayout = true) {
items(reversedTerminalItems) { item ->
Text("${item.date.toString().subSequence(11, 19)} ${item.label}",
Text(
"${item.date.toString().subSequence(11, 19)} ${item.label}",
style = TextStyle(fontFamily = FontFamily.Monospace, fontSize = 18.sp, color = MaterialTheme.colors.primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@@ -173,7 +173,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
.clickable {
ModalManager.shared.showModal {
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(item.details)
Text(item.details, modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING))
}
}
}.padding(horizontal = 8.dp, vertical = 4.dp)

View File

@@ -47,7 +47,6 @@ fun ChatInfoView(
customUserProfile: Profile?,
localAlias: String,
close: () -> Unit,
onChatUpdated: (Chat) -> Unit,
) {
BackHandler(onBack = close)
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
@@ -61,7 +60,7 @@ fun ChatInfoView(
localAlias,
developerTools,
onLocalAliasChanged = {
setContactAlias(chat.chatInfo.apiId, it, chatModel, onChatUpdated)
setContactAlias(chat.chatInfo.apiId, it, chatModel)
},
deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) },
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) },
@@ -350,10 +349,9 @@ fun DeleteContactButton(onClick: () -> Unit) {
)
}
private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: ChatModel, onChatUpdated: (Chat) -> Unit) = withApi {
private fun setContactAlias(contactApiId: Long, localAlias: String, chatModel: ChatModel) = withApi {
chatModel.controller.apiSetContactAlias(contactApiId, localAlias)?.let {
chatModel.updateContact(it)
onChatUpdated(chatModel.getChat(chatModel.chatId.value ?: return@withApi) ?: return@withApi)
}
}

View File

@@ -65,20 +65,30 @@ fun ChatView(chatModel: ChatModel) {
LaunchedEffect(Unit) {
// snapshotFlow here is because it reacts much faster on changes in chatModel.chatId.value.
// With LaunchedEffect(chatModel.chatId.value) there is a noticeable delay before reconstruction of the view
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
.collect {
if (activeChat.value?.id != chatModel.chatId.value) {
activeChat.value = if (chatModel.chatId.value == null) {
null
} else {
launch {
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
.collect {
if (activeChat.value?.id != chatModel.chatId.value && chatModel.chatId.value != null) {
// Redisplay the whole hierarchy if the chat is different to make going from groups to direct chat working correctly
// Also for situation when chatId changes after clicking in notification, etc
chatModel.getChat(chatModel.chatId.value!!)
activeChat.value = chatModel.getChat(chatModel.chatId.value!!)
}
markUnreadChatAsRead(activeChat, chatModel)
}
markUnreadChatAsRead(activeChat, chatModel)
}
}
launch {
// .toList() is important for making observation working
snapshotFlow { chatModel.chats.toList() }
.distinctUntilChanged()
.collect { chats ->
chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }.let {
// Only changed chatInfo is important thing. Other properties can be skipped for reducing recompositions
if (it?.chatInfo != activeChat.value?.chatInfo) {
activeChat.value = it
}}
}
}
}
if (activeChat.value == null || user == null) {
@@ -121,9 +131,7 @@ fun ChatView(chatModel: ChatModel) {
if (cInfo is ChatInfo.Direct) {
val contactInfo = chatModel.controller.apiContactInfo(cInfo.apiId)
ModalManager.shared.showModalCloseable(true) { close ->
ChatInfoView(chatModel, cInfo.contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, close) {
activeChat.value = it
}
ChatInfoView(chatModel, cInfo.contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, close)
}
} else if (cInfo is ChatInfo.Group) {
setGroupMembers(cInfo.groupInfo, chatModel)

View File

@@ -243,7 +243,7 @@ fun ComposeView(
}
}
}
val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery()) { processPickedImage(it, null) }
val galleryLauncher = rememberLauncherForActivityResult(contract = PickMultipleFromGallery()) { processPickedImage(it, null) }
val galleryLauncherFallback = rememberGetMultipleContentsLauncher { processPickedImage(it, null) }
val filesLauncher = rememberGetContentLauncher { processPickedFile(it, null) }
@@ -550,7 +550,16 @@ fun ComposeView(
}
}
class PickFromGallery: ActivityResultContract<Int, List<Uri>>() {
class PickFromGallery: ActivityResultContract<Int, Uri?>() {
override fun createIntent(context: Context, input: Int) =
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply {
type = "image/*"
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? = intent?.data
}
class PickMultipleFromGallery: ActivityResultContract<Int, List<Uri>>() {
override fun createIntent(context: Context, input: Int) =
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.INTERNAL_CONTENT_URI).apply {
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)

View File

@@ -3,25 +3,20 @@ package chat.simplex.app.views.helpers
import android.util.Log
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import chat.simplex.app.R
import chat.simplex.app.TAG
class AlertManager {
var alertView = mutableStateOf<(@Composable () -> Unit)?>(null)
var presentAlert = mutableStateOf<Boolean>(false)
var alertViews = mutableStateListOf<(@Composable () -> Unit)>()
fun showAlert(alert: @Composable () -> Unit) {
Log.d(TAG, "AlertManager.showAlert")
alertView.value = alert
presentAlert.value = true
alertViews.add(alert)
}
fun hideAlert() {
presentAlert.value = false
alertView.value = null
alertViews.removeLastOrNull()
}
fun showAlertDialogButtons(
@@ -101,7 +96,7 @@ class AlertManager {
@Composable
fun showInView() {
if (presentAlert.value) alertView.value?.invoke()
remember { alertViews }.lastOrNull()?.invoke()
}
companion object {

View File

@@ -2,8 +2,7 @@ package chat.simplex.app.views.helpers
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.*
import android.content.pm.PackageManager
import android.graphics.*
import android.net.Uri
@@ -31,6 +30,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.views.chat.PickFromGallery
import chat.simplex.app.views.newchat.ActionButton
import java.io.ByteArrayOutputStream
import java.io.File
@@ -170,7 +170,7 @@ fun GetImageBottomSheet(
hideBottomSheet: () -> Unit
) {
val context = LocalContext.current
val galleryLauncher = rememberGetContentLauncher { uri: Uri? ->
val processPickedImage = { uri: Uri? ->
if (uri != null) {
val source = ImageDecoder.createSource(context.contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source)
@@ -178,6 +178,8 @@ fun GetImageBottomSheet(
onImageChange(bitmap)
}
}
val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery()) { processPickedImage(it) }
val galleryLauncherFallback = rememberGetContentLauncher { processPickedImage(it) }
val cameraLauncher = rememberCameraLauncher { bitmap: Bitmap? ->
if (bitmap != null) {
imageBitmap.value = bitmap
@@ -219,7 +221,11 @@ fun GetImageBottomSheet(
}
}
ActionButton(null, stringResource(R.string.from_gallery_button), icon = Icons.Outlined.Collections) {
galleryLauncher.launch("image/*")
try {
galleryLauncher.launch(0)
} catch (e: ActivityNotFoundException) {
galleryLauncherFallback.launch("image/*")
}
hideBottomSheet()
}
}