Merge pull request #12612 from nextcloud/feature/assistant

Assistant Feature
This commit is contained in:
Tobias Kaminsky 2024-03-13 16:03:24 +01:00 committed by GitHub
commit c1c29891ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 3205 additions and 172 deletions

View file

@ -2,9 +2,45 @@
<profile version="1.0">
<option name="myName" value="ktlint" />
<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 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" />
</profile>
</component>
</component>

View file

@ -19,7 +19,7 @@ buildscript {
plugins {
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'
@ -223,6 +223,7 @@ android {
dataBinding true
viewBinding true
aidl true
compose = true
}
compileOptions {
@ -246,6 +247,10 @@ android {
// Adds exported schema location as test app assets.
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
}
dependencies {
@ -255,6 +260,14 @@ dependencies {
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'
// 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
FunctionNaming:
active: true
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
functionPattern: '^([a-z$A-Z][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
excludes:

View file

@ -1194,4 +1194,4 @@
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f26afed3b9b87a3acb578947a26223ac')"
]
}
}
}

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

View file

@ -27,6 +27,7 @@ import android.content.Intent;
import com.nextcloud.client.preferences.SubFolderRule;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.databinding.SyncedFoldersLayoutBinding;
import com.owncloud.android.datamodel.MediaFolderType;
import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
import com.owncloud.android.ui.activity.SyncedFoldersActivity;
@ -51,9 +52,11 @@ public class SyncedFoldersActivityIT extends AbstractIT {
@Test
@ScreenshotTest
public void open() {
Activity sut = activityRule.launchActivity(null);
screenshot(sut);
SyncedFoldersActivity activity = activityRule.launchActivity(null);
activity.adapter.clear();
SyncedFoldersLayoutBinding sut = activity.binding;
shortSleep();
screenshot(sut.emptyList.emptyListView);
}
@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 {
OCCapability ocCapability = getCapability();
assumeTrue(ocCapability.getVersion().isNewerOrEqual(version));
}
protected OCCapability getCapability() throws AccountUtils.AccountNotFoundException {
NextcloudClient client = OwnCloudClientFactory.createNextcloudClient(user, targetContext);
OCCapability ocCapability = (OCCapability) new GetCapabilitiesRemoteOperation()
.execute(client)
.getSingleData();
assumeTrue(ocCapability.getVersion().isNewerOrEqual(version));
return ocCapability;
}
@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) {
RemoteOperationResult check = new ExistenceCheckRemoteOperation(remotePath, false).execute(client);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,22 +1,4 @@
<?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/>.
-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- Used for document scanning, but lib declares it as required, which it's not -->
<uses-permission android:name="android.permission.WRITE_CALENDAR" /> <!-- Used for document scanning, but lib declares it as required, which it's not -->
<uses-feature
android:name="android.hardware.camera2"
android:required="false"
tools:node="replace" />
<!-- WRITE_EXTERNAL_STORAGE may be enabled or disabled by the user after installation in
API >= 23; the app needs to handle this -->
<!--
WRITE_EXTERNAL_STORAGE may be enabled or disabled by the user after installation in
API >= 23; the app needs to handle this
-->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"
@ -45,9 +26,7 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Next permissions are always approved in installation time, the apps needs to do nothing special in runtime -->
<uses-permission android:name="android.permission.VIBRATE" /> <!-- 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.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
@ -62,35 +41,72 @@
<uses-permission
android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
android:maxSdkVersion="25" />
<!-- Apps that target Android 9 (API level 28) or higher and use foreground services
must request the FOREGROUND_SERVICE permission -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Runtime permissions introduced in Android 13 (API level 33) -->
<!--
Apps that target Android 9 (API level 28) or higher and use foreground services
must request the FOREGROUND_SERVICE permission
-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Runtime permissions introduced in Android 13 (API level 33) -->
<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_VIDEO" />
<!-- Needed for Android 14 (API level 34) -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- 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_DATA_SYNC" />
<!-- Some Chromebooks don't support touch. Although not essential,
it's a good idea to explicitly include this declaration. -->
<!--
Some Chromebooks don't support touch. Although not essential,
it's a good idea to explicitly include this declaration.
-->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
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
android:name=".MainApp"
android:allowBackup="false"
android:fullBackupContent="@xml/backup_config"
android:dataExtractionRules="@xml/backup_rules"
android:fullBackupContent="@xml/backup_config"
android:icon="@mipmap/ic_launcher"
android:installLocation="internalOnly"
android:label="@string/app_name"
@ -101,8 +117,11 @@
android:supportsRtl="true"
android:theme="@style/Theme.ownCloud.Toolbar"
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
android:name="org.apache.http.legacy"
@ -203,8 +222,8 @@
<activity
android:name=".ui.activity.SetupEncryptionActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:theme="@style/Theme.NoBackground"
android:exported="false" />
android:exported="false"
android:theme="@style/Theme.NoBackground" />
<activity
android:name=".ui.activity.ContactsPreferenceActivity"
android:exported="false"
@ -217,12 +236,16 @@
android:theme="@style/Theme.ownCloud.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
@ -236,9 +259,10 @@
android:theme="@style/Theme.ownCloud.Overlay" />
<activity
android:name=".ui.preview.PreviewMediaActivity"
android:exported="false"
android:configChanges="orientation|screenLayout|screenSize|keyboardHidden"
android:exported="false"
android:theme="@style/Theme.ownCloud.Media" />
<service
android:name=".authentication.AccountAuthenticatorService"
android:exported="false">
@ -264,8 +288,8 @@
</service>
<service
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
android:name=".providers.FileContentProvider"
@ -303,14 +327,12 @@
android:readPermission="false"
android:writePermission="false" />
</provider>
<provider
android:name=".providers.UsersAndGroupsSearchProvider"
android:authorities="@string/users_and_groups_search_authority"
android:enabled="true"
android:exported="false"
android:label="@string/share_search" />
<provider
android:name=".providers.DocumentsStorageProvider"
android:authorities="@string/document_provider_authority"
@ -321,9 +343,7 @@
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
<!-- new provider used to generate URIs without file:// scheme (forbidden from Android 7) -->
</provider> <!-- new provider used to generate URIs without file:// scheme (forbidden from Android 7) -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="@string/file_provider_authority"
@ -338,8 +358,7 @@
android:authorities="@string/image_cache_provider_authority"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS" />
<!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
android:permission="android.permission.MANAGE_DOCUMENTS" /> <!-- Disable WorkManager initialization. Whoever designed this, should pay closer attention -->
<!-- to "best before" dates in his fridge. -->
<!-- disable default provider -->
<provider
@ -395,12 +414,12 @@
android:exported="false" />
<service
android:name="com.nextcloud.client.jobs.transfer.FileTransferService"
android:foregroundServiceType="dataSync"
android:exported="false" />
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name="com.nextcloud.client.media.PlayerService"
android:foregroundServiceType="mediaPlayback"
android:exported="false" />
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
<activity
android:name=".ui.activity.PassCodeActivity"
@ -478,6 +497,7 @@
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/users_and_groups_searchable" />
@ -514,7 +534,6 @@
android:name="com.nextcloud.client.editimage.EditImageActivity"
android:exported="false"
android:theme="@style/Theme.ownCloud.Toolbar.NullBackground" />
<activity
android:name="com.nmc.android.ui.LauncherActivity"
android:exported="true"
@ -525,42 +544,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</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 = 75, to = 76),
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
)

