Skip to content

Commit d8b778d

Browse files
Merge pull request #16324 from nextcloud/feat/action-process-text-support
feat(assistant-screen): PROCESS_TEXT support
2 parents 5b7629a + 5eeae80 commit d8b778d

File tree

7 files changed

+111
-17
lines changed

7 files changed

+111
-17
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,14 @@
135135

136136
<activity
137137
android:name="com.nextcloud.ui.composeActivity.ComposeActivity"
138-
android:exported="false" />
138+
android:exported="true"
139+
android:label="@string/compose_activity_label">
140+
<intent-filter>
141+
<action android:name="android.intent.action.PROCESS_TEXT" />
142+
<category android:name="android.intent.category.DEFAULT" />
143+
<data android:mimeType="text/plain" />
144+
</intent-filter>
145+
</activity>
139146

140147
<uses-library
141148
android:name="org.apache.http.legacy"

app/src/main/java/com/nextcloud/client/assistant/AssistantScreen.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,8 @@ import androidx.compose.runtime.DisposableEffect
4141
import androidx.compose.runtime.LaunchedEffect
4242
import androidx.compose.runtime.collectAsState
4343
import androidx.compose.runtime.getValue
44-
import androidx.compose.runtime.mutableStateOf
4544
import androidx.compose.runtime.remember
4645
import androidx.compose.runtime.rememberCoroutineScope
47-
import androidx.compose.runtime.setValue
4846
import androidx.compose.ui.Alignment
4947
import androidx.compose.ui.Modifier
5048
import androidx.compose.ui.graphics.ColorFilter
@@ -60,13 +58,15 @@ import com.nextcloud.client.assistant.conversation.ConversationScreen
6058
import com.nextcloud.client.assistant.conversation.ConversationViewModel
6159
import com.nextcloud.client.assistant.conversation.repository.MockConversationRemoteRepository
6260
import com.nextcloud.client.assistant.extensions.getInputTitle
61+
import com.nextcloud.client.assistant.model.AssistantPage
6362
import com.nextcloud.client.assistant.model.AssistantScreenState
6463
import com.nextcloud.client.assistant.model.ScreenOverlayState
6564
import com.nextcloud.client.assistant.repository.local.MockAssistantLocalRepository
6665
import com.nextcloud.client.assistant.repository.remote.MockAssistantRemoteRepository
6766
import com.nextcloud.client.assistant.task.TaskView
6867
import com.nextcloud.client.assistant.taskTypes.TaskTypesRow
6968
import com.nextcloud.ui.composeActivity.ComposeActivity
69+
import com.nextcloud.ui.composeActivity.ComposeViewModel
7070
import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
7171
import com.nextcloud.ui.composeComponents.bottomSheet.MoreActionsBottomSheet
7272
import com.nextcloud.utils.extensions.getChat
@@ -84,11 +84,13 @@ private const val PULL_TO_REFRESH_DELAY = 1500L
8484
@OptIn(ExperimentalMaterial3Api::class)
8585
@Composable
8686
fun AssistantScreen(
87+
composeViewModel: ComposeViewModel,
8788
viewModel: AssistantViewModel,
8889
conversationViewModel: ConversationViewModel,
8990
capability: OCCapability,
9091
activity: Activity
9192
) {
93+
val selectedText by composeViewModel.selectedText.collectAsState()
9294
val sessionId by viewModel.sessionId.collectAsState()
9395
val messageId by viewModel.snackbarMessageId.collectAsState()
9496
val screenOverlayState by viewModel.screenOverlayState.collectAsState()
@@ -99,7 +101,8 @@ fun AssistantScreen(
99101
val scope = rememberCoroutineScope()
100102
val pullRefreshState = rememberPullToRefreshState()
101103
val snackbarHostState = remember { SnackbarHostState() }
102-
val pagerState = rememberPagerState(initialPage = 1, pageCount = { 2 })
104+
val pagerState =
105+
rememberPagerState(initialPage = AssistantPage.Content.id, pageCount = { AssistantPage.entries.size })
103106

104107
LaunchedEffect(messageId) {
105108
messageId?.let {
@@ -108,6 +111,21 @@ fun AssistantScreen(
108111
}
109112
}
110113

114+
LaunchedEffect(selectedText) {
115+
selectedText?.let {
116+
if (it.isBlank()) {
117+
return@LaunchedEffect
118+
}
119+
120+
if (pagerState.currentPage == AssistantPage.Conversation.id) {
121+
pagerState.scrollToPage(AssistantPage.Content.id)
122+
}
123+
124+
viewModel.updateInputBarText(it)
125+
snackbarHostState.showSnackbar(activity.getString(R.string.assistant_screen_text_selected))
126+
}
127+
}
128+
111129
LaunchedEffect(sessionId) {
112130
viewModel.startPolling(sessionId)
113131

@@ -127,22 +145,22 @@ fun AssistantScreen(
127145
userScrollEnabled = taskTypes.getChat() != null
128146
) { page ->
129147
when (page) {
130-
0 -> {
148+
AssistantPage.Conversation.id -> {
131149
ConversationScreen(viewModel = conversationViewModel, close = {
132150
scope.launch {
133-
pagerState.scrollToPage(1)
151+
pagerState.scrollToPage(AssistantPage.Content.id)
134152
}
135153
}, openChat = { newSessionId ->
136154
viewModel.initSessionId(newSessionId)
137155
taskTypes.getChat()?.let { chatTaskType ->
138156
viewModel.selectTaskType(chatTaskType)
139157
}
140158
scope.launch {
141-
pagerState.scrollToPage(1)
159+
pagerState.scrollToPage(AssistantPage.Content.id)
142160
}
143161
})
144162
}
145-
1 -> {
163+
AssistantPage.Content.id -> {
146164
Scaffold(
147165
modifier = Modifier.pullToRefresh(
148166
false,
@@ -166,14 +184,14 @@ fun AssistantScreen(
166184
viewModel.selectTaskType(task)
167185
}, navigateToConversationList = {
168186
scope.launch {
169-
pagerState.scrollToPage(0)
187+
pagerState.scrollToPage(AssistantPage.Conversation.id)
170188
}
171189
})
172190
}
173191
},
174192
bottomBar = {
175193
if (!taskTypes.isNullOrEmpty()) {
176-
ChatInputBar(
194+
InputBar(
177195
sessionId,
178196
selectedTaskType,
179197
viewModel
@@ -233,9 +251,9 @@ fun AssistantScreen(
233251

234252
@Suppress("LongMethod")
235253
@Composable
236-
private fun ChatInputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, viewModel: AssistantViewModel) {
254+
private fun InputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, viewModel: AssistantViewModel) {
237255
val scope = rememberCoroutineScope()
238-
var text by remember { mutableStateOf("") }
256+
val text by viewModel.inputBarText.collectAsState()
239257

240258
Surface(
241259
tonalElevation = 3.dp,
@@ -264,7 +282,7 @@ private fun ChatInputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, view
264282
) {
265283
OutlinedTextField(
266284
value = text,
267-
onValueChange = { text = it },
285+
onValueChange = { viewModel.updateInputBarText(it) },
268286
modifier = Modifier
269287
.weight(1f)
270288
.padding(end = 8.dp),
@@ -291,7 +309,7 @@ private fun ChatInputBar(sessionId: Long?, selectedTaskType: TaskTypeData?, view
291309

292310
scope.launch {
293311
delay(CHAT_INPUT_DELAY)
294-
text = ""
312+
viewModel.updateInputBarText("")
295313
}
296314
}
297315
) {
@@ -413,6 +431,7 @@ private fun AssistantScreenPreview() {
413431
MaterialTheme(
414432
content = {
415433
AssistantScreen(
434+
composeViewModel = ComposeViewModel(),
416435
conversationViewModel = getMockConversationViewModel(),
417436
viewModel = getMockAssistantViewModel(false),
418437
activity = ComposeActivity(),
@@ -431,6 +450,7 @@ private fun AssistantEmptyScreenPreview() {
431450
MaterialTheme(
432451
content = {
433452
AssistantScreen(
453+
composeViewModel = ComposeViewModel(),
434454
conversationViewModel = getMockConversationViewModel(),
435455
viewModel = getMockAssistantViewModel(true),
436456
activity = ComposeActivity(),

app/src/main/java/com/nextcloud/client/assistant/AssistantViewModel.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class AssistantViewModel(
4444
private const val POLLING_INTERVAL_MS = 15_000L
4545
}
4646

47+
private val _inputBarText = MutableStateFlow<String>("")
48+
val inputBarText: StateFlow<String> = _inputBarText
49+
4750
private val _screenState = MutableStateFlow<AssistantScreenState?>(null)
4851
val screenState: StateFlow<AssistantScreenState?> = _screenState
4952

@@ -314,6 +317,12 @@ class AssistantViewModel(
314317
}
315318
}
316319

320+
fun updateInputBarText(value: String) {
321+
_inputBarText.update {
322+
value
323+
}
324+
}
325+
317326
private fun removeTaskFromList(id: Long) {
318327
_filteredTaskList.update { currentList ->
319328
currentList?.filter { it.id != id }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.client.assistant.model
9+
10+
enum class AssistantPage(val id: Int) {
11+
Conversation(0),
12+
Content(1)
13+
}

app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
*/
88
package com.nextcloud.ui.composeActivity
99

10+
import android.content.Intent
1011
import android.os.Bundle
1112
import android.view.MenuItem
1213
import android.view.View
14+
import androidx.activity.viewModels
1315
import androidx.compose.material3.MaterialTheme
1416
import androidx.compose.runtime.Composable
1517
import androidx.compose.runtime.LaunchedEffect
@@ -35,6 +37,7 @@ import com.owncloud.android.ui.activity.DrawerActivity
3537
class ComposeActivity : DrawerActivity() {
3638

3739
lateinit var binding: ActivityComposeBinding
40+
private val composeViewModel: ComposeViewModel by viewModels()
3841

3942
companion object {
4043
const val DESTINATION = "DESTINATION"
@@ -46,9 +49,8 @@ class ComposeActivity : DrawerActivity() {
4649
setContentView(binding.root)
4750

4851
val destination =
49-
intent.getParcelableArgument(DESTINATION, ComposeDestination::class.java) ?: throw IllegalArgumentException(
50-
"destination is not exists"
51-
)
52+
intent.getParcelableArgument(DESTINATION, ComposeDestination::class.java)
53+
?: ComposeDestination.getAssistantScreen(this)
5254

5355
setupActivityUIFor(destination)
5456

@@ -60,6 +62,22 @@ class ComposeActivity : DrawerActivity() {
6062
}
6163
)
6264
}
65+
66+
processText(intent)
67+
}
68+
69+
override fun onNewIntent(intent: Intent) {
70+
super.onNewIntent(intent)
71+
processText(intent)
72+
}
73+
74+
private fun processText(intent: Intent) {
75+
val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
76+
if (text.isNullOrEmpty()) {
77+
return
78+
}
79+
80+
composeViewModel.updateSelectedText(text.toString())
6381
}
6482

6583
private fun setupActivityUIFor(destination: ComposeDestination) {
@@ -105,6 +123,7 @@ class ComposeActivity : DrawerActivity() {
105123
val client = nextcloudClient ?: return
106124

107125
AssistantScreen(
126+
composeViewModel = composeViewModel,
108127
viewModel = AssistantViewModel(
109128
accountName = userAccountManager.user.accountName,
110129
remoteRepository = AssistantRemoteRepositoryImpl(client, capabilities),
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nextcloud.ui.composeActivity
9+
10+
import androidx.lifecycle.ViewModel
11+
import kotlinx.coroutines.flow.MutableStateFlow
12+
import kotlinx.coroutines.flow.StateFlow
13+
import kotlinx.coroutines.flow.update
14+
15+
class ComposeViewModel : ViewModel() {
16+
private val _selectedText = MutableStateFlow<String?>(null)
17+
val selectedText: StateFlow<String?> = _selectedText
18+
19+
fun updateSelectedText(value: String) {
20+
_selectedText.update {
21+
value
22+
}
23+
}
24+
}

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
<string name="conversation_screen_delete_error_title">Failed to delete conversation</string>
8181
<string name="conversation_screen_delete_button_title">Delete conversation</string>
8282

83+
<string name="compose_activity_label">Nextcloud Assistant</string>
84+
<string name="assistant_screen_text_selected">Text copied from another app</string>
8385

8486
<!-- Assistant General -->
8587
<string name="assistant_output_generation_warning_text">Output shown here is generated by AI. Make sure to always double-check.</string>

0 commit comments

Comments
 (0)