Merge remote-tracking branch 'origin/master' into dev

This commit is contained in:
Tobias Kaminsky 2024-03-14 03:43:12 +01:00
commit b449bc4bf1
119 changed files with 3539 additions and 210 deletions

View file

@ -49,7 +49,7 @@ jobs:
repository: ${{ steps.get-vars.outputs.repo }} repository: ${{ steps.get-vars.outputs.repo }}
ref: ${{ steps.get-vars.outputs.branch }} ref: ${{ steps.get-vars.outputs.branch }}
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -21,7 +21,7 @@ jobs:
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: set up JDK 17 - name: set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -21,7 +21,7 @@ jobs:
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -43,7 +43,7 @@ jobs:
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -18,7 +18,7 @@ jobs:
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
if: ${{ steps.check-secrets.outputs.ok == 'true' }} if: ${{ steps.check-secrets.outputs.ok == 'true' }}
- name: set up JDK 17 - name: set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
if: ${{ steps.check-secrets.outputs.ok == 'true' }} if: ${{ steps.check-secrets.outputs.ok == 'true' }}
with: with:
distribution: "temurin" distribution: "temurin"

View file

@ -40,7 +40,7 @@ jobs:
~/.android/adb* ~/.android/adb*
key: avd-${{ matrix.api-level }} key: avd-${{ matrix.api-level }}
- uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 - uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -20,7 +20,7 @@ jobs:
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@9704b39bf258b59bc04b50fa2dd55e9ed76b47a8 # v4.1.0 uses: actions/setup-java@5896cecc08fd8a1fbdfaf517e29b571164b031f7 # v4.2.0
with: with:
distribution: "temurin" distribution: "temurin"
java-version: 17 java-version: 17

View file

@ -2,9 +2,45 @@
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="ktlint" /> <option name="myName" value="ktlint" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true"> <inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.nio.channels.FileChannel,position" /> <option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.nio.channels.FileChannel,position,okhttp3.Call,execute" />
</inspection_tool> </inspection_tool>
<inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" /> <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" /> <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
</profile> </profile>
</component> </component>

View file

@ -19,7 +19,7 @@ buildscript {
plugins { plugins {
id "com.diffplug.spotless" version "6.20.0" id "com.diffplug.spotless" version "6.20.0"
id 'com.google.devtools.ksp' version '1.9.23-1.0.19' apply false id 'com.google.devtools.ksp' version '1.9.22-1.0.17' apply false
} }
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
@ -223,6 +223,7 @@ android {
dataBinding true dataBinding true
viewBinding true viewBinding true
aidl true aidl true
compose = true
} }
compileOptions { compileOptions {
@ -246,6 +247,10 @@ android {
// Adds exported schema location as test app assets. // Adds exported schema location as test app assets.
androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
} }
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
} }
dependencies { dependencies {
@ -255,6 +260,14 @@ dependencies {
exclude group: 'org.ogce', module: 'xpp3' // unused in Android and brings wrong Junit version exclude group: 'org.ogce', module: 'xpp3' // unused in Android and brings wrong Junit version
} }
// Jetpack Compose
implementation(platform("androidx.compose:compose-bom:2024.02.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview:1.6.2")
debugImplementation 'androidx.compose.ui:ui-tooling:1.6.2'
compileOnly 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' compileOnly 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2'
// remove after entire switch to lib v2 // remove after entire switch to lib v2
implementation "commons-httpclient:commons-httpclient:3.1@jar" // remove after entire switch to lib v2 implementation "commons-httpclient:commons-httpclient:3.1@jar" // remove after entire switch to lib v2

View file

@ -199,7 +199,7 @@ naming:
minimumFunctionNameLength: 3 minimumFunctionNameLength: 3
FunctionNaming: FunctionNaming:
active: true active: true
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' functionPattern: '^([a-z$A-Z][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^' excludeClassPattern: '$^'
ignoreOverridden: true ignoreOverridden: true
excludes: excludes:

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -21,6 +21,7 @@
*/ */
package com.nextcloud.client package com.nextcloud.client
import android.view.View
import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso
import androidx.test.espresso.contrib.DrawerActions import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.intent.rule.IntentsTestRule import androidx.test.espresso.intent.rule.IntentsTestRule
@ -60,15 +61,19 @@ class ActivitiesActivityIT : AbstractIT() {
@Test @Test
@ScreenshotTest @ScreenshotTest
fun loading() { fun loading() {
val sut: ActivitiesActivity = activityRule.launchActivity(null) val sut: ActivitiesActivity = activityRule.launchActivity(null).apply {
sut.runOnUiThread { runOnUiThread {
sut.dismissSnackbar() dismissSnackbar()
binding.emptyList.root.visibility = View.GONE
binding.swipeContainingList.visibility = View.GONE
binding.loadingContent.visibility = View.VISIBLE
}
} }
shortSleep() shortSleep()
waitForIdleSync() waitForIdleSync()
Screenshot.snapActivity(sut).record() Screenshot.snap(sut.binding.loadingContent).record()
} }
@Test @Test

View file

@ -27,6 +27,7 @@ import android.content.Intent;
import com.nextcloud.client.preferences.SubFolderRule; import com.nextcloud.client.preferences.SubFolderRule;
import com.owncloud.android.AbstractIT; import com.owncloud.android.AbstractIT;
import com.owncloud.android.databinding.SyncedFoldersLayoutBinding;
import com.owncloud.android.datamodel.MediaFolderType; import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.SyncedFolderDisplayItem; import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
import com.owncloud.android.ui.activity.SyncedFoldersActivity; import com.owncloud.android.ui.activity.SyncedFoldersActivity;
@ -51,9 +52,11 @@ public class SyncedFoldersActivityIT extends AbstractIT {
@Test @Test
@ScreenshotTest @ScreenshotTest
public void open() { public void open() {
Activity sut = activityRule.launchActivity(null); SyncedFoldersActivity activity = activityRule.launchActivity(null);
activity.adapter.clear();
screenshot(sut); SyncedFoldersLayoutBinding sut = activity.binding;
shortSleep();
screenshot(sut.emptyList.emptyListView);
} }
@Test @Test

View file

@ -0,0 +1,109 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant
import com.nextcloud.client.assistant.repository.AssistantRepository
import com.owncloud.android.AbstractOnServerIT
import com.owncloud.android.lib.resources.status.NextcloudVersion
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@Suppress("MagicNumber")
class AssistantRepositoryTests : AbstractOnServerIT() {
private var sut: AssistantRepository? = null
@Before
fun setup() {
sut = AssistantRepository(nextcloudClient)
}
@Test
fun testGetTaskTypes() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
val result = sut?.getTaskTypes()
assertTrue(result?.isSuccess == true)
val taskTypes = result?.resultData?.types
assertTrue(taskTypes?.isNotEmpty() == true)
}
@Test
fun testGetTaskList() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
val result = sut?.getTaskList("assistant")
assertTrue(result?.isSuccess == true)
val taskList = result?.resultData?.tasks
assertTrue(taskList?.isEmpty() == true || (taskList?.size ?: 0) > 0)
}
@Test
fun testCreateTask() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
val input = "Give me some random output for test purpose"
val type = "OCP\\TextProcessing\\FreePromptTaskType"
val result = sut?.createTask(input, type)
assertTrue(result?.isSuccess == true)
}
@Test
fun testDeleteTask() {
testOnlyOnServer(NextcloudVersion.nextcloud_28)
if (capability.assistant.isFalse) {
return
}
testCreateTask()
sleep(120)
val resultOfTaskList = sut?.getTaskList("assistant")
assertTrue(resultOfTaskList?.isSuccess == true)
sleep(120)
val taskList = resultOfTaskList?.resultData?.tasks
assert((taskList?.size ?: 0) > 0)
val result = sut?.deleteTask(taskList!!.first().id)
assertTrue(result?.isSuccess == true)
}
}

View file

@ -195,13 +195,18 @@ public abstract class AbstractIT {
} }
protected void testOnlyOnServer(OwnCloudVersion version) throws AccountUtils.AccountNotFoundException { protected void testOnlyOnServer(OwnCloudVersion version) throws AccountUtils.AccountNotFoundException {
OCCapability ocCapability = getCapability();
assumeTrue(ocCapability.getVersion().isNewerOrEqual(version));
}
protected OCCapability getCapability() throws AccountUtils.AccountNotFoundException {
NextcloudClient client = OwnCloudClientFactory.createNextcloudClient(user, targetContext); NextcloudClient client = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
OCCapability ocCapability = (OCCapability) new GetCapabilitiesRemoteOperation() OCCapability ocCapability = (OCCapability) new GetCapabilitiesRemoteOperation()
.execute(client) .execute(client)
.getSingleData(); .getSingleData();
assumeTrue(ocCapability.getVersion().isNewerOrEqual(version)); return ocCapability;
} }
@Before @Before
@ -334,6 +339,14 @@ public abstract class AbstractIT {
} }
} }
protected void sleep(int second) {
try {
Thread.sleep(1000L * second);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public OCFile createFolder(String remotePath) { public OCFile createFolder(String remotePath) {
RemoteOperationResult check = new ExistenceCheckRemoteOperation(remotePath, false).execute(client); RemoteOperationResult check = new ExistenceCheckRemoteOperation(remotePath, false).execute(client);

View file

@ -53,8 +53,6 @@ class NotificationsActivityIT : AbstractIT() {
@ScreenshotTest @ScreenshotTest
@SuppressWarnings("MagicNumber") @SuppressWarnings("MagicNumber")
fun showNotifications() { fun showNotifications() {
val sut: NotificationsActivity = activityRule.launchActivity(null)
val date = GregorianCalendar() val date = GregorianCalendar()
date.set(2005, 4, 17, 10, 35, 30) // random date date.set(2005, 4, 17, 10, 35, 30) // random date
@ -133,11 +131,13 @@ class NotificationsActivityIT : AbstractIT() {
) )
) )
sut.runOnUiThread { sut.populateList(notifications) } activityRule.launchActivity(null).apply {
runOnUiThread {
populateList(notifications)
}
shortSleep() shortSleep()
screenshot(binding.list)
screenshot(sut) }
} }
@Test @Test

View file

@ -72,7 +72,7 @@ class UploadFilesActivityIT : AbstractIT() {
waitForIdleSync() waitForIdleSync()
shortSleep() shortSleep()
screenshot(sut) screenshot(sut.fileListFragment.binding.emptyList.emptyListView)
} }
@Test @Test

View file

@ -94,12 +94,6 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
@ScreenshotTest @ScreenshotTest
@Suppress("MagicNumber") @Suppress("MagicNumber")
fun showDetailsActivities() { fun showDetailsActivities() {
val activity = testActivityRule.launchActivity(null)
val sut = FileDetailFragment.newInstance(oCFile, user, 0)
activity.addFragment(sut)
waitForIdleSync()
val date = GregorianCalendar() val date = GregorianCalendar()
date.set(2005, 4, 17, 10, 35, 30) // random date date.set(2005, 4, 17, 10, 35, 30) // random date
@ -152,13 +146,16 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
) )
) )
activity.runOnUiThread { val sut = FileDetailFragment.newInstance(oCFile, user, 0)
testActivityRule.launchActivity(null).apply {
addFragment(sut)
waitForIdleSync()
runOnUiThread {
sut.fileDetailActivitiesFragment.populateList(activities as List<Any>?, true) sut.fileDetailActivitiesFragment.populateList(activities as List<Any>?, true)
} }
longSleep()
shortSleep() screenshot(sut.fileDetailActivitiesFragment.binding.swipeContainingList)
shortSleep() }
screenshot(activity)
} }
// @Test // @Test
@ -176,7 +173,7 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
shortSleep() shortSleep()
shortSleep() shortSleep()
screenshot(activity) screenshot(sut.fileDetailActivitiesFragment.binding.list)
} }
@Test @Test
@ -197,7 +194,7 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
shortSleep() shortSleep()
shortSleep() shortSleep()
screenshot(activity) screenshot(sut.fileDetailActivitiesFragment.binding.emptyList.emptyListView)
} }
@Test @Test

View file

@ -89,7 +89,7 @@ class TrashbinActivityIT : AbstractIT() {
shortSleep() shortSleep()
waitForIdleSync() waitForIdleSync()
screenshot(sut) screenshot(sut.binding.emptyList.emptyListView)
} }
@Test @Test
@ -105,7 +105,7 @@ class TrashbinActivityIT : AbstractIT() {
shortSleep() shortSleep()
screenshot(sut) screenshot(sut.binding.listFragmentLayout)
} }
@Test @Test

View file

@ -610,7 +610,7 @@ public class EncryptionTestIT extends AbstractIT {
EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, publicKey); EncryptionUtils.encryptFileDropFiles(decryptedFolderMetadata1, encryptedFolderMetadata1, publicKey);
// serialize // serialize
String encryptedJson = serializeJSON(encryptedFolderMetadata1); String encryptedJson = serializeJSON(encryptedFolderMetadata1, true);
// de-serialize // de-serialize
EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson, EncryptedFolderMetadataFileV1 encryptedFolderMetadata2 = deserializeJSON(encryptedJson,
@ -626,8 +626,8 @@ public class EncryptionTestIT extends AbstractIT {
folderID); folderID);
// compare // compare
assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1), assertFalse(compareJsonStrings(serializeJSON(decryptedFolderMetadata1, true),
serializeJSON(decryptedFolderMetadata2))); serializeJSON(decryptedFolderMetadata2, true)));
assertEquals(decryptedFolderMetadata1.getFiles().size() + decryptedFolderMetadata1.getFiledrop().size(), assertEquals(decryptedFolderMetadata1.getFiles().size() + decryptedFolderMetadata1.getFiledrop().size(),
decryptedFolderMetadata2.getFiles().size()); decryptedFolderMetadata2.getFiles().size());

View file