View file

@ -32,6 +32,8 @@ data class CapabilityEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = ProviderTableMeta._ID)
val id: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ASSISTANT)
val assistant: Int?,
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_ACCOUNT_NAME)
val accountName: String?,
@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.ImageDetailFragment;
import com.nextcloud.ui.SetStatusDialogFragment;
import com.nextcloud.ui.composeActivity.ComposeActivity;
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
import com.nmc.android.ui.LauncherActivity;
import com.owncloud.android.MainApp;
@ -199,6 +200,9 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract CommunityActivity participateActivity();
@ContributesAndroidInjector
abstract ComposeActivity composeActivity();
@ContributesAndroidInjector
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());
contentValues.put(ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION,
capability.getFilesLockingVersion());
contentValues.put(ProviderTableMeta.CAPABILITIES_ASSISTANT, capability.getAssistant().getValue());
contentValues.put(ProviderTableMeta.CAPABILITIES_GROUPFOLDERS, capability.getGroupfolders().getValue());
contentValues.put(ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT, capability.getDropAccount().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));
capability.setFilesLockingVersion(
getString(cursor, ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION));
capability.setAssistant(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_ASSISTANT));
capability.setGroupfolders(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_GROUPFOLDERS));
capability.setDropAccount(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT));
capability.setSecurityGuard(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SECURITY_GUARD));

