Skip to content

Commit 051465b

Browse files
committed
feat(assistant): translate
Signed-off-by: alperozturk96 <alper_ozturk@proton.me> # Conflicts: # gradle/libs.versions.toml
1 parent c11f99d commit 051465b

7 files changed

Lines changed: 223 additions & 1 deletion

File tree

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import com.nextcloud.client.assistant.repository.local.MockAssistantLocalReposit
6767
import com.nextcloud.client.assistant.repository.remote.MockAssistantRemoteRepository
6868
import com.nextcloud.client.assistant.task.TaskView
6969
import com.nextcloud.client.assistant.taskTypes.TaskTypesRow
70+
import com.nextcloud.client.assistant.translate.TranslationScreen
7071
import com.nextcloud.ui.composeActivity.ComposeActivity
7172
import com.nextcloud.ui.composeActivity.ComposeViewModel
7273
import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
@@ -192,7 +193,7 @@ fun AssistantScreen(
192193
}
193194
},
194195
bottomBar = {
195-
if (!taskTypes.isNullOrEmpty()) {
196+
if (!taskTypes.isNullOrEmpty() && selectedTaskType?.isTranslate() == false) {
196197
ChatInputBar(
197198
sessionId,
198199
selectedTaskType,
@@ -231,6 +232,10 @@ fun AssistantScreen(
231232
)
232233
}
233234

235+
AssistantScreenState.Translation -> {
236+
TranslationScreen(selectedText ?: "")
237+
}
238+
234239
else -> EmptyContent(
235240
paddingValues,
236241
iconId = R.drawable.spinner_inner,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,11 @@ class AssistantViewModel(
165165
_filteredTaskList
166166
) { selectedTask, chats, tasks ->
167167
val isChat = selectedTask?.isChat() == true
168+
val isTranslation = selectedTask?.isTranslate() == true
168169

169170
when {
170171
selectedTask == null -> AssistantScreenState.Loading
172+
isTranslation -> AssistantScreenState.Translation
171173
isChat && chats.isEmpty() -> AssistantScreenState.emptyChatList()
172174
isChat -> AssistantScreenState.ChatContent
173175
!isChat && (tasks == null || tasks.isEmpty()) -> AssistantScreenState.emptyTaskList()

app/src/main/java/com/nextcloud/client/assistant/model/AssistantScreenState.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ sealed class AssistantScreenState {
1616

1717
data object ChatContent : AssistantScreenState()
1818

19+
data object Translation : AssistantScreenState()
20+
1921
data class EmptyContent(val iconId: Int?, val titleId: Int?, val descriptionId: Int?) : AssistantScreenState()
2022

2123
companion object {
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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.translate
9+
10+
import androidx.compose.foundation.clickable
11+
import androidx.compose.foundation.layout.Row
12+
import androidx.compose.foundation.layout.Spacer
13+
import androidx.compose.foundation.layout.fillMaxSize
14+
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.heightIn
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.layout.width
18+
import androidx.compose.foundation.lazy.LazyColumn
19+
import androidx.compose.material3.DropdownMenu
20+
import androidx.compose.material3.DropdownMenuItem
21+
import androidx.compose.material3.ExperimentalMaterial3Api
22+
import androidx.compose.material3.FloatingActionButton
23+
import androidx.compose.material3.HorizontalDivider
24+
import androidx.compose.material3.Icon
25+
import androidx.compose.material3.MaterialTheme
26+
import androidx.compose.material3.Scaffold
27+
import androidx.compose.material3.Text
28+
import androidx.compose.material3.TextField
29+
import androidx.compose.material3.TextFieldDefaults
30+
import androidx.compose.runtime.Composable
31+
import androidx.compose.runtime.getValue
32+
import androidx.compose.runtime.mutableStateOf
33+
import androidx.compose.runtime.remember
34+
import androidx.compose.runtime.setValue
35+
import androidx.compose.ui.Modifier
36+
import androidx.compose.ui.graphics.Color
37+
import androidx.compose.ui.res.painterResource
38+
import androidx.compose.ui.res.stringResource
39+
import androidx.compose.ui.unit.dp
40+
import com.owncloud.android.R
41+
42+
@OptIn(ExperimentalMaterial3Api::class)
43+
@Composable
44+
fun TranslationScreen(textToTranslate: String) {
45+
var originText by remember { mutableStateOf(textToTranslate) }
46+
var originLanguage by remember { mutableStateOf("English") }
47+
var showOriginDropdownMenu by remember { mutableStateOf(false) }
48+
49+
var targetText by remember { mutableStateOf("") }
50+
var targetLanguage by remember { mutableStateOf("Spanish") }
51+
var showTargetDropdownMenu by remember { mutableStateOf(false) }
52+
53+
val languages = listOf("English", "Spanish", "French", "German", "Turkish", "Japanese")
54+
55+
Scaffold(
56+
modifier = Modifier
57+
.fillMaxSize()
58+
.padding(16.dp)
59+
.padding(top = 32.dp), floatingActionButton = {
60+
FloatingActionButton(onClick = {
61+
62+
}, content = {
63+
Icon(
64+
painter = painterResource(R.drawable.ic_translate),
65+
contentDescription = "translate button"
66+
)
67+
})
68+
}) {
69+
LazyColumn(
70+
modifier = Modifier.padding(it)
71+
) {
72+
item {
73+
LanguageSelector(
74+
title = originLanguage,
75+
languages = languages,
76+
titleId = R.string.translation_screen_label_from,
77+
expanded = showOriginDropdownMenu,
78+
expand = {
79+
showOriginDropdownMenu = it
80+
}, onLanguageSelect = {
81+
originLanguage = it
82+
}
83+
)
84+
85+
TranslationTextField(titleId = R.string.translation_screen_hint_source, originText, onValueChange = {
86+
originText = it
87+
})
88+
}
89+
90+
item {
91+
HorizontalDivider(
92+
modifier = Modifier.padding(vertical = 16.dp),
93+
thickness = 1.dp,
94+
color = MaterialTheme.colorScheme.outlineVariant
95+
)
96+
}
97+
98+
item {
99+
LanguageSelector(
100+
title = targetLanguage,
101+
languages = languages,
102+
titleId = R.string.translation_screen_label_to,
103+
expanded = showTargetDropdownMenu,
104+
expand = {
105+
showTargetDropdownMenu = it
106+
}, onLanguageSelect = {
107+
targetLanguage = it
108+
}
109+
)
110+
111+
TranslationTextField(titleId = R.string.translation_screen_hint_target, targetText, onValueChange = {
112+
targetText = it
113+
})
114+
}
115+
}
116+
}
117+
}
118+
119+
@Composable
120+
private fun TranslationTextField(titleId: Int, value: String, onValueChange: (String) -> Unit) {
121+
TextField(
122+
value = value,
123+
onValueChange = {
124+
onValueChange(it)
125+
},
126+
modifier = Modifier
127+
.fillMaxWidth()
128+
.heightIn(min = 120.dp, max = 240.dp),
129+
placeholder = {
130+
Text(
131+
text = stringResource(titleId),
132+
style = MaterialTheme.typography.headlineSmall
133+
)
134+
},
135+
textStyle = MaterialTheme.typography.headlineSmall,
136+
colors = TextFieldDefaults.colors(
137+
focusedContainerColor = Color.Transparent,
138+
unfocusedContainerColor = Color.Transparent,
139+
disabledContainerColor = Color.Transparent,
140+
focusedIndicatorColor = Color.Transparent,
141+
unfocusedIndicatorColor = Color.Transparent
142+
)
143+
)
144+
}
145+
146+
@Composable
147+
private fun LanguageSelector(
148+
title: String,
149+
languages: List<String>,
150+
titleId: Int,
151+
expanded: Boolean,
152+
expand: (Boolean) -> Unit,
153+
onLanguageSelect: (String) -> Unit
154+
) {
155+
Row(
156+
modifier = Modifier
157+
.padding(16.dp)
158+
.clickable(onClick = {
159+
expand(!expanded)
160+
})
161+
) {
162+
Text(
163+
text = stringResource(titleId, title),
164+
style = MaterialTheme.typography.labelLarge,
165+
color = MaterialTheme.colorScheme.primary,
166+
)
167+
168+
Spacer(modifier = Modifier.width(8.dp))
169+
170+
Text(
171+
text = title,
172+
style = MaterialTheme.typography.labelLarge,
173+
)
174+
175+
DropdownMenu(
176+
expanded = expanded,
177+
onDismissRequest = { expand(false) }
178+
) {
179+
languages.forEach { language ->
180+
DropdownMenuItem(
181+
text = { Text(language) },
182+
onClick = {
183+
expand(false)
184+
onLanguageSelect(language)
185+
}
186+
)
187+
}
188+
}
189+
}
190+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="960"
5+
android:viewportHeight="960">
6+
<path
7+
android:pathData="m476,880 l182,-480h84L924,880h-84l-43,-122L603,758L560,880h-84ZM160,760l-56,-56 202,-202q-35,-35 -63.5,-80T190,320h84q20,39 40,68t48,58q33,-33 68.5,-92.5T484,240L40,240v-80h280v-80h80v80h280v80L564,240q-21,72 -63,148t-83,116l96,98 -30,82 -122,-125 -202,201ZM628,688h144l-72,-204 -72,204Z"
8+
android:fillColor="#e3e3e3"/>
9+
</vector>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@
7171
<string name="assistant_task_detail_screen_input_button_title">Input</string>
7272
<string name="assistant_task_detail_screen_output_button_title">Output</string>
7373

74+
<!-- Translation Screen -->
75+
<string name="translation_screen_label_from">Source Language: </string>
76+
<string name="translation_screen_label_to">Translate to: </string>
77+
<string name="translation_screen_hint_source">Enter text to translate…</string>
78+
<string name="translation_screen_hint_target">Translation will appear here…</string>
79+
7480
<!-- Conversation Screen -->
7581
<string name="conversation_screen_title">Conversations</string>
7682
<string name="conversation_screen_empty_conversation_list_title">No conversations yet</string>

gradle/verification-metadata.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20012,6 +20012,14 @@
2001220012
<sha256 value="7dab0f91c62dcc0e1e07b026c517a97bc62edf90b5c874bd0243b4fad814170e" origin="Generated by Gradle" reason="Artifact is not signed"/>
2001320013
</artifact>
2001420014
</component>
20015+
<component group="com.github.nextcloud" name="android-library" version="7db0d8d735">
20016+
<artifact name="android-library-7db0d8d735.aar">
20017+
<sha256 value="8daab0839b335853a1d5408d87866890b403e8e77c3c7ba1fbe76886f0440288" origin="Generated by Gradle" reason="Artifact is not signed"/>
20018+
</artifact>
20019+
<artifact name="android-library-7db0d8d735.module">
20020+
<sha256 value="11cb881d2323282c4ff121e182fd8f3e2228b05681da3823df20504b61624183" origin="Generated by Gradle" reason="Artifact is not signed"/>
20021+
</artifact>
20022+
</component>
2001520023
<component group="com.github.nextcloud" name="android-library" version="827db94ca661d39ca7fae5c608eab1282b629b84">
2001620024
<artifact name="android-library-827db94ca661d39ca7fae5c608eab1282b629b84.aar">
2001720025
<sha256 value="d498cd9e68c7c4d1bfa3013372e3a575d692d9f6bb525df7e881fed0a718a246" origin="Generated by Gradle" reason="Artifact is not signed"/>

0 commit comments

Comments
 (0)