diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index bbf247909..3c1d1da83 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -63,6 +63,12 @@
+
+
+
+
+
+
{
+ // Close active chat and show a list of chats
+ chatModel.chatId.value = null
+ chatModel.clearOverlays.value = true
+ when {
+ "text/plain" == intent.type -> intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
+ chatModel.sharedContent.value = SharedContent.Text(it)
+ }
+ intent.type?.startsWith("image/") == true -> (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let {
+ chatModel.sharedContent.value = SharedContent.Image(it)
+ } // All other mime types
+ else -> (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let {
+ chatModel.sharedContent.value = SharedContent.File(it)
+ }
+ }
+ }
+ }
+}
+
fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
if (chatModel.currentUser.value == null) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
index 37affd8bd..5372ac6b6 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
@@ -10,8 +10,7 @@ import androidx.compose.ui.text.style.TextDecoration
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.call.*
-import chat.simplex.app.views.helpers.DBMigrationResult
-import chat.simplex.app.views.helpers.generalGetString
+import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.onboarding.OnboardingStage
import chat.simplex.app.views.usersettings.NotificationPreviewMode
import chat.simplex.app.views.usersettings.NotificationsMode
@@ -64,6 +63,9 @@ class ChatModel(val controller: ChatController) {
val showCallView = mutableStateOf(false)
val switchingCall = mutableStateOf(false)
+ // working with external intents
+ val sharedContent = mutableStateOf(null as SharedContent?)
+
fun updateUserProfile(profile: LocalProfile) {
val user = currentUser.value
if (user != null) {
@@ -621,7 +623,7 @@ data class GroupInfo (
override val chatType get() = ChatType.Group
override val id get() = "#$groupId"
override val apiId get() = groupId
- override val ready get() = true
+ override val ready get() = membership.memberActive
override val sendMsgEnabled get() = membership.memberActive
override val ntfsEnabled get() = chatSettings.enableNtfs
override val displayName get() = groupProfile.displayName
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt
index 90da13cef..5a873dccd 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt
@@ -222,9 +222,7 @@ fun ComposeView(
}
}
}
- val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery(), processPickedImage)
- val galleryLauncherFallback = rememberGetContentLauncher(processPickedImage)
- val filesLauncher = rememberGetContentLauncher { uri: Uri? ->
+ val processPickedFile = { uri: Uri? ->
if (uri != null) {
val fileSize = getFileSize(context, uri)
if (fileSize != null && fileSize <= MAX_FILE_SIZE) {
@@ -241,6 +239,9 @@ fun ComposeView(
}
}
}
+ val galleryLauncher = rememberLauncherForActivityResult(contract = PickFromGallery(), processPickedImage)
+ val galleryLauncherFallback = rememberGetContentLauncher(processPickedImage)
+ val filesLauncher = rememberGetContentLauncher(processPickedFile)
LaunchedEffect(attachmentOption.value) {
when (attachmentOption.value) {
@@ -495,6 +496,16 @@ fun ComposeView(
}
}
+ LaunchedEffect(chatModel.sharedContent.value) {
+ when (val shared = chatModel.sharedContent.value) {
+ is SharedContent.Text -> onMessageChange(shared.text)
+ is SharedContent.Image -> processPickedImage(shared.uri)
+ is SharedContent.File -> processPickedFile(shared.uri)
+ null -> {}
+ }
+ chatModel.sharedContent.value = null
+ }
+
Column {
contextItemView()
when {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
index 0b8d2f7d4..52762f234 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
@@ -177,7 +177,7 @@ private fun ConnectButton(text: String, onClick: () -> Unit) {
}
@Composable
-fun ChatListToolbar(chatModel: ChatModel, scaffoldCtrl: ScaffoldController, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
+private fun ChatListToolbar(chatModel: ChatModel, scaffoldCtrl: ScaffoldController, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
var showSearch by rememberSaveable { mutableStateOf(false) }
val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
if (showSearch) {
@@ -236,7 +236,7 @@ fun ChatListToolbar(chatModel: ChatModel, scaffoldCtrl: ScaffoldController, stop
}
@Composable
-fun ChatList(chatModel: ChatModel, search: String) {
+private fun ChatList(chatModel: ChatModel, search: String) {
val filter: (Chat) -> Boolean = { chat: Chat ->
chat.chatInfo.chatViewName.lowercase().contains(search.lowercase())
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListNavLinkView.kt
new file mode 100644
index 000000000..e9a007b1c
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListNavLinkView.kt
@@ -0,0 +1,66 @@
+package chat.simplex.app.views.chatlist
+
+import SectionItemView
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import chat.simplex.app.model.*
+import chat.simplex.app.ui.theme.Indigo
+import chat.simplex.app.views.helpers.ProfileImage
+
+@Composable
+fun ShareListNavLinkView(chat: Chat, chatModel: ChatModel) {
+ val stopped = chatModel.chatRunning.value == false
+ when (chat.chatInfo) {
+ is ChatInfo.Direct ->
+ ShareListNavLinkLayout(
+ chatLinkPreview = { SharePreviewView(chat) },
+ click = { directChatAction(chat.chatInfo, chatModel) },
+ stopped
+ )
+ is ChatInfo.Group ->
+ ShareListNavLinkLayout(
+ chatLinkPreview = { SharePreviewView(chat) },
+ click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) },
+ stopped
+ )
+ is ChatInfo.ContactRequest, is ChatInfo.ContactConnection -> {}
+ }
+}
+
+@Composable
+private fun ShareListNavLinkLayout(
+ chatLinkPreview: @Composable () -> Unit,
+ click: () -> Unit,
+ stopped: Boolean
+) {
+ SectionItemView(minHeight = 50.dp, click = click, disabled = stopped) {
+ chatLinkPreview()
+ }
+ Divider(Modifier.padding(horizontal = 8.dp))
+}
+
+@Composable
+private fun SharePreviewView(chat: Chat) {
+ Row(
+ Modifier.fillMaxSize(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ ProfileImage(size = 46.dp, chat.chatInfo.image)
+ Text(
+ chat.chatInfo.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
+ color = if (chat.chatInfo.incognito) Indigo else Color.Unspecified
+ )
+ }
+ }
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt
new file mode 100644
index 000000000..e4df296c7
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ShareListView.kt
@@ -0,0 +1,138 @@
+package chat.simplex.app.views.chatlist
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.*
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material.icons.outlined.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.capitalize
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.unit.dp
+import chat.simplex.app.R
+import chat.simplex.app.model.*
+import chat.simplex.app.ui.theme.HighOrLowlight
+import chat.simplex.app.ui.theme.Indigo
+import chat.simplex.app.views.helpers.*
+
+@Composable
+fun ShareListView(chatModel: ChatModel, stopped: Boolean) {
+ var searchInList by rememberSaveable { mutableStateOf("") }
+ Scaffold(
+ topBar = { Column { ShareListToolbar(chatModel, stopped) { searchInList = it.trim() } } },
+ ) {
+ Box(Modifier.padding(it)) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colors.background)
+ ) {
+ if (chatModel.chats.isNotEmpty()) {
+ ShareList(chatModel, search = searchInList)
+ } else {
+ EmptyList()
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun EmptyList() {
+ Box {
+ Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = HighOrLowlight)
+ }
+}
+
+@Composable
+private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
+ var showSearch by rememberSaveable { mutableStateOf(false) }
+ val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
+ if (showSearch) {
+ BackHandler(onBack = hideSearchOnBack)
+ }
+ val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
+ if (chatModel.chats.size >= 8) {
+ barButtons.add {
+ IconButton({ showSearch = true }) {
+ Icon(Icons.Outlined.Search, stringResource(android.R.string.search_go).capitalize(Locale.current), tint = MaterialTheme.colors.primary)
+ }
+ }
+ }
+ if (stopped) {
+ barButtons.add {
+ IconButton(onClick = {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(R.string.chat_is_stopped_indication),
+ generalGetString(R.string.you_can_start_chat_via_setting_or_by_restarting_the_app)
+ )
+ }) {
+ Icon(
+ Icons.Filled.Report,
+ generalGetString(R.string.chat_is_stopped_indication),
+ tint = Color.Red,
+ )
+ }
+ }
+ }
+
+ DefaultTopAppBar(
+ navigationButton = { if (showSearch) NavigationButtonBack(hideSearchOnBack) else NavigationButtonBack { chatModel.sharedContent.value = null } },
+ title = {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ when (chatModel.sharedContent.value) {
+ is SharedContent.Text -> stringResource(R.string.share_message)
+ is SharedContent.Image -> stringResource(R.string.share_image)
+ is SharedContent.File -> stringResource(R.string.share_file)
+ else -> stringResource(R.string.share_message)
+ },
+ color = MaterialTheme.colors.onBackground,
+ fontWeight = FontWeight.SemiBold,
+ )
+ if (chatModel.incognito.value) {
+ Icon(
+ Icons.Filled.TheaterComedy,
+ stringResource(R.string.incognito),
+ tint = Indigo,
+ modifier = Modifier.padding(10.dp).size(26.dp)
+ )
+ }
+ }
+ },
+ onTitleClick = null,
+ showSearch = showSearch,
+ onSearchValueChanged = onSearchValueChanged,
+ buttons = barButtons
+ )
+ Divider()
+}
+
+@Composable
+private fun ShareList(chatModel: ChatModel, search: String) {
+ val filter: (Chat) -> Boolean = { chat: Chat ->
+ chat.chatInfo.chatViewName.lowercase().contains(search.lowercase())
+ }
+ val chats by remember(search) {
+ derivedStateOf {
+ if (search.isEmpty()) chatModel.chats.filter { it.chatInfo.ready } else chatModel.chats.filter { it.chatInfo.ready }.filter(filter)
+ }
+ }
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ items(chats) { chat ->
+ ShareListNavLinkView(chat, chatModel)
+ }
+ }
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt
new file mode 100644
index 000000000..d12ecd84b
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Enums.kt
@@ -0,0 +1,9 @@
+package chat.simplex.app.views.helpers
+
+import android.net.Uri
+
+sealed class SharedContent {
+ data class Text(val text: String): SharedContent()
+ data class Image(val uri: Uri): SharedContent()
+ data class File(val uri: Uri): SharedContent()
+}
diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml
index 16f753a34..472525d67 100644
--- a/apps/android/app/src/main/res/values-de/strings.xml
+++ b/apps/android/app/src/main/res/values-de/strings.xml
@@ -170,6 +170,11 @@
Chatten Sie mit den Entwicklern
Sie haben keine Chats
+
+ Share message…
+ Share image…
+ Share file…
+
Anhängen
Kontextsymbol
diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml
index 4a7ba2ade..f12840a1c 100644
--- a/apps/android/app/src/main/res/values-ru/strings.xml
+++ b/apps/android/app/src/main/res/values-ru/strings.xml
@@ -170,6 +170,11 @@
Соединиться с разработчиками
У вас нет чатов
+
+ Отправить сообщение…
+ Отправить изображение…
+ Отправить файл…
+
Прикрепить
Значок контекста
diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml
index 3ec7a3e4c..f3425051d 100644
--- a/apps/android/app/src/main/res/values/strings.xml
+++ b/apps/android/app/src/main/res/values/strings.xml
@@ -170,6 +170,11 @@
Chat with the developers
You have no chats
+
+ Share message…
+ Share image…
+ Share file…
+
Attach
Context icon