View file

@ -35,7 +35,7 @@ import java.util.List;
*/
public class ProviderMeta {
public static final String DB_NAME = "filelist";
public static final int DB_VERSION = 78;
public static final int DB_VERSION = 79;
private ProviderMeta() {
// No instance
@ -265,6 +265,7 @@ public class ProviderMeta {
public static final String CAPABILITIES_ETAG = "etag";
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_ASSISTANT = "assistant";
public static final String CAPABILITIES_GROUPFOLDERS = "groupfolders";
public static final String CAPABILITIES_DROP_ACCOUNT = "drop_account";
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 {
private static final String TAG = ActivitiesActivity.class.getSimpleName();
private ActivityListLayoutBinding binding;
ActivityListLayoutBinding binding;
private ActivityListAdapter adapter;
private int lastGiven;
private boolean isLoadingActivities;

View file

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

View file

@ -74,6 +74,8 @@ import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.common.NextcloudClient;
import com.nextcloud.java.util.Optional;
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.R;
import com.owncloud.android.authentication.PassCodeManager;
@ -123,6 +125,7 @@ import org.greenrobot.eventbus.ThreadMode;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
@ -357,15 +360,22 @@ public abstract class DrawerActivity extends ToolbarActivity
if (getResources().getBoolean(R.bool.is_branded_client) || !preferences.isShowEcosystemApps()) {
ecosystemApps.setVisibility(View.GONE);
} else {
LinearLayout[] views = {
ecosystemApps.findViewById(R.id.drawer_ecosystem_notes),
ecosystemApps.findViewById(R.id.drawer_ecosystem_talk),
ecosystemApps.findViewById(R.id.drawer_ecosystem_more)
};
LinearLayout notesView = ecosystemApps.findViewById(R.id.drawer_ecosystem_notes);
LinearLayout talkView = ecosystemApps.findViewById(R.id.drawer_ecosystem_talk);
LinearLayout moreView = 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"));
views[1].setOnClickListener(v -> openAppOrStore("com.nextcloud.talk2"));
views[2].setOnClickListener(v -> openAppStore("Nextcloud", true));
notesView.setOnClickListener(v -> openAppOrStore("it.niedermann.owncloud.notes"));
talkView.setOnClickListener(v -> openAppOrStore("com.nextcloud.talk2"));
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;
if (Hct.fromInt(primaryColor).getTone() < 80.0) {
@ -373,6 +383,7 @@ public abstract class DrawerActivity extends ToolbarActivity
} else {
iconColor = getColor(R.color.grey_800_transparent);
}
for (LinearLayout view : views) {
ImageView imageView = (ImageView) view.getChildAt(0);
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.
* Will attempt to open browser when no app store is available.
* Open app store page of specified app or search for specified string. Will attempt to open browser when no app
* store is available.
*
* @param string packageName or url-encoded search 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.filterActivityMenuItem(menu, capability);
DrawerMenuUtil.filterGroupfoldersMenuItem(menu, capability);
DrawerMenuUtil.filterAssistantMenuItem(menu, capability, getResources());
DrawerMenuUtil.setupHomeMenuItem(menu, getResources());
DrawerMenuUtil.removeMenuItem(menu, R.id.nav_community,
@ -535,6 +546,8 @@ public abstract class DrawerActivity extends ToolbarActivity
startSharedSearch(menuItem);
} else if (itemId == R.id.nav_recently_modified) {
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) {
MainApp.showOnlyFilesOnDevice(false);
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) {
startActivity(new Intent(getApplicationContext(), activity));
}
@ -692,8 +713,8 @@ public abstract class DrawerActivity extends ToolbarActivity
/**
* 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
* DrawerLayout#LOCK_MODE_LOCKED_CLOSED} or {@link DrawerLayout#LOCK_MODE_LOCKED_OPEN}.
* @param lockMode The new lock mode for the given drawer. One of {@link DrawerLayout#LOCK_MODE_UNLOCKED},
* {@link DrawerLayout#LOCK_MODE_LOCKED_CLOSED} or {@link DrawerLayout#LOCK_MODE_LOCKED_OPEN}.
*/
public void setDrawerLockMode(int lockMode) {
if (mDrawerLayout != null) {
@ -1155,7 +1176,7 @@ public abstract class DrawerActivity extends ToolbarActivity
return true;
}
public AppPreferences getAppPreferences(){
public AppPreferences getAppPreferences() {
return preferences;
}

View file

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

View file

@ -55,7 +55,7 @@ import com.owncloud.android.utils.PushUtils
*/
class NotificationsActivity : DrawerActivity(), NotificationsContract.View {
private lateinit var binding: NotificationsLayoutBinding
lateinit var binding: NotificationsLayoutBinding
private var adapter: NotificationListAdapter? = 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.
*/
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
class SyncedFoldersActivity :
FileActivity(),
SyncedFolderAdapter.ClickListener,
@ -165,8 +165,8 @@ class SyncedFoldersActivity :
@Inject
lateinit var syncedFolderProvider: SyncedFolderProvider
private lateinit var binding: SyncedFoldersLayoutBinding
private lateinit var adapter: SyncedFolderAdapter
lateinit var binding: SyncedFoldersLayoutBinding
lateinit var adapter: SyncedFolderAdapter
private var syncedFolderPreferencesDialogFragment: SyncedFolderPreferencesDialogFragment? = null
private var path: String? = null

View file

@ -50,6 +50,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
/**
* 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() {
if (syncFolderItems.size() > 0) {
return syncFolderItems.size() + 1;

View file

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

View file

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

View file

@ -84,7 +84,7 @@ class TrashbinActivity :
var trashbinPresenter: TrashbinPresenter? = null
private var active = false
private lateinit var binding: TrashbinActivityBinding
lateinit var binding: TrashbinActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
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) {
if (capability != null && !capability.getGroupfolders().isTrue()) {
filterMenuItems(menu, R.id.nav_groupfolders);

View file

@ -123,7 +123,7 @@ class WebViewUtil(private val context: Context) {
* @return
*/
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
@Suppress("TooGenericExceptionCaught")
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth")
fun setProxyKKPlus(webView: WebView) {
val proxyHost = OwnCloudClientManagerFactory.getProxyHost()
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: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
android:id="@+id/drawer_ecosystem_talk"
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
android:id="@+id/drawer_menu_standard"
android:checkableBehavior="single">
<item
android:id="@+id/nav_all_files"
android:icon="@drawable/all_files"
android:orderInCategory="0"
android:title="@string/drawer_item_all_files" />
<item
android:id="@+id/nav_personal_files"
android:icon="@drawable/ic_user"
@ -74,6 +76,13 @@
android:id="@+id/nav_notifications"
android:icon="@drawable/nav_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
android:id="@+id/nav_uploads"
android:icon="@drawable/uploads"

View file

@ -18,6 +18,33 @@
<string name="menu_item_sort_by_size_biggest_first">Biggest first</string>
<string name="menu_item_sort_by_size_smallest_first">Smallest first</string>
<string name="ecosystem_apps_display_assistant">Assistant</string>
<string name="assistant_screen_task_types_error_state_message">Cannot able to fetch task types, please check your internet connection.</string>
<string name="assistant_screen_task_list_error_state_message">Cannot able to fetch task list, please check your internet connection.</string>
<string name="assistant_screen_top_bar_title">Assistant</string>
<string name="assistant_screen_loading">Task List are loading, please wait</string>
<string name="assistant_screen_no_task_available_for_all_task_filter_text">No task available. Select a task type to create a new task.</string>
<string name="assistant_screen_no_task_available_text">No task available for %s task type, you can create a new task from bottom right.</string>
<string name="assistant_screen_delete_task_alert_dialog_title">Delete Task</string>
<string name="assistant_screen_delete_task_alert_dialog_description">Are you sure you want to delete this task?</string>
<string name="assistant_screen_task_more_actions_bottom_sheet_delete_action">Delete Task</string>
<string name="assistant_screen_task_create_success_message">Task successfully created</string>
<string name="assistant_screen_task_create_fail_message">An error occurred while creating the task</string>
<string name="assistant_screen_task_delete_success_message">An error occurred while deleting the task</string>
<string name="assistant_screen_task_delete_fail_message">Task successfully deleted</string>
<string name="assistant_screen_create_task_alert_dialog_input_field_placeholder">Type some text</string>
<string name="assistant_screen_all_task_type">All</string>
<string name="assistant_screen_task_view_show_more">Show more</string>
<string name="assistant_screen_task_view_show_less">Show less</string>
<string name="drawer_item_assistant">Assistant</string>
<string name="drawer_item_all_files">All files</string>
<string name="drawer_item_personal_files">Personal files</string>
<string name="drawer_item_home">Home</string>
@ -1135,7 +1162,7 @@
<string name="image_preview_unit_megapixel">%s MP</string>
<string name="image_preview_filedetails">File details</string>
<string name="image_preview_image_taking_conditions">Image taking conditions</string>
<string translatable="false" name="make_model">%1$s %2$s</string>
<string name="make_model" translatable="false">%1$s %2$s</string>
<string name="sub_folder_rule_year">Year</string>
<string name="sub_folder_rule_month">Year/Month</string>
<string name="sub_folder_rule_day">Year/Month/Day</string>

View file

@ -10,11 +10,11 @@ apply plugin: 'kotlin-android'
android {
namespace 'com.nextcloud.appscan'
compileSdk 33
compileSdk 34
defaultConfig {
minSdk 21
targetSdk 33
targetSdk 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View file

@ -1,6 +1,6 @@
buildscript {
ext {
androidLibraryVersion ="5f92d92c490d2d9039bfd392cf16c61a37738646"
androidLibraryVersion ="0c886d61f6"
androidPluginVersion = '8.3.0'
androidxMediaVersion = '1.3.0'
androidxTestVersion = "1.5.0"
@ -11,7 +11,7 @@ buildscript {
espressoVersion = "3.5.1"
fidoVersion = "4.1.0-patch2"
jacoco_version = '0.8.11'
kotlin_version = '1.9.23'
kotlin_version = '1.9.22'
markwonVersion = "4.6.2"
mockitoVersion = "4.11.0"
mockitoKotlinVersion = "4.1.0"

View file

@ -16,3 +16,6 @@ kotlin.daemon.jvmargs=-Xmx4096m
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configureondemand=true
# Needed for local libs
# org.gradle.dependency.verification=lenient

View file

@ -187,6 +187,7 @@
<trusting group="androidx.room"/>
<trusting group="androidx.sqlite"/>
<trusting group="androidx.webkit" name="webkit" version="1.10.0"/>
<trusting group="androidx.webkit" name="webkit" version="1.9.0"/>
<trusting group="androidx.work"/>
</trusted-key>
<trusted-key id="84789D24DF77A32433CE1F079EB80E92EB2135B1">
@ -439,6 +440,9 @@
</artifact>
</component>
<component group="androidx.annotation" name="annotation" version="1.0.0">
<artifact name="annotation-1.0.0.jar">
<sha256 value="0baae9755f7caf52aa80cd04324b91ba93af55d4d1d17dcc9a7b53d99ef7c016" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="annotation-1.0.0.pom">
<sha256 value="a179c12db43d9c0300c9db63f4811db496504be5401b951d422b78490ad1e5b4" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
@ -534,6 +538,9 @@
</artifact>
</component>
<component group="androidx.annotation" name="annotation-jvm" version="1.6.0">
<artifact name="annotation-jvm-1.6.0.jar">
<sha256 value="60b10b5ef5769b79570172e015b8159405c92f034ba88b9391a977589c9deb4e" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="annotation-jvm-1.6.0.module">
<sha256 value="3f5a8faa19de667e63dca9730ff8ef0e478e4bafb5feeb8258e5c086246dc90c" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
@ -1229,6 +1236,14 @@
<sha256 value="64cad9fa6a92d5cbde2dfc0994619aec3704994923b8eb0aa8c0a0581bee18a8" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.databinding" name="viewbinding" version="8.2.0">
<artifact name="viewbinding-8.2.0.aar">
<sha256 value="7d20274fccaf968d1b9c7b30a61363e8f9bcab89d9a3c3a0472c670e334f3260" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="viewbinding-8.2.0.module">
<sha256 value="45b4e919739627f4f98e3c693ea583125e6fdd39ac1913b4fc8a30111754e0eb" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.databinding" name="viewbinding" version="8.2.2">
<artifact name="viewbinding-8.2.2.aar">
<sha256 value="b808b84a1f998b6758540550e8e12bb163f293b13fa99c147a318b72e4cf67d0" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1396,6 +1411,14 @@
<sha256 value="b3955b619e8a16c38af39c19126867c72d1954db05551709e58c082b946078c4" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment-ktx" version="1.3.6">
<artifact name="fragment-ktx-1.3.6.aar">
<sha256 value="3f84a013fdeb8bac92d4ab607aebf39a4ff945f4585a635960ed769cd0255df1" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="fragment-ktx-1.3.6.module">
<sha256 value="a775bab4e5ef78605c2b0f8bc9dcd9e2c2dd6b5d5d9320012a69a5d01375059a" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.fragment" name="fragment-ktx" version="1.6.0">
<artifact name="fragment-ktx-1.6.0.aar">
<sha256 value="48c57ecebc6c1b07baf7dd1b77095560bb1f158aec83ba155645b77a1ce53307" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1513,6 +1536,11 @@
<sha256 value="5fb7c8514d8c56cada5e29ef89dc0289e71942ab4cb0b2e6dca137b9dcb8fdd4" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.4.0">
<artifact name="lifecycle-common-2.4.0.module">
<sha256 value="5ad5eafc22e8b04e58fa81d18d2570562971977e18f009500b5bd449bc6337bc" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common" version="2.5.1">
<artifact name="lifecycle-common-2.5.1.jar">
<sha256 value="20ad1520f625cf455e6afd7290988306d3a9886efa993e0860fbabf4bb3f7bda" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -1529,6 +1557,14 @@
<sha256 value="93747a9145cb36bc71005f598ede32e2b1149ade5a16e62b0e4969345bc62d85" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-common-java8" version="2.6.1">
<artifact name="lifecycle-common-java8-2.6.1.jar">
<sha256 value="c6deada2fac53b8ea6523dbda77597b128006674616f140f04df23264c6d1aa3" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="lifecycle-common-java8-2.6.1.module">
<sha256 value="1beb0b9fffb630a005deca1d3583d2acbec8685d6de809a3a6e0e433f418b6c3" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="androidx.lifecycle" name="lifecycle-livedata" version="2.0.0">
<artifact name="lifecycle-livedata-2.0.0.aar">
<sha256 value="c82609ced8c498f0a701a30fb6771bb7480860daee84d82e0a81ee86edf7ba39" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -2715,6 +2751,11 @@
<sha256 value="f4063ab5094f35aa586ebfd26834f0f914fbcae33e7dea1b0238dab380e40089" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.android.application" name="com.android.application.gradle.plugin" version="8.3.0">
<artifact name="com.android.application.gradle.plugin-8.3.0.pom">
<sha256 value="f3d60bb5f262d10d4ed77fe987f69192a9595d28b991fd0c4a1d8524db65b3d5" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.android.databinding" name="baseLibrary" version="8.2.2">
<artifact name="baseLibrary-8.2.2.jar">
<sha256 value="794113709dab21b06c262b3795e73cb708fbacae61715f34361e1af6237a1870" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -2731,6 +2772,11 @@
<sha256 value="15c10e317f27ab7e563c2ddd219b2cf5c9b95c85ec7e912d4610e8c1c30d998d" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.android.library" name="com.android.library.gradle.plugin" version="8.3.0">
<artifact name="com.android.library.gradle.plugin-8.3.0.pom">
<sha256 value="b309d7723d3a21151538cc8843393da70d0dd1b4066a54f620f79464121aada4" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.android.tools" name="annotations" version="31.2.2">
<artifact name="annotations-31.2.2.jar">
<sha256 value="ee3bfd9cdb5012bdb61520f8654a785577e9bb337e5939c5c6149a446684ee16" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -3957,6 +4003,14 @@
<sha256 value="6a91a2139a3cae8126c509cf65d136d49c35cb032b581ac1a56cb6a649cc0245" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="0c886d61f6">
<artifact name="android-library-0c886d61f6.aar">
<sha256 value="9c3a87487717acd878305a00f0d6f2337b9f5512d541f7ac46b9bdb5a53020b9" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-0c886d61f6.module">
<sha256 value="c29795ee883fc3364b2f16be5b9246b927271b961214f1a661b2caa2f42459a8" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="2b1da4cb14e2cd4b79e231b0be54e0bae699f143">
<artifact name="android-library-2b1da4cb14e2cd4b79e231b0be54e0bae699f143.aar">
<sha256 value="bdc44e874f1e14338213ae5723e71710940a31416ff1c52c9eb2f282e5d3f29a" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -3973,6 +4027,14 @@
<sha256 value="9f5dc2343cdf500b2cc17756418fce0506d13ef480ab8276cda415950e795991" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="6dcffdb0ba">
<artifact name="android-library-6dcffdb0ba.aar">
<sha256 value="00e121d803e9258b36cb2d6e20904552c9e88dbff6b193b3b9adc0fcd224959a" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-6dcffdb0ba.module">
<sha256 value="0e72841878595f83c6d8f93aa51f78a67e821f53446fcb7a5d94cfaad8ebbbac" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="acc7df66e4a43ed7f450136c13753f2743fb245e">
<artifact name="android-library-acc7df66e4a43ed7f450136c13753f2743fb245e.aar">
<sha256 value="f30c2d22c923c6ff8c605eb4cf713cfaf49eb967611f70dc3d139725b0891483" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -3989,6 +4051,14 @@
<sha256 value="ddd0f25a7d363aba6de79e8160ab02be999706afa51d9f9e8a30e40399421697" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="fcb36db7ba">
<artifact name="android-library-fcb36db7ba.aar">
<sha256 value="2bae1a11ea687d92fcf6a76a52f4c20ee338e710d2281ce4cd8cae5d1e108151" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-fcb36db7ba.module">
<sha256 value="9396eb66e3150b0d412d35e2dc9b29b078fbfc16e3c6e1c4f157318de797abfb" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud-deps" name="qrcodescanner" version="0.1.2.4">
<artifact name="qrcodescanner-0.1.2.4.aar">
<sha256 value="b286128792cc04f59b0defa2c937c86d9e2fc824a8011b9af9eea7fd0ea84303" origin="Generated by Gradle" reason="Artifact is not signed"/>
@ -6050,6 +6120,11 @@
<sha256 value="5be0fd6355d0e9539899cbb0ff733906de36a64897255b86fde6880e8ad1d871" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.gitlab.arturbosch.detekt" name="io.gitlab.arturbosch.detekt.gradle.plugin" version="1.23.5">
<artifact name="io.gitlab.arturbosch.detekt.gradle.plugin-1.23.5.pom">
<sha256 value="a326d380421a273859c741cb186a2ddba8cebf032ffa5feef94ff6d577e01185" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="io.grpc" name="grpc-api" version="1.45.1">
<artifact name="grpc-api-1.45.1.jar">
<sha256 value="dc381fe018fb10bba8cc66f98db1050a70cee49a8270017c22ec6f77b10f13e5" origin="Generated by Gradle"/>
@ -7996,6 +8071,16 @@
<sha256 value="5cb2601f0b639e17ed323637faaaf78585772a2383a43957c23655019a06fc00" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin.android" name="org.jetbrains.kotlin.android.gradle.plugin" version="1.9.22">
<artifact name="org.jetbrains.kotlin.android.gradle.plugin-1.9.22.pom">
<sha256 value="c1b02fe072557f4c45b68c9772dfcbadb27d87b80a39e1a2796408ebbe4d0211" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="org.jetbrains.kotlin.kapt" name="org.jetbrains.kotlin.kapt.gradle.plugin" version="1.9.22">
<artifact name="org.jetbrains.kotlin.kapt.gradle.plugin-1.9.22.pom">
<sha256 value="1daf64ddd8e90a6aa8a831f3e649b4b094e72fe91df0dfd91b5b1ba1dcd54d54" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="atomicfu" version="0.16.1">
<artifact name="atomicfu-0.16.1.module">
<sha256 value="fdcf04fc25f6a43f557f341ee0053caa25e759f591169c86566f1dad37fc77a6" origin="Generated by Gradle"/>

View file

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStorePath=wrapper/dists