@ -831,7 +831,7 @@ class EncryptionUtilsV2IT : AbstractIT() {
val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encryptedFolderMetadata1) val signature = encryptionUtilsV2.getMessageSignature(enc1Cert, enc1PrivateKey, encryptedFolderMetadata1)
// serialize // serialize
val encryptedJson = EncryptionUtils.serializeJSON(encryptedFolderMetadata1) val encryptedJson = EncryptionUtils.serializeJSON(encryptedFolderMetadata1, true)
// de-serialize // de-serialize
val encryptedFolderMetadata2 = EncryptionUtils.deserializeJSON( val encryptedFolderMetadata2 = EncryptionUtils.deserializeJSON(

View file

@ -1,22 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
Nextcloud Android client application
Copyright (C) 2012 Bartek Przybylski
Copyright (C) 2012-2016 ownCloud Inc.
Copyright (C) 2016 Nextcloud
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
@ -24,16 +6,15 @@
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> <!-- Used for document scanning, but lib declares it as required, which it's not -->
<!-- Used for document scanning, but lib declares it as required, which it's not -->
<uses-feature <uses-feature
android:name="android.hardware.camera2" android:name="android.hardware.camera2"
android:required="false" android:required="false"
tools:node="replace" /> tools:node="replace" />
<!--
<!-- WRITE_EXTERNAL_STORAGE may be enabled or disabled by the user after installation in WRITE_EXTERNAL_STORAGE may be enabled or disabled by the user after installation in
API >= 23; the app needs to handle this --> API >= 23; the app needs to handle this
-->
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" android:maxSdkVersion="29"
@ -45,9 +26,7 @@
android:name="android.permission.READ_EXTERNAL_STORAGE" android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" /> <!-- Next permissions are always approved in installation time, the apps needs to do nothing special in runtime -->
<!-- Next permissions are always approved in installation time, the apps needs to do nothing special in runtime -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" /> <uses-permission android:name="android.permission.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
@ -62,35 +41,72 @@
<uses-permission <uses-permission
android:name="com.android.launcher.permission.INSTALL_SHORTCUT" android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
android:maxSdkVersion="25" /> android:maxSdkVersion="25" />
<!--
<!-- Apps that target Android 9 (API level 28) or higher and use foreground services Apps that target Android 9 (API level 28) or higher and use foreground services
must request the FOREGROUND_SERVICE permission --> must request the FOREGROUND_SERVICE permission
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Runtime permissions introduced in Android 13 (API level 33) -->
<!-- Runtime permissions introduced in Android 13 (API level 33) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- Needed for Android 14 (API level 34) -->
<!-- Needed for Android 14 (API level 34) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!--
<!-- Some Chromebooks don't support touch. Although not essential, Some Chromebooks don't support touch. Although not essential,
it's a good idea to explicitly include this declaration. --> it's a good idea to explicitly include this declaration.
-->
<uses-feature <uses-feature
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false" /> android:required="false" />
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
<queries>
<package android:name="it.niedermann.nextcloud.deck" />
<package android:name="it.niedermann.nextcloud.deck.play" />
<package android:name="it.niedermann.nextcloud.deck.dev" />
<package android:name="at.bitfire.davdroid" />
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.PICK" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
</intent>
<intent>
<action android:name="android.media.action.VIDEO_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
</intent>
</queries>
<application <application
android:name=".MainApp" android:name=".MainApp"
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="@xml/backup_config"
android:dataExtractionRules="@xml/backup_rules" android:dataExtractionRules="@xml/backup_rules"
android:fullBackupContent="@xml/backup_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:installLocation="internalOnly" android:installLocation="internalOnly"
android:label="@string/app_name" android:label="@string/app_name"
@ -101,8 +117,11 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.ownCloud.Toolbar" android:theme="@style/Theme.ownCloud.Toolbar"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:replace="android:allowBackup" tools:ignore="UnusedAttribute"
tools:ignore="UnusedAttribute"> tools:replace="android:allowBackup">
<activity
android:name="com.nextcloud.ui.composeActivity.ComposeActivity"
android:exported="false" />
<uses-library <uses-library
android:name="org.apache.http.legacy" android:name="org.apache.http.legacy"
@ -203,8 +222,8 @@
<activity <activity
android:name=".ui.activity.SetupEncryptionActivity" android:name=".ui.activity.SetupEncryptionActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:theme="@style/Theme.NoBackground" android:exported="false"
android:exported="false" /> android:theme="@style/Theme.NoBackground" />
<activity <activity
android:name=".ui.activity.ContactsPreferenceActivity" android:name=".ui.activity.ContactsPreferenceActivity"
android:exported="false" android:exported="false"
@ -217,12 +236,16 @@
android:theme="@style/Theme.ownCloud.NoActionBar"> android:theme="@style/Theme.ownCloud.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" /> <action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -236,9 +259,10 @@
android:theme="@style/Theme.ownCloud.Overlay" /> android:theme="@style/Theme.ownCloud.Overlay" />
<activity <activity
android:name=".ui.preview.PreviewMediaActivity" android:name=".ui.preview.PreviewMediaActivity"
android:exported="false"
android:configChanges="orientation|screenLayout|screenSize|keyboardHidden" android:configChanges="orientation|screenLayout|screenSize|keyboardHidden"
android:exported="false"
android:theme="@style/Theme.ownCloud.Media" /> android:theme="@style/Theme.ownCloud.Media" />
<service <service
android:name=".authentication.AccountAuthenticatorService" android:name=".authentication.AccountAuthenticatorService"
android:exported="false"> android:exported="false">
@ -264,8 +288,8 @@
</service> </service>
<service <service
android:name="com.nextcloud.client.widget.DashboardWidgetService" android:name="com.nextcloud.client.widget.DashboardWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="true"
android:exported="true" /> android:permission="android.permission.BIND_REMOTEVIEWS" />
<provider <provider
android:name=".providers.FileContentProvider" android:name=".providers.FileContentProvider"
@ -303,14 +327,12 @@
android:readPermission="false" android:readPermission="false"
android:writePermission="false" /> android:writePermission="false" />
</provider> </provider>
<provider <provider
android:name=".providers.UsersAndGroupsSearchProvider" android:name=".providers.UsersAndGroupsSearchProvider"
android:authorities="@string/users_and_groups_search_authority" android:authorities="@string/users_and_groups_search_authority"
android:enabled="true" android:enabled="true"
android:exported="false" android:exported="false"
android:label="@string/share_search" /> android:label="@string/share_search" />
<provider <provider
android:name=".providers.DocumentsStorageProvider" android:name=".providers.DocumentsStorageProvider"
android:authorities="@string/document_provider_authority" android:authorities="@string/document_provider_authority"
@ -321,9 +343,7 @@
<intent-filter> <intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" /> <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter> </intent-filter>
</provider> </provider> <!-- new provider used to generate URIs without file:// scheme (forbidden from Android 7) -->
<!-- new provider used to generate URIs without file:// scheme (forbidden from Android 7) -->
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="@string/file_provider_authority" android:authorities="@string/file_provider_authority"
@ -338,8 +358,7 @@
android:authorities="@string/image_cache_provider_authority" android:authorities="@string/image_cache_provider_authority"
android:exported="true" android:exported="true"
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS" /> android:permission="android.permission.MANAGE_DOCUMENTS" /> <!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
<!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
<!-- to "best before" dates in his fridge. --> <!-- to "best before" dates in his fridge. -->
<!-- disable default provider --> <!-- disable default provider -->
<provider <provider
@ -395,12 +414,12 @@
android:exported="false" /> android:exported="false" />
<service <service
android:name="com.nextcloud.client.jobs.transfer.FileTransferService" android:name="com.nextcloud.client.jobs.transfer.FileTransferService"
android:foregroundServiceType="dataSync" android:exported="false"
android:exported="false" /> android:foregroundServiceType="dataSync" />
<service <service
android:name="com.nextcloud.client.media.PlayerService" android:name="com.nextcloud.client.media.PlayerService"
android:foregroundServiceType="mediaPlayback" android:exported="false"
android:exported="false" /> android:foregroundServiceType="mediaPlayback" />
<activity <activity
android:name=".ui.activity.PassCodeActivity" android:name=".ui.activity.PassCodeActivity"
@ -478,6 +497,7 @@
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.app.searchable" android:name="android.app.searchable"
android:resource="@xml/users_and_groups_searchable" /> android:resource="@xml/users_and_groups_searchable" />
@ -514,7 +534,6 @@
android:name="com.nextcloud.client.editimage.EditImageActivity" android:name="com.nextcloud.client.editimage.EditImageActivity"
android:exported="false" android:exported="false"
android:theme="@style/Theme.ownCloud.Toolbar.NullBackground" /> android:theme="@style/Theme.ownCloud.Toolbar.NullBackground" />
<activity <activity
android:name="com.nmc.android.ui.LauncherActivity" android:name="com.nmc.android.ui.LauncherActivity"
android:exported="true" android:exported="true"
@ -525,42 +544,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
<queries>
<package android:name="it.niedermann.nextcloud.deck" />
<package android:name="it.niedermann.nextcloud.deck.play" />
<package android:name="it.niedermann.nextcloud.deck.dev" />
<package android:name="at.bitfire.davdroid"/>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.intent.action.PICK" />
<data android:mimeType="*/*" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE_SECURE" />
</intent>
<intent>
<action android:name="android.media.action.VIDEO_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
</intent>
</queries>
</manifest> </manifest>

View file

@ -0,0 +1,179 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.nextcloud.client.assistant.repository.AssistantRepositoryType
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.lib.resources.assistant.model.Task
import com.owncloud.android.lib.resources.assistant.model.TaskType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class AssistantViewModel(private val repository: AssistantRepositoryType) : ViewModel() {
sealed class State {
data object Idle : State()
data object Loading : State()
data class Error(val messageId: Int) : State()
data class TaskCreated(val messageId: Int) : State()
data class TaskDeleted(val messageId: Int) : State()
}
private val _state = MutableStateFlow<State>(State.Loading)
val state: StateFlow<State> = _state
private val _selectedTaskType = MutableStateFlow<TaskType?>(null)
val selectedTaskType: StateFlow<TaskType?> = _selectedTaskType
private val _taskTypes = MutableStateFlow<List<TaskType>?>(null)
val taskTypes: StateFlow<List<TaskType>?> = _taskTypes
private var _taskList: List<Task>? = null
private val _filteredTaskList = MutableStateFlow<List<Task>?>(null)
val filteredTaskList: StateFlow<List<Task>?> = _filteredTaskList
init {
getTaskTypes()
getTaskList()
}
fun createTask(
input: String,
type: String
) {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.createTask(input, type)
val messageId = if (result.isSuccess) {
R.string.assistant_screen_task_create_success_message
} else {
R.string.assistant_screen_task_create_fail_message
}
_state.update {
State.TaskCreated(messageId)
}
}
}
fun selectTaskType(task: TaskType) {
_selectedTaskType.update {
filterTaskList(task.id)
task
}
}
private fun getTaskTypes() {
viewModelScope.launch(Dispatchers.IO) {
val allTaskType = MainApp.getAppContext().getString(R.string.assistant_screen_all_task_type)
val result = arrayListOf(TaskType(null, allTaskType, null))
val taskTypesResult = repository.getTaskTypes()
if (taskTypesResult.isSuccess) {
result.addAll(taskTypesResult.resultData.types)
_taskTypes.update {
result.toList()
}
_selectedTaskType.update {
result.first()
}
} else {
_state.update {
State.Error(R.string.assistant_screen_task_types_error_state_message)
}
}
}
}
fun getTaskList(appId: String = "assistant", onCompleted: () -> Unit = {}) {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.getTaskList(appId)
if (result.isSuccess) {
_taskList = result.resultData.tasks
filterTaskList(_selectedTaskType.value?.id)
_state.update {
State.Idle
}
onCompleted()
} else {
_state.update {
State.Error(R.string.assistant_screen_task_list_error_state_message)
}
}
}
}
fun deleteTask(id: Long) {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.deleteTask(id)
val messageId = if (result.isSuccess) {
R.string.assistant_screen_task_delete_success_message
} else {
R.string.assistant_screen_task_delete_fail_message
}
_state.update {
State.TaskDeleted(messageId)
}
if (result.isSuccess) {
removeTaskFromList(id)
}
}
}
fun resetState() {
_state.update {
State.Idle
}
}
private fun filterTaskList(taskTypeId: String?) {
if (taskTypeId == null) {
_filteredTaskList.update {
_taskList
}
} else {
_filteredTaskList.update {
_taskList?.filter { it.type == taskTypeId }
}
}
}
private fun removeTaskFromList(id: Long) {
_filteredTaskList.update { currentList ->
currentList?.filter { it.id != id }
}
}
}

View file

@ -0,0 +1,279 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant
import android.app.Activity
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nextcloud.client.assistant.component.AddTaskAlertDialog
import com.nextcloud.client.assistant.component.CenterText
import com.nextcloud.client.assistant.component.TaskTypesRow
import com.nextcloud.client.assistant.component.TaskView
import com.nextcloud.client.assistant.repository.AssistantMockRepository
import com.nextcloud.ui.composeActivity.ComposeActivity
import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
import com.owncloud.android.R
import com.owncloud.android.lib.resources.assistant.model.Task
import com.owncloud.android.lib.resources.assistant.model.TaskType
import com.owncloud.android.utils.DisplayUtils
import kotlinx.coroutines.delay
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AssistantScreen(viewModel: AssistantViewModel, activity: Activity) {
val state by viewModel.state.collectAsState()
val selectedTaskType by viewModel.selectedTaskType.collectAsState()
val filteredTaskList by viewModel.filteredTaskList.collectAsState()
val taskTypes by viewModel.taskTypes.collectAsState()
var showAddTaskAlertDialog by remember { mutableStateOf(false) }
var showDeleteTaskAlertDialog by remember { mutableStateOf(false) }
var taskIdToDeleted: Long? by remember {
mutableStateOf(null)
}
val pullRefreshState = rememberPullToRefreshState()
@Suppress("MagicNumber")
if (pullRefreshState.isRefreshing) {
LaunchedEffect(true) {
delay(1500)
viewModel.getTaskList(onCompleted = {
pullRefreshState.endRefresh()
})
}
}
Box(Modifier.nestedScroll(pullRefreshState.nestedScrollConnection)) {
if (state == AssistantViewModel.State.Loading || pullRefreshState.isRefreshing) {
CenterText(text = stringResource(id = R.string.assistant_screen_loading))
} else {
if (filteredTaskList.isNullOrEmpty()) {
EmptyTaskList(selectedTaskType, taskTypes, viewModel)
} else {
AssistantContent(
filteredTaskList!!,
taskTypes,
selectedTaskType,
viewModel,
showDeleteTaskAlertDialog = { taskId ->
taskIdToDeleted = taskId
showDeleteTaskAlertDialog = true
}
)
}
}
if (pullRefreshState.isRefreshing) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
} else {
LinearProgressIndicator(progress = { pullRefreshState.progress }, modifier = Modifier.fillMaxWidth())
}
if (selectedTaskType?.name != stringResource(id = R.string.assistant_screen_all_task_type)) {
FloatingActionButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp),
onClick = {
showAddTaskAlertDialog = true
}
) {
Icon(Icons.Filled.Add, "Add Task Icon")
}
}
}
ScreenState(state, activity, viewModel)
if (showDeleteTaskAlertDialog) {
taskIdToDeleted?.let { id ->
SimpleAlertDialog(
title = stringResource(id = R.string.assistant_screen_delete_task_alert_dialog_title),
description = stringResource(id = R.string.assistant_screen_delete_task_alert_dialog_description),
dismiss = { showDeleteTaskAlertDialog = false },
onComplete = { viewModel.deleteTask(id) }
)
}
}
if (showAddTaskAlertDialog) {
selectedTaskType?.let { taskType ->
AddTaskAlertDialog(
title = taskType.name,
description = taskType.description,
addTask = { input ->
taskType.id?.let {
viewModel.createTask(input = input, type = it)
}
},
dismiss = {
showAddTaskAlertDialog = false
}
)
}
}
}
@Composable
private fun ScreenState(
state: AssistantViewModel.State,
activity: Activity,
viewModel: AssistantViewModel
) {
val messageId: Int? = when (state) {
is AssistantViewModel.State.Error -> {
state.messageId
}
is AssistantViewModel.State.TaskCreated -> {
state.messageId
}
is AssistantViewModel.State.TaskDeleted -> {
state.messageId
}
else -> {
null
}
}
messageId?.let {
DisplayUtils.showSnackMessage(
activity,
stringResource(id = messageId)
)
viewModel.resetState()
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun AssistantContent(
taskList: List<Task>,
taskTypes: List<TaskType>?,
selectedTaskType: TaskType?,
viewModel: AssistantViewModel,
showDeleteTaskAlertDialog: (Long) -> Unit
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
stickyHeader {
TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
viewModel.selectTaskType(task)
}
Spacer(modifier = Modifier.height(8.dp))
}
items(taskList) { task ->
TaskView(task, showDeleteTaskAlertDialog = { showDeleteTaskAlertDialog(task.id) })
Spacer(modifier = Modifier.height(8.dp))
}
}
}
@Composable
private fun EmptyTaskList(selectedTaskType: TaskType?, taskTypes: List<TaskType>?, viewModel: AssistantViewModel) {
val text = if (selectedTaskType?.name == stringResource(id = R.string.assistant_screen_all_task_type)) {
stringResource(id = R.string.assistant_screen_no_task_available_for_all_task_filter_text)
} else {
stringResource(
id = R.string.assistant_screen_no_task_available_text,
selectedTaskType?.name ?: ""
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
viewModel.selectTaskType(task)
}
Spacer(modifier = Modifier.height(8.dp))
CenterText(text = text)
}
}
@Composable
@Preview
private fun AssistantScreenPreview() {
val mockRepository = AssistantMockRepository()
MaterialTheme(
content = {
AssistantScreen(
viewModel = AssistantViewModel(repository = mockRepository),
activity = ComposeActivity()
)
}
)
}
@Composable
@Preview
private fun AssistantEmptyScreenPreview() {
val mockRepository = AssistantMockRepository(giveEmptyTasks = true)
MaterialTheme(
content = {
AssistantScreen(
viewModel = AssistantViewModel(repository = mockRepository),
activity = ComposeActivity()
)
}
)
}

View file

@ -0,0 +1,74 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.component
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
import com.owncloud.android.R
@Composable
fun AddTaskAlertDialog(title: String?, description: String?, addTask: (String) -> Unit, dismiss: () -> Unit) {
var input by remember {
mutableStateOf("")
}
SimpleAlertDialog(
title = title ?: "",
description = description ?: "",
dismiss = { dismiss() },
onComplete = {
addTask(input)
},
content = {
TextField(
placeholder = {
Text(
text = stringResource(
id = R.string.assistant_screen_create_task_alert_dialog_input_field_placeholder
)
)
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
value = input,
onValueChange = {
input = it
}
)
}
)
}
@Composable
@Preview
private fun AddTaskAlertDialogPreview() {
AddTaskAlertDialog(title = "Title", description = "Description", addTask = { }, dismiss = {})
}

View file

@ -0,0 +1,42 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.component
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
@Composable
fun CenterText(text: String) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
text = text,
fontSize = 18.sp,
textAlign = TextAlign.Center
)
}
}

View file

@ -0,0 +1,65 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.component
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.owncloud.android.lib.resources.assistant.model.TaskType
@Composable
fun TaskTypesRow(selectedTaskType: TaskType?, data: List<TaskType>?, selectTaskType: (TaskType) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
) {
data?.forEach { taskType ->
taskType.name?.let { taskTypeName ->
FilledTonalButton(
onClick = { selectTaskType(taskType) },
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedTaskType?.id == taskType.id) {
Color.Unspecified
} else {
Color.Gray
}
)
) {
Text(text = taskTypeName)
}
Spacer(modifier = Modifier.padding(end = 8.dp))
}
}
}
}

View file

@ -0,0 +1,174 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.component
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.nextcloud.ui.composeComponents.bottomSheet.MoreActionsBottomSheet
import com.owncloud.android.R
import com.owncloud.android.lib.resources.assistant.model.Task
@OptIn(ExperimentalFoundationApi::class)
@Suppress("LongMethod", "MagicNumber")
@Composable
fun TaskView(
task: Task,
showDeleteTaskAlertDialog: (Long) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
var showMoreActionsBottomSheet by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.primary)
.combinedClickable(onClick = {
expanded = !expanded
}, onLongClick = {
showMoreActionsBottomSheet = true
})
.padding(start = 8.dp)
) {
Spacer(modifier = Modifier.height(8.dp))
task.input?.let {
Text(
text = it,
color = Color.White,
fontSize = 18.sp
)
}
Spacer(modifier = Modifier.height(16.dp))
task.output?.let {
HorizontalDivider(modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp))
Text(
text = if (expanded) it else it.take(100) + "...",
fontSize = 12.sp,
color = Color.White,
modifier = Modifier
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessLow
)
)
)
}
if ((task.output?.length ?: 0) >= 100) {
Text(
text = if (!expanded) {
stringResource(id = R.string.assistant_screen_task_view_show_more)
} else {
stringResource(id = R.string.assistant_screen_task_view_show_less)
},
textAlign = TextAlign.End,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
if (showMoreActionsBottomSheet) {
val bottomSheetAction = listOf(
Triple(
R.drawable.ic_delete,
R.string.assistant_screen_task_more_actions_bottom_sheet_delete_action
) {
showDeleteTaskAlertDialog(task.id)
}
)
MoreActionsBottomSheet(
title = task.input,
actions = bottomSheetAction,
dismiss = { showMoreActionsBottomSheet = false }
)
}
}
}
@Preview
@Composable
private fun TaskViewPreview() {
val output =
"Lorem Ipsum is simply dummy text of the printing and " +
"typesetting industry. Lorem Ipsum has been the " +
"industry's standard dummy text ever since the 1500s, " +
"when an unknown printer took a galley of type and " +
"scrambled it to make a type specimen book. " +
"It has survived not only five centuries, but also " +
"the leap into electronic typesetting, remaining" +
" essentially unchanged. It wLorem Ipsum is simply dummy" +
" text of the printing and typesetting industry. " +
"Lorem Ipsum has been the industry's standard dummy " +
"text ever since the 1500s, when an unknown printer took a" +
" galley of type and scrambled it to make a type specimen book. " +
"It has survived not only five centuries, but also the leap " +
"into electronic typesetting, remaining essentially unchanged."
TaskView(
task = Task(
1,
"Free Prompt",
0,
"1",
"1",
"Give me text",
output,
"",
""
)
) {
}
}

View file

@ -0,0 +1,93 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.repository
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.assistant.model.Task
import com.owncloud.android.lib.resources.assistant.model.TaskList
import com.owncloud.android.lib.resources.assistant.model.TaskType
import com.owncloud.android.lib.resources.assistant.model.TaskTypes
class AssistantMockRepository(private val giveEmptyTasks: Boolean = false) : AssistantRepositoryType {
override fun getTaskTypes(): RemoteOperationResult<TaskTypes> {
return RemoteOperationResult<TaskTypes>(RemoteOperationResult.ResultCode.OK).apply {
resultData = TaskTypes(
listOf(
TaskType("1", "FreePrompt", "You can create free prompt text"),
TaskType("2", "Generate Headline", "You can create generate headline text")
)
)
}
}
override fun createTask(input: String, type: String): RemoteOperationResult<Void> {
return RemoteOperationResult<Void>(RemoteOperationResult.ResultCode.OK)
}
override fun getTaskList(appId: String): RemoteOperationResult<TaskList> {
val taskList = if (giveEmptyTasks) {
TaskList(listOf())
} else {
TaskList(
listOf(
Task(
1,
"FreePrompt",
null,
"12",
"",
"Give me some text",
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. " +
"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s," +
" when an unknown printer took a galley of type and scrambled it to make a type" +
" specimen book. It has survived not only five centuries, " +
"but also the leap into electronic typesetting, remaining essentially unchanged." +
" It was popularised in the 1960s with the release of Letraset sheets containing " +
"Lorem Ipsum passages, and more recently with desktop publishing software like Aldus" +
" PageMaker including versions of Lorem Ipsum",
"",
""
),
Task(
2,
"GenerateHeadline",
null,
"12",
"",
"Give me some text 2",
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"",
""
)
)
)
}
return RemoteOperationResult<TaskList>(RemoteOperationResult.ResultCode.OK).apply {
resultData = taskList
}
}
override fun deleteTask(id: Long): RemoteOperationResult<Void> {
return RemoteOperationResult<Void>(RemoteOperationResult.ResultCode.OK)
}
}

