Skip to content

Commit 68b8c32

Browse files
committed
feat(assistant-screen): PROCESS_TEXT support
Signed-off-by: alperozturk96 <alper_ozturk@proton.me>
1 parent 18d4441 commit 68b8c32

6 files changed

Lines changed: 97 additions & 10 deletions

File tree

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: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,22 @@ import com.nextcloud.client.assistant.conversation.ConversationViewModel
6161
import com.nextcloud.client.assistant.conversation.repository.MockConversationRemoteRepository
6262
import com.nextcloud.client.assistant.extensions.getInputTitle
6363
import com.nextcloud.client.assistant.model.AssistantScreenState
64+
import com.nextcloud.client.assistant.model.AssistantPage
6465
import com.nextcloud.client.assistant.model.ScreenOverlayState
6566
import com.nextcloud.client.assistant.repository.local.MockAssistantLocalRepository
6667
import com.nextcloud.client.assistant.repository.remote.MockAssistantRemoteRepository
6768
import com.nextcloud.client.assistant.task.TaskView
6869
import com.nextcloud.client.assistant.taskTypes.TaskTypesRow
6970
import com.nextcloud.ui.composeActivity.ComposeActivity
71+
import com.nextcloud.ui.composeActivity.ComposeViewModel
7072
import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
7173
import com.nextcloud.ui.composeComponents.bottomSheet.MoreActionsBottomSheet
7274
import com.nextcloud.utils.extensions.getChat
7375
import com.owncloud.android.R
7476
import com.owncloud.android.lib.resources.assistant.v2.model.Task
7577
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
7678
import com.owncloud.android.lib.resources.status.OCCapability
79+
import com.owncloud.android.utils.ClipboardUtil
7780
import kotlinx.coroutines.delay
7881
import kotlinx.coroutines.launch
7982

@@ -84,11 +87,13 @@ private const val PULL_TO_REFRESH_DELAY = 1500L
8487
@OptIn(ExperimentalMaterial3Api::class)
8588
@Composable
8689
fun AssistantScreen(
90+
composeViewModel: ComposeViewModel,
8791
viewModel: AssistantViewModel,
8892
conversationViewModel: ConversationViewModel,
8993
capability: OCCapability,
9094
activity: Activity
9195
) {
96+
val selectedText by composeViewModel.selectedText.collectAsState()
9297
val sessionId by viewModel.sessionId.collectAsState()
9398
val messageId by viewModel.snackbarMessageId.collectAsState()
9499
val screenOverlayState by viewModel.screenOverlayState.collectAsState()
@@ -99,7 +104,8 @@ fun AssistantScreen(
99104
val scope = rememberCoroutineScope()
100105
val pullRefreshState = rememberPullToRefreshState()
101106
val snackbarHostState = remember { SnackbarHostState() }
102-
val pagerState = rememberPagerState(initialPage = 1, pageCount = { 2 })
107+
val pagerState =
108+
rememberPagerState(initialPage = AssistantPage.Content.id, pageCount = { AssistantPage.entries.size })
103109

104110
LaunchedEffect(messageId) {
105111
messageId?.let {
@@ -108,6 +114,20 @@ fun AssistantScreen(
108114
}
109115
}
110116

117+
LaunchedEffect(selectedText) {
118+
if (selectedText.isNullOrEmpty()) {
119+
return@LaunchedEffect
120+
}
121+
122+
if (pagerState.currentPage == AssistantPage.Conversation.id) {
123+
pagerState.scrollToPage(AssistantPage.Content.id)
124+
}
125+
126+
ClipboardUtil.copyToClipboard(activity, selectedText, false)
127+
128+
snackbarHostState.showSnackbar(activity.getString(R.string.assistant_screen_text_selected))
129+
}
130+
111131
LaunchedEffect(sessionId) {
112132
viewModel.startPolling(sessionId)
113133

@@ -127,22 +147,22 @@ fun AssistantScreen(
127147
userScrollEnabled = taskTypes.getChat() != null
128148
) { page ->
129149
when (page) {
130-
0 -> {
150+
AssistantPage.Conversation.id -> {
131151
ConversationScreen(viewModel = conversationViewModel, close = {
132152
scope.launch {
133-
pagerState.scrollToPage(1)
153+
pagerState.scrollToPage(AssistantPage.Content.id)
134154
}
135155
}, openChat = { newSessionId ->
136156
viewModel.initSessionId(newSessionId)
137157
taskTypes.getChat()?.let { chatTaskType ->
138158
viewModel.selectTaskType(chatTaskType)
139159
}
140160
scope.launch {
141-
pagerState.scrollToPage(1)
161+
pagerState.scrollToPage(AssistantPage.Content.id)
142162
}
143163
})
144164
}
145-
1 -> {
165+
AssistantPage.Content.id -> {
146166
Scaffold(
147167
modifier = Modifier.pullToRefresh(
148168
false,
@@ -166,7 +186,7 @@ fun AssistantScreen(
166186
viewModel.selectTaskType(task)
167187
}, navigateToConversationList = {
168188
scope.launch {
169-
pagerState.scrollToPage(0)
189+
pagerState.scrollToPage(AssistantPage.Conversation.id)
170190
}
171191
})
172192
}
@@ -413,6 +433,7 @@ private fun AssistantScreenPreview() {
413433
MaterialTheme(
414434
content = {
415435
AssistantScreen(
436+
composeViewModel = ComposeViewModel(),
416437
conversationViewModel = getMockConversationViewModel(),
417438
viewModel = getMockAssistantViewModel(false),
418439
activity = ComposeActivity(),
@@ -431,6 +452,7 @@ private fun AssistantEmptyScreenPreview() {
431452
MaterialTheme(
432453
content = {
433454
AssistantScreen(
455+
composeViewModel = ComposeViewModel(),
434456
conversationViewModel = getMockConversationViewModel(),
435457
viewModel = getMockAssistantViewModel(true),
436458
activity = ComposeActivity(),
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)