From cd075e0904e326d13d94c176e2c2b77da0c1c6d7 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 20 Jan 2026 13:23:03 +0100 Subject: [PATCH 1/2] feat(assistant-screen): PROCESS_TEXT support Signed-off-by: alperozturk96 --- app/src/main/AndroidManifest.xml | 9 ++++- .../client/assistant/AssistantScreen.kt | 34 +++++++++++++++---- .../client/assistant/model/AssistantPage.kt | 13 +++++++ .../ui/composeActivity/ComposeActivity.kt | 25 ++++++++++++-- .../ui/composeActivity/ComposeViewModel.kt | 24 +++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 6 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/assistant/model/AssistantPage.kt create mode 100644 app/src/main/java/com/nextcloud/ui/composeActivity/ComposeViewModel.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b9589dd3854c..ee6e7fd2bc80 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -135,7 +135,14 @@ + android:exported="true" + android:label="@string/compose_activity_label"> + + + + + + when (page) { - 0 -> { + AssistantPage.Conversation.id -> { ConversationScreen(viewModel = conversationViewModel, close = { scope.launch { - pagerState.scrollToPage(1) + pagerState.scrollToPage(AssistantPage.Content.id) } }, openChat = { newSessionId -> viewModel.initSessionId(newSessionId) @@ -138,11 +158,11 @@ fun AssistantScreen( viewModel.selectTaskType(chatTaskType) } scope.launch { - pagerState.scrollToPage(1) + pagerState.scrollToPage(AssistantPage.Content.id) } }) } - 1 -> { + AssistantPage.Content.id -> { Scaffold( modifier = Modifier.pullToRefresh( false, @@ -166,7 +186,7 @@ fun AssistantScreen( viewModel.selectTaskType(task) }, navigateToConversationList = { scope.launch { - pagerState.scrollToPage(0) + pagerState.scrollToPage(AssistantPage.Conversation.id) } }) } @@ -413,6 +433,7 @@ private fun AssistantScreenPreview() { MaterialTheme( content = { AssistantScreen( + composeViewModel = ComposeViewModel(), conversationViewModel = getMockConversationViewModel(), viewModel = getMockAssistantViewModel(false), activity = ComposeActivity(), @@ -431,6 +452,7 @@ private fun AssistantEmptyScreenPreview() { MaterialTheme( content = { AssistantScreen( + composeViewModel = ComposeViewModel(), conversationViewModel = getMockConversationViewModel(), viewModel = getMockAssistantViewModel(true), activity = ComposeActivity(), diff --git a/app/src/main/java/com/nextcloud/client/assistant/model/AssistantPage.kt b/app/src/main/java/com/nextcloud/client/assistant/model/AssistantPage.kt new file mode 100644 index 000000000000..5fd71ae1073a --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/assistant/model/AssistantPage.kt @@ -0,0 +1,13 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.assistant.model + +enum class AssistantPage(val id: Int) { + Conversation(0), + Content(1) +} diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt index 1428ab363b76..d7d1c0a75bc4 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt @@ -7,9 +7,11 @@ */ package com.nextcloud.ui.composeActivity +import android.content.Intent import android.os.Bundle import android.view.MenuItem import android.view.View +import androidx.activity.viewModels import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -35,6 +37,7 @@ import com.owncloud.android.ui.activity.DrawerActivity class ComposeActivity : DrawerActivity() { lateinit var binding: ActivityComposeBinding + private val composeViewModel: ComposeViewModel by viewModels() companion object { const val DESTINATION = "DESTINATION" @@ -46,9 +49,8 @@ class ComposeActivity : DrawerActivity() { setContentView(binding.root) val destination = - intent.getParcelableArgument(DESTINATION, ComposeDestination::class.java) ?: throw IllegalArgumentException( - "destination is not exists" - ) + intent.getParcelableArgument(DESTINATION, ComposeDestination::class.java) + ?: ComposeDestination.getAssistantScreen(this) setupActivityUIFor(destination) @@ -60,6 +62,22 @@ class ComposeActivity : DrawerActivity() { } ) } + + processText(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + processText(intent) + } + + private fun processText(intent: Intent) { + val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT) + if (text.isNullOrEmpty()) { + return + } + + composeViewModel.updateSelectedText(text.toString()) } private fun setupActivityUIFor(destination: ComposeDestination) { @@ -105,6 +123,7 @@ class ComposeActivity : DrawerActivity() { val client = nextcloudClient ?: return AssistantScreen( + composeViewModel = composeViewModel, viewModel = AssistantViewModel( accountName = userAccountManager.user.accountName, remoteRepository = AssistantRemoteRepositoryImpl(client, capabilities), diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeViewModel.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeViewModel.kt new file mode 100644 index 000000000000..86b94a633cd8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeViewModel.kt @@ -0,0 +1,24 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.ui.composeActivity + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update + +class ComposeViewModel : ViewModel() { + private val _selectedText = MutableStateFlow(null) + val selectedText: StateFlow = _selectedText + + fun updateSelectedText(value: String) { + _selectedText.update { + value + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec083c6fe0d8..80af487a9618 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,6 +80,8 @@ Failed to delete conversation Delete conversation + Nextcloud Assistant + Text copied from another app Output shown here is generated by AI. Make sure to always double-check. From 5eeae807d22d5e831c5c1ed2a38700b3502e879b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 21 Jan 2026 11:10:37 +0100 Subject: [PATCH 2/2] copy selected text to input bar Signed-off-by: alperozturk96 --- .../client/assistant/AssistantScreen.kt | 34 +++++++++---------- .../client/assistant/AssistantViewModel.kt | 9 +++++ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/assistant/AssistantScreen.kt b/app/src/main/java/com/nextcloud/client/assistant/AssistantScreen.kt index dc84e333060b..1739cfe494eb 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/AssistantScreen.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/AssistantScreen.kt @@ -41,10 +41,8 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter @@ -60,8 +58,8 @@ import com.nextcloud.client.assistant.conversation.ConversationScreen import com.nextcloud.client.assistant.conversation.ConversationViewModel import com.nextcloud.client.assistant.conversation.repository.MockConversationRemoteRepository import com.nextcloud.client.assistant.extensions.getInputTitle -import com.nextcloud.client.assistant.model.AssistantScreenState import com.nextcloud.client.assistant.model.AssistantPage +import com.nextcloud.client.assistant.model.AssistantScreenState import com.nextcloud.client.assistant.model.ScreenOverlayState import com.nextcloud.client.assistant.repository.local.MockAssistantLocalRepository import com.nextcloud.client.assistant.repository.remote.MockAssistantRemoteRepository @@ -76,7 +74,6 @@ import com.owncloud.android.R import com.owncloud.android.lib.resources.assistant.v2.model.Task import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData import com.owncloud.android.lib.resources.status.OCCapability -import com.owncloud.android.utils.ClipboardUtil import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -115,17 +112,18 @@ fun AssistantScreen( } LaunchedEffect(selectedText) { - if (selectedText.isNullOrEmpty()) { - return@LaunchedEffect - } - - if (pagerState.currentPage == AssistantPage.Conversation.id) { - pagerState.scrollToPage(AssistantPage.Content.id) - } + selectedText?.let { + if (it.isBlank()) { + return@LaunchedEffect + } - ClipboardUtil.copyToClipboard(activity, selectedText, false) + if (pagerState.currentPage == AssistantPage.Conversation.id) { + pagerState.scrollToPage(AssistantPage.Content.id) + } - snackbarHostState.showSnackbar(activity.getString(R.string.assistant_screen_text_selected)) + viewModel.updateInputBarText(it) + snackbarHostState.showSnackbar(activity.getString(R.string.assistant_screen_text_selected)) + } } LaunchedEffect(sessionId) { @@ -193,7 +191,7 @@ fun AssistantScreen( }, bottomBar = { if (!taskTypes.isNullOrEmpty()) { - ChatInputBar( + InputBar( sessionId, selectedTaskType, viewModel @@ -253,9 +251,9 @@ fun AssistantScreen( @Suppress("LongMethod") @Composable -private fun ChatInputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, viewModel: AssistantViewModel) { +private fun InputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, viewModel: AssistantViewModel) { val scope = rememberCoroutineScope() - var text by remember { mutableStateOf("") } + val text by viewModel.inputBarText.collectAsState() Surface( tonalElevation = 3.dp, @@ -284,7 +282,7 @@ private fun ChatInputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, view ) { OutlinedTextField( value = text, - onValueChange = { text = it }, + onValueChange = { viewModel.updateInputBarText(it) }, modifier = Modifier .weight(1f) .padding(end = 8.dp), @@ -311,7 +309,7 @@ private fun ChatInputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, view scope.launch { delay(CHAT_INPUT_DELAY) - text = "" + viewModel.updateInputBarText("") } } ) { diff --git a/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt b/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt index 6e49c5adfa79..820bbafb63e7 100644 --- a/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt +++ b/app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt @@ -44,6 +44,9 @@ class AssistantViewModel( private const val POLLING_INTERVAL_MS = 15_000L } + private val _inputBarText = MutableStateFlow("") + val inputBarText: StateFlow = _inputBarText + private val _screenState = MutableStateFlow(null) val screenState: StateFlow = _screenState @@ -314,6 +317,12 @@ class AssistantViewModel( } } + fun updateInputBarText(value: String) { + _inputBarText.update { + value + } + } + private fun removeTaskFromList(id: Long) { _filteredTaskList.update { currentList -> currentList?.filter { it.id != id }