View file

@ -0,0 +1,53 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.repository
import com.nextcloud.common.NextcloudClient
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.assistant.CreateTaskRemoteOperation
import com.owncloud.android.lib.resources.assistant.DeleteTaskRemoteOperation
import com.owncloud.android.lib.resources.assistant.GetTaskListRemoteOperation
import com.owncloud.android.lib.resources.assistant.GetTaskTypesRemoteOperation
import com.owncloud.android.lib.resources.assistant.model.TaskList
import com.owncloud.android.lib.resources.assistant.model.TaskTypes
class AssistantRepository(private val client: NextcloudClient) : AssistantRepositoryType {
override fun getTaskTypes(): RemoteOperationResult<TaskTypes> {
return GetTaskTypesRemoteOperation().execute(client)
}
override fun createTask(
input: String,
type: String
): RemoteOperationResult<Void> {
return CreateTaskRemoteOperation(input, type).execute(client)
}
override fun getTaskList(appId: String): RemoteOperationResult<TaskList> {
return GetTaskListRemoteOperation(appId).execute(client)
}
override fun deleteTask(id: Long): RemoteOperationResult<Void> {
return DeleteTaskRemoteOperation(id).execute(client)
}
}

View file

@ -0,0 +1,39 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.client.assistant.repository
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.resources.assistant.model.TaskList
import com.owncloud.android.lib.resources.assistant.model.TaskTypes
interface AssistantRepositoryType {
fun getTaskTypes(): RemoteOperationResult<TaskTypes>
fun createTask(
input: String,
type: String
): RemoteOperationResult<Void>
fun getTaskList(appId: String): RemoteOperationResult<TaskList>
fun deleteTask(id: Long): RemoteOperationResult<Void>
}

View file

@ -71,7 +71,8 @@ import com.owncloud.android.db.ProviderMeta
AutoMigration(from = 74, to = 75), AutoMigration(from = 74, to = 75),
AutoMigration(from = 75, to = 76), AutoMigration(from = 75, to = 76),
AutoMigration(from = 76, to = 77), AutoMigration(from = 76, to = 77),
AutoMigration(from = 77, to = 78) AutoMigration(from = 77, to = 78),
AutoMigration(from = 78, to = 79, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
], ],
exportSchema = true exportSchema = true
) )

View file

@ -32,6 +32,8 @@ data class CapabilityEntity(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = ProviderTableMeta._ID) @ColumnInfo(name = ProviderTableMeta._ID)
val id: Int?, val id: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ASSISTANT)
val assistant: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME) @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME)
val accountName: String?, val accountName: String?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_VERSION_MAYOR) @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_VERSION_MAYOR)

View file

@ -41,6 +41,7 @@ import com.nextcloud.client.widget.DashboardWidgetService;
import com.nextcloud.ui.ChooseAccountDialogFragment; import com.nextcloud.ui.ChooseAccountDialogFragment;
import com.nextcloud.ui.ImageDetailFragment; import com.nextcloud.ui.ImageDetailFragment;
import com.nextcloud.ui.SetStatusDialogFragment; import com.nextcloud.ui.SetStatusDialogFragment;
import com.nextcloud.ui.composeActivity.ComposeActivity;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet; import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nmc.android.ui.LauncherActivity; import com.nmc.android.ui.LauncherActivity;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
@ -199,6 +200,9 @@ abstract class ComponentsModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract CommunityActivity participateActivity(); abstract CommunityActivity participateActivity();
@ContributesAndroidInjector
abstract ComposeActivity composeActivity();
@ContributesAndroidInjector @ContributesAndroidInjector
abstract PassCodeActivity passCodeActivity(); abstract PassCodeActivity passCodeActivity();

View file

@ -0,0 +1,133 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.ui.composeActivity
import android.content.Context
import android.os.Bundle
import android.view.MenuItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.nextcloud.client.assistant.AssistantScreen
import com.nextcloud.client.assistant.AssistantViewModel
import com.nextcloud.client.assistant.repository.AssistantRepository
import com.nextcloud.common.NextcloudClient
import com.nextcloud.common.User
import com.nextcloud.utils.extensions.getSerializableArgument
import com.owncloud.android.R
import com.owncloud.android.databinding.ActivityComposeBinding
import com.owncloud.android.lib.common.OwnCloudClientFactory
import com.owncloud.android.lib.common.accounts.AccountUtils
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.activity.DrawerActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class ComposeActivity : DrawerActivity() {
lateinit var binding: ActivityComposeBinding
private var menuItemId: Int? = null
companion object {
const val DESTINATION = "DESTINATION"
const val TITLE = "TITLE"
const val MENU_ITEM = "MENU_ITEM"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityComposeBinding.inflate(layoutInflater)
setContentView(binding.root)
val destination = intent.getSerializableArgument(DESTINATION, ComposeDestination::class.java)
val titleId = intent.getIntExtra(TITLE, R.string.empty)
menuItemId = intent.getIntExtra(MENU_ITEM, -1)
setupToolbar()
updateActionBarTitleAndHomeButtonByString(getString(titleId))
if (menuItemId != -1) {
setupDrawer(menuItemId!!)
}
binding.composeView.setContent {
MaterialTheme(
colorScheme = viewThemeUtils.getColorScheme(this),
content = {
Content(destination, storageManager.user, this)
}
)
}
}
override fun onResume() {
super.onResume()
if (menuItemId != -1) {
setDrawerMenuItemChecked(R.id.nav_assistant)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
if (isDrawerOpen) closeDrawer() else openDrawer()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@Composable
private fun Content(destination: ComposeDestination?, user: User, context: Context) {
var nextcloudClient by remember { mutableStateOf<NextcloudClient?>(null) }
LaunchedEffect(Unit) {
nextcloudClient = getNextcloudClient(user, context)
}
if (destination == ComposeDestination.AssistantScreen) {
nextcloudClient?.let { client ->
AssistantScreen(
viewModel = AssistantViewModel(
repository = AssistantRepository(client)
),
activity = this
)
}
}
}
private suspend fun getNextcloudClient(user: User, context: Context): NextcloudClient? {
return withContext(Dispatchers.IO) {
try {
OwnCloudClientFactory.createNextcloudClient(user, context)
} catch (e: AccountUtils.AccountNotFoundException) {
Log_OC.e(this, "Error caught at init of createNextcloudClient", e)
null
}
}
}
}

View file

@ -0,0 +1,28 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.ui.composeActivity
import java.io.Serializable
enum class ComposeDestination : Serializable {
AssistantScreen
}

View file

@ -0,0 +1,93 @@
/*
* Nextcloud Android client application
*
* @author Alper Ozturk
* Copyright (C) 2024 Alper Ozturk
* Copyright (C) 2024 Nextcloud GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.nextcloud.ui.composeComponents.alertDialog
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.owncloud.android.R
@Suppress("LongParameterList")
@Composable
fun SimpleAlertDialog(
title: String,
description: String?,
heightFraction: Float? = null,
content: @Composable (() -> Unit)? = null,
onComplete: () -> Unit,
dismiss: () -> Unit
) {
val modifier = if (heightFraction != null) {
Modifier
.fillMaxWidth()
.fillMaxHeight(heightFraction)
} else {
Modifier.fillMaxWidth()
}
AlertDialog(
onDismissRequest = { dismiss() },
title = {
Text(text = title)
},
text = {
Column(modifier = modifier) {
description?.let {
Text(text = description)
}
content?.let {
Spacer(modifier = Modifier.height(16.dp))
content()
}
}
},
confirmButton = {
FilledTonalButton(onClick = {
onComplete()
dismiss()
}) {
Text(
stringResource(id = R.string.common_ok)
)
}
},
dismissButton = {
TextButton(onClick = { dismiss() }) {
Text(
stringResource(id = R.string.common_cancel)
)
}
}
)
}

View file

@ -0,0 +1,100 @@
package com.nextcloud.ui.composeComponents.bottomSheet
import android.annotation.SuppressLint
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
@SuppressLint("ResourceAsColor")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreActionsBottomSheet(
title: String? = null,
actions: List<Triple<Int, Int, () -> Unit>>,
dismiss: () -> Unit
) {
val sheetState = rememberModalBottomSheetState()
val scope = rememberCoroutineScope()
ModalBottomSheet(
modifier = Modifier.padding(top = 32.dp),
onDismissRequest = {
dismiss()
},
sheetState = sheetState
) {
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
modifier = Modifier
.fillMaxWidth()
.padding(all = 8.dp)
) {
title?.let {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth()) {
Text(text = title, fontSize = 18.sp)
}
}
Spacer(modifier = Modifier.height(16.dp))
actions.forEach { action ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
scope
.launch { sheetState.hide() }
.invokeOnCompletion {
if (!sheetState.isVisible) {
action.third()
dismiss()
}
}
}
.padding(all = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = action.first),
contentDescription = "action icon",
tint = colorScheme.primary,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = stringResource(action.second),
fontSize = 16.sp
)
}
}
Spacer(modifier = Modifier.height(32.dp))
}
}
}

View file

@ -2005,6 +2005,7 @@ public class FileDataStorageManager {
capability.getUserStatusSupportsEmoji().getValue()); capability.getUserStatusSupportsEmoji().getValue());
contentValues.put(ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION, contentValues.put(ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION,
capability.getFilesLockingVersion()); capability.getFilesLockingVersion());
contentValues.put(ProviderTableMeta.CAPABILITIES_ASSISTANT, capability.getAssistant().getValue());
contentValues.put(ProviderTableMeta.CAPABILITIES_GROUPFOLDERS, capability.getGroupfolders().getValue()); contentValues.put(ProviderTableMeta.CAPABILITIES_GROUPFOLDERS, capability.getGroupfolders().getValue());
contentValues.put(ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT, capability.getDropAccount().getValue()); contentValues.put(ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT, capability.getDropAccount().getValue());
contentValues.put(ProviderTableMeta.CAPABILITIES_SECURITY_GUARD, capability.getSecurityGuard().getValue()); contentValues.put(ProviderTableMeta.CAPABILITIES_SECURITY_GUARD, capability.getSecurityGuard().getValue());
@ -2173,6 +2174,7 @@ public class FileDataStorageManager {
getBoolean(cursor, ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI)); getBoolean(cursor, ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI));
capability.setFilesLockingVersion( capability.setFilesLockingVersion(
getString(cursor, ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION)); getString(cursor, ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION));
capability.setAssistant(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_ASSISTANT));
capability.setGroupfolders(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_GROUPFOLDERS)); capability.setGroupfolders(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_GROUPFOLDERS));
capability.setDropAccount(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT)); capability.setDropAccount(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT));
capability.setSecurityGuard(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SECURITY_GUARD)); capability.setSecurityGuard(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SECURITY_GUARD));

View file

@ -35,7 +35,7 @@ import java.util.List;
*/ */
public class ProviderMeta { public class ProviderMeta {
public static final String DB_NAME = "filelist"; public static final String DB_NAME = "filelist";
public static final int DB_VERSION = 78; public static final int DB_VERSION = 79;
private ProviderMeta() { private ProviderMeta() {
// No instance // No instance
@ -265,6 +265,7 @@ public class ProviderMeta {
public static final String CAPABILITIES_ETAG = "etag"; public static final String CAPABILITIES_ETAG = "etag";
public static final String CAPABILITIES_USER_STATUS = "user_status"; public static final String CAPABILITIES_USER_STATUS = "user_status";
public static final String CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI = "user_status_supports_emoji"; public static final String CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI = "user_status_supports_emoji";
public static final String CAPABILITIES_ASSISTANT = "assistant";
public static final String CAPABILITIES_GROUPFOLDERS = "groupfolders"; public static final String CAPABILITIES_GROUPFOLDERS = "groupfolders";
public static final String CAPABILITIES_DROP_ACCOUNT = "drop_account"; public static final String CAPABILITIES_DROP_ACCOUNT = "drop_account";
public static final String CAPABILITIES_SECURITY_GUARD = "security_guard"; public static final String CAPABILITIES_SECURITY_GUARD = "security_guard";

View file

@ -61,7 +61,7 @@ import static com.owncloud.android.ui.activity.FileActivity.EXTRA_USER;
public class ActivitiesActivity extends DrawerActivity implements ActivityListInterface, ActivitiesContract.View { public class ActivitiesActivity extends DrawerActivity implements ActivityListInterface, ActivitiesContract.View {
private static final String TAG = ActivitiesActivity.class.getSimpleName(); private static final String TAG = ActivitiesActivity.class.getSimpleName();
private ActivityListLayoutBinding binding; ActivityListLayoutBinding binding;
private ActivityListAdapter adapter; private ActivityListAdapter adapter;
private int lastGiven; private int lastGiven;
private boolean isLoadingActivities; private boolean isLoadingActivities;

View file

@ -42,7 +42,6 @@ open class CommunityActivity : DrawerActivity() {
setupToolbar() setupToolbar()
updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_community)) updateActionBarTitleAndHomeButtonByString(getString(R.string.drawer_community))
setupDrawer(R.id.nav_community) setupDrawer(R.id.nav_community)
binding.communityReleaseCandidateText.movementMethod = LinkMovementMethod.getInstance() binding.communityReleaseCandidateText.movementMethod = LinkMovementMethod.getInstance()
setupContributeForumView() setupContributeForumView()

View file

@ -74,6 +74,8 @@ import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.common.NextcloudClient; import com.nextcloud.common.NextcloudClient;
import com.nextcloud.java.util.Optional; import com.nextcloud.java.util.Optional;
import com.nextcloud.ui.ChooseAccountDialogFragment; import com.nextcloud.ui.ChooseAccountDialogFragment;
import com.nextcloud.ui.composeActivity.ComposeActivity;
import com.nextcloud.ui.composeActivity.ComposeDestination;
import com.owncloud.android.MainApp; import com.owncloud.android.MainApp;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.authentication.PassCodeManager; import com.owncloud.android.authentication.PassCodeManager;
@ -123,6 +125,7 @@ import org.greenrobot.eventbus.ThreadMode;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -357,15 +360,22 @@ public abstract class DrawerActivity extends ToolbarActivity
if (getResources().getBoolean(R.bool.is_branded_client) || !preferences.isShowEcosystemApps()) { if (getResources().getBoolean(R.bool.is_branded_client) || !preferences.isShowEcosystemApps()) {
ecosystemApps.setVisibility(View.GONE); ecosystemApps.setVisibility(View.GONE);
} else { } else {
LinearLayout[] views = { LinearLayout notesView = ecosystemApps.findViewById(R.id.drawer_ecosystem_notes);
ecosystemApps.findViewById(R.id.drawer_ecosystem_notes), LinearLayout talkView = ecosystemApps.findViewById(R.id.drawer_ecosystem_talk);
ecosystemApps.findViewById(R.id.drawer_ecosystem_talk), LinearLayout moreView = ecosystemApps.findViewById(R.id.drawer_ecosystem_more);
ecosystemApps.findViewById(R.id.drawer_ecosystem_more) LinearLayout assistantView = ecosystemApps.findViewById(R.id.drawer_ecosystem_assistant);
};
views[0].setOnClickListener(v -> openAppOrStore("it.niedermann.owncloud.notes")); notesView.setOnClickListener(v -> openAppOrStore("it.niedermann.owncloud.notes"));
views[1].setOnClickListener(v -> openAppOrStore("com.nextcloud.talk2")); talkView.setOnClickListener(v -> openAppOrStore("com.nextcloud.talk2"));
views[2].setOnClickListener(v -> openAppStore("Nextcloud", true)); moreView.setOnClickListener(v -> openAppStore("Nextcloud", true));
assistantView.setOnClickListener(v -> startComposeActivity(ComposeDestination.AssistantScreen, R.string.assistant_screen_top_bar_title, -1));
if (getCapabilities() != null && getCapabilities().getAssistant().isTrue()) {
assistantView.setVisibility(View.VISIBLE);
} else {
assistantView.setVisibility(View.GONE);
}
List<LinearLayout> views = Arrays.asList(notesView, talkView, moreView, assistantView);
int iconColor; int iconColor;
if (Hct.fromInt(primaryColor).getTone() < 80.0) { if (Hct.fromInt(primaryColor).getTone() < 80.0) {
@ -373,6 +383,7 @@ public abstract class DrawerActivity extends ToolbarActivity
} else { } else {
iconColor = getColor(R.color.grey_800_transparent); iconColor = getColor(R.color.grey_800_transparent);
} }
for (LinearLayout view : views) { for (LinearLayout view : views) {
ImageView imageView = (ImageView) view.getChildAt(0); ImageView imageView = (ImageView) view.getChildAt(0);
imageView.setImageTintList(ColorStateList.valueOf(iconColor)); imageView.setImageTintList(ColorStateList.valueOf(iconColor));
@ -404,8 +415,8 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
/** /**
* Open app store page of specified app or search for specified string. * Open app store page of specified app or search for specified string. Will attempt to open browser when no app
* Will attempt to open browser when no app store is available. * store is available.
* *
* @param string packageName or url-encoded search string * @param string packageName or url-encoded search string
* @param search false -> show app corresponding to packageName; true -> open search for string * @param search false -> show app corresponding to packageName; true -> open search for string
@ -467,7 +478,7 @@ public abstract class DrawerActivity extends ToolbarActivity
DrawerMenuUtil.filterTrashbinMenuItem(menu, capability); DrawerMenuUtil.filterTrashbinMenuItem(menu, capability);
DrawerMenuUtil.filterActivityMenuItem(menu, capability); DrawerMenuUtil.filterActivityMenuItem(menu, capability);
DrawerMenuUtil.filterGroupfoldersMenuItem(menu, capability); DrawerMenuUtil.filterGroupfoldersMenuItem(menu, capability);
DrawerMenuUtil.filterAssistantMenuItem(menu, capability, getResources());
DrawerMenuUtil.setupHomeMenuItem(menu, getResources()); DrawerMenuUtil.setupHomeMenuItem(menu, getResources());
DrawerMenuUtil.removeMenuItem(menu, R.id.nav_community, DrawerMenuUtil.removeMenuItem(menu, R.id.nav_community,
@ -535,6 +546,8 @@ public abstract class DrawerActivity extends ToolbarActivity
startSharedSearch(menuItem); startSharedSearch(menuItem);
} else if (itemId == R.id.nav_recently_modified) { } else if (itemId == R.id.nav_recently_modified) {
startRecentlyModifiedSearch(menuItem); startRecentlyModifiedSearch(menuItem);
} else if (itemId == R.id.nav_assistant) {
startComposeActivity(ComposeDestination.AssistantScreen, R.string.assistant_screen_top_bar_title, itemId);
} else if (itemId == R.id.nav_groupfolders) { } else if (itemId == R.id.nav_groupfolders) {
MainApp.showOnlyFilesOnDevice(false); MainApp.showOnlyFilesOnDevice(false);
Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class); Intent intent = new Intent(getApplicationContext(), FileDisplayActivity.class);
@ -553,6 +566,14 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
} }
private void startComposeActivity(ComposeDestination destination, int titleId, int menuItemId) {
Intent composeActivity = new Intent(getApplicationContext(), ComposeActivity.class);
composeActivity.putExtra(ComposeActivity.DESTINATION, destination);
composeActivity.putExtra(ComposeActivity.TITLE, titleId);
composeActivity.putExtra(ComposeActivity.MENU_ITEM, menuItemId);
startActivity(composeActivity);
}
private void startActivity(Class<? extends Activity> activity) { private void startActivity(Class<? extends Activity> activity) {
startActivity(new Intent(getApplicationContext(), activity)); startActivity(new Intent(getApplicationContext(), activity));
} }
@ -692,8 +713,8 @@ public abstract class DrawerActivity extends ToolbarActivity
/** /**
* Enable or disable interaction with all drawers. * Enable or disable interaction with all drawers.
* *
* @param lockMode The new lock mode for the given drawer. One of {@link DrawerLayout#LOCK_MODE_UNLOCKED}, {@link * @param lockMode The new lock mode for the given drawer. One of {@link DrawerLayout#LOCK_MODE_UNLOCKED},
* DrawerLayout#LOCK_MODE_LOCKED_CLOSED} or {@link DrawerLayout#LOCK_MODE_LOCKED_OPEN}. * {@link DrawerLayout#LOCK_MODE_LOCKED_CLOSED} or {@link DrawerLayout#LOCK_MODE_LOCKED_OPEN}.
*/ */
public void setDrawerLockMode(int lockMode) { public void setDrawerLockMode(int lockMode) {
if (mDrawerLayout != null) { if (mDrawerLayout != null) {
@ -1155,7 +1176,7 @@ public abstract class DrawerActivity extends ToolbarActivity
return true; return true;
} }
public AppPreferences getAppPreferences(){ public AppPreferences getAppPreferences() {
return preferences; return preferences;
} }

View file

@ -150,7 +150,6 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Stack;
import javax.inject.Inject; import javax.inject.Inject;

View file

@ -55,7 +55,7 @@ import com.owncloud.android.utils.PushUtils
*/ */
class NotificationsActivity : DrawerActivity(), NotificationsContract.View { class NotificationsActivity : DrawerActivity(), NotificationsContract.View {
private lateinit var binding: NotificationsLayoutBinding lateinit var binding: NotificationsLayoutBinding
private var adapter: NotificationListAdapter? = null private var adapter: NotificationListAdapter? = null
private var snackbar: Snackbar? = null private var snackbar: Snackbar? = null

View file

@ -80,7 +80,7 @@ import javax.inject.Inject
/** /**
* Activity displaying all auto-synced folders and/or instant upload media folders. * Activity displaying all auto-synced folders and/or instant upload media folders.
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions", "LargeClass")
class SyncedFoldersActivity : class SyncedFoldersActivity :
FileActivity(), FileActivity(),
SyncedFolderAdapter.ClickListener, SyncedFolderAdapter.ClickListener,
@ -165,8 +165,8 @@ class SyncedFoldersActivity :
@Inject @Inject
lateinit var syncedFolderProvider: SyncedFolderProvider lateinit var syncedFolderProvider: SyncedFolderProvider
private lateinit var binding: SyncedFoldersLayoutBinding lateinit var binding: SyncedFoldersLayoutBinding
private lateinit var adapter: SyncedFolderAdapter lateinit var adapter: SyncedFolderAdapter
private var syncedFolderPreferencesDialogFragment: SyncedFolderPreferencesDialogFragment? = null private var syncedFolderPreferencesDialogFragment: SyncedFolderPreferencesDialogFragment? = null
private var path: String? = null private var path: String? = null

View file

@ -50,6 +50,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/** /**
* Adapter to display all auto-synced folders and/or instant upload media folders. * Adapter to display all auto-synced folders and/or instant upload media folders.
@ -179,6 +180,12 @@ public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SectionedV
} }
} }
@VisibleForTesting
public void clear() {
filteredSyncFolderItems.clear();
syncFolderItems.clear();
}
public int getUnfilteredSectionCount() { public int getUnfilteredSectionCount() {
if (syncFolderItems.size() > 0) { if (syncFolderItems.size() > 0) {
return syncFolderItems.size() + 1; return syncFolderItems.size() + 1;

View file

@ -142,6 +142,10 @@ public class ExtendedListFragment extends Fragment implements
private ListFragmentBinding binding; private ListFragmentBinding binding;
public ListFragmentBinding getBinding() {
return binding;
}
protected void setRecyclerViewAdapter(RecyclerView.Adapter recyclerViewAdapter) { protected void setRecyclerViewAdapter(RecyclerView.Adapter recyclerViewAdapter) {
mRecyclerView.setAdapter(recyclerViewAdapter); mRecyclerView.setAdapter(recyclerViewAdapter);
} }

View file

@ -106,7 +106,7 @@ public class FileDetailActivitiesFragment extends Fragment implements
private FileOperationsHelper operationsHelper; private FileOperationsHelper operationsHelper;
private VersionListInterface.CommentCallback callback; private VersionListInterface.CommentCallback callback;
private FileDetailsActivitiesFragmentBinding binding; FileDetailsActivitiesFragmentBinding binding;
@Inject UserAccountManager accountManager; @Inject UserAccountManager accountManager;
@Inject ClientFactory clientFactory; @Inject ClientFactory clientFactory;

View file

@ -84,7 +84,7 @@ class TrashbinActivity :
var trashbinPresenter: TrashbinPresenter? = null var trashbinPresenter: TrashbinPresenter? = null
private var active = false private var active = false
private lateinit var binding: TrashbinActivityBinding lateinit var binding: TrashbinActivityBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -64,6 +64,13 @@ public final class DrawerMenuUtil {
} }
} }
public static void filterAssistantMenuItem(Menu menu, @Nullable OCCapability capability, Resources resources) {
boolean showCondition = capability != null && capability.getAssistant().isTrue() && !resources.getBoolean(R.bool.is_branded_client);
if (!showCondition) {
filterMenuItems(menu, R.id.nav_assistant);
}
}
public static void filterGroupfoldersMenuItem(Menu menu, @Nullable OCCapability capability) { public static void filterGroupfoldersMenuItem(Menu menu, @Nullable OCCapability capability) {
if (capability != null && !capability.getGroupfolders().isTrue()) { if (capability != null && !capability.getGroupfolders().isTrue()) {
filterMenuItems(menu, R.id.nav_groupfolders); filterMenuItems(menu, R.id.nav_groupfolders);

View file

@ -123,7 +123,7 @@ class WebViewUtil(private val context: Context) {
* @return * @return
*/ */
@SuppressLint("PrivateApi", "DiscouragedPrivateApi") @SuppressLint("PrivateApi", "DiscouragedPrivateApi")
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught", "NestedBlockDepth")
fun setProxyKKPlus(webView: WebView) { fun setProxyKKPlus(webView: WebView) {
val proxyHost = OwnCloudClientManagerFactory.getProxyHost() val proxyHost = OwnCloudClientManagerFactory.getProxyHost()
val proxyPort = OwnCloudClientManagerFactory.getProxyPort() val proxyPort = OwnCloudClientManagerFactory.getProxyPort()

View file

@ -0,0 +1,32 @@
<!--
~ Nextcloud Android client application
~
~ @author Alper Ozturk
~ Copyright (C) 2024 Alper Ozturk
~ Copyright (C) 2024 Nextcloud GmbH
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="1dp"
android:height="1dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M9,4.5a0.75,0.75 0,0 1,0.721 0.544l0.813,2.846a3.75,3.75 0,0 0,2.576 2.576l2.846,0.813a0.75,0.75 0,0 1,0 1.442l-2.846,0.813a3.75,3.75 0,0 0,-2.576 2.576l-0.813,2.846a0.75,0.75 0,0 1,-1.442 0l-0.813,-2.846a3.75,3.75 0,0 0,-2.576 -2.576l-2.846,-0.813a0.75,0.75 0,0 1,0 -1.442l2.846,-0.813A3.75,3.75 0,0 0,7.466 7.89l0.813,-2.846A0.75,0.75 0,0 1,9 4.5ZM18,1.5a0.75,0.75 0,0 1,0.728 0.568l0.258,1.036c0.236,0.94 0.97,1.674 1.91,1.91l1.036,0.258a0.75,0.75 0,0 1,0 1.456l-1.036,0.258c-0.94,0.236 -1.674,0.97 -1.91,1.91l-0.258,1.036a0.75,0.75 0,0 1,-1.456 0l-0.258,-1.036a2.625,2.625 0,0 0,-1.91 -1.91l-1.036,-0.258a0.75,0.75 0,0 1,0 -1.456l1.036,-0.258a2.625,2.625 0,0 0,1.91 -1.91l0.258,-1.036A0.75,0.75 0,0 1,18 1.5ZM16.5,15a0.75,0.75 0,0 1,0.712 0.513l0.394,1.183c0.15,0.447 0.5,0.799 0.948,0.948l1.183,0.395a0.75,0.75 0,0 1,0 1.422l-1.183,0.395c-0.447,0.15 -0.799,0.5 -0.948,0.948l-0.395,1.183a0.75,0.75 0,0 1,-1.422 0l-0.395,-1.183a1.5,1.5 0,0 0,-0.948 -0.948l-1.183,-0.395a0.75,0.75 0,0 1,0 -1.422l1.183,-0.395c0.447,-0.15 0.799,-0.5 0.948,-0.948l0.395,-1.183A0.75,0.75 0,0 1,16.5 15Z"
android:strokeWidth="0"
android:fillColor="#FF000000"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Android client application
~
~ @author Alper Ozturk
~ Copyright (C) 2024 Alper Ozturk
~ Copyright (C) 2024 Nextcloud GmbH
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:fitsSystemWindows="true"
android:focusable="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar_standard" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<include
layout="@layout/drawer"
android:layout_width="@dimen/drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"/>
</androidx.drawerlayout.widget.DrawerLayout>

View file

@ -71,6 +71,37 @@
android:layout_marginBottom="@dimen/standard_half_margin" android:layout_marginBottom="@dimen/standard_half_margin"
android:orientation="horizontal"> android:orientation="horizontal">
<LinearLayout
android:id="@+id/drawer_ecosystem_assistant"
android:layout_width="wrap_content"
android:layout_marginEnd="30dp"
android:layout_height="wrap_content"
android:gravity="center"
android:background="?android:selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:orientation="vertical">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="4dp"
android:background="@drawable/white_outline"
android:contentDescription="@string/ecosystem_apps_talk"
android:padding="8dp"
android:src="@drawable/ic_assistant"
app:tint="@color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/ecosystem_apps_display_assistant"
android:textColor="@color/white"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/drawer_ecosystem_talk" android:id="@+id/drawer_ecosystem_talk"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Android client application
~
~ @author Alper Ozturk
~ Copyright (C) 2024 Alper Ozturk
~ Copyright (C) 2024 Nextcloud GmbH
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -25,11 +25,13 @@
<group <group
android:id="@+id/drawer_menu_standard" android:id="@+id/drawer_menu_standard"
android:checkableBehavior="single"> android:checkableBehavior="single">
<item <item
android:id="@+id/nav_all_files" android:id="@+id/nav_all_files"
android:icon="@drawable/all_files" android:icon="@drawable/all_files"
android:orderInCategory="0" android:orderInCategory="0"
android:title="@string/drawer_item_all_files" /> android:title="@string/drawer_item_all_files" />
<item <item
android:id="@+id/nav_personal_files" android:id="@+id/nav_personal_files"
android:icon="@drawable/ic_user" android:icon="@drawable/ic_user"
@ -74,6 +76,13 @@
android:id="@+id/nav_notifications" android:id="@+id/nav_notifications"
android:icon="@drawable/nav_notifications" android:icon="@drawable/nav_notifications"
android:title="@string/drawer_item_notifications"/> android:title="@string/drawer_item_notifications"/>
<item
android:id="@+id/nav_assistant"
android:icon="@drawable/ic_assistant"
android:orderInCategory="0"
android:title="@string/drawer_item_assistant" />
<item <item
android:id="@+id/nav_uploads" android:id="@+id/nav_uploads"
android:icon="@drawable/uploads" android:icon="@drawable/uploads"

View file

@ -37,6 +37,8 @@
<string name="allow_resharing">Allow resharing</string> <string name="allow_resharing">Allow resharing</string>
<string name="app_widget_description">Shows one widget from dashboard</string> <string name="app_widget_description">Shows one widget from dashboard</string>
<string name="appbar_search_in">Search in %s</string> <string name="appbar_search_in">Search in %s</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Type some text</string>
<string name="assistant_screen_task_view_show_less">Show less</string>
<string name="associated_account_not_found">Associated account not found!</string> <string name="associated_account_not_found">Associated account not found!</string>
<string name="auth_access_failed">Access failed: %1$s</string> <string name="auth_access_failed">Access failed: %1$s</string>
<string name="auth_account_does_not_exist">The account is not added on this device yet</string> <string name="auth_account_does_not_exist">The account is not added on this device yet</string>

View file

@ -37,6 +37,8 @@
<string name="allow_resharing">Weiterteilen erlauben</string> <string name="allow_resharing">Weiterteilen erlauben</string>
<string name="app_widget_description">Zeigt ein Widget aus dem Dashboard an</string> <string name="app_widget_description">Zeigt ein Widget aus dem Dashboard an</string>
<string name="appbar_search_in">Suche in %s</string> <string name="appbar_search_in">Suche in %s</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Bitte einen Text eingeben</string>
<string name="assistant_screen_task_view_show_less">Weniger anzeigen</string>
<string name="associated_account_not_found">Verknüpftes Konto nicht gefunden!</string> <string name="associated_account_not_found">Verknüpftes Konto nicht gefunden!</string>
<string name="auth_access_failed">Zugriffsfehler: %1$s</string> <string name="auth_access_failed">Zugriffsfehler: %1$s</string>
<string name="auth_account_does_not_exist">Das Konto ist bislang auf dem Gerät nicht vorhanden</string> <string name="auth_account_does_not_exist">Das Konto ist bislang auf dem Gerät nicht vorhanden</string>

View file

@ -34,6 +34,8 @@
<string name="allow_resharing">Επιτρέπεται ο επαναδιαμοιρασμός</string> <string name="allow_resharing">Επιτρέπεται ο επαναδιαμοιρασμός</string>
<string name="app_widget_description">Εμφάνιση ενός γραφικού στοιχείου από τον πίνακα ελέγχου</string> <string name="app_widget_description">Εμφάνιση ενός γραφικού στοιχείου από τον πίνακα ελέγχου</string>
<string name="appbar_search_in">Αναζήτηση στο %s</string> <string name="appbar_search_in">Αναζήτηση στο %s</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Πληκτρολογήστε κάποιο κείμενο</string>
<string name="assistant_screen_task_view_show_less">Εμφάνιση λιγότερων</string>
<string name="associated_account_not_found">Δεν βρέθηκε ο συνδεδεμένος λογαριασμός!</string> <string name="associated_account_not_found">Δεν βρέθηκε ο συνδεδεμένος λογαριασμός!</string>
<string name="auth_access_failed">Αποτυχία πρόσβασης: %1$s</string> <string name="auth_access_failed">Αποτυχία πρόσβασης: %1$s</string>
<string name="auth_account_does_not_exist">Ο λογαριασμός δεν υπάρχει ακόμα στη συσκευή</string> <string name="auth_account_does_not_exist">Ο λογαριασμός δεν υπάρχει ακόμα στη συσκευή</string>

View file

@ -32,6 +32,7 @@
<string name="advanced_settings">Detalaj agordoj</string> <string name="advanced_settings">Detalaj agordoj</string>
<string name="allow_resharing">Permesi rekunhavigon</string> <string name="allow_resharing">Permesi rekunhavigon</string>
<string name="appbar_search_in">Serĉi en 1%s</string> <string name="appbar_search_in">Serĉi en 1%s</string>
<string name="assistant_screen_task_view_show_less">Montri malpli</string>
<string name="auth_access_failed">Aliro malsukcesis: %1$s</string> <string name="auth_access_failed">Aliro malsukcesis: %1$s</string>
<string name="auth_account_does_not_exist">La konto ankoraŭ ne aldoniĝis al tiu ĉi aparato</string> <string name="auth_account_does_not_exist">La konto ankoraŭ ne aldoniĝis al tiu ĉi aparato</string>
<string name="auth_account_not_new">Konto pri samaj uzanto kaj servilo jam ekzistas tiuaparate</string> <string name="auth_account_not_new">Konto pri samaj uzanto kaj servilo jam ekzistas tiuaparate</string>

View file

@ -35,6 +35,8 @@
<string name="allow_resharing">Permitir volver a compartir</string> <string name="allow_resharing">Permitir volver a compartir</string>
<string name="app_widget_description">Muestra un widget del panel de control</string> <string name="app_widget_description">Muestra un widget del panel de control</string>
<string name="appbar_search_in">Compartir en %s</string> <string name="appbar_search_in">Compartir en %s</string>
<string name="assistant_screen_task_delete_fail_message">Tarea eliminada con éxito</string>
<string name="assistant_screen_task_view_show_less">Mostrar menos</string>
<string name="associated_account_not_found">¡Cuenta asociada no encontrada!</string> <string name="associated_account_not_found">¡Cuenta asociada no encontrada!</string>
<string name="auth_access_failed">Acceso fallido: %1$s</string> <string name="auth_access_failed">Acceso fallido: %1$s</string>
<string name="auth_account_does_not_exist">La cuenta aún no ha sido agregada a este dispositivo </string> <string name="auth_account_does_not_exist">La cuenta aún no ha sido agregada a este dispositivo </string>

View file

@ -569,6 +569,7 @@
<string name="placeholder_timestamp">2012/05/18 12:23 PM</string> <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
<string name="player_stop">detener</string> <string name="player_stop">detener</string>
<string name="player_toggle">alternar</string> <string name="player_toggle">alternar</string>
<string name="power_save_check_dialog_message">¡Desactivar la comprobación de ahorro de energía podría resultar en la carga de archivos cuando la batería esté baja!</string>
<string name="pref_behaviour_entries_delete_file">Borrado</string> <string name="pref_behaviour_entries_delete_file">Borrado</string>
<string name="pref_behaviour_entries_keep_file">mantenido en la carpeta original</string> <string name="pref_behaviour_entries_keep_file">mantenido en la carpeta original</string>
<string name="pref_behaviour_entries_move">movido a la carpeta de la aplicación</string> <string name="pref_behaviour_entries_move">movido a la carpeta de la aplicación</string>
@ -581,6 +582,7 @@
<string name="prefs_add_account">Agregar cuenta</string> <string name="prefs_add_account">Agregar cuenta</string>
<string name="prefs_calendar_contacts">Sincronizar calendario y contactos</string> <string name="prefs_calendar_contacts">Sincronizar calendario y contactos</string>
<string name="prefs_calendar_contacts_no_store_error">No ha sido instalado ni F-Droid o Google Play </string> <string name="prefs_calendar_contacts_no_store_error">No ha sido instalado ni F-Droid o Google Play </string>
<string name="prefs_calendar_contacts_summary">Configurar DAVx5 (antes conocido como DAVdroid) (v1.3.0+) para la cuenta actual</string>
<string name="prefs_calendar_contacts_sync_setup_successful">Sincronización de calendario y contactos configurada</string> <string name="prefs_calendar_contacts_sync_setup_successful">Sincronización de calendario y contactos configurada</string>
<string name="prefs_category_about">Acerca de</string> <string name="prefs_category_about">Acerca de</string>
<string name="prefs_category_details">Detalles</string> <string name="prefs_category_details">Detalles</string>
@ -589,6 +591,7 @@
<string name="prefs_category_more">Más</string> <string name="prefs_category_more">Más</string>
<string name="prefs_daily_backup_summary">Respaldar diariamente el calendario y contactos</string> <string name="prefs_daily_backup_summary">Respaldar diariamente el calendario y contactos</string>
<string name="prefs_daily_contact_backup_summary">Respaldo diario de tus contactos</string> <string name="prefs_daily_contact_backup_summary">Respaldo diario de tus contactos</string>
<string name="prefs_davx5_setup_error">Error inesperado al configurar DAVx5 (anteriormente conocido como DAVdroid)</string>
<string name="prefs_e2e_active">¡El cifrado punto a punto está configurado!</string> <string name="prefs_e2e_active">¡El cifrado punto a punto está configurado!</string>
<string name="prefs_e2e_mnemonic">Mnemotécnico de E2E</string> <string name="prefs_e2e_mnemonic">Mnemotécnico de E2E</string>
<string name="prefs_e2e_no_device_credentials">Para mostrar el mnemotécnico, por favor, habilite las credenciales del dispositivo.</string> <string name="prefs_e2e_no_device_credentials">Para mostrar el mnemotécnico, por favor, habilite las credenciales del dispositivo.</string>
@ -618,6 +621,7 @@
<string name="prefs_remove_e2e">Eliminar el cifrado local</string> <string name="prefs_remove_e2e">Eliminar el cifrado local</string>
<string name="prefs_setup_e2e">Configurar el cifrado punto a punto</string> <string name="prefs_setup_e2e">Configurar el cifrado punto a punto</string>
<string name="prefs_show_ecosystem_apps">Mostrar el conmutador de aplicaciones</string> <string name="prefs_show_ecosystem_apps">Mostrar el conmutador de aplicaciones</string>
<string name="prefs_show_ecosystem_apps_summary">Sugerencias de aplicaciones de Nextcloud en el encabezado de navegación</string>
<string name="prefs_show_hidden_files">Mostrar archivos ocultos</string> <string name="prefs_show_hidden_files">Mostrar archivos ocultos</string>
<string name="prefs_sourcecode">Obtener el código fuente</string> <string name="prefs_sourcecode">Obtener el código fuente</string>
<string name="prefs_storage_path">Carpeta de almacenamiento de datos</string> <string name="prefs_storage_path">Carpeta de almacenamiento de datos</string>
@ -644,50 +648,109 @@
<string name="refresh_content">Actualizar contenido</string> <string name="refresh_content">Actualizar contenido</string>
<string name="reload">Volver a cargar</string> <string name="reload">Volver a cargar</string>
<string name="remote">(remoto)</string> <string name="remote">(remoto)</string>
<string name="remote_file_fetch_failed">¡No se pudo encontrar el archivo!</string>
<string name="remove_e2e">Puede eliminar el cifrado punto a punto local en este cliente</string>
<string name="remove_e2e_message">Puede eliminar el cifrado punto a punto local en este cliente. Los archivos cifrados permanecerán en el servidor, pero no se sincronizarán con esta computadora.</string>
<string name="remove_fail_msg">Falla al eliminar</string> <string name="remove_fail_msg">Falla al eliminar</string>
<string name="remove_local_account">Eliminar cuenta local</string>
<string name="remove_local_account_details">Eliminar la cuenta del dispositivo y borrar todos los archivos locales</string>
<string name="remove_notification_failed">No se pudo eliminar la notificación.</string>
<string name="remove_push_notification">Eliminar</string> <string name="remove_push_notification">Eliminar</string>
<string name="remove_success_msg">Eliminado</string> <string name="remove_success_msg">Eliminado</string>
<string name="rename_dialog_title">Ingresa un nombre nuevo</string> <string name="rename_dialog_title">Ingresa un nombre nuevo</string>
<string name="rename_local_fail_msg">No se pudo renombrar la copia local, intenta con un nombre diferente </string> <string name="rename_local_fail_msg">No se pudo renombrar la copia local, intenta con un nombre diferente </string>
<string name="rename_server_fail_msg">No fue posible renombrar, el nombre ya está ocupado</string> <string name="rename_server_fail_msg">No fue posible renombrar, el nombre ya está ocupado</string>
<string name="request_account_deletion">Solicitar borrado de cuenta</string> <string name="request_account_deletion">Solicitar borrado de cuenta</string>
<string name="request_account_deletion_button">Solicitar eliminación</string>
<string name="request_account_deletion_details">Solicitar la eliminación permanente de la cuenta al proveedor del servicio</string>
<string name="reshare_not_allowed">No está permitido recompartir</string> <string name="reshare_not_allowed">No está permitido recompartir</string>
<string name="resharing_is_not_allowed">No está permitido recompartir</string> <string name="resharing_is_not_allowed">No está permitido recompartir</string>
<string name="resized_image_not_possible_download">No hay una imagen a escala disponible. ¿Descargar la imagen completa? </string> <string name="resized_image_not_possible_download">No hay una imagen a escala disponible. ¿Descargar la imagen completa? </string>
<string name="restore">Restaurar archivo</string>
<string name="restore_backup">Restaurar respaldo</string>
<string name="restore_button_description">Restaurar archivo eliminado</string> <string name="restore_button_description">Restaurar archivo eliminado</string>
<string name="restore_selected">Restaurar seleccionados</string>
<string name="retrieving_file">Recuperando archivo...</string>
<string name="richdocuments_failed_to_load_document">¡No se pudo cargar el documento!</string>
<string name="scanQR_description">Iniciar sesión usando un código QR</string>
<string name="scan_page">Escanear página</string>
<string name="screenshot_01_gridView_heading">Protegiendo sus datos</string>
<string name="screenshot_01_gridView_subline">productividad auto alojada</string>
<string name="screenshot_02_listView_heading">Navegar y compartir</string>
<string name="screenshot_02_listView_subline">todas las acciones en la punta de sus dedos</string>
<string name="screenshot_03_drawer_heading">Actividad, compartidos, ...</string> <string name="screenshot_03_drawer_heading">Actividad, compartidos, ...</string>
<string name="screenshot_03_drawer_subline">todo accesible rápidamente</string>
<string name="screenshot_04_accounts_heading">Todas sus cuentas</string>
<string name="screenshot_04_accounts_subline">en un solo lugar</string>
<string name="screenshot_05_autoUpload_heading">Carga automática</string>
<string name="screenshot_05_autoUpload_subline">para sus fotos y videos</string>
<string name="screenshot_06_davdroid_heading">Calendario y contactos</string>
<string name="screenshot_06_davdroid_subline">Sincronizar con DAVx5</string>
<string name="search_error">Error al obtener los resultados de búsqueda</string>
<string name="secure_share_not_set_up">Compartir en modo seguro no está configurado para este usuario</string>
<string name="secure_share_search">Compartir de forma segura ...</string>
<string name="select_all">Seleccionar todo</string> <string name="select_all">Seleccionar todo</string>
<string name="select_media_folder">Establecer la carpeta multimedia</string>
<string name="select_one_template">Por favor, seleccione una plantilla</string>
<string name="select_template">Seleccionar plantilla</string> <string name="select_template">Seleccionar plantilla</string>
<string name="send">Enviar</string> <string name="send">Enviar</string>
<string name="send_share">Enviar recurso compartido</string>
<string name="sendbutton_description">Ícono de botón de envío</string> <string name="sendbutton_description">Ícono de botón de envío</string>
<string name="set_as">Establecer Como</string> <string name="set_as">Establecer Como</string>
<string name="set_note">Establecer nota</string>
<string name="set_picture_as">Usar imagen como</string> <string name="set_picture_as">Usar imagen como</string>
<string name="set_status">Establecer estado</string> <string name="set_status">Establecer estado</string>
<string name="set_status_message">Establecer mensaje de estado</string> <string name="set_status_message">Establecer mensaje de estado</string>
<string name="setup_e2e">Durante la configuración del cifrado punto a punto, recibirá un mnemotécnico aleatorio de 12 palabras, que necesitará para abrir sus archivos en otros dispositivos. Esto sólo se almacenará en este dispositivo y se puede volver a mostrar en esta pantalla. ¡Por favor, guárdelo en un lugar seguro!</string>
<string name="share">Compartir</string> <string name="share">Compartir</string>
<string name="share_copy_link">Compartir y copiar enlace</string>
<string name="share_dialog_title">Compartiendo</string> <string name="share_dialog_title">Compartiendo</string>
<string name="share_expiration_date_format">%1$s</string>
<string name="share_expiration_date_label">Expira el %1$s</string> <string name="share_expiration_date_label">Expira el %1$s</string>
<string name="share_file">Compartir %1$s</string> <string name="share_file">Compartir %1$s</string>
<string name="share_group_clarification">%1$s (grupo)</string> <string name="share_group_clarification">%1$s (grupo)</string>
<string name="share_internal_link">Compartir enlace interno</string> <string name="share_internal_link">Compartir enlace interno</string>
<string name="share_internal_link_to_file_text">El enlace compartido interno sólo funciona para usuarios con acceso a este archivo</string>
<string name="share_internal_link_to_folder_text">El enlace compartido interno sólo funciona para usuarios con acceso a esta carpeta</string>
<string name="share_known_remote_on_clarification">en %1$s</string>
<string name="share_link">Compartir liga</string> <string name="share_link">Compartir liga</string>
<string name="share_link_empty_password">Debes ingresar una contraseña</string> <string name="share_link_empty_password">Debes ingresar una contraseña</string>
<string name="share_link_file_error">Ocurrió un error al intentar compartir este archivo o carpeta.</string>
<string name="share_link_file_no_exist">No se puede compartir. Por favor, verifique que el archivo exista.</string>
<string name="share_link_forbidden_permissions">para compartir este archivo</string> <string name="share_link_forbidden_permissions">para compartir este archivo</string>
<string name="share_link_optional_password_title">Ingrese una contraseña opcional</string>
<string name="share_link_password_title">Ingresa una contraseña</string> <string name="share_link_password_title">Ingresa una contraseña</string>
<string name="share_link_with_label">Compartir enlace (%1$s)</string>
<string name="share_no_expiration_date_label">Establece la fecha de expiración</string> <string name="share_no_expiration_date_label">Establece la fecha de expiración</string>
<string name="share_no_password_title">Establecer contraseña</string> <string name="share_no_password_title">Establecer contraseña</string>
<string name="share_not_allowed_when_file_drop">No se permite volver a compartir durante la entrega segura de archivos</string>
<string name="share_password_title">Protegido con contraseña</string> <string name="share_password_title">Protegido con contraseña</string>
<string name="share_permission_can_edit">Puede editar</string> <string name="share_permission_can_edit">Puede editar</string>
<string name="share_permission_file_drop">Entrega de archivos</string>
<string name="share_permission_secure_file_drop">Entrega de archivos segura</string>
<string name="share_permission_view_only">Sólo lectura</string> <string name="share_permission_view_only">Sólo lectura</string>
<string name="share_permissions">Permisos del recurso compartido</string>
<string name="share_remote_clarification">%1$s (remoto)</string> <string name="share_remote_clarification">%1$s (remoto)</string>
<string name="share_room_clarification">%1$s (conversación)</string>
<string name="share_search">Nombre, identificador de nube federada o dirección de correo electrónico ...</string>
<string name="share_send_new_email">Enviar nuevo correo electrónico</string>
<string name="share_send_note">Nota al destinatario</string>
<string name="share_settings">Ajustes</string> <string name="share_settings">Ajustes</string>
<string name="share_via_link_hide_download">Ocultar descarga</string> <string name="share_via_link_hide_download">Ocultar descarga</string>
<string name="share_via_link_section_title">Compartir liga</string> <string name="share_via_link_section_title">Compartir liga</string>
<string name="share_via_link_send_link_label">Enviar enlace</string>
<string name="share_via_link_unset_password">Desestablecer</string> <string name="share_via_link_unset_password">Desestablecer</string>
<string name="share_with_title">Compartir con…</string> <string name="share_with_title">Compartir con…</string>
<string name="shared_avatar_desc">Avatar del usuario compartido</string>
<string name="shared_icon_share">compartir</string> <string name="shared_icon_share">compartir</string>
<string name="shared_icon_shared">compartido</string> <string name="shared_icon_shared">compartido</string>
<string name="shared_icon_shared_via_link">compartido mediante enlace</string>
<string name="shared_with_you_by">Compartido con Ud. por %1$s</string>
<string name="sharee_add_failed">Se presentó una falla al agregar una persona para compartir</string> <string name="sharee_add_failed">Se presentó una falla al agregar una persona para compartir</string>
<string name="show_images">Mostrar fotos</string>
<string name="show_video">Mostrar videos</string>
<string name="signup_with_provider">Registrarse con el proveedor</string>
<string name="single_sign_on_request_token" formatted="true">¿Permitir que %1$s acceda a su cuenta de Nextcloud %2$s?</string>
<string name="sort_by">Ordenar por</string> <string name="sort_by">Ordenar por</string>
<string name="ssl_validator_btn_details_hide">Ocultar</string> <string name="ssl_validator_btn_details_hide">Ocultar</string>
<string name="ssl_validator_btn_details_see">Detalles</string> <string name="ssl_validator_btn_details_see">Detalles</string>
@ -715,10 +778,32 @@
<string name="ssl_validator_reason_cert_not_yet_valid">- Las fechas del certificado del servidor están en el futuro</string> <string name="ssl_validator_reason_cert_not_yet_valid">- Las fechas del certificado del servidor están en el futuro</string>
<string name="ssl_validator_reason_hostname_not_verified">- La URL no corresponde con el nombre del servidor en el certificado</string> <string name="ssl_validator_reason_hostname_not_verified">- La URL no corresponde con el nombre del servidor en el certificado</string>
<string name="status_message">Mensaje de estado</string> <string name="status_message">Mensaje de estado</string>
<string name="storage_camera">Cámara</string>
<string name="storage_choose_location">Elegir la ubicación del almacenamiento</string>
<string name="storage_description_default">Predeterminado</string> <string name="storage_description_default">Predeterminado</string>
<string name="storage_documents">Documentos</string>
<string name="storage_downloads">Descargas</string> <string name="storage_downloads">Descargas</string>
<string name="storage_internal_storage">Almacenamiento interno</string>
<string name="storage_movies">Películas</string>
<string name="storage_music">Música</string>
<string name="storage_permission_full_access">Acceso completo</string>
<string name="storage_permission_media_read_only">Sólo lectura de medios</string>
<string name="storage_pictures">Imágenes</string>
<string name="store_short_desc">La plataforma de productividad auto alojada que le mantiene en control</string>
<string name="store_short_dev_desc">La plataforma de productividad auto alojada que le mantiene en control (versión preliminar para desarrolladores)</string>
<string name="stream">Transmitir con...</string>
<string name="stream_not_possible_headline">La transmisión interna no es posible</string>
<string name="stream_not_possible_message">Por favor, descargue el medio o utilice una aplicación externa.</string>
<string name="strict_mode">¡Modo estricto: no se permiten conexiones HTTP!</string>
<string name="sub_folder_rule_day">Año/Mes/Día</string>
<string name="sub_folder_rule_month">Año/Mes</string>
<string name="sub_folder_rule_year">Año</string>
<string name="subject_shared_with_you">\"%1$s\" ha sido compartido contigo</string> <string name="subject_shared_with_you">\"%1$s\" ha sido compartido contigo</string>
<string name="subject_user_shared_with_you">%1$s ha compartido \"%2$s\" contigo</string> <string name="subject_user_shared_with_you">%1$s ha compartido \"%2$s\" contigo</string>
<string name="subtitle_photos_only">Sólo fotos</string>
<string name="subtitle_photos_videos">Fotos y videos</string>
<string name="subtitle_videos_only">Sólo videos</string>
<string name="suggest">Sugerir</string>
<string name="sync_conflicts_in_favourites_ticker">Se encontraron conflictos</string> <string name="sync_conflicts_in_favourites_ticker">Se encontraron conflictos</string>
<string name="sync_current_folder_was_removed">La carpeta %1$s ya no existe</string> <string name="sync_current_folder_was_removed">La carpeta %1$s ya no existe</string>
<string name="sync_fail_content">No fue posible sincronizar %1$s</string> <string name="sync_fail_content">No fue posible sincronizar %1$s</string>
@ -727,14 +812,20 @@
<string name="sync_fail_ticker">Falla en la sincronización</string> <string name="sync_fail_ticker">Falla en la sincronización</string>
<string name="sync_fail_ticker_unauthorized">La sincronización falló, inicia sesión de nuevo</string> <string name="sync_fail_ticker_unauthorized">La sincronización falló, inicia sesión de nuevo</string>
<string name="sync_file_nothing_to_do_msg">Los contenidos del archivo ya han sido sincronizados</string> <string name="sync_file_nothing_to_do_msg">Los contenidos del archivo ya han sido sincronizados</string>
<string name="sync_folder_failed_content">La sincronización de la carpeta %1$sno pudo ser completada</string>
<string name="sync_foreign_files_forgotten_explanation">A partir de la versión 1.3.16, los archivos cargados desde este dispositivo serán copiados a la carpeta local %1$s para prevenir pérdidas de datos cuando un archivo se sincroniza entre múltiples cuentas. \n\nDerivado de este cambio, todos los archivos cargados con versiones anteriores de la aplicación fueron copiados a la carpeta %2$s. Sin embargo, un error evitó que se completara esta operación durante la sincronización de la cuenta. Puedes dejar el(los) archivo(s) como está(n) y eliminar el enlace a %3$s, o bien, mover el(los) archivo(s) a la carpeta %1$s y mantener el enlace a %4$s.\n\nSe enlistan a continuación los archivos locales así como los archivos remotos en %5$s a donde estaban ligados</string> <string name="sync_foreign_files_forgotten_explanation">A partir de la versión 1.3.16, los archivos cargados desde este dispositivo serán copiados a la carpeta local %1$s para prevenir pérdidas de datos cuando un archivo se sincroniza entre múltiples cuentas. \n\nDerivado de este cambio, todos los archivos cargados con versiones anteriores de la aplicación fueron copiados a la carpeta %2$s. Sin embargo, un error evitó que se completara esta operación durante la sincronización de la cuenta. Puedes dejar el(los) archivo(s) como está(n) y eliminar el enlace a %3$s, o bien, mover el(los) archivo(s) a la carpeta %1$s y mantener el enlace a %4$s.\n\nSe enlistan a continuación los archivos locales así como los archivos remotos en %5$s a donde estaban ligados</string>
<string name="sync_foreign_files_forgotten_ticker">Algunos archivos locales se han perdido</string> <string name="sync_foreign_files_forgotten_ticker">Algunos archivos locales se han perdido</string>
<string name="sync_in_progress">Obteniendo la versión más reciente del archivo.</string> <string name="sync_in_progress">Obteniendo la versión más reciente del archivo.</string>
<string name="sync_not_enough_space_dialog_action_choose">Elija qué sincronizar</string>
<string name="sync_not_enough_space_dialog_action_free_space">Liberar espacio</string>
<string name="sync_not_enough_space_dialog_placeholder">%1$s tiene %2$s, pero sólo hay %3$s disponibles en el dispositivo.</string>
<string name="sync_not_enough_space_dialog_title">No hay espacio suficiente</string>
<string name="sync_status_button">Botón de status de sincronización</string> <string name="sync_status_button">Botón de status de sincronización</string>
<string name="sync_string_files">Archivos</string> <string name="sync_string_files">Archivos</string>
<string name="synced_folder_settings_button">Botón de configuración</string> <string name="synced_folder_settings_button">Botón de configuración</string>
<string name="synced_folders_configure_folders">Configurar carpetas</string> <string name="synced_folders_configure_folders">Configurar carpetas</string>
<string name="synced_folders_new_info">Las cargas instantáneas se han mejorado por completo. Re-configura tu carga automática desde el menú principal.\n\nDisfruta la nueva y mejorada carga automática. </string> <string name="synced_folders_new_info">Las cargas instantáneas se han mejorado por completo. Re-configura tu carga automática desde el menú principal.\n\nDisfruta la nueva y mejorada carga automática. </string>
<string name="synced_folders_no_results">No se encontraron carpetas de medios</string>
<string name="synced_folders_preferences_folder_path">Para %1$s</string> <string name="synced_folders_preferences_folder_path">Para %1$s</string>
<string name="synced_folders_type">Tipo</string> <string name="synced_folders_type">Tipo</string>
<string name="synced_icon">Sincronizado</string> <string name="synced_icon">Sincronizado</string>
@ -743,26 +834,42 @@
<string name="thirtyMinutes">30 minutos</string> <string name="thirtyMinutes">30 minutos</string>
<string name="thisWeek">Esta semana</string> <string name="thisWeek">Esta semana</string>
<string name="thumbnail">Miniatura</string> <string name="thumbnail">Miniatura</string>
<string name="thumbnail_for_existing_file_description">Miniatura para el archivo existente</string>
<string name="thumbnail_for_new_file_desc">Miniatura para el nuevo archivo</string>
<string name="timeout_richDocuments">La carga está tardando más de lo esperado</string>
<string name="today">Hoy</string> <string name="today">Hoy</string>
<string name="trashbin_activity_title">Papelera</string> <string name="trashbin_activity_title">Papelera</string>
<string name="trashbin_empty_headline">No hay archivos eliminados</string> <string name="trashbin_empty_headline">No hay archivos eliminados</string>
<string name="trashbin_empty_message">Podrás recuperar los archivos eliminados desde aquí </string> <string name="trashbin_empty_message">Podrás recuperar los archivos eliminados desde aquí </string>
<string name="trashbin_file_not_deleted">¡El archivo %1$s no pudo ser eliminado!</string> <string name="trashbin_file_not_deleted">¡El archivo %1$s no pudo ser eliminado!</string>
<string name="trashbin_file_not_restored">¡No se pudo restaurar el archivo %1$s!</string>
<string name="trashbin_file_remove">Borrar permanentemente</string> <string name="trashbin_file_remove">Borrar permanentemente</string>
<string name="trashbin_loading_failed">¡No se pudo cargar la papelera de reciclaje!</string>
<string name="trashbin_not_emptied">¡Los archivos no pudieron ser eliminados permanentemente!</string> <string name="trashbin_not_emptied">¡Los archivos no pudieron ser eliminados permanentemente!</string>
<string name="unlock_file">Archivo desbloqueado</string> <string name="unlock_file">Archivo desbloqueado</string>
<string name="unread_comments">Existen comentarios sin leer</string>
<string name="unset_encrypted">Desestablecer encripción</string> <string name="unset_encrypted">Desestablecer encripción</string>
<string name="unset_favorite">Eliminar de favoritos</string> <string name="unset_favorite">Eliminar de favoritos</string>
<string name="unshare_link_file_error">Ocurrió un error al intentar dejar de compartir este archivo o carpeta.</string>
<string name="unshare_link_file_no_exist">No se pudo dejar de compartir. Por favor, verifique que el archivo exista.</string>
<string name="unshare_link_forbidden_permissions">para dejar de compartir este archivo</string> <string name="unshare_link_forbidden_permissions">para dejar de compartir este archivo</string>
<string name="unsharing_failed">Se presentó una falla al dejar de compartir</string> <string name="unsharing_failed">Se presentó una falla al dejar de compartir</string>
<string name="untrusted_domain">Accede a través de un dominio no de confianza. Por favor consulta la documentación para más información.</string> <string name="untrusted_domain">Accede a través de un dominio no de confianza. Por favor consulta la documentación para más información.</string>
<string name="update_link_file_error">Ocurrió un error al tratar de actualizar el recurso compartido</string>
<string name="update_link_file_no_exist">No se pudo actualizar. Por favor, verifique que el archivo exista.</string>
<string name="update_link_forbidden_permissions">para actualizar este recurso compartido</string> <string name="update_link_forbidden_permissions">para actualizar este recurso compartido</string>
<string name="updating_share_failed">Se presentó una falla al actualizar el elemento compartido</string> <string name="updating_share_failed">Se presentó una falla al actualizar el elemento compartido</string>
<string name="upload_action_cancelled_clear">Limpiar cargas canceladas</string>
<string name="upload_action_cancelled_resume">Reanudar cargas canceladas</string>
<string name="upload_action_failed_clear">Borrar cargas fallidas</string> <string name="upload_action_failed_clear">Borrar cargas fallidas</string>
<string name="upload_action_failed_retry">Reintentar cargas fallidas</string> <string name="upload_action_failed_retry">Reintentar cargas fallidas</string>
<string name="upload_action_file_not_exist_message">Algunos archivos no existen, estos archivos no se pueden resumir</string>
<string name="upload_action_global_upload_pause">Pausar todas las cargas</string>
<string name="upload_action_global_upload_resume">Reanudar todas las cargas</string>
<string name="upload_cannot_create_file">No se puede crear el archivo local</string> <string name="upload_cannot_create_file">No se puede crear el archivo local</string>
<string name="upload_chooser_title">Cargar forma…</string> <string name="upload_chooser_title">Cargar forma…</string>
<string name="upload_content_from_other_apps">Cargar contenido de otras aplicaciones</string> <string name="upload_content_from_other_apps">Cargar contenido de otras aplicaciones</string>
<string name="upload_direct_camera_upload">Cargar desde la cámara</string>
<string name="upload_file_dialog_filename">Nombre de archivo</string> <string name="upload_file_dialog_filename">Nombre de archivo</string>
<string name="upload_file_dialog_filetype">Tipo de archivo</string> <string name="upload_file_dialog_filetype">Tipo de archivo</string>
<string name="upload_file_dialog_filetype_googlemap_shortcut">Archivo de acceso directo a Google Maps(%s)</string> <string name="upload_file_dialog_filetype_googlemap_shortcut">Archivo de acceso directo a Google Maps(%s)</string>
@ -770,11 +877,21 @@
<string name="upload_file_dialog_filetype_snippet_text">Archivo snippet de texto(.txt)</string> <string name="upload_file_dialog_filetype_snippet_text">Archivo snippet de texto(.txt)</string>
<string name="upload_file_dialog_title">Ingresa el nombre y el tipo del archivo a cargar</string> <string name="upload_file_dialog_title">Ingresa el nombre y el tipo del archivo a cargar</string>
<string name="upload_files">Cargar archivos</string> <string name="upload_files">Cargar archivos</string>
<string name="upload_global_pause_title">Todas las cargas están pausadas</string>
<string name="upload_item_action_button">Botón de cargar elemento</string> <string name="upload_item_action_button">Botón de cargar elemento</string>
<string name="upload_list_delete">Eliminar</string> <string name="upload_list_delete">Eliminar</string>
<string name="upload_list_empty_headline">No hay cargas disponibles</string> <string name="upload_list_empty_headline">No hay cargas disponibles</string>
<string name="upload_list_empty_text_auto_upload">Carga algún contenido o activa la carga automática</string> <string name="upload_list_empty_text_auto_upload">Carga algún contenido o activa la carga automática</string>
<string name="upload_list_resolve_conflict">Resolver conflicto</string>
<string name="upload_local_storage_full">Almacenamiento local lleno</string>
<string name="upload_local_storage_not_copied">No se pudo copiar el archivo al almacenamiento local</string>
<string name="upload_lock_failed">No se pudo bloquear la carpeta</string>
<string name="upload_manually_cancelled">La carga fue cancelada por el usuario</string>
<string name="upload_old_android">El cifrado sólo es posible con &gt;= Android 5.0</string>
<string name="upload_query_move_foreign_files">El espacio insuficiente evita que se copien los archivos seleccionados dentro de la carpeta %1$s. ¿Te gustaría moverlos ahí en su lugar?</string> <string name="upload_query_move_foreign_files">El espacio insuficiente evita que se copien los archivos seleccionados dentro de la carpeta %1$s. ¿Te gustaría moverlos ahí en su lugar?</string>
<string name="upload_quota_exceeded">Se excedió la cuota de almacenamiento</string>
<string name="upload_scan_doc_upload">Escanear documento desde la cámara</string>
<string name="upload_sync_conflict">Conflicto de sincronización, por favor, resuélvalo manualmente</string>
<string name="upload_unknown_error">Se presentó un error desconocido</string> <string name="upload_unknown_error">Se presentó un error desconocido</string>
<string name="uploader_btn_alternative_text">Seleccionar</string> <string name="uploader_btn_alternative_text">Seleccionar</string>
<string name="uploader_btn_upload_text">Cargar</string> <string name="uploader_btn_upload_text">Cargar</string>
@ -782,14 +899,21 @@
<string name="uploader_error_message_read_permission_not_granted">%1$s no tiene permitido leer un archivo recibido</string> <string name="uploader_error_message_read_permission_not_granted">%1$s no tiene permitido leer un archivo recibido</string>
<string name="uploader_error_message_source_file_not_copied">No fue posible copiar el archivo a una carpeta temporal. Por favor intenta enviarlo de nuevo.</string> <string name="uploader_error_message_source_file_not_copied">No fue posible copiar el archivo a una carpeta temporal. Por favor intenta enviarlo de nuevo.</string>
<string name="uploader_error_message_source_file_not_found">El archivo seleccionado para cargar no fue encontrado. Por favor verifica si el archivo existe.</string> <string name="uploader_error_message_source_file_not_found">El archivo seleccionado para cargar no fue encontrado. Por favor verifica si el archivo existe.</string>
<string name="uploader_error_title_file_cannot_be_uploaded">No se puede cargar este archivo</string>
<string name="uploader_error_title_no_file_to_upload">No hay un archivo a cargar</string> <string name="uploader_error_title_no_file_to_upload">No hay un archivo a cargar</string>
<string name="uploader_file_not_found_message">Archivo no encontrado. ¿Está seguro que este archivo existe o tiene un conflicto previo que no ha sido resuelto?</string>
<string name="uploader_file_not_found_on_server_message">No se pudo ubicar el archivo en el servidor. Otro usuario pudo haber eliminado el archivo</string>
<string name="uploader_info_dirname">Nombre de la carpeta</string> <string name="uploader_info_dirname">Nombre de la carpeta</string>
<string name="uploader_local_files_uploaded">Reintentar la carga de archivos locales fallidos</string>
<string name="uploader_top_message">Selecciona la carpeta de cargas</string> <string name="uploader_top_message">Selecciona la carpeta de cargas</string>
<string name="uploader_upload_failed_content_single">No fue posible cargar %1$s</string> <string name="uploader_upload_failed_content_single">No fue posible cargar %1$s</string>
<string name="uploader_upload_failed_credentials_error">La carga falló, inicia sesión de nuevo</string> <string name="uploader_upload_failed_credentials_error">La carga falló, inicia sesión de nuevo</string>
<string name="uploader_upload_failed_sync_conflict_error">Conflicto de carga del archivo</string>
<string name="uploader_upload_failed_sync_conflict_error_content">Elegir qué versión mantener de %1$s</string>
<string name="uploader_upload_failed_ticker">Falla en la carga</string> <string name="uploader_upload_failed_ticker">Falla en la carga</string>
<string name="uploader_upload_files_behaviour">Opción de carga:</string> <string name="uploader_upload_files_behaviour">Opción de carga:</string>
<string name="uploader_upload_files_behaviour_move_to_nextcloud_folder">Mover el archivo a la carpeta %1$s</string> <string name="uploader_upload_files_behaviour_move_to_nextcloud_folder">Mover el archivo a la carpeta %1$s</string>
<string name="uploader_upload_files_behaviour_not_writable">la carpeta de origen es de sólo lectura; el archivo solo se cargará</string>
<string name="uploader_upload_files_behaviour_only_upload">Mantener el archivo en la carpeta de origen</string> <string name="uploader_upload_files_behaviour_only_upload">Mantener el archivo en la carpeta de origen</string>
<string name="uploader_upload_files_behaviour_upload_and_delete_from_source">Borrar el archivo de la carpeta de origen</string> <string name="uploader_upload_files_behaviour_upload_and_delete_from_source">Borrar el archivo de la carpeta de origen</string>
<string name="uploader_upload_forbidden_permissions">para cargar a esta carpeta</string> <string name="uploader_upload_forbidden_permissions">para cargar a esta carpeta</string>
@ -816,12 +940,15 @@
<string name="uploads_view_upload_status_failed_localfile_error">No se encontró el archivo local</string> <string name="uploads_view_upload_status_failed_localfile_error">No se encontró el archivo local</string>
<string name="uploads_view_upload_status_failed_permission_error">Error de permisos</string> <string name="uploads_view_upload_status_failed_permission_error">Error de permisos</string>
<string name="uploads_view_upload_status_failed_ssl_certificate_not_trusted">El certificado del servidor no es de confianza</string> <string name="uploads_view_upload_status_failed_ssl_certificate_not_trusted">El certificado del servidor no es de confianza</string>
<string name="uploads_view_upload_status_fetching_server_version">Obteniendo la versión del servidor...</string>
<string name="uploads_view_upload_status_service_interrupted">La aplicación ha sido terminada</string> <string name="uploads_view_upload_status_service_interrupted">La aplicación ha sido terminada</string>
<string name="uploads_view_upload_status_succeeded">Completado</string> <string name="uploads_view_upload_status_succeeded">Completado</string>
<string name="uploads_view_upload_status_succeeded_same_file">Se encontró el mismo archivo en el remoto, omitiendo carga</string>
<string name="uploads_view_upload_status_unknown_fail">Error desconocido</string> <string name="uploads_view_upload_status_unknown_fail">Error desconocido</string>
<string name="uploads_view_upload_status_virus_detected">Virus detectado. ¡La carga no puede ser completada!</string> <string name="uploads_view_upload_status_virus_detected">Virus detectado. ¡La carga no puede ser completada!</string>
<string name="uploads_view_upload_status_waiting_exit_power_save_mode">Esperando a salir de modo de conservación de energía</string> <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Esperando a salir de modo de conservación de energía</string>
<string name="uploads_view_upload_status_waiting_for_charging">Aguardando la regarga del dispositivo </string> <string name="uploads_view_upload_status_waiting_for_charging">Aguardando la regarga del dispositivo </string>
<string name="uploads_view_upload_status_waiting_for_wifi">Esperando un Wi-Fi de uso no medido</string>
<string name="user_icon">Usuario</string> <string name="user_icon">Usuario</string>
<string name="user_info_address">Dirección</string> <string name="user_info_address">Dirección</string>
<string name="user_info_email">Correo electrónico</string> <string name="user_info_email">Correo electrónico</string>
@ -833,18 +960,91 @@
<string name="userinfo_no_info_text">Agrega tu nombre, una imagen y detalles de contacto en tu página de perfil. </string> <string name="userinfo_no_info_text">Agrega tu nombre, una imagen y detalles de contacto en tu página de perfil. </string>
<string name="username">Usuario</string> <string name="username">Usuario</string>
<string name="version_dev_download">Descargar</string> <string name="version_dev_download">Descargar</string>
<string name="video_overlay_icon">Ícono de superposición de video</string>
<string name="wait_a_moment">Aguarda un momento…</string> <string name="wait_a_moment">Aguarda un momento…</string>
<string name="wait_checking_credentials">Verificando credenciales almacenadas</string> <string name="wait_checking_credentials">Verificando credenciales almacenadas</string>
<string name="wait_for_tmp_copy_from_private_storage">Copiando el archivo desde almacenamiento privado</string> <string name="wait_for_tmp_copy_from_private_storage">Copiando el archivo desde almacenamiento privado</string>
<string name="webview_version_check_alert_dialog_message">Por favor, actualice la aplicación WebView del sistema de Android para iniciar sesión</string>
<string name="webview_version_check_alert_dialog_positive_button_title">Actualizar</string> <string name="webview_version_check_alert_dialog_positive_button_title">Actualizar</string>
<string name="webview_version_check_alert_dialog_title">Actualice la aplicación WebView del sistema de Android</string>
<string name="what_s_new_image">Imagen de qué es nuevo</string> <string name="what_s_new_image">Imagen de qué es nuevo</string>
<string name="whats_new_skip">Omitir</string> <string name="whats_new_skip">Omitir</string>
<string name="whats_new_title">Nuevo en %1$s</string> <string name="whats_new_title">Nuevo en %1$s</string>
<string name="whats_your_status">¿Cuál es su estado?</string>
<string name="widgets_not_available">Los widgets sólo están disponibles para %1$s 25 o superior</string>
<string name="widgets_not_available_title">No disponible</string>
<string name="worker_download">Descargando archivos…</string> <string name="worker_download">Descargando archivos…</string>
<string name="write_email">Enviar correo electrónico</string> <string name="write_email">Enviar correo electrónico</string>
<string name="wrong_storage_path">¡La carpeta de almacenamiento de datos no existe!</string>
<string name="wrong_storage_path_desc">Esto puede deberse a una restauración de un respaldo en otro dispositivo. Se volverá a la configuración predeterminada. Por favor, compruebe la configuración para ajustar la carpeta de almacenamiento de datos.</string>
<plurals name="sync_fail_in_favourites_content">
<item quantity="one">No se pudo sincronizar %1$d archivo (conflictos: %2$d)</item>
<item quantity="many">No se pudieron sincronizar %1$d archivos (conflictos: %2$d)</item>
<item quantity="other">No se pudieron sincronizar %1$d archivos (conflictos: %2$d)</item>
</plurals>
<plurals name="sync_foreign_files_forgotten_content">
<item quantity="one">No se pudo copiar %1$d archivo de la carpeta %2$s en</item>
<item quantity="many">No se pudieron copiar %1$d archivos de la carpeta %2$s en</item>
<item quantity="other">No se pudieron copiar %1$d archivos de la carpeta %2$s en</item>
</plurals>
<plurals name="wrote_n_events_to">
<item quantity="one">Se escribió %1$d evento en %2$s</item>
<item quantity="many">Se escribieron %1$d eventos en %2$s</item>
<item quantity="other">Se escribieron %1$d eventos en %2$s</item>
</plurals>
<plurals name="created_n_uids_to">
<item quantity="one">Se creó %1$d nuevos UID</item>
<item quantity="many">Se crearon %1$d nuevos UIDs</item>
<item quantity="other">Se crearon %1$d nuevos UIDs</item>
</plurals>
<plurals name="processed_n_entries">
<item quantity="one">Se procesó %d entrada.</item>
<item quantity="many">Se procesaron %d entradas.</item>
<item quantity="other">Se procesaron %d entradas.</item>
</plurals>
<plurals name="found_n_duplicates">
<item quantity="one">Se encontró %d entrada duplicada.</item>
<item quantity="many">Se encontraron %d entradas duplicadas.</item>
<item quantity="other">Se encontraron %d entradas duplicadas.</item>
</plurals>
<plurals name="export_successful">
<item quantity="one">%d archivo exportado</item>
<item quantity="many">%d archivos exportados</item>
<item quantity="other">%d archivos exportados</item>
</plurals>
<plurals name="export_failed">
<item quantity="one">No se pudo exportar %d archivo</item>
<item quantity="many">No se pudieron exportar %d archivos</item>
<item quantity="other">No se pudieron exportar %d archivos</item>
</plurals>
<plurals name="export_partially_failed">
<item quantity="one">Se exportó %d archivo, se omitió el resto debido a un error</item>
<item quantity="many">Se exportaron %d archivos, se omitió el resto debido a un error</item>
<item quantity="other">Se exportaron %d archivos, se omitió el resto debido a un error</item>
</plurals>
<plurals name="export_start">
<item quantity="one">%d archivo se exportará. Vea la notificación para más detalles.</item>
<item quantity="many">%d archivos se exportarán. Vea la notificación para más detalles.</item>
<item quantity="other">%d archivos se exportarán. Vea la notificación para más detalles.</item>
</plurals>
<plurals name="file_list__footer__folder">
<item quantity="one">%1$d carpeta</item>
<item quantity="many">%1$d carpetas</item>
<item quantity="other">%1$d carpetas</item>
</plurals>
<plurals name="file_list__footer__file"> <plurals name="file_list__footer__file">
<item quantity="one">%1$d archivo</item> <item quantity="one">%1$d archivo</item>
<item quantity="many">%1$d archivos</item> <item quantity="many">%1$d archivos</item>
<item quantity="other">%1$d archivos</item> <item quantity="other">%1$d archivos</item>
</plurals> </plurals>
</resources> <plurals name="synced_folders_show_hidden_folders">
<item quantity="one">Mostrar %1$d carpeta oculta</item>
<item quantity="many">Mostrar %1$d carpetas ocultas</item>
<item quantity="other">Mostrar %1$d carpetas ocultas</item>
</plurals>
<plurals name="items_selected_count">
<item quantity="one">%d seleccionado</item>
<item quantity="many">%d seleccionados</item>
<item quantity="other">%d seleccionados</item>
</plurals>
</resources>

View file

@ -24,6 +24,7 @@
<string name="activity_icon">Actividad</string> <string name="activity_icon">Actividad</string>
<string name="add_to_cloud">Agregar a %1$s</string> <string name="add_to_cloud">Agregar a %1$s</string>
<string name="allow_resharing">Permitir volver a compartir</string> <string name="allow_resharing">Permitir volver a compartir</string>
<string name="assistant_screen_all_task_type">Todos</string>
<string name="auth_account_does_not_exist">La cuenta aún no ha sido agregada a este dispositivo </string> <string name="auth_account_does_not_exist">La cuenta aún no ha sido agregada a este dispositivo </string>
<string name="auth_account_not_new">Ya existe una cuenta en el dispositivo para el mismo usuario y servidor </string> <string name="auth_account_not_new">Ya existe una cuenta en el dispositivo para el mismo usuario y servidor </string>
<string name="auth_account_not_the_same">El usuario ingresado no corresponde con el usuario de esta cuenta</string> <string name="auth_account_not_the_same">El usuario ingresado no corresponde con el usuario de esta cuenta</string>

View file

@ -36,6 +36,8 @@
<string name="allow_resharing">Permitir que se vuelva a compartir</string> <string name="allow_resharing">Permitir que se vuelva a compartir</string>
<string name="app_widget_description">Muestra un widget del tablero</string> <string name="app_widget_description">Muestra un widget del tablero</string>
<string name="appbar_search_in">Buscar en %s</string> <string name="appbar_search_in">Buscar en %s</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Escriba algo de texto</string>
<string name="assistant_screen_task_view_show_less">Ver menos</string>
<string name="associated_account_not_found">¡Cuenta asociada no encontrada!</string> <string name="associated_account_not_found">¡Cuenta asociada no encontrada!</string>
<string name="auth_access_failed">Acceso fallido: %1$s</string> <string name="auth_access_failed">Acceso fallido: %1$s</string>
<string name="auth_account_does_not_exist">La cuenta no se ha añadido aún en este dispositivo</string> <string name="auth_account_does_not_exist">La cuenta no se ha añadido aún en este dispositivo</string>

View file

@ -30,6 +30,7 @@
<string name="add_another_public_share_link">Lisa veel üks link</string> <string name="add_another_public_share_link">Lisa veel üks link</string>
<string name="advanced_settings">Täpsemad seaded</string> <string name="advanced_settings">Täpsemad seaded</string>
<string name="allow_resharing">Luba edasijagamine</string> <string name="allow_resharing">Luba edasijagamine</string>
<string name="assistant_screen_all_task_type">Kõik</string>
<string name="associated_account_not_found">Seotud kontot ei leitud!</string> <string name="associated_account_not_found">Seotud kontot ei leitud!</string>
<string name="auth_access_failed">Ligipääs ebaõnnestus: %1$s</string> <string name="auth_access_failed">Ligipääs ebaõnnestus: %1$s</string>
<string name="auth_account_does_not_exist">Seda kontot pole veel sellesse seadmesse lisatud</string> <string name="auth_account_does_not_exist">Seda kontot pole veel sellesse seadmesse lisatud</string>

View file

@ -37,6 +37,11 @@
<string name="allow_resharing">Baimendu birpartekatzea</string> <string name="allow_resharing">Baimendu birpartekatzea</string>
<string name="app_widget_description">Paneleko trepeta bat erakusten du</string> <string name="app_widget_description">Paneleko trepeta bat erakusten du</string>
<string name="appbar_search_in">Bilatu %s(e)n</string> <string name="appbar_search_in">Bilatu %s(e)n</string>
<string name="assistant_screen_all_task_type">Denak</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Idatzi testu bat</string>
<string name="assistant_screen_task_delete_fail_message">Zeregina ongi ezabatu da</string>
<string name="assistant_screen_task_view_show_less">Erakutsi gutxiago</string>
<string name="assistant_screen_task_view_show_more">Erakutsi gehiago</string>
<string name="associated_account_not_found">Ez da aurkitu lotutako konturik</string> <string name="associated_account_not_found">Ez da aurkitu lotutako konturik</string>
<string name="auth_access_failed">Huts egin du atzitzean: %1$s</string> <string name="auth_access_failed">Huts egin du atzitzean: %1$s</string>
<string name="auth_account_does_not_exist">Kontua ez da gailu honetan gehitu oraindik</string> <string name="auth_account_does_not_exist">Kontua ez da gailu honetan gehitu oraindik</string>

View file

@ -37,6 +37,11 @@
<string name="allow_resharing">مجاز به اشتراک گذاری مجدد</string> <string name="allow_resharing">مجاز به اشتراک گذاری مجدد</string>
<string name="app_widget_description">نمایش یک ابزارک از پیشخوان</string> <string name="app_widget_description">نمایش یک ابزارک از پیشخوان</string>
<string name="appbar_search_in">جستجو در %s</string> <string name="appbar_search_in">جستجو در %s</string>
<string name="assistant_screen_all_task_type">همه</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">مقداری متن را تایپ کنید</string>
<string name="assistant_screen_task_delete_fail_message">Task successfully deleted</string>
<string name="assistant_screen_task_view_show_less">Show less</string>
<string name="assistant_screen_task_view_show_more">Show more</string>
<string name="associated_account_not_found">حساب مرتبط یافت نشد!</string> <string name="associated_account_not_found">حساب مرتبط یافت نشد!</string>
<string name="auth_access_failed">دسترسی خطای %1$s</string> <string name="auth_access_failed">دسترسی خطای %1$s</string>
<string name="auth_account_does_not_exist">حساب به این وسیله اضافه نشده است</string> <string name="auth_account_does_not_exist">حساب به این وسیله اضافه نشده است</string>

View file

@ -34,6 +34,10 @@
<string name="allow_resharing">Salli uudelleenjakaminen</string> <string name="allow_resharing">Salli uudelleenjakaminen</string>
<string name="app_widget_description">Näyttää yhden pienoissovelluksen konsolista</string> <string name="app_widget_description">Näyttää yhden pienoissovelluksen konsolista</string>
<string name="appbar_search_in">Etsi kohteesta %s</string> <string name="appbar_search_in">Etsi kohteesta %s</string>
<string name="assistant_screen_all_task_type">Kaikki</string>
<string name="assistant_screen_task_delete_fail_message">Tehtävä poistettu</string>
<string name="assistant_screen_task_view_show_less">Näytä vähemmän</string>
<string name="assistant_screen_task_view_show_more">Näytä enemmän</string>
<string name="associated_account_not_found">Liitettyä tiliä ei löydy!</string> <string name="associated_account_not_found">Liitettyä tiliä ei löydy!</string>
<string name="auth_access_failed">Pääsy epäonnistui: %1$s</string> <string name="auth_access_failed">Pääsy epäonnistui: %1$s</string>
<string name="auth_account_does_not_exist">Tälle laitteelle ei ole vielä asennettu tiliä.</string> <string name="auth_account_does_not_exist">Tälle laitteelle ei ole vielä asennettu tiliä.</string>

View file

@ -37,6 +37,11 @@
<string name="allow_resharing">Autoriser le repartage</string> <string name="allow_resharing">Autoriser le repartage</string>
<string name="app_widget_description">Affiche un widget du tableau de bord</string> <string name="app_widget_description">Affiche un widget du tableau de bord</string>
<string name="appbar_search_in">Recherche dans %s</string> <string name="appbar_search_in">Recherche dans %s</string>
<string name="assistant_screen_all_task_type">Tout</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Tapez du texte</string>
<string name="assistant_screen_task_delete_fail_message">Tâche supprimée avec succès</string>
<string name="assistant_screen_task_view_show_less">Afficher moins</string>
<string name="assistant_screen_task_view_show_more">Afficher plus</string>
<string name="associated_account_not_found">Compte associé introuvable !</string> <string name="associated_account_not_found">Compte associé introuvable !</string>
<string name="auth_access_failed">L\'accès a échoué: %1$s</string> <string name="auth_access_failed">L\'accès a échoué: %1$s</string>
<string name="auth_account_does_not_exist">Le compte n\'est pas encore ajouté sur cet appareil</string> <string name="auth_account_does_not_exist">Le compte n\'est pas encore ajouté sur cet appareil</string>

View file

@ -36,6 +36,11 @@
<string name="allow_resharing">Permitir compartir</string> <string name="allow_resharing">Permitir compartir</string>
<string name="app_widget_description">Amosa un trebello do taboleiro</string> <string name="app_widget_description">Amosa un trebello do taboleiro</string>
<string name="appbar_search_in">Buscar en %s</string> <string name="appbar_search_in">Buscar en %s</string>
<string name="assistant_screen_all_task_type">Todo</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Escriba algún texto</string>
<string name="assistant_screen_task_delete_fail_message">A tarefa foi eliminada satisfactoriamente</string>
<string name="assistant_screen_task_view_show_less">Amosar menos</string>
<string name="assistant_screen_task_view_show_more">Amosar máis</string>
<string name="associated_account_not_found">Non se atopou unha conta asociada!</string> <string name="associated_account_not_found">Non se atopou unha conta asociada!</string>
<string name="auth_access_failed">Acceso fallado: %1$s</string> <string name="auth_access_failed">Acceso fallado: %1$s</string>
<string name="auth_account_does_not_exist">A conta aínda non existe no dispositivo</string> <string name="auth_account_does_not_exist">A conta aínda non existe no dispositivo</string>

View file

@ -33,6 +33,9 @@
<string name="advanced_settings">Napredne postavke</string> <string name="advanced_settings">Napredne postavke</string>
<string name="allow_resharing">Dopusti ponovno dijeljenje</string> <string name="allow_resharing">Dopusti ponovno dijeljenje</string>
<string name="appbar_search_in">Traži u %s</string> <string name="appbar_search_in">Traži u %s</string>
<string name="assistant_screen_all_task_type">Sve</string>
<string name="assistant_screen_task_view_show_less">Prikaži manje</string>
<string name="assistant_screen_task_view_show_more">Prikaži više</string>
<string name="associated_account_not_found">Pripadajući račun nije pronađen!</string> <string name="associated_account_not_found">Pripadajući račun nije pronađen!</string>
<string name="auth_access_failed">Neuspjeli pristup: %1$s</string> <string name="auth_access_failed">Neuspjeli pristup: %1$s</string>
<string name="auth_account_does_not_exist">Račun još nije dodan na ovaj uređaj</string> <string name="auth_account_does_not_exist">Račun još nije dodan na ovaj uređaj</string>

View file

@ -36,6 +36,11 @@
<string name="allow_resharing">Újra megosztás engedélyezése</string> <string name="allow_resharing">Újra megosztás engedélyezése</string>
<string name="app_widget_description">Egy modult jelenít meg a irányítópultról</string> <string name="app_widget_description">Egy modult jelenít meg a irányítópultról</string>
<string name="appbar_search_in">Keresés itt: %s</string> <string name="appbar_search_in">Keresés itt: %s</string>
<string name="assistant_screen_all_task_type">Összes</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Gépeljen be szöveget</string>
<string name="assistant_screen_task_delete_fail_message">Feladat sikeresen törölve</string>
<string name="assistant_screen_task_view_show_less">Kevesebb megjelenítése</string>
<string name="assistant_screen_task_view_show_more">Több megjelenítése</string>
<string name="associated_account_not_found">A kapcsolódó fiók nem található!</string> <string name="associated_account_not_found">A kapcsolódó fiók nem található!</string>
<string name="auth_access_failed">Hozzáférési hiba: %1$s</string> <string name="auth_access_failed">Hozzáférési hiba: %1$s</string>
<string name="auth_account_does_not_exist">Az eszközön még nem létezik a fiók</string> <string name="auth_account_does_not_exist">Az eszközön még nem létezik a fiók</string>

View file

@ -34,6 +34,7 @@
<string name="advanced_settings">Pengaturan Tambahan</string> <string name="advanced_settings">Pengaturan Tambahan</string>
<string name="allow_resharing">Izinkan pembagian ulang</string> <string name="allow_resharing">Izinkan pembagian ulang</string>
<string name="appbar_search_in">Cari dalam %s</string> <string name="appbar_search_in">Cari dalam %s</string>
<string name="assistant_screen_all_task_type">Semua</string>
<string name="associated_account_not_found">Akun terkait tidak ditemukan!</string> <string name="associated_account_not_found">Akun terkait tidak ditemukan!</string>
<string name="auth_access_failed">Akses gagal: %1$s</string> <string name="auth_access_failed">Akses gagal: %1$s</string>
<string name="auth_account_does_not_exist">Akun ini belum ditambahkan ke perangkat ini</string> <string name="auth_account_does_not_exist">Akun ini belum ditambahkan ke perangkat ini</string>

View file

@ -35,6 +35,7 @@
<string name="allow_resharing">Leyfa endurdeilingu</string> <string name="allow_resharing">Leyfa endurdeilingu</string>
<string name="app_widget_description">Sýnir einn viðmótshluta af stjórnborði</string> <string name="app_widget_description">Sýnir einn viðmótshluta af stjórnborði</string>
<string name="appbar_search_in">Leita í %s</string> <string name="appbar_search_in">Leita í %s</string>
<string name="assistant_screen_all_task_type">Allt</string>
<string name="associated_account_not_found">Tengdur notandaaðgangur fannst ekki!</string> <string name="associated_account_not_found">Tengdur notandaaðgangur fannst ekki!</string>
<string name="auth_access_failed">Aðgangur mistókst: %1$s</string> <string name="auth_access_failed">Aðgangur mistókst: %1$s</string>
<string name="auth_account_does_not_exist">Aðgangur er ekki ennþá til á tækinu</string> <string name="auth_account_does_not_exist">Aðgangur er ekki ennþá til á tækinu</string>

View file

@ -36,6 +36,9 @@
<string name="allow_resharing">Consenti la ri-condivisione</string> <string name="allow_resharing">Consenti la ri-condivisione</string>
<string name="app_widget_description">Mostra un widget dal cruscotto</string> <string name="app_widget_description">Mostra un widget dal cruscotto</string>
<string name="appbar_search_in">Cerca in %s</string> <string name="appbar_search_in">Cerca in %s</string>
<string name="assistant_screen_all_task_type">Tutti</string>
<string name="assistant_screen_task_view_show_less">Mostra meno</string>
<string name="assistant_screen_task_view_show_more">Mostra più</string>
<string name="associated_account_not_found">Account associato non trovato.</string> <string name="associated_account_not_found">Account associato non trovato.</string>
<string name="auth_access_failed">Accesso non riuscito: %1$s</string> <string name="auth_access_failed">Accesso non riuscito: %1$s</string>
<string name="auth_account_does_not_exist">L\'account non è ancora aggiunto su questo dispositivo</string> <string name="auth_account_does_not_exist">L\'account non è ancora aggiunto su questo dispositivo</string>

View file

@ -34,6 +34,9 @@
<string name="allow_resharing">לאפשר שיתוף מחדש</string> <string name="allow_resharing">לאפשר שיתוף מחדש</string>
<string name="app_widget_description">מצג וידג׳ט אחד מלוח הבקרה</string> <string name="app_widget_description">מצג וידג׳ט אחד מלוח הבקרה</string>
<string name="appbar_search_in">חפש ב %s</string> <string name="appbar_search_in">חפש ב %s</string>
<string name="assistant_screen_all_task_type">הכול</string>
<string name="assistant_screen_task_view_show_less">להציג פחות</string>
<string name="assistant_screen_task_view_show_more">להציג יותר</string>
<string name="associated_account_not_found">לא נמצא חשבון משויך!</string> <string name="associated_account_not_found">לא נמצא חשבון משויך!</string>
<string name="auth_access_failed">גישה נכשלה: %1$s</string> <string name="auth_access_failed">גישה נכשלה: %1$s</string>
<string name="auth_account_does_not_exist">החשבון לא נוסף למכשיר הזה עדיין</string> <string name="auth_account_does_not_exist">החשבון לא נוסף למכשיר הזה עדיין</string>

View file

@ -37,6 +37,9 @@
<string name="allow_resharing">再共有を許可する</string> <string name="allow_resharing">再共有を許可する</string>
<string name="app_widget_description">ダッシュボードから一つのウィジェットを表示</string> <string name="app_widget_description">ダッシュボードから一つのウィジェットを表示</string>
<string name="appbar_search_in">%s の中を検索</string> <string name="appbar_search_in">%s の中を検索</string>
<string name="assistant_screen_all_task_type">すべて</string>
<string name="assistant_screen_task_view_show_less">表示を減らす</string>
<string name="assistant_screen_task_view_show_more">表示を増やす</string>
<string name="associated_account_not_found">関連付けられたアカウントが見つかりません!</string> <string name="associated_account_not_found">関連付けられたアカウントが見つかりません!</string>
<string name="auth_access_failed">アクセスに失敗しました: %1$s</string> <string name="auth_access_failed">アクセスに失敗しました: %1$s</string>
<string name="auth_account_does_not_exist">このアカウントはまだこのデバイスに追加されていません</string> <string name="auth_account_does_not_exist">このアカウントはまだこのデバイスに追加されていません</string>

View file

@ -23,6 +23,7 @@
<string name="activity_chooser_title">გააგზავნეთ ბმული…</string> <string name="activity_chooser_title">გააგზავნეთ ბმული…</string>
<string name="activity_icon">აქტივობა</string> <string name="activity_icon">აქტივობა</string>
<string name="allow_resharing">ხელახალი გაზიარების დაშვება</string> <string name="allow_resharing">ხელახალი გაზიარების დაშვება</string>
<string name="assistant_screen_all_task_type">ყველა</string>
<string name="auth_account_does_not_exist">ანგარიში ამ მოწყობილობაზე ჯერ არაა დამატებული</string> <string name="auth_account_does_not_exist">ანგარიში ამ მოწყობილობაზე ჯერ არაა დამატებული</string>
<string name="auth_account_not_new">მოწყობილობაზე ანგარიში ამ მომხმარებლით და სერვერით უკვე არსებობს</string> <string name="auth_account_not_new">მოწყობილობაზე ანგარიში ამ მომხმარებლით და სერვერით უკვე არსებობს</string>
<string name="auth_account_not_the_same">შეყვანილი მომხმარებელი არ ემთხვევა ამ ანგარიშის მომხმარებელს</string> <string name="auth_account_not_the_same">შეყვანილი მომხმარებელი არ ემთხვევა ამ ანგარიშის მომხმარებელს</string>

View file

@ -36,6 +36,9 @@
<string name="allow_resharing">Allow resharing</string> <string name="allow_resharing">Allow resharing</string>
<string name="app_widget_description">Shows one widget from dashboard</string> <string name="app_widget_description">Shows one widget from dashboard</string>
<string name="appbar_search_in">Search in %s</string> <string name="appbar_search_in">Search in %s</string>
<string name="assistant_screen_all_task_type">All</string>
<string name="assistant_screen_task_view_show_less">Show less</string>
<string name="assistant_screen_task_view_show_more">Show more</string>
<string name="associated_account_not_found">Associated account not found!</string> <string name="associated_account_not_found">Associated account not found!</string>
<string name="auth_access_failed">Access failed: %1$s</string> <string name="auth_access_failed">Access failed: %1$s</string>
<string name="auth_account_does_not_exist">The account is not added on this device yet</string> <string name="auth_account_does_not_exist">The account is not added on this device yet</string>

View file

@ -36,6 +36,9 @@
<string name="allow_resharing">다시 공유 허용</string> <string name="allow_resharing">다시 공유 허용</string>
<string name="app_widget_description">대시보드에 위젯 하나만 표시</string> <string name="app_widget_description">대시보드에 위젯 하나만 표시</string>
<string name="appbar_search_in">%s에서 검색</string> <string name="appbar_search_in">%s에서 검색</string>
<string name="assistant_screen_all_task_type">모두</string>
<string name="assistant_screen_task_view_show_less">적게 보기</string>
<string name="assistant_screen_task_view_show_more">더 보기</string>
<string name="associated_account_not_found">관련 계정을 찾을 수 없습니다!</string> <string name="associated_account_not_found">관련 계정을 찾을 수 없습니다!</string>
<string name="auth_access_failed">접근 실패: %1$s</string> <string name="auth_access_failed">접근 실패: %1$s</string>
<string name="auth_account_does_not_exist">이 장치에 아직 계정이 추가되지 않았음</string> <string name="auth_account_does_not_exist">이 장치에 아직 계정이 추가되지 않았음</string>

View file

@ -33,6 +33,9 @@
<string name="advanced_settings">Išplėstiniai nustatymai</string> <string name="advanced_settings">Išplėstiniai nustatymai</string>
<string name="allow_resharing">Leisti bendrinti iš naujo</string> <string name="allow_resharing">Leisti bendrinti iš naujo</string>
<string name="appbar_search_in">Ieškoti %s</string> <string name="appbar_search_in">Ieškoti %s</string>
<string name="assistant_screen_all_task_type">Visos</string>
<string name="assistant_screen_task_view_show_less">Rodyti mažiau</string>
<string name="assistant_screen_task_view_show_more">Rodyti daugiau</string>
<string name="associated_account_not_found">Susieta paskyra nerasta!</string> <string name="associated_account_not_found">Susieta paskyra nerasta!</string>
<string name="auth_access_failed">Prieiga nepavyko: %1$s</string> <string name="auth_access_failed">Prieiga nepavyko: %1$s</string>
<string name="auth_account_does_not_exist">Paskyra kol kas šiame įrenginyje nėra pridėta</string> <string name="auth_account_does_not_exist">Paskyra kol kas šiame įrenginyje nėra pridėta</string>

View file

@ -30,6 +30,7 @@
<string name="add_to_cloud">Pievienot %1$s</string> <string name="add_to_cloud">Pievienot %1$s</string>
<string name="advanced_settings">Paplašināti iestatījumi</string> <string name="advanced_settings">Paplašināti iestatījumi</string>
<string name="allow_resharing">Atļaut atkārtotu koplietošanu</string> <string name="allow_resharing">Atļaut atkārtotu koplietošanu</string>
<string name="assistant_screen_all_task_type">Visi</string>
<string name="associated_account_not_found">Saistītais konts nav atrasts!</string> <string name="associated_account_not_found">Saistītais konts nav atrasts!</string>
<string name="auth_access_failed">Piekļuve neizdevās: %1$s</string> <string name="auth_access_failed">Piekļuve neizdevās: %1$s</string>
<string name="auth_account_does_not_exist">Konts vēl nav pievienots šai iekārtai</string> <string name="auth_account_does_not_exist">Konts vēl nav pievienots šai iekārtai</string>

View file

@ -34,6 +34,9 @@
<string name="allow_resharing">Дозволи повторно споделување</string> <string name="allow_resharing">Дозволи повторно споделување</string>
<string name="app_widget_description">Прикажува еден виџет од контролната табла</string> <string name="app_widget_description">Прикажува еден виџет од контролната табла</string>
<string name="appbar_search_in">Барај во %s</string> <string name="appbar_search_in">Барај во %s</string>
<string name="assistant_screen_all_task_type">Сите</string>
<string name="assistant_screen_task_view_show_less">Помалку</string>
<string name="assistant_screen_task_view_show_more">Прикажи повеќе</string>
<string name="associated_account_not_found">Не е пронајдена поврзана сметка!</string> <string name="associated_account_not_found">Не е пронајдена поврзана сметка!</string>
<string name="auth_access_failed">Неуспешен пристап: %1$s</string> <string name="auth_access_failed">Неуспешен пристап: %1$s</string>
<string name="auth_account_does_not_exist">Сметката сеуште не е додадена на овој уред</string> <string name="auth_account_does_not_exist">Сметката сеуште не е додадена на овој уред</string>

View file

@ -37,6 +37,11 @@
<string name="allow_resharing">TIllat videre deling</string> <string name="allow_resharing">TIllat videre deling</string>
<string name="app_widget_description">Viser en widget fra dashbordet</string> <string name="app_widget_description">Viser en widget fra dashbordet</string>
<string name="appbar_search_in">Søk i %s</string> <string name="appbar_search_in">Søk i %s</string>
<string name="assistant_screen_all_task_type">Alle</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Skriv inn litt tekst</string>
<string name="assistant_screen_task_delete_fail_message">Oppgaven er slettet</string>
<string name="assistant_screen_task_view_show_less">Vis mindre</string>
<string name="assistant_screen_task_view_show_more">Vis mer</string>
<string name="associated_account_not_found">Tilknyttet bruker ikke funnet!</string> <string name="associated_account_not_found">Tilknyttet bruker ikke funnet!</string>
<string name="auth_access_failed">Tilgang mislyktes: %1$s</string> <string name="auth_access_failed">Tilgang mislyktes: %1$s</string>
<string name="auth_account_does_not_exist">Kontoen er ikke lagt til på denne enheten enda</string> <string name="auth_account_does_not_exist">Kontoen er ikke lagt til på denne enheten enda</string>

View file

@ -35,6 +35,9 @@
<string name="allow_resharing">Opnieuw delen toestaan</string> <string name="allow_resharing">Opnieuw delen toestaan</string>
<string name="app_widget_description">Toont één widget van dashboard</string> <string name="app_widget_description">Toont één widget van dashboard</string>
<string name="appbar_search_in">Zoeken in %s</string> <string name="appbar_search_in">Zoeken in %s</string>
<string name="assistant_screen_all_task_type">Alle</string>
<string name="assistant_screen_task_view_show_less">Toon minder</string>
<string name="assistant_screen_task_view_show_more">Toon meer</string>
<string name="associated_account_not_found">Bijbehorend account niet gevonden!</string> <string name="associated_account_not_found">Bijbehorend account niet gevonden!</string>
<string name="auth_access_failed">Toegang mislukt: %1$s</string> <string name="auth_access_failed">Toegang mislukt: %1$s</string>
<string name="auth_account_does_not_exist">Dit account is nog niet toegevoegd op dit apparaat</string> <string name="auth_account_does_not_exist">Dit account is nog niet toegevoegd op dit apparaat</string>

View file

@ -36,6 +36,9 @@
<string name="allow_resharing">Zezwalaj na udostępnianie dalej</string> <string name="allow_resharing">Zezwalaj na udostępnianie dalej</string>
<string name="app_widget_description">Pokazuje jeden widżet z pulpitu nawigacyjnego</string> <string name="app_widget_description">Pokazuje jeden widżet z pulpitu nawigacyjnego</string>
<string name="appbar_search_in">Szukaj w %s</string> <string name="appbar_search_in">Szukaj w %s</string>
<string name="assistant_screen_all_task_type">Wszystkie</string>
<string name="assistant_screen_task_view_show_less">Pokaż mniej</string>
<string name="assistant_screen_task_view_show_more">Pokaż więcej</string>
<string name="associated_account_not_found">Nie znaleziono powiązanego konta!</string> <string name="associated_account_not_found">Nie znaleziono powiązanego konta!</string>
<string name="auth_access_failed">Dostęp nieudany: %1$s</string> <string name="auth_access_failed">Dostęp nieudany: %1$s</string>
<string name="auth_account_does_not_exist">Konto nie jest jeszcze dodane na tym urządzeniu</string> <string name="auth_account_does_not_exist">Konto nie jest jeszcze dodane na tym urządzeniu</string>

View file

@ -37,6 +37,11 @@
<string name="allow_resharing">Permitir recompartilhamento</string> <string name="allow_resharing">Permitir recompartilhamento</string>
<string name="app_widget_description">Mostra um widget do painel</string> <string name="app_widget_description">Mostra um widget do painel</string>
<string name="appbar_search_in">Pesquisar em %s</string> <string name="appbar_search_in">Pesquisar em %s</string>
<string name="assistant_screen_all_task_type">Tudos</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Digite algum texto</string>
<string name="assistant_screen_task_delete_fail_message">Tarefa excluída com sucesso</string>
<string name="assistant_screen_task_view_show_less">Mostrar menos</string>
<string name="assistant_screen_task_view_show_more">Mostrar mais</string>
<string name="associated_account_not_found">Conta associada não encontrada!</string> <string name="associated_account_not_found">Conta associada não encontrada!</string>
<string name="auth_access_failed">O acesso falhou: %1$s</string> <string name="auth_access_failed">O acesso falhou: %1$s</string>
<string name="auth_account_does_not_exist">A conta ainda não está adicionada neste dispositivo</string> <string name="auth_account_does_not_exist">A conta ainda não está adicionada neste dispositivo</string>

View file

@ -35,6 +35,7 @@
<string name="allow_resharing">Permitir repartilha</string> <string name="allow_resharing">Permitir repartilha</string>
<string name="app_widget_description">Mostra um \'\'widget\'\' do painel</string> <string name="app_widget_description">Mostra um \'\'widget\'\' do painel</string>
<string name="appbar_search_in">Procurar em %s</string> <string name="appbar_search_in">Procurar em %s</string>
<string name="assistant_screen_all_task_type">Todos</string>
<string name="associated_account_not_found">Conta associada não encontrada!</string> <string name="associated_account_not_found">Conta associada não encontrada!</string>
<string name="auth_access_failed">Acesso falhou: %1$s</string> <string name="auth_access_failed">Acesso falhou: %1$s</string>
<string name="auth_account_does_not_exist">A conta ainda não foi adicionada a este dispositivo</string> <string name="auth_account_does_not_exist">A conta ainda não foi adicionada a este dispositivo</string>

View file

@ -35,6 +35,7 @@
<string name="allow_resharing">Permite repartajarea</string> <string name="allow_resharing">Permite repartajarea</string>
<string name="app_widget_description">Arată un singur widget din panoul principal</string> <string name="app_widget_description">Arată un singur widget din panoul principal</string>
<string name="appbar_search_in">Caută in %s</string> <string name="appbar_search_in">Caută in %s</string>
<string name="assistant_screen_all_task_type">Toate </string>
<string name="associated_account_not_found">Contul asociat nu a fost găsit!</string> <string name="associated_account_not_found">Contul asociat nu a fost găsit!</string>
<string name="auth_access_failed">Accesul a eșuat: %1$s</string> <string name="auth_access_failed">Accesul a eșuat: %1$s</string>
<string name="auth_account_does_not_exist">Contul nu există încă pe dispozitiv</string> <string name="auth_account_does_not_exist">Contul nu există încă pe dispozitiv</string>

View file

@ -37,6 +37,11 @@
<string name="allow_resharing">Разрешить повторную публикацию</string> <string name="allow_resharing">Разрешить повторную публикацию</string>
<string name="app_widget_description">Показывает один виджет с главного экрана.</string> <string name="app_widget_description">Показывает один виджет с главного экрана.</string>
<string name="appbar_search_in">Искать в %s</string> <string name="appbar_search_in">Искать в %s</string>
<string name="assistant_screen_all_task_type">Все</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Наберите какой-то текст</string>
<string name="assistant_screen_task_delete_fail_message">Задача удалена</string>
<string name="assistant_screen_task_view_show_less">Показывать меньше</string>
<string name="assistant_screen_task_view_show_more">Показывать больше</string>
<string name="associated_account_not_found">Связанный аккаунт не найден!</string> <string name="associated_account_not_found">Связанный аккаунт не найден!</string>
<string name="auth_access_failed">Доступ запрещен: %1$s</string> <string name="auth_access_failed">Доступ запрещен: %1$s</string>
<string name="auth_account_does_not_exist">Учётная запись ещё не создана на этом устройстве</string> <string name="auth_account_does_not_exist">Учётная запись ещё не создана на этом устройстве</string>

View file

@ -33,6 +33,9 @@
<string name="advanced_settings">Cunfiguratziones avantzadas</string> <string name="advanced_settings">Cunfiguratziones avantzadas</string>
<string name="allow_resharing">Permite sa re-cumpartzidura</string> <string name="allow_resharing">Permite sa re-cumpartzidura</string>
<string name="appbar_search_in">Chirca in %s</string> <string name="appbar_search_in">Chirca in %s</string>
<string name="assistant_screen_all_task_type">Totu</string>
<string name="assistant_screen_task_view_show_less">Mustra prus pagu</string>
<string name="assistant_screen_task_view_show_more">Mustra de prus</string>
<string name="associated_account_not_found">Contu assotziadu no agatadu!</string> <string name="associated_account_not_found">Contu assotziadu no agatadu!</string>
<string name="auth_access_failed">Atzessu faddidu: %1$s</string> <string name="auth_access_failed">Atzessu faddidu: %1$s</string>
<string name="auth_account_does_not_exist">Su contu non s\'agatat ancora in custu dispositivu</string> <string name="auth_account_does_not_exist">Su contu non s\'agatat ancora in custu dispositivu</string>

Some files were not shown because too many files have changed in this diff Show more