added DEV version to repo
This commit is contained in:
parent
1ef725ef20
commit
23e673bfdf
2135 changed files with 97033 additions and 21206 deletions
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.files
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-FileCopyrightText: 2019 David Luhmer <david-dev@live.de>
|
||||
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2019 Edvard Holst <edvard.holst@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 David Luhmer <david-dev@live.de>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*
|
||||
* More information here: https://github.com/abeluck/android-streams-ipc
|
||||
*/
|
||||
|
|
@ -237,7 +237,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
|
|||
case "POST":
|
||||
method = new PostMethod(requestUrl);
|
||||
if (requestBodyInputStream != null) {
|
||||
RequestEntity requestEntity = new InputStreamRequestEntity(requestBodyInputStream, -1);
|
||||
RequestEntity requestEntity = new InputStreamRequestEntity(requestBodyInputStream);
|
||||
((PostMethod) method).setRequestEntity(requestEntity);
|
||||
} else if (request.getRequestBody() != null) {
|
||||
StringRequestEntity requestEntity = new StringRequestEntity(
|
||||
|
|
@ -251,7 +251,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
|
|||
case "PATCH":
|
||||
method = new PatchMethod(requestUrl);
|
||||
if (requestBodyInputStream != null) {
|
||||
RequestEntity requestEntity = new InputStreamRequestEntity(requestBodyInputStream, -1);
|
||||
RequestEntity requestEntity = new InputStreamRequestEntity(requestBodyInputStream);
|
||||
((PatchMethod) method).setRequestEntity(requestEntity);
|
||||
} else if (request.getRequestBody() != null) {
|
||||
StringRequestEntity requestEntity = new StringRequestEntity(
|
||||
|
|
@ -265,7 +265,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
|
|||
case "PUT":
|
||||
method = new PutMethod(requestUrl);
|
||||
if (requestBodyInputStream != null) {
|
||||
RequestEntity requestEntity = new InputStreamRequestEntity(requestBodyInputStream, -1);
|
||||
RequestEntity requestEntity = new InputStreamRequestEntity(requestBodyInputStream);
|
||||
((PutMethod) method).setRequestEntity(requestEntity);
|
||||
} else if (request.getRequestBody() != null) {
|
||||
StringRequestEntity requestEntity = new StringRequestEntity(
|
||||
|
|
@ -502,12 +502,15 @@ public class InputStreamBinder extends IInputStreamService.Stub {
|
|||
|
||||
@VisibleForTesting
|
||||
public static NameValuePair[] convertMapToNVP(Map<String, String> map) {
|
||||
NameValuePair[] nvp = new NameValuePair[map.size()];
|
||||
final var nvp = new NameValuePair[map.size()];
|
||||
int i = 0;
|
||||
for (String key : map.keySet()) {
|
||||
nvp[i] = new NameValuePair(key, map.get(key));
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
final var nameValuePair = new NameValuePair(entry.getKey(), entry.getValue());
|
||||
nvp[i] = nameValuePair;
|
||||
i++;
|
||||
}
|
||||
|
||||
return nvp;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2021 Timo Triebensky <timo@binsky.org>
|
||||
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*
|
||||
* More information here: https://github.com/abeluck/android-streams-ipc
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2017 David Luhmer <david-dev@live.de>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso.aidl;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-FileCopyrightText: 2021 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH
|
||||
* SPDX-FileCopyrightText: 2017 David Luhmer <david-dev@live.de>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso.aidl;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2017 David Luhmer <david-dev@live.de>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.android.sso.aidl;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.appReview
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.appReview
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.appReview
|
||||
|
||||
|
|
@ -18,7 +18,6 @@ class InAppReviewModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun providesInAppReviewHelper(appPreferences: AppPreferences): InAppReviewHelper {
|
||||
return InAppReviewHelperImpl(appPreferences)
|
||||
}
|
||||
internal fun providesInAppReviewHelper(appPreferences: AppPreferences): InAppReviewHelper =
|
||||
InAppReviewHelperImpl(appPreferences)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 ZetaTom
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ import com.google.gson.annotations.SerializedName
|
|||
import com.owncloud.android.MainApp
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.internal.http.HTTP_OK
|
||||
import java.net.HttpURLConnection.HTTP_OK
|
||||
import java.net.URLEncoder
|
||||
|
||||
class NominatimClient constructor(geocoderBaseUrl: String, email: String) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account
|
||||
|
||||
|
|
@ -23,7 +23,9 @@ import java.net.URI
|
|||
* It serves as a semantically correct "empty value", allowing simplification of logic
|
||||
* in various components requiring user data, such as DB queries.
|
||||
*/
|
||||
internal data class AnonymousUser(private val accountType: String) : User, Parcelable {
|
||||
internal data class AnonymousUser(private val accountType: String) :
|
||||
User,
|
||||
Parcelable {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
|
@ -47,21 +49,14 @@ internal data class AnonymousUser(private val accountType: String) : User, Parce
|
|||
override val server = Server(URI.create(""), MainApp.MINIMUM_SUPPORTED_SERVER_VERSION)
|
||||
override val isAnonymous = true
|
||||
|
||||
override fun toPlatformAccount(): Account {
|
||||
return Account(accountName, accountType)
|
||||
}
|
||||
override fun toPlatformAccount(): Account = Account(accountName, accountType)
|
||||
|
||||
override fun toOwnCloudAccount(): OwnCloudAccount {
|
||||
return OwnCloudAccount(Uri.EMPTY, OwnCloudBasicCredentials("", ""))
|
||||
}
|
||||
override fun toOwnCloudAccount(): OwnCloudAccount = OwnCloudAccount(Uri.EMPTY, OwnCloudBasicCredentials("", ""))
|
||||
|
||||
override fun nameEquals(user: User?): Boolean {
|
||||
return user?.accountName.equals(accountName, true)
|
||||
}
|
||||
override fun nameEquals(user: User?): Boolean = user?.accountName.equals(accountName, true)
|
||||
|
||||
override fun nameEquals(accountName: CharSequence?): Boolean {
|
||||
return accountName?.toString().equals(this.accountType, true)
|
||||
}
|
||||
override fun nameEquals(accountName: CharSequence?): Boolean =
|
||||
accountName?.toString().equals(this.accountType, true)
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account;
|
||||
|
||||
import android.accounts.Account;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This interface provides access to currently selected user.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account
|
||||
|
||||
|
|
@ -20,7 +20,9 @@ import java.net.URI
|
|||
* This is a mock user object suitable for integration tests. Mocks obtained from code generators
|
||||
* such as Mockito or MockK cannot be transported in Intent extras.
|
||||
*/
|
||||
data class MockUser(override val accountName: String, val accountType: String) : User, Parcelable {
|
||||
data class MockUser(override val accountName: String, val accountType: String) :
|
||||
User,
|
||||
Parcelable {
|
||||
|
||||
constructor() : this(DEFAULT_MOCK_ACCOUNT_NAME, DEFAULT_MOCK_ACCOUNT_TYPE)
|
||||
|
||||
|
|
@ -42,21 +44,14 @@ data class MockUser(override val accountName: String, val accountType: String) :
|
|||
override val server = Server(URI.create(""), MainApp.MINIMUM_SUPPORTED_SERVER_VERSION)
|
||||
override val isAnonymous = false
|
||||
|
||||
override fun toPlatformAccount(): Account {
|
||||
return Account(accountName, accountType)
|
||||
}
|
||||
override fun toPlatformAccount(): Account = Account(accountName, accountType)
|
||||
|
||||
override fun toOwnCloudAccount(): OwnCloudAccount {
|
||||
return OwnCloudAccount(Uri.EMPTY, OwnCloudBasicCredentials("", ""))
|
||||
}
|
||||
override fun toOwnCloudAccount(): OwnCloudAccount = OwnCloudAccount(Uri.EMPTY, OwnCloudBasicCredentials("", ""))
|
||||
|
||||
override fun nameEquals(user: User?): Boolean {
|
||||
return user?.accountName.equals(accountName, true)
|
||||
}
|
||||
override fun nameEquals(user: User?): Boolean = user?.accountName.equals(accountName, true)
|
||||
|
||||
override fun nameEquals(accountName: CharSequence?): Boolean {
|
||||
return accountName?.toString().equals(this.accountType, true)
|
||||
}
|
||||
override fun nameEquals(accountName: CharSequence?): Boolean =
|
||||
accountName?.toString().equals(this.accountType, true)
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account
|
||||
|
||||
|
|
@ -41,21 +41,14 @@ internal data class RegisteredUser(
|
|||
return account.name
|
||||
}
|
||||
|
||||
override fun toPlatformAccount(): Account {
|
||||
return account
|
||||
}
|
||||
override fun toPlatformAccount(): Account = account
|
||||
|
||||
override fun toOwnCloudAccount(): OwnCloudAccount {
|
||||
return ownCloudAccount
|
||||
}
|
||||
override fun toOwnCloudAccount(): OwnCloudAccount = ownCloudAccount
|
||||
|
||||
override fun nameEquals(user: User?): Boolean {
|
||||
return nameEquals(user?.accountName)
|
||||
}
|
||||
override fun nameEquals(user: User?): Boolean = nameEquals(user?.accountName)
|
||||
|
||||
override fun nameEquals(accountName: CharSequence?): Boolean {
|
||||
return accountName?.toString().equals(this.accountName, true)
|
||||
}
|
||||
override fun nameEquals(accountName: CharSequence?): Boolean =
|
||||
accountName?.toString().equals(this.accountName, true)
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account
|
||||
|
||||
|
|
@ -11,7 +11,9 @@ import android.accounts.Account
|
|||
import android.os.Parcelable
|
||||
import com.owncloud.android.lib.common.OwnCloudAccount
|
||||
|
||||
interface User : Parcelable, com.nextcloud.common.User {
|
||||
interface User :
|
||||
Parcelable,
|
||||
com.nextcloud.common.User {
|
||||
override val accountName: String
|
||||
val server: Server
|
||||
val isAnonymous: Boolean
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 TSI-mc
|
||||
* SPDX-FileCopyrightText: 2023-2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.account;
|
||||
|
||||
|
|
@ -18,10 +18,11 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.nextcloud.client.onboarding.FirstRunActivity;
|
||||
import com.nextcloud.common.NextcloudClient;
|
||||
import com.nextcloud.utils.extensions.AccountExtensionsKt;
|
||||
import com.nmc.android.ui.LauncherActivity;
|
||||
import com.owncloud.android.MainApp;
|
||||
import com.owncloud.android.R;
|
||||
import com.owncloud.android.authentication.AuthenticatorActivity;
|
||||
|
|
@ -112,25 +113,78 @@ public class UserAccountManagerImpl implements UserAccountManager {
|
|||
|
||||
@Override
|
||||
public boolean exists(Account account) {
|
||||
Account[] nextcloudAccounts = getAccounts();
|
||||
try {
|
||||
if (account == null) {
|
||||
Log_OC.d(TAG, "account is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
Account[] nextcloudAccounts = getAccounts();
|
||||
if (nextcloudAccounts.length == 0) {
|
||||
Log_OC.d(TAG, "nextcloudAccounts are empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (account.name.isEmpty()) {
|
||||
Log_OC.d(TAG, "account name is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (account != null && account.name != null) {
|
||||
int lastAtPos = account.name.lastIndexOf('@');
|
||||
if (lastAtPos == -1) {
|
||||
Log_OC.d(TAG, "lastAtPos cannot be found");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isLastAtPosInBoundsForHostAndPort = lastAtPos + 1 < account.name.length();
|
||||
if (!isLastAtPosInBoundsForHostAndPort) {
|
||||
Log_OC.d(TAG, "lastAtPos not in bounds");
|
||||
return false;
|
||||
}
|
||||
|
||||
String hostAndPort = account.name.substring(lastAtPos + 1);
|
||||
|
||||
String username = account.name.substring(0, lastAtPos);
|
||||
if (hostAndPort.isEmpty() || username.isEmpty()) {
|
||||
Log_OC.d(TAG, "hostAndPort or username is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
String otherHostAndPort;
|
||||
String otherUsername;
|
||||
|
||||
for (Account otherAccount : nextcloudAccounts) {
|
||||
// Skip null accounts or accounts with null names
|
||||
if (otherAccount == null || otherAccount.name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lastAtPos = otherAccount.name.lastIndexOf('@');
|
||||
|
||||
// Skip invalid account names
|
||||
if (lastAtPos == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isLastAtPosInBoundsForOtherHostAndPort = lastAtPos + 1 < otherAccount.name.length();
|
||||
if (!isLastAtPosInBoundsForOtherHostAndPort) {
|
||||
continue;
|
||||
}
|
||||
otherHostAndPort = otherAccount.name.substring(lastAtPos + 1);
|
||||
|
||||
otherUsername = otherAccount.name.substring(0, lastAtPos);
|
||||
|
||||
if (otherHostAndPort.equals(hostAndPort) &&
|
||||
otherUsername.equalsIgnoreCase(username)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Log_OC.d(TAG, "Exception caught at UserAccountManagerImpl.exists(): " + e);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -180,19 +234,20 @@ public class UserAccountManagerImpl implements UserAccountManager {
|
|||
*/
|
||||
@Nullable
|
||||
private User createUserFromAccount(@NonNull Account account) {
|
||||
if (AccountExtensionsKt.isAnonymous(account, context)) {
|
||||
Context safeContext = context != null ? context : MainApp.getAppContext();
|
||||
if (safeContext == null) {
|
||||
Log_OC.e(TAG, "Unable to obtain a valid context");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
Log_OC.d(TAG, "Context is null MainApp.getAppContext() used");
|
||||
context = MainApp.getAppContext();
|
||||
if (AccountExtensionsKt.isAnonymous(account, safeContext)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
OwnCloudAccount ownCloudAccount;
|
||||
try {
|
||||
ownCloudAccount = new OwnCloudAccount(account, context);
|
||||
} catch (AccountUtils.AccountNotFoundException ex) {
|
||||
ownCloudAccount = new OwnCloudAccount(account, safeContext);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +267,7 @@ public class UserAccountManagerImpl implements UserAccountManager {
|
|||
*/
|
||||
String serverAddressStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL);
|
||||
if (serverAddressStr == null || serverAddressStr.isEmpty()) {
|
||||
return AnonymousUser.fromContext(context);
|
||||
return AnonymousUser.fromContext(safeContext);
|
||||
}
|
||||
URI serverUri = URI.create(serverAddressStr); // TODO: validate
|
||||
|
||||
|
|
@ -398,6 +453,10 @@ public class UserAccountManagerImpl implements UserAccountManager {
|
|||
|
||||
@Override
|
||||
public void startAccountCreation(final Activity activity) {
|
||||
|
||||
// skipping AuthenticatorActivity redirection when user is on Launcher or FirstRun Activity
|
||||
if (activity instanceof LauncherActivity || activity instanceof FirstRunActivity) return;
|
||||
|
||||
Intent intent = new Intent(context, AuthenticatorActivity.class);
|
||||
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.appinfo
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.appinfo
|
||||
|
||||
|
|
@ -16,17 +16,11 @@ class AppInfoImpl : AppInfo {
|
|||
override val versionCode: Int = BuildConfig.VERSION_CODE
|
||||
override val isDebugBuild: Boolean = BuildConfig.DEBUG
|
||||
|
||||
override fun getAppVersion(context: Context): String {
|
||||
return try {
|
||||
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
if (pInfo != null) {
|
||||
pInfo.versionName
|
||||
} else {
|
||||
"n/a"
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log_OC.e(this, "Trying to get packageName", e.cause)
|
||||
"n/a"
|
||||
}
|
||||
override fun getAppVersion(context: Context): String = try {
|
||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
packageInfo.versionName ?: "n/a"
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log_OC.e(this, "Trying to get packageName", e.cause)
|
||||
"n/a"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.appinfo
|
||||
|
||||
|
|
@ -12,7 +12,5 @@ import dagger.Provides
|
|||
@Module
|
||||
class AppInfoModule {
|
||||
@Provides
|
||||
fun appInfo(): AppInfo {
|
||||
return AppInfoImpl()
|
||||
}
|
||||
fun appInfo(): AppInfo = AppInfoImpl()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,57 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.assistant
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.nextcloud.client.assistant.model.ScreenOverlayState
|
||||
import com.nextcloud.client.assistant.model.ScreenState
|
||||
import com.nextcloud.client.assistant.repository.AssistantRepositoryType
|
||||
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.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class AssistantViewModel(
|
||||
private val repository: AssistantRepositoryType,
|
||||
private val context: WeakReference<Context>
|
||||
) : ViewModel() {
|
||||
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 _screenState = MutableStateFlow<ScreenState?>(null)
|
||||
val screenState: StateFlow<ScreenState?> = _screenState
|
||||
|
||||
private val _state = MutableStateFlow<State>(State.Loading)
|
||||
val state: StateFlow<State> = _state
|
||||
private val _screenOverlayState = MutableStateFlow<ScreenOverlayState?>(null)
|
||||
val screenOverlayState: StateFlow<ScreenOverlayState?> = _screenOverlayState
|
||||
|
||||
private val _selectedTaskType = MutableStateFlow<TaskType?>(null)
|
||||
val selectedTaskType: StateFlow<TaskType?> = _selectedTaskType
|
||||
private val _snackbarMessageId = MutableStateFlow<Int?>(null)
|
||||
val snackbarMessageId: StateFlow<Int?> = _snackbarMessageId
|
||||
|
||||
private val _taskTypes = MutableStateFlow<List<TaskType>?>(null)
|
||||
val taskTypes: StateFlow<List<TaskType>?> = _taskTypes
|
||||
private val _selectedTaskType = MutableStateFlow<TaskTypeData?>(null)
|
||||
val selectedTaskType: StateFlow<TaskTypeData?> = _selectedTaskType
|
||||
|
||||
private var _taskList: List<Task>? = null
|
||||
private val _taskTypes = MutableStateFlow<List<TaskTypeData>?>(null)
|
||||
val taskTypes: StateFlow<List<TaskTypeData>?> = _taskTypes
|
||||
|
||||
private var taskList: List<Task>? = null
|
||||
|
||||
private val _filteredTaskList = MutableStateFlow<List<Task>?>(null)
|
||||
val filteredTaskList: StateFlow<List<Task>?> = _filteredTaskList
|
||||
|
||||
init {
|
||||
fetchTaskTypes()
|
||||
fetchTaskList()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun createTask(
|
||||
input: String,
|
||||
type: String
|
||||
) {
|
||||
fun createTask(input: String, taskType: TaskTypeData) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val result = repository.createTask(input, type)
|
||||
val result = repository.createTask(input, taskType)
|
||||
|
||||
val messageId = if (result.isSuccess) {
|
||||
R.string.assistant_screen_task_create_success_message
|
||||
|
|
@ -68,62 +59,73 @@ class AssistantViewModel(
|
|||
R.string.assistant_screen_task_create_fail_message
|
||||
}
|
||||
|
||||
_state.update {
|
||||
State.TaskCreated(messageId)
|
||||
}
|
||||
updateSnackbarMessage(messageId)
|
||||
|
||||
delay(2000L)
|
||||
fetchTaskList()
|
||||
}
|
||||
}
|
||||
|
||||
fun selectTaskType(task: TaskType) {
|
||||
fun selectTaskType(task: TaskTypeData) {
|
||||
_selectedTaskType.update {
|
||||
filterTaskList(task.id)
|
||||
task
|
||||
}
|
||||
|
||||
fetchTaskList()
|
||||
}
|
||||
|
||||
private fun fetchTaskTypes() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val allTaskType = context.get()?.getString(R.string.assistant_screen_all_task_type)
|
||||
val excludedIds = listOf("OCA\\ContextChat\\TextProcessing\\ContextChatTaskType")
|
||||
val result = arrayListOf(TaskType(null, allTaskType, null))
|
||||
val taskTypesResult = repository.getTaskTypes()
|
||||
|
||||
if (taskTypesResult.isSuccess) {
|
||||
val excludedTaskTypes = taskTypesResult.resultData.types.filter { item -> item.id !in excludedIds }
|
||||
result.addAll(excludedTaskTypes)
|
||||
_taskTypes.update {
|
||||
result.toList()
|
||||
}
|
||||
|
||||
selectTaskType(result.first())
|
||||
} else {
|
||||
_state.update {
|
||||
State.Error(R.string.assistant_screen_task_types_error_state_message)
|
||||
}
|
||||
if (taskTypesResult == null) {
|
||||
updateSnackbarMessage(R.string.assistant_screen_task_types_error_state_message)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (taskTypesResult.isEmpty()) {
|
||||
updateSnackbarMessage(R.string.assistant_screen_task_list_empty_message)
|
||||
return@launch
|
||||
}
|
||||
|
||||
_taskTypes.update {
|
||||
taskTypesResult
|
||||
}
|
||||
|
||||
selectTaskType(taskTypesResult.first())
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchTaskList(appId: String = "assistant", onCompleted: () -> Unit = {}) {
|
||||
fun fetchTaskList() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val result = repository.getTaskList(appId)
|
||||
if (result.isSuccess) {
|
||||
_taskList = result.resultData.tasks
|
||||
_screenState.update {
|
||||
ScreenState.Refreshing
|
||||
}
|
||||
|
||||
filterTaskList(_selectedTaskType.value?.id)
|
||||
|
||||
_state.update {
|
||||
State.Idle
|
||||
val taskType = _selectedTaskType.value?.id ?: return@launch
|
||||
val result = repository.getTaskList(taskType)
|
||||
if (result != null) {
|
||||
taskList = result
|
||||
_filteredTaskList.update {
|
||||
taskList?.sortedByDescending { task ->
|
||||
task.id
|
||||
}
|
||||
}
|
||||
|
||||
onCompleted()
|
||||
updateSnackbarMessage(null)
|
||||
} else {
|
||||
_state.update {
|
||||
State.Error(R.string.assistant_screen_task_list_error_state_message)
|
||||
}
|
||||
updateSnackbarMessage(R.string.assistant_screen_task_list_error_state_message)
|
||||
}
|
||||
|
||||
updateScreenState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateScreenState() {
|
||||
_screenState.update {
|
||||
if (_filteredTaskList.value?.isEmpty() == true) {
|
||||
ScreenState.EmptyContent
|
||||
} else {
|
||||
ScreenState.Content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +140,7 @@ class AssistantViewModel(
|
|||
R.string.assistant_screen_task_delete_fail_message
|
||||
}
|
||||
|
||||
_state.update {
|
||||
State.TaskDeleted(messageId)
|
||||
}
|
||||
updateSnackbarMessage(messageId)
|
||||
|
||||
if (result.isSuccess) {
|
||||
removeTaskFromList(id)
|
||||
|
|
@ -148,27 +148,15 @@ class AssistantViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun resetState() {
|
||||
_state.update {
|
||||
State.Idle
|
||||
fun updateSnackbarMessage(value: Int?) {
|
||||
_snackbarMessageId.update {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterTaskList(taskTypeId: String?) {
|
||||
if (taskTypeId == null) {
|
||||
_filteredTaskList.update {
|
||||
_taskList
|
||||
}
|
||||
} else {
|
||||
_filteredTaskList.update {
|
||||
_taskList?.filter { it.type == taskTypeId }
|
||||
}
|
||||
}
|
||||
|
||||
_filteredTaskList.update {
|
||||
it?.sortedByDescending { task ->
|
||||
task.id
|
||||
}
|
||||
fun updateScreenState(value: ScreenOverlayState?) {
|
||||
_screenOverlayState.update {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
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
|
||||
|
|
@ -25,180 +24,250 @@ 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.PullToRefreshState
|
||||
import androidx.compose.material3.pulltorefresh.pullToRefresh
|
||||
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.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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.taskTypes.TaskTypesRow
|
||||
import com.nextcloud.client.assistant.task.TaskView
|
||||
import com.nextcloud.client.assistant.extensions.getInputTitle
|
||||
import com.nextcloud.client.assistant.model.ScreenOverlayState
|
||||
import com.nextcloud.client.assistant.model.ScreenState
|
||||
import com.nextcloud.client.assistant.repository.AssistantMockRepository
|
||||
import com.nextcloud.client.assistant.task.TaskView
|
||||
import com.nextcloud.client.assistant.taskTypes.TaskTypesRow
|
||||
import com.nextcloud.ui.composeActivity.ComposeActivity
|
||||
import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
|
||||
import com.nextcloud.ui.composeComponents.bottomSheet.MoreActionsBottomSheet
|
||||
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.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
import com.owncloud.android.lib.resources.status.OCCapability
|
||||
import com.owncloud.android.utils.DisplayUtils
|
||||
import kotlinx.coroutines.delay
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AssistantScreen(viewModel: AssistantViewModel, activity: Activity) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
fun AssistantScreen(viewModel: AssistantViewModel, capability: OCCapability, activity: Activity) {
|
||||
val messageId by viewModel.snackbarMessageId.collectAsState()
|
||||
val screenOverlayState by viewModel.screenOverlayState.collectAsState()
|
||||
|
||||
val selectedTaskType by viewModel.selectedTaskType.collectAsState()
|
||||
val filteredTaskList by viewModel.filteredTaskList.collectAsState()
|
||||
val screenState by viewModel.screenState.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 scope = rememberCoroutineScope()
|
||||
val pullRefreshState = rememberPullToRefreshState()
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
if (pullRefreshState.isRefreshing) {
|
||||
LaunchedEffect(true) {
|
||||
delay(1500)
|
||||
viewModel.fetchTaskList(onCompleted = {
|
||||
pullRefreshState.endRefresh()
|
||||
})
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.pullToRefresh(
|
||||
screenState == ScreenState.Refreshing,
|
||||
pullRefreshState,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
delay(1500)
|
||||
viewModel.fetchTaskList()
|
||||
}
|
||||
}
|
||||
)
|
||||
) {
|
||||
ShowScreenState(screenState, selectedTaskType, taskTypes, viewModel, filteredTaskList, capability)
|
||||
|
||||
ShowLinearProgressIndicator(screenState, pullRefreshState)
|
||||
|
||||
AddFloatingActionButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(16.dp),
|
||||
selectedTaskType,
|
||||
viewModel
|
||||
)
|
||||
}
|
||||
|
||||
Box(Modifier.nestedScroll(pullRefreshState.nestedScrollConnection)) {
|
||||
if (state == AssistantViewModel.State.Loading || pullRefreshState.isRefreshing) {
|
||||
showSnackBarMessage(messageId, activity, viewModel)
|
||||
ShowOverlayState(screenOverlayState, activity, viewModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowScreenState(
|
||||
screenState: ScreenState?,
|
||||
selectedTaskType: TaskTypeData?,
|
||||
taskTypes: List<TaskTypeData>?,
|
||||
viewModel: AssistantViewModel,
|
||||
filteredTaskList: List<Task>?,
|
||||
capability: OCCapability
|
||||
) {
|
||||
when (screenState) {
|
||||
ScreenState.Refreshing -> {
|
||||
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())
|
||||
ScreenState.EmptyContent -> {
|
||||
EmptyTaskList(selectedTaskType, taskTypes, viewModel)
|
||||
}
|
||||
|
||||
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) }
|
||||
ScreenState.Content -> {
|
||||
AssistantContent(
|
||||
filteredTaskList ?: listOf(),
|
||||
taskTypes,
|
||||
selectedTaskType,
|
||||
viewModel,
|
||||
capability
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ShowLinearProgressIndicator(screenState: ScreenState?, pullToRefreshState: PullToRefreshState) {
|
||||
if (screenState == ScreenState.Refreshing) {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
LinearProgressIndicator(
|
||||
progress = { pullToRefreshState.distanceFraction },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenState(
|
||||
state: AssistantViewModel.State,
|
||||
activity: Activity,
|
||||
private fun AddFloatingActionButton(
|
||||
modifier: Modifier,
|
||||
selectedTaskType: TaskTypeData?,
|
||||
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
|
||||
FloatingActionButton(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
selectedTaskType?.let {
|
||||
val newState = ScreenOverlayState.AddTask(it, "")
|
||||
viewModel.updateScreenState(newState)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Filled.Add, "Add Task Icon")
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSnackBarMessage(messageId: Int?, activity: Activity, viewModel: AssistantViewModel) {
|
||||
messageId?.let {
|
||||
DisplayUtils.showSnackMessage(
|
||||
activity,
|
||||
stringResource(id = messageId)
|
||||
activity.getString(it)
|
||||
)
|
||||
|
||||
viewModel.resetState()
|
||||
viewModel.updateSnackbarMessage(null)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun ShowOverlayState(state: ScreenOverlayState?, activity: Activity, viewModel: AssistantViewModel) {
|
||||
when (state) {
|
||||
is ScreenOverlayState.AddTask -> {
|
||||
AddTaskAlertDialog(
|
||||
title = state.taskType.name,
|
||||
description = state.taskType.description,
|
||||
defaultInput = state.input,
|
||||
addTask = { input ->
|
||||
state.taskType.let { taskType ->
|
||||
viewModel.createTask(input = input, taskType = taskType)
|
||||
}
|
||||
},
|
||||
dismiss = {
|
||||
viewModel.updateScreenState(null)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is ScreenOverlayState.DeleteTask -> {
|
||||
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 = { viewModel.updateScreenState(null) },
|
||||
onComplete = { viewModel.deleteTask(state.id) }
|
||||
)
|
||||
}
|
||||
|
||||
is ScreenOverlayState.TaskActions -> {
|
||||
val actions = state.getActions(activity, onEditCompleted = { addTask ->
|
||||
viewModel.updateScreenState(addTask)
|
||||
}, onDeleteCompleted = { deleteTask ->
|
||||
viewModel.updateScreenState(deleteTask)
|
||||
})
|
||||
|
||||
MoreActionsBottomSheet(
|
||||
title = state.task.getInputTitle(),
|
||||
actions = actions,
|
||||
dismiss = { viewModel.updateScreenState(null) }
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun AssistantContent(
|
||||
taskList: List<Task>,
|
||||
taskTypes: List<TaskType>?,
|
||||
selectedTaskType: TaskType?,
|
||||
taskTypes: List<TaskTypeData>?,
|
||||
selectedTaskType: TaskTypeData?,
|
||||
viewModel: AssistantViewModel,
|
||||
showDeleteTaskAlertDialog: (Long) -> Unit
|
||||
capability: OCCapability
|
||||
) {
|
||||
LazyColumn(
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
taskTypes?.let {
|
||||
TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
|
||||
viewModel.selectTaskType(task)
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(12.dp)
|
||||
) {
|
||||
items(taskList) { task ->
|
||||
TaskView(
|
||||
task,
|
||||
capability,
|
||||
showTaskActions = {
|
||||
val newState = ScreenOverlayState.TaskActions(task)
|
||||
viewModel.updateScreenState(newState)
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyTaskList(
|
||||
selectedTaskType: TaskTypeData?,
|
||||
taskTypes: List<TaskTypeData>?,
|
||||
viewModel: AssistantViewModel
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
stickyHeader {
|
||||
taskTypes?.let {
|
||||
TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
|
||||
viewModel.selectTaskType(task)
|
||||
}
|
||||
|
|
@ -206,39 +275,15 @@ private fun AssistantContent(
|
|||
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 ?: ""
|
||||
CenterText(
|
||||
text = stringResource(
|
||||
id = R.string.assistant_screen_create_a_new_task_from_bottom_right_text
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
|
||||
viewModel.selectTaskType(task)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
CenterText(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
@Preview
|
||||
private fun AssistantScreenPreview() {
|
||||
|
|
@ -246,16 +291,17 @@ private fun AssistantScreenPreview() {
|
|||
MaterialTheme(
|
||||
content = {
|
||||
AssistantScreen(
|
||||
viewModel = AssistantViewModel(
|
||||
repository = mockRepository,
|
||||
context = WeakReference(LocalContext.current)
|
||||
),
|
||||
activity = ComposeActivity()
|
||||
viewModel = AssistantViewModel(repository = mockRepository),
|
||||
activity = ComposeActivity(),
|
||||
capability = OCCapability().apply {
|
||||
versionMayor = 30
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
@Preview
|
||||
private fun AssistantEmptyScreenPreview() {
|
||||
|
|
@ -263,11 +309,11 @@ private fun AssistantEmptyScreenPreview() {
|
|||
MaterialTheme(
|
||||
content = {
|
||||
AssistantScreen(
|
||||
viewModel = AssistantViewModel(
|
||||
repository = mockRepository,
|
||||
context = WeakReference(LocalContext.current)
|
||||
),
|
||||
activity = ComposeActivity()
|
||||
viewModel = AssistantViewModel(repository = mockRepository),
|
||||
activity = ComposeActivity(),
|
||||
capability = OCCapability().apply {
|
||||
versionMayor = 30
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.assistant.component
|
||||
|
||||
|
|
@ -22,13 +22,19 @@ import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
|
|||
import com.owncloud.android.R
|
||||
|
||||
@Composable
|
||||
fun AddTaskAlertDialog(title: String?, description: String?, addTask: (String) -> Unit, dismiss: () -> Unit) {
|
||||
fun AddTaskAlertDialog(
|
||||
title: String,
|
||||
description: String?,
|
||||
defaultInput: String = "",
|
||||
addTask: (String) -> Unit,
|
||||
dismiss: () -> Unit
|
||||
) {
|
||||
var input by remember {
|
||||
mutableStateOf("")
|
||||
mutableStateOf(defaultInput)
|
||||
}
|
||||
|
||||
SimpleAlertDialog(
|
||||
title = title ?: "",
|
||||
title = title,
|
||||
description = description ?: "",
|
||||
dismiss = { dismiss() },
|
||||
onComplete = {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.assistant.component
|
||||
|
||||
|
|
@ -13,8 +13,10 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.owncloud.android.R
|
||||
|
||||
@Composable
|
||||
fun CenterText(text: String) {
|
||||
|
|
@ -22,7 +24,8 @@ fun CenterText(text: String) {
|
|||
Text(
|
||||
text = text,
|
||||
fontSize = 18.sp,
|
||||
textAlign = TextAlign.Center
|
||||
textAlign = TextAlign.Center,
|
||||
color = colorResource(R.color.text_color)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,40 +3,130 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud
|
||||
* contributors
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.assistant.extensions
|
||||
|
||||
import android.content.Context
|
||||
import com.nextcloud.utils.date.DateFormatPattern
|
||||
import com.nextcloud.utils.date.DateFormatter
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.assistant.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.status.NextcloudVersion
|
||||
import com.owncloud.android.lib.resources.status.OCCapability
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun Task.getInputAndOutput(): String {
|
||||
val inputText = input?.input ?: ""
|
||||
val outputText = output?.output ?: ""
|
||||
|
||||
return "$inputText\n\n$outputText"
|
||||
}
|
||||
|
||||
fun Task.getInput(): String? = input?.input
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Task.statusData(): Pair<Int, Int> {
|
||||
return when (status) {
|
||||
0L -> {
|
||||
Pair(R.drawable.ic_unknown, R.string.assistant_screen_unknown_task_status_text)
|
||||
}
|
||||
1L -> {
|
||||
Pair(R.drawable.ic_clock, R.string.assistant_screen_scheduled_task_status_text)
|
||||
}
|
||||
2L -> {
|
||||
Pair(R.drawable.ic_modification_desc, R.string.assistant_screen_running_task_text)
|
||||
}
|
||||
3L -> {
|
||||
Pair(R.drawable.ic_info, R.string.assistant_screen_successful_task_text)
|
||||
}
|
||||
4L -> {
|
||||
Pair(R.drawable.image_fail, R.string.assistant_screen_failed_task_text)
|
||||
}
|
||||
else -> {
|
||||
Pair(R.drawable.ic_unknown, R.string.assistant_screen_unknown_task_status_text)
|
||||
}
|
||||
fun Task.getInputTitle(): String {
|
||||
val maxTitleLength = 20
|
||||
val title = getInput() ?: ""
|
||||
|
||||
return if (title.length > maxTitleLength) {
|
||||
title.take(maxTitleLength) + "..."
|
||||
} else {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add
|
||||
fun Task.completionDateRepresentation(): String {
|
||||
return completionExpectedAt ?: "TODO IMPLEMENT IT"
|
||||
fun Task.getStatusIcon(capability: OCCapability): Int =
|
||||
if (capability.version.isNewerOrEqual(NextcloudVersion.nextcloud_30)) {
|
||||
getStatusIconV2()
|
||||
} else {
|
||||
getStatusIconV1()
|
||||
}
|
||||
|
||||
private fun Task.getStatusIconV1(): Int = when (status) {
|
||||
"0" -> {
|
||||
R.drawable.ic_unknown
|
||||
}
|
||||
"1" -> {
|
||||
R.drawable.ic_clock
|
||||
}
|
||||
"2" -> {
|
||||
R.drawable.ic_modification_desc
|
||||
}
|
||||
"3" -> {
|
||||
R.drawable.ic_check_circle_outline
|
||||
}
|
||||
"4" -> {
|
||||
R.drawable.image_fail
|
||||
}
|
||||
else -> {
|
||||
R.drawable.ic_unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun Task.getStatusIconV2(): Int = when (status) {
|
||||
"STATUS_UNKNOWN" -> {
|
||||
R.drawable.ic_unknown
|
||||
}
|
||||
"STATUS_SCHEDULED" -> {
|
||||
R.drawable.ic_clock
|
||||
}
|
||||
"STATUS_RUNNING" -> {
|
||||
R.drawable.ic_modification_desc
|
||||
}
|
||||
"STATUS_SUCCESSFUL" -> {
|
||||
R.drawable.ic_check_circle_outline
|
||||
}
|
||||
"STATUS_FAILED" -> {
|
||||
R.drawable.image_fail
|
||||
}
|
||||
else -> {
|
||||
R.drawable.ic_unknown
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Task.getModifiedAtRepresentation(context: Context): String? {
|
||||
if (lastUpdated == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val modifiedAt = lastUpdated!!.toLong()
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
val timeDifference = (currentTime - modifiedAt).toInt()
|
||||
val timeDifferenceInMinutes = (timeDifference / 60)
|
||||
val timeDifferenceInHours = (timeDifference / 3600)
|
||||
|
||||
return when {
|
||||
timeDifference < 0 -> {
|
||||
context.getString(R.string.common_now)
|
||||
}
|
||||
|
||||
timeDifference < TimeUnit.MINUTES.toSeconds(1) -> {
|
||||
context.resources.getQuantityString(R.plurals.time_seconds_ago, timeDifference, timeDifference)
|
||||
}
|
||||
|
||||
timeDifference < TimeUnit.HOURS.toSeconds(1) -> {
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.time_minutes_ago,
|
||||
timeDifferenceInMinutes,
|
||||
timeDifferenceInMinutes
|
||||
)
|
||||
}
|
||||
|
||||
timeDifference < TimeUnit.DAYS.toSeconds(1) -> {
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.time_hours_ago,
|
||||
timeDifferenceInHours,
|
||||
timeDifferenceInHours
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
DateFormatter.timestampToDateRepresentation(modifiedAt, DateFormatPattern.MonthWithDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.assistant.model
|
||||
|
||||
import android.app.Activity
|
||||
import com.nextcloud.client.assistant.extensions.getInput
|
||||
import com.nextcloud.client.assistant.extensions.getInputAndOutput
|
||||
import com.nextcloud.utils.extensions.showShareIntent
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
import com.owncloud.android.utils.ClipboardUtil
|
||||
|
||||
sealed class ScreenOverlayState {
|
||||
data class DeleteTask(val id: Long) : ScreenOverlayState()
|
||||
data class AddTask(val taskType: TaskTypeData, val input: String) : ScreenOverlayState()
|
||||
data class TaskActions(val task: Task) : ScreenOverlayState() {
|
||||
private fun getInputAndOutput(): String = task.getInputAndOutput()
|
||||
private fun getInput(): String? = task.getInput()
|
||||
|
||||
private fun getCopyToClipboardAction(activity: Activity): Triple<Int, Int, () -> Unit> = Triple(
|
||||
R.drawable.ic_content_copy,
|
||||
R.string.common_copy
|
||||
) {
|
||||
ClipboardUtil.copyToClipboard(activity, getInputAndOutput(), showToast = false)
|
||||
}
|
||||
|
||||
private fun getShareAction(activity: Activity): Triple<Int, Int, () -> Unit> = Triple(
|
||||
R.drawable.ic_share,
|
||||
R.string.common_share
|
||||
) {
|
||||
activity.showShareIntent(getInputAndOutput())
|
||||
}
|
||||
|
||||
private fun getEditAction(activity: Activity, onComplete: (AddTask) -> Unit): Triple<Int, Int, () -> Unit> =
|
||||
Triple(
|
||||
R.drawable.ic_edit,
|
||||
R.string.action_edit
|
||||
) {
|
||||
val taskType = TaskTypeData(
|
||||
task.type,
|
||||
activity.getString(R.string.assistant_screen_add_task_alert_dialog_title),
|
||||
null,
|
||||
emptyMap(),
|
||||
emptyMap()
|
||||
)
|
||||
val newState = AddTask(taskType, getInput() ?: "")
|
||||
onComplete(newState)
|
||||
}
|
||||
|
||||
private fun getDeleteAction(onComplete: (DeleteTask) -> Unit): Triple<Int, Int, () -> Unit> = Triple(
|
||||
R.drawable.ic_delete,
|
||||
R.string.assistant_screen_task_more_actions_bottom_sheet_delete_action
|
||||
) {
|
||||
val newState = DeleteTask(task.id)
|
||||
onComplete(newState)
|
||||
}
|
||||
|
||||
fun getActions(
|
||||
activity: Activity,
|
||||
onEditCompleted: (AddTask) -> Unit,
|
||||
onDeleteCompleted: (DeleteTask) -> Unit
|
||||
): List<Triple<Int, Int, () -> Unit>> = listOf(
|
||||
getShareAction(activity),
|
||||
getCopyToClipboardAction(activity),
|
||||
getEditAction(activity, onComplete = {
|
||||
onEditCompleted(it)
|
||||
}),
|
||||
getDeleteAction(onComplete = {
|
||||
onDeleteCompleted(it)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.assistant.model
|
||||
|
||||
enum class ScreenState {
|
||||
Refreshing,
|
||||
EmptyContent,
|
||||
Content
|
||||
}
|
||||
|
|
@ -1,128 +1,68 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.assistant.repository
|
||||
|
||||
import com.nextcloud.utils.extensions.getRandomString
|
||||
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
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Shape
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskInput
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskOutput
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
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 getTaskTypes(): List<TaskTypeData> = listOf(
|
||||
TaskTypeData(
|
||||
id = "core:text2text",
|
||||
name = "Free text to text prompt",
|
||||
description = "Runs an arbitrary prompt through a language model that returns a reply",
|
||||
inputShape = mapOf(
|
||||
"input" to Shape(
|
||||
name = "Prompt",
|
||||
description = "Describe a task that you want the assistant to do or ask a question",
|
||||
type = "Text"
|
||||
)
|
||||
),
|
||||
outputShape = mapOf(
|
||||
"output" to Shape(
|
||||
name = "Generated reply",
|
||||
description = "The generated text from the assistant",
|
||||
type = "Text"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
override fun createTask(input: String, type: String): RemoteOperationResult<Void> {
|
||||
return RemoteOperationResult<Void>(RemoteOperationResult.ResultCode.OK)
|
||||
}
|
||||
override fun createTask(input: String, taskType: TaskTypeData): RemoteOperationResult<Void> =
|
||||
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 long text 1",
|
||||
"Lorem ipsum".getRandomString(100),
|
||||
""
|
||||
),
|
||||
Task(
|
||||
2,
|
||||
"GenerateHeadline",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
"Give me some text 2",
|
||||
"Lorem".getRandomString(100),
|
||||
"",
|
||||
""
|
||||
),
|
||||
Task(
|
||||
3,
|
||||
"FreePrompt",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
"Give me some text 3",
|
||||
"Lorem".getRandomString(300),
|
||||
"",
|
||||
""
|
||||
),
|
||||
Task(
|
||||
4,
|
||||
"FreePrompt",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
"Give me some text 4",
|
||||
"Lorem".getRandomString(300),
|
||||
"",
|
||||
""
|
||||
),
|
||||
Task(
|
||||
5,
|
||||
"FreePrompt",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
"Give me some text 5",
|
||||
"Lorem".getRandomString(300),
|
||||
"",
|
||||
""
|
||||
),
|
||||
Task(
|
||||
6,
|
||||
"FreePrompt",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
"Give me some text 6",
|
||||
"Lorem".getRandomString(300),
|
||||
"",
|
||||
""
|
||||
),
|
||||
Task(
|
||||
7,
|
||||
"FreePrompt",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
"Give me some text 7",
|
||||
"Lorem".getRandomString(300),
|
||||
"",
|
||||
""
|
||||
)
|
||||
)
|
||||
override fun getTaskList(taskType: String): List<Task> = if (giveEmptyTasks) {
|
||||
listOf()
|
||||
} else {
|
||||
listOf(
|
||||
Task(
|
||||
1,
|
||||
"FreePrompt",
|
||||
null,
|
||||
"12",
|
||||
"",
|
||||
TaskInput("Give me some long text 1"),
|
||||
TaskOutput("Lorem ipsum".getRandomString(100)),
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337
|
||||
)
|
||||
}
|
||||
|
||||
return RemoteOperationResult<TaskList>(RemoteOperationResult.ResultCode.OK).apply {
|
||||
resultData = taskList
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun deleteTask(id: Long): RemoteOperationResult<Void> {
|
||||
return RemoteOperationResult<Void>(RemoteOperationResult.ResultCode.OK)
|
||||
}
|
||||
override fun deleteTask(id: Long): RemoteOperationResult<Void> =
|
||||
RemoteOperationResult<Void>(RemoteOperationResult.ResultCode.OK)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,80 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
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
|
||||
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
|
||||
import com.owncloud.android.lib.resources.assistant.v1.CreateTaskRemoteOperationV1
|
||||
import com.owncloud.android.lib.resources.assistant.v1.DeleteTaskRemoteOperationV1
|
||||
import com.owncloud.android.lib.resources.assistant.v1.GetTaskListRemoteOperationV1
|
||||
import com.owncloud.android.lib.resources.assistant.v1.GetTaskTypesRemoteOperationV1
|
||||
import com.owncloud.android.lib.resources.assistant.v1.model.toV2
|
||||
import com.owncloud.android.lib.resources.assistant.v2.CreateTaskRemoteOperationV2
|
||||
import com.owncloud.android.lib.resources.assistant.v2.DeleteTaskRemoteOperationV2
|
||||
import com.owncloud.android.lib.resources.assistant.v2.GetTaskListRemoteOperationV2
|
||||
import com.owncloud.android.lib.resources.assistant.v2.GetTaskTypesRemoteOperationV2
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
import com.owncloud.android.lib.resources.status.NextcloudVersion
|
||||
import com.owncloud.android.lib.resources.status.OCCapability
|
||||
|
||||
class AssistantRepository(private val client: NextcloudClient) : AssistantRepositoryType {
|
||||
class AssistantRepository(private val client: NextcloudClient, capability: OCCapability) : AssistantRepositoryType {
|
||||
|
||||
override fun getTaskTypes(): RemoteOperationResult<TaskTypes> {
|
||||
return GetTaskTypesRemoteOperation().execute(client)
|
||||
private val supportsV2 = capability.version.isNewerOrEqual(NextcloudVersion.nextcloud_30)
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
override fun getTaskTypes(): List<TaskTypeData>? {
|
||||
if (supportsV2) {
|
||||
val result = GetTaskTypesRemoteOperationV2().execute(client)
|
||||
if (result.isSuccess) {
|
||||
return result.resultData
|
||||
}
|
||||
} else {
|
||||
val result = GetTaskTypesRemoteOperationV1().execute(client)
|
||||
if (result.isSuccess) {
|
||||
return result.resultData.toV2()
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createTask(
|
||||
input: String,
|
||||
type: String
|
||||
): RemoteOperationResult<Void> {
|
||||
return CreateTaskRemoteOperation(input, type).execute(client)
|
||||
override fun createTask(input: String, taskType: TaskTypeData): RemoteOperationResult<Void> = if (supportsV2) {
|
||||
CreateTaskRemoteOperationV2(input, taskType).execute(client)
|
||||
} else {
|
||||
if (taskType.id.isNullOrEmpty()) {
|
||||
RemoteOperationResult<Void>(ResultCode.CANCELLED)
|
||||
} else {
|
||||
CreateTaskRemoteOperationV1(input, taskType.id!!).execute(client)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTaskList(appId: String): RemoteOperationResult<TaskList> {
|
||||
return GetTaskListRemoteOperation(appId).execute(client)
|
||||
@Suppress("ReturnCount")
|
||||
override fun getTaskList(taskType: String): List<Task>? {
|
||||
if (supportsV2) {
|
||||
val result = GetTaskListRemoteOperationV2(taskType).execute(client)
|
||||
if (result.isSuccess) {
|
||||
return result.resultData.tasks.filter { it.appId == "assistant" }
|
||||
}
|
||||
} else {
|
||||
val result = GetTaskListRemoteOperationV1("assistant").execute(client)
|
||||
if (result.isSuccess) {
|
||||
return result.resultData.toV2().tasks.filter { it.type == taskType }
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun deleteTask(id: Long): RemoteOperationResult<Void> {
|
||||
return DeleteTaskRemoteOperation(id).execute(client)
|
||||
override fun deleteTask(id: Long): RemoteOperationResult<Void> = if (supportsV2) {
|
||||
DeleteTaskRemoteOperationV2(id).execute(client)
|
||||
} else {
|
||||
DeleteTaskRemoteOperationV1(id).execute(client)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,21 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
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
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
|
||||
interface AssistantRepositoryType {
|
||||
fun getTaskTypes(): RemoteOperationResult<TaskTypes>
|
||||
fun getTaskTypes(): List<TaskTypeData>?
|
||||
|
||||
fun createTask(
|
||||
input: String,
|
||||
type: String
|
||||
): RemoteOperationResult<Void>
|
||||
fun createTask(input: String, taskType: TaskTypeData): RemoteOperationResult<Void>
|
||||
|
||||
fun getTaskList(appId: String): RemoteOperationResult<TaskList>
|
||||
fun getTaskList(taskType: String): List<com.owncloud.android.lib.resources.assistant.v2.model.Task>?
|
||||
|
||||
fun deleteTask(id: Long): RemoteOperationResult<Void>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.assistant.task
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
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.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nextcloud.client.assistant.extensions.getModifiedAtRepresentation
|
||||
import com.nextcloud.client.assistant.extensions.getStatusIcon
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskInput
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskOutput
|
||||
import com.owncloud.android.lib.resources.status.OCCapability
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Composable
|
||||
fun TaskStatusView(task: Task, capability: OCCapability) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val iconId = task.getStatusIcon(capability)
|
||||
val description = task.getModifiedAtRepresentation(context)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = iconId),
|
||||
modifier = Modifier.size(16.dp),
|
||||
colorFilter = ColorFilter.tint(color = colorResource(R.color.text_color)),
|
||||
contentDescription = "status icon"
|
||||
)
|
||||
|
||||
description?.let {
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Text(text = description, color = colorResource(R.color.text_color))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
@Composable
|
||||
@Preview
|
||||
private fun TaskStatusViewPreview() {
|
||||
val currentTime = System.currentTimeMillis() / 1000
|
||||
|
||||
val tasks = listOf(
|
||||
Task(
|
||||
id = 1L,
|
||||
type = "type1",
|
||||
status = "STATUS_RUNNING",
|
||||
userId = "user1",
|
||||
appId = "app1",
|
||||
input = TaskInput("input1"),
|
||||
output = TaskOutput("output1"),
|
||||
scheduledAt = currentTime.toInt(),
|
||||
lastUpdated = currentTime.toInt()
|
||||
),
|
||||
|
||||
Task(
|
||||
id = 2L,
|
||||
type = "type2",
|
||||
status = "STATUS_SUCCESSFUL",
|
||||
userId = "user2",
|
||||
appId = "app2",
|
||||
input = TaskInput("input2"),
|
||||
output = TaskOutput("output2"),
|
||||
lastUpdated = (currentTime - TimeUnit.MINUTES.toSeconds(5)).toInt()
|
||||
),
|
||||
|
||||
Task(
|
||||
id = 3L,
|
||||
type = "type3",
|
||||
status = "STATUS_RUNNING",
|
||||
userId = "user3",
|
||||
appId = "app3",
|
||||
input = TaskInput("input3"),
|
||||
output = TaskOutput("output3"),
|
||||
lastUpdated = (currentTime - TimeUnit.HOURS.toSeconds(5)).toInt()
|
||||
),
|
||||
|
||||
Task(
|
||||
id = 4L,
|
||||
type = "type4",
|
||||
status = "STATUS_SUCCESSFUL",
|
||||
userId = "user4",
|
||||
appId = "app4",
|
||||
input = TaskInput("input4"),
|
||||
output = TaskOutput("output4"),
|
||||
lastUpdated = (currentTime - TimeUnit.DAYS.toSeconds(5)).toInt()
|
||||
),
|
||||
|
||||
Task(
|
||||
id = 5L,
|
||||
type = "type5",
|
||||
status = "STATUS_SUCCESSFUL",
|
||||
userId = "user5",
|
||||
appId = "app5",
|
||||
input = TaskInput("input5"),
|
||||
output = TaskOutput("output5"),
|
||||
lastUpdated = (currentTime - TimeUnit.DAYS.toSeconds(60)).toInt()
|
||||
),
|
||||
|
||||
Task(
|
||||
id = 6L,
|
||||
type = "type7",
|
||||
status = "STATUS_UNKNOWN",
|
||||
userId = "user7",
|
||||
appId = "app7",
|
||||
input = TaskInput("input7"),
|
||||
output = TaskOutput("output7"),
|
||||
scheduledAt = null,
|
||||
lastUpdated = null
|
||||
)
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
items(tasks) {
|
||||
TaskStatusView(
|
||||
it,
|
||||
OCCapability().apply {
|
||||
versionMayor = 30
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +1,120 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.assistant.task
|
||||
|
||||
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.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
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.client.assistant.taskDetail.TaskDetailBottomSheet
|
||||
import com.nextcloud.ui.composeComponents.bottomSheet.MoreActionsBottomSheet
|
||||
import com.nextcloud.utils.extensions.getRandomString
|
||||
import com.nextcloud.utils.extensions.truncateWithEllipsis
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.assistant.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskInput
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskOutput
|
||||
import com.owncloud.android.lib.resources.status.OCCapability
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
@Composable
|
||||
fun TaskView(
|
||||
task: Task,
|
||||
showDeleteTaskAlertDialog: (Long) -> Unit
|
||||
) {
|
||||
fun TaskView(task: Task, capability: OCCapability, showTaskActions: () -> Unit) {
|
||||
var showTaskDetailBottomSheet by remember { mutableStateOf(false) }
|
||||
var showMoreActionsBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
.combinedClickable(onClick = {
|
||||
showTaskDetailBottomSheet = true
|
||||
}, 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 = it.take(100),
|
||||
fontSize = 12.sp,
|
||||
color = Color.White,
|
||||
modifier = Modifier
|
||||
.height(100.dp)
|
||||
.animateContentSize(
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
TaskStatus(task, foregroundColor = Color.White)
|
||||
|
||||
if (showMoreActionsBottomSheet) {
|
||||
val bottomSheetAction = listOf(
|
||||
Triple(
|
||||
R.drawable.ic_delete,
|
||||
R.string.assistant_screen_task_more_actions_bottom_sheet_delete_action
|
||||
) {
|
||||
showDeleteTaskAlertDialog(task.id)
|
||||
Box {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(color = colorResource(R.color.task_container))
|
||||
.clickable {
|
||||
showTaskDetailBottomSheet = true
|
||||
}
|
||||
)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
MoreActionsBottomSheet(
|
||||
title = task.input,
|
||||
actions = bottomSheetAction,
|
||||
dismiss = { showMoreActionsBottomSheet = false }
|
||||
)
|
||||
task.input?.input?.let {
|
||||
Text(
|
||||
text = it.truncateWithEllipsis(30),
|
||||
color = colorResource(R.color.text_color),
|
||||
fontSize = 18.sp,
|
||||
textAlign = TextAlign.Left,
|
||||
maxLines = 1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.width(300.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
task.output?.output?.let {
|
||||
Text(
|
||||
text = it.truncateWithEllipsis(100),
|
||||
fontSize = 18.sp,
|
||||
color = colorResource(R.color.text_color),
|
||||
textAlign = TextAlign.Left,
|
||||
modifier = Modifier
|
||||
.animateContentSize(
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
TaskStatusView(task, capability)
|
||||
|
||||
if (showTaskDetailBottomSheet) {
|
||||
TaskDetailBottomSheet(task, showTaskActions = {
|
||||
showTaskDetailBottomSheet = false
|
||||
showTaskActions()
|
||||
}) {
|
||||
showTaskDetailBottomSheet = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showTaskDetailBottomSheet) {
|
||||
TaskDetailBottomSheet(task) {
|
||||
showTaskDetailBottomSheet = false
|
||||
}
|
||||
IconButton(
|
||||
modifier = Modifier.align(Alignment.TopEnd),
|
||||
onClick = showTaskActions
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = "More button",
|
||||
tint = colorResource(R.color.text_color)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,20 +123,28 @@ fun TaskView(
|
|||
@Preview
|
||||
@Composable
|
||||
private fun TaskViewPreview() {
|
||||
val output = "Lorem".getRandomString(100)
|
||||
|
||||
TaskView(
|
||||
task = Task(
|
||||
1,
|
||||
"Free Prompt",
|
||||
0,
|
||||
"STATUS_COMPLETED",
|
||||
"1",
|
||||
"1",
|
||||
"Give me text",
|
||||
output,
|
||||
"",
|
||||
""
|
||||
)
|
||||
) {
|
||||
}
|
||||
TaskInput("What about other promising tokens like"),
|
||||
TaskOutput(
|
||||
"Several tokens show promise for future growth in the" +
|
||||
"cryptocurrency market"
|
||||
),
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337
|
||||
),
|
||||
OCCapability().apply {
|
||||
versionMayor = 30
|
||||
},
|
||||
showTaskActions = {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.assistant.taskDetail
|
||||
|
||||
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.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -19,45 +17,41 @@ 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.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.nextcloud.client.assistant.task.TaskStatus
|
||||
import com.nextcloud.utils.extensions.getRandomString
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.assistant.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.Task
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskInput
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskOutput
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TaskDetailBottomSheet(task: Task, dismiss: () -> Unit) {
|
||||
var showInput by remember { mutableStateOf(true) }
|
||||
fun TaskDetailBottomSheet(task: Task, showTaskActions: () -> Unit, dismiss: () -> Unit) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
ModalBottomSheet(
|
||||
modifier = Modifier.padding(top = 32.dp),
|
||||
containerColor = Color.White,
|
||||
onDismissRequest = {
|
||||
dismiss()
|
||||
},
|
||||
containerColor = colorResource(R.color.bg_default),
|
||||
onDismissRequest = { dismiss() },
|
||||
sheetState = sheetState
|
||||
) {
|
||||
LazyColumn(
|
||||
|
|
@ -67,80 +61,68 @@ fun TaskDetailBottomSheet(task: Task, dismiss: () -> Unit) {
|
|||
) {
|
||||
stickyHeader {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = colorResource(id = R.color.light_grey), shape = RoundedCornerShape(8.dp))
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
TextInputSelectButton(
|
||||
Modifier.weight(1f),
|
||||
R.string.assistant_task_detail_screen_input_button_title,
|
||||
showInput,
|
||||
onClick = {
|
||||
showInput = true
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
TextInputSelectButton(
|
||||
Modifier.weight(1f),
|
||||
R.string.assistant_task_detail_screen_output_button_title,
|
||||
!showInput,
|
||||
onClick = {
|
||||
showInput = false
|
||||
}
|
||||
)
|
||||
IconButton(onClick = showTaskActions) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.MoreVert,
|
||||
contentDescription = "More button",
|
||||
tint = colorResource(R.color.text_color)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = colorResource(id = R.color.light_grey), shape = RoundedCornerShape(8.dp))
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (showInput) {
|
||||
task.input ?: ""
|
||||
} else {
|
||||
task.output ?: ""
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
color = Color.Black,
|
||||
modifier = Modifier
|
||||
.animateContentSize(
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
TaskStatus(task, foregroundColor = Color.Black)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
InputOutputCard(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextInputSelectButton(modifier: Modifier, titleId: Int, highlightCondition: Boolean, onClick: () -> Unit) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
colors = if (highlightCondition) {
|
||||
ButtonDefaults.buttonColors(containerColor = Color.White)
|
||||
} else {
|
||||
ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.light_grey))
|
||||
},
|
||||
modifier = modifier
|
||||
.widthIn(min = 0.dp, max = 200.dp)
|
||||
.padding(horizontal = 4.dp)
|
||||
fun InputOutputCard(task: Task) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Transparent, shape = RoundedCornerShape(8.dp))
|
||||
) {
|
||||
Text(text = stringResource(id = titleId), color = Color.Black)
|
||||
TitleDescriptionBox(
|
||||
title = stringResource(R.string.assistant_task_detail_screen_input_button_title),
|
||||
description = task.input?.input ?: ""
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
TitleDescriptionBox(
|
||||
title = stringResource(R.string.assistant_task_detail_screen_output_button_title),
|
||||
description = task.output?.output ?: stringResource(R.string.assistant_screen_task_output_empty_text)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TitleDescriptionBox(title: String, description: String?) {
|
||||
Text(
|
||||
text = title,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
color = colorResource(R.color.text_color)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.background(color = colorResource(R.color.task_container), RoundedCornerShape(8.dp))
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = description ?: "",
|
||||
color = colorResource(R.color.text_color)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,14 +134,19 @@ private fun TaskDetailScreenPreview() {
|
|||
task = Task(
|
||||
1,
|
||||
"Free Prompt",
|
||||
0,
|
||||
null,
|
||||
"1",
|
||||
"1",
|
||||
"Give me text".getRandomString(100),
|
||||
"output".getRandomString(300),
|
||||
"",
|
||||
""
|
||||
)
|
||||
TaskInput("Give me text".getRandomString(100)),
|
||||
TaskOutput("output".getRandomString(300)),
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337,
|
||||
1707692337
|
||||
),
|
||||
showTaskActions = {
|
||||
}
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +1,66 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.assistant.taskTypes
|
||||
|
||||
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 android.annotation.SuppressLint
|
||||
import androidx.compose.material3.ScrollableTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRowDefaults
|
||||
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||
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.res.colorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.owncloud.android.lib.resources.assistant.model.TaskType
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.lib.resources.assistant.v2.model.TaskTypeData
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
@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)
|
||||
}
|
||||
fun TaskTypesRow(selectedTaskType: TaskTypeData?, data: List<TaskTypeData>, selectTaskType: (TaskTypeData) -> Unit) {
|
||||
val selectedTabIndex = data.indexOfFirst { it.id == selectedTaskType?.id }.takeIf { it >= 0 } ?: 0
|
||||
|
||||
Spacer(modifier = Modifier.padding(end = 8.dp))
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
edgePadding = 0.dp,
|
||||
containerColor = colorResource(R.color.actionbar_color),
|
||||
indicator = {
|
||||
TabRowDefaults.SecondaryIndicator(
|
||||
Modifier.tabIndicatorOffset(it[selectedTabIndex]),
|
||||
color = colorResource(R.color.primary)
|
||||
)
|
||||
}
|
||||
) {
|
||||
data.forEach { taskType ->
|
||||
if (taskType.name.isNotEmpty()) {
|
||||
Tab(
|
||||
selected = selectedTaskType?.id == taskType.id,
|
||||
onClick = { selectTaskType(taskType) },
|
||||
selectedContentColor = colorResource(R.color.text_color),
|
||||
unselectedContentColor = colorResource(R.color.disabled_text),
|
||||
text = { Text(text = taskType.name) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun TaskTypesRowPreview() {
|
||||
val selectedTaskType = TaskTypeData("1", "Free text to text prompt", "", emptyMap(), emptyMap())
|
||||
val taskTypes = listOf(
|
||||
TaskTypeData("1", "Free text to text prompt", "", emptyMap(), emptyMap()),
|
||||
TaskTypeData("2", "Extract topics", "", emptyMap(), emptyMap()),
|
||||
TaskTypeData("3", "Generate Headline", "", emptyMap(), emptyMap()),
|
||||
TaskTypeData("4", "Summarize", "", emptyMap(), emptyMap())
|
||||
)
|
||||
|
||||
TaskTypesRow(selectedTaskType, taskTypes) { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
@ -19,9 +19,7 @@ import android.os.IBinder
|
|||
*
|
||||
* One can subclass it to create own service interaction API.
|
||||
*/
|
||||
abstract class LocalConnection<S : Service>(
|
||||
protected val context: Context
|
||||
) : ServiceConnection {
|
||||
abstract class LocalConnection<S : Service>(protected val context: Context) : ServiceConnection {
|
||||
|
||||
private var serviceBinder: LocalBinder<S>? = null
|
||||
val service: S? get() = serviceBinder?.service
|
||||
|
|
@ -35,9 +33,7 @@ abstract class LocalConnection<S : Service>(
|
|||
*
|
||||
* @see [bind]
|
||||
*/
|
||||
protected open fun createBindIntent(): Intent? {
|
||||
return null
|
||||
}
|
||||
protected open fun createBindIntent(): Intent? = null
|
||||
|
||||
/**
|
||||
* Bind local service. If [createBindIntent] returns null, it no-ops.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
@ -20,14 +20,12 @@ class ManualAsyncRunner : AsyncRunner {
|
|||
task: () -> T,
|
||||
onResult: OnResultCallback<T>?,
|
||||
onError: OnErrorCallback?
|
||||
): Cancellable {
|
||||
return postTask(
|
||||
task = { _: OnProgressCallback<Any>, _: IsCancelled -> task.invoke() },
|
||||
onResult = onResult,
|
||||
onError = onError,
|
||||
onProgress = null
|
||||
)
|
||||
}
|
||||
): Cancellable = postTask(
|
||||
task = { _: OnProgressCallback<Any>, _: IsCancelled -> task.invoke() },
|
||||
onResult = onResult,
|
||||
onError = onError,
|
||||
onProgress = null
|
||||
)
|
||||
|
||||
override fun <T, P> postTask(
|
||||
task: TaskFunction<T, P>,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
@ -20,7 +20,8 @@ internal class Task<T, P>(
|
|||
private val onSuccess: OnResultCallback<T>?,
|
||||
private val onError: OnErrorCallback?,
|
||||
private val onProgress: OnProgressCallback<P>?
|
||||
) : Runnable, Cancellable {
|
||||
) : Runnable,
|
||||
Cancellable {
|
||||
|
||||
val isCancelled: Boolean
|
||||
get() = cancelled.get()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.core
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ import android.content.Context
|
|||
import com.nextcloud.client.core.Clock
|
||||
import com.nextcloud.client.database.dao.ArbitraryDataDao
|
||||
import com.nextcloud.client.database.dao.FileDao
|
||||
import com.nextcloud.client.database.dao.OfflineOperationDao
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -20,17 +21,15 @@ class DatabaseModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun database(context: Context, clock: Clock): NextcloudDatabase {
|
||||
return NextcloudDatabase.getInstance(context, clock)
|
||||
}
|
||||
fun database(context: Context, clock: Clock): NextcloudDatabase = NextcloudDatabase.getInstance(context, clock)
|
||||
|
||||
@Provides
|
||||
fun arbitraryDataDao(nextcloudDatabase: NextcloudDatabase): ArbitraryDataDao {
|
||||
return nextcloudDatabase.arbitraryDataDao()
|
||||
}
|
||||
fun arbitraryDataDao(nextcloudDatabase: NextcloudDatabase): ArbitraryDataDao = nextcloudDatabase.arbitraryDataDao()
|
||||
|
||||
@Provides
|
||||
fun fileDao(nextcloudDatabase: NextcloudDatabase): FileDao {
|
||||
return nextcloudDatabase.fileDao()
|
||||
}
|
||||
fun fileDao(nextcloudDatabase: NextcloudDatabase): FileDao = nextcloudDatabase.fileDao()
|
||||
|
||||
@Provides
|
||||
fun offlineOperationsDao(nextcloudDatabase: NextcloudDatabase): OfflineOperationDao =
|
||||
nextcloudDatabase.offlineOperationDao()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database
|
||||
|
||||
|
|
@ -12,23 +12,31 @@ import androidx.room.AutoMigration
|
|||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import com.nextcloud.client.core.Clock
|
||||
import com.nextcloud.client.core.ClockImpl
|
||||
import com.nextcloud.client.database.dao.ArbitraryDataDao
|
||||
import com.nextcloud.client.database.dao.FileDao
|
||||
import com.nextcloud.client.database.dao.OfflineOperationDao
|
||||
import com.nextcloud.client.database.dao.RecommendedFileDao
|
||||
import com.nextcloud.client.database.dao.UploadDao
|
||||
import com.nextcloud.client.database.entity.ArbitraryDataEntity
|
||||
import com.nextcloud.client.database.entity.CapabilityEntity
|
||||
import com.nextcloud.client.database.entity.ExternalLinkEntity
|
||||
import com.nextcloud.client.database.entity.FileEntity
|
||||
import com.nextcloud.client.database.entity.FilesystemEntity
|
||||
import com.nextcloud.client.database.entity.OfflineOperationEntity
|
||||
import com.nextcloud.client.database.entity.RecommendedFileEntity
|
||||
import com.nextcloud.client.database.entity.ShareEntity
|
||||
import com.nextcloud.client.database.entity.SyncedFolderEntity
|
||||
import com.nextcloud.client.database.entity.UploadEntity
|
||||
import com.nextcloud.client.database.entity.VirtualEntity
|
||||
import com.nextcloud.client.database.migrations.DatabaseMigrationUtil
|
||||
import com.nextcloud.client.database.migrations.MIGRATION_88_89
|
||||
import com.nextcloud.client.database.migrations.Migration67to68
|
||||
import com.nextcloud.client.database.migrations.RoomMigration
|
||||
import com.nextcloud.client.database.migrations.addLegacyMigrations
|
||||
import com.nextcloud.client.database.typeConverter.OfflineOperationTypeConverter
|
||||
import com.owncloud.android.db.ProviderMeta
|
||||
|
||||
@Database(
|
||||
|
|
@ -41,7 +49,9 @@ import com.owncloud.android.db.ProviderMeta
|
|||
ShareEntity::class,
|
||||
SyncedFolderEntity::class,
|
||||
UploadEntity::class,
|
||||
VirtualEntity::class
|
||||
VirtualEntity::class,
|
||||
OfflineOperationEntity::class,
|
||||
RecommendedFileEntity::class
|
||||
],
|
||||
version = ProviderMeta.DB_VERSION,
|
||||
autoMigrations = [
|
||||
|
|
@ -59,40 +69,55 @@ import com.owncloud.android.db.ProviderMeta
|
|||
AutoMigration(from = 77, to = 78),
|
||||
AutoMigration(from = 78, to = 79, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||
AutoMigration(from = 79, to = 80),
|
||||
AutoMigration(from = 80, to = 81)
|
||||
AutoMigration(from = 80, to = 81),
|
||||
AutoMigration(from = 81, to = 82),
|
||||
AutoMigration(from = 82, to = 83),
|
||||
AutoMigration(from = 83, to = 84),
|
||||
AutoMigration(from = 84, to = 85, spec = DatabaseMigrationUtil.DeleteColumnSpec::class),
|
||||
AutoMigration(from = 85, to = 86, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||
AutoMigration(from = 86, to = 87, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||
AutoMigration(from = 87, to = 88, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
|
||||
// manual migration used for 88 to 89
|
||||
AutoMigration(from = 89, to = 90),
|
||||
AutoMigration(from = 90, to = 91),
|
||||
AutoMigration(from = 91, to = 92),
|
||||
AutoMigration(from = 92, to = 93, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class)
|
||||
],
|
||||
exportSchema = true
|
||||
)
|
||||
@Suppress("Detekt.UnnecessaryAbstractClass") // needed by Room
|
||||
@TypeConverters(OfflineOperationTypeConverter::class)
|
||||
abstract class NextcloudDatabase : RoomDatabase() {
|
||||
|
||||
abstract fun arbitraryDataDao(): ArbitraryDataDao
|
||||
abstract fun fileDao(): FileDao
|
||||
abstract fun offlineOperationDao(): OfflineOperationDao
|
||||
abstract fun uploadDao(): UploadDao
|
||||
abstract fun recommendedFileDao(): RecommendedFileDao
|
||||
|
||||
companion object {
|
||||
const val FIRST_ROOM_DB_VERSION = 65
|
||||
private var INSTANCE: NextcloudDatabase? = null
|
||||
private var instance: NextcloudDatabase? = null
|
||||
|
||||
@JvmStatic
|
||||
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||
@Deprecated("Here for legacy purposes, inject this class or use getInstance(context, clock) instead")
|
||||
fun getInstance(context: Context): NextcloudDatabase {
|
||||
return getInstance(context, ClockImpl())
|
||||
}
|
||||
fun getInstance(context: Context): NextcloudDatabase = getInstance(context, ClockImpl())
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(context: Context, clock: Clock): NextcloudDatabase {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Room
|
||||
if (instance == null) {
|
||||
instance = Room
|
||||
.databaseBuilder(context, NextcloudDatabase::class.java, ProviderMeta.DB_NAME)
|
||||
.allowMainThreadQueries()
|
||||
.addTypeConverter(OfflineOperationTypeConverter())
|
||||
.addLegacyMigrations(clock, context)
|
||||
.addMigrations(RoomMigration())
|
||||
.addMigrations(Migration67to68())
|
||||
.fallbackToDestructiveMigration()
|
||||
.addMigrations(MIGRATION_88_89)
|
||||
.build()
|
||||
}
|
||||
return INSTANCE!!
|
||||
return instance!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.dao
|
||||
|
||||
|
|
|
|||
|
|
@ -3,20 +3,38 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Dariusz Olszewski <starypatyk@users.noreply.github.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.nextcloud.client.database.entity.FileEntity
|
||||
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
|
||||
import com.owncloud.android.utils.MimeType
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Dao
|
||||
interface FileDao {
|
||||
@Query(
|
||||
"""
|
||||
SELECT DISTINCT parent
|
||||
FROM filelist
|
||||
WHERE path IN (:subfilePaths)
|
||||
"""
|
||||
)
|
||||
fun getParentIdsOfSubfiles(subfilePaths: List<String>): List<Long>
|
||||
|
||||
@Update
|
||||
fun update(entity: FileEntity)
|
||||
|
||||
@Query("SELECT * FROM filelist WHERE _id = :id LIMIT 1")
|
||||
fun getFileById(id: Long): FileEntity?
|
||||
|
||||
@Query("SELECT * FROM filelist WHERE local_id = :localId LIMIT 1")
|
||||
fun getFileByLocalId(localId: Long): FileEntity?
|
||||
|
||||
@Query("SELECT * FROM filelist WHERE path = :path AND file_owner = :fileOwner LIMIT 1")
|
||||
fun getFileByEncryptedRemotePath(path: String, fileOwner: String): FileEntity?
|
||||
|
||||
|
|
@ -49,4 +67,45 @@ interface FileDao {
|
|||
|
||||
@Query("SELECT * FROM filelist where file_owner = :fileOwner AND etag_in_conflict IS NOT NULL")
|
||||
fun getFilesWithSyncConflict(fileOwner: String): List<FileEntity>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM filelist where file_owner = :fileOwner AND internal_two_way_sync_timestamp >= 0 " +
|
||||
"ORDER BY internal_two_way_sync_timestamp DESC"
|
||||
)
|
||||
fun getInternalTwoWaySyncFolders(fileOwner: String): List<FileEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM filelist
|
||||
WHERE parent = :parentId
|
||||
AND file_owner = :accountName
|
||||
AND is_encrypted = 0
|
||||
AND (content_type = :dirType OR content_type = :webdavType)
|
||||
ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}
|
||||
"""
|
||||
)
|
||||
fun getNonEncryptedSubfolders(
|
||||
parentId: Long,
|
||||
accountName: String,
|
||||
dirType: String = MimeType.DIRECTORY,
|
||||
webdavType: String = MimeType.WEBDAV_FOLDER
|
||||
): List<FileEntity>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM filelist
|
||||
WHERE parent = :parentId
|
||||
AND file_owner = :accountName
|
||||
AND (content_type != :dirType AND content_type != :webdavType)
|
||||
ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER}
|
||||
"""
|
||||
)
|
||||
fun getSubfiles(
|
||||
parentId: Long,
|
||||
accountName: String,
|
||||
dirType: String = MimeType.DIRECTORY,
|
||||
webdavType: String = MimeType.WEBDAV_FOLDER
|
||||
): List<FileEntity>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import com.nextcloud.client.database.entity.OfflineOperationEntity
|
||||
|
||||
@Dao
|
||||
interface OfflineOperationDao {
|
||||
@Query("SELECT * FROM offline_operations")
|
||||
fun getAll(): List<OfflineOperationEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg entity: OfflineOperationEntity)
|
||||
|
||||
@Update
|
||||
fun update(entity: OfflineOperationEntity)
|
||||
|
||||
@Delete
|
||||
fun delete(entity: OfflineOperationEntity)
|
||||
|
||||
@Query("DELETE FROM offline_operations WHERE offline_operations_path = :path")
|
||||
fun deleteByPath(path: String)
|
||||
|
||||
@Query("SELECT * FROM offline_operations WHERE offline_operations_path = :path LIMIT 1")
|
||||
fun getByPath(path: String): OfflineOperationEntity?
|
||||
|
||||
@Query("SELECT * FROM offline_operations WHERE offline_operations_parent_oc_file_id = :parentOCFileId")
|
||||
fun getSubEntitiesByParentOCFileId(parentOCFileId: Long): List<OfflineOperationEntity>
|
||||
|
||||
@Query("DELETE FROM offline_operations")
|
||||
fun clearTable()
|
||||
|
||||
@Query("DELETE FROM offline_operations WHERE _id = :id")
|
||||
fun deleteById(id: Int)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import com.nextcloud.client.database.entity.RecommendedFileEntity
|
||||
import com.owncloud.android.db.ProviderMeta
|
||||
|
||||
@Dao
|
||||
interface RecommendedFileDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(recommendedFiles: List<RecommendedFileEntity>)
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM ${ProviderMeta.ProviderTableMeta.RECOMMENDED_FILE_TABLE_NAME} WHERE account_name = :accountName"
|
||||
)
|
||||
suspend fun getAll(accountName: String): List<RecommendedFileEntity>
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import com.nextcloud.client.database.entity.UploadEntity
|
||||
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
|
||||
|
||||
@Dao
|
||||
interface UploadDao {
|
||||
@Query(
|
||||
"SELECT _id FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME +
|
||||
" WHERE " + ProviderTableMeta.UPLOADS_STATUS + " = :status AND " +
|
||||
ProviderTableMeta.UPLOADS_ACCOUNT_NAME + " = :accountName AND _id IS NOT NULL"
|
||||
)
|
||||
fun getAllIds(status: Int, accountName: String): List<Int>
|
||||
|
||||
@Query(
|
||||
"SELECT * FROM " + ProviderTableMeta.UPLOADS_TABLE_NAME +
|
||||
" WHERE " + ProviderTableMeta._ID + " IN (:ids) AND " +
|
||||
ProviderTableMeta.UPLOADS_ACCOUNT_NAME + " = :accountName"
|
||||
)
|
||||
fun getUploadsByIds(ids: LongArray, accountName: String): List<UploadEntity>
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
@ -122,5 +122,25 @@ data class CapabilityEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT)
|
||||
val dropAccount: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SECURITY_GUARD)
|
||||
val securityGuard: Int?
|
||||
val securityGuard: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAME_CHARACTERS)
|
||||
val forbiddenFileNameCharacters: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FILENAMES)
|
||||
val forbiddenFileNames: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_EXTENSIONS)
|
||||
val forbiddenFileNameExtensions: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FORBIDDEN_FORBIDDEN_FILENAME_BASE_NAMES)
|
||||
val forbiddenFilenameBaseNames: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FILES_DOWNLOAD_LIMIT)
|
||||
val filesDownloadLimit: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_FILES_DOWNLOAD_LIMIT_DEFAULT)
|
||||
val filesDownloadLimitDefault: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_RECOMMENDATION)
|
||||
val recommendation: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_NOTES_FOLDER_PATH)
|
||||
val notesFolderPath: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_DEFAULT_PERMISSIONS)
|
||||
val defaultPermissions: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.CAPABILITIES_USER_STATUS_SUPPORTS_BUSY)
|
||||
val userStatusSupportsBusy: Int?
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ data class FileEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.FILE_ETAG_ON_SERVER)
|
||||
val etagOnServer: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_SHARED_VIA_LINK)
|
||||
val sharedViaLink: Int?,
|
||||
var sharedViaLink: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_PERMISSIONS)
|
||||
val permissions: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_REMOTE_ID)
|
||||
|
|
@ -73,7 +73,7 @@ data class FileEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.FILE_ETAG_IN_CONFLICT)
|
||||
val etagInConflict: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_SHARED_WITH_SHAREE)
|
||||
val sharedWithSharee: Int?,
|
||||
var sharedWithSharee: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_MOUNT_TYPE)
|
||||
val mountType: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_HAS_PREVIEW)
|
||||
|
|
@ -115,5 +115,11 @@ data class FileEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.FILE_METADATA_GPS)
|
||||
val metadataGPS: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_E2E_COUNTER)
|
||||
val e2eCounter: Long?
|
||||
val e2eCounter: Long?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_TIMESTAMP)
|
||||
val internalTwoWaySync: Long?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_INTERNAL_TWO_WAY_SYNC_RESULT)
|
||||
val internalTwoWaySyncResult: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.FILE_UPLOADED)
|
||||
val uploaded: Long?
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.nextcloud.model.OfflineOperationType
|
||||
import com.owncloud.android.R
|
||||
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
|
||||
|
||||
@Entity(tableName = ProviderTableMeta.OFFLINE_OPERATION_TABLE_NAME)
|
||||
data class OfflineOperationEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ProviderTableMeta._ID)
|
||||
val id: Int? = null,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.OFFLINE_OPERATION_PARENT_OC_FILE_ID)
|
||||
var parentOCFileId: Long? = null,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.OFFLINE_OPERATION_PATH)
|
||||
var path: String? = null,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.OFFLINE_OPERATION_TYPE)
|
||||
var type: OfflineOperationType? = null,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.OFFLINE_OPERATION_FILE_NAME)
|
||||
var filename: String? = null,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.OFFLINE_OPERATION_CREATED_AT)
|
||||
var createdAt: Long? = null,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.OFFLINE_OPERATION_MODIFIED_AT)
|
||||
var modifiedAt: Long? = null
|
||||
) {
|
||||
fun isRenameOrRemove(): Boolean =
|
||||
(type is OfflineOperationType.RenameFile || type is OfflineOperationType.RemoveFile)
|
||||
|
||||
fun isCreate(): Boolean = (type is OfflineOperationType.CreateFile || type is OfflineOperationType.CreateFolder)
|
||||
|
||||
fun getConflictText(context: Context): String {
|
||||
val resId = when (type) {
|
||||
is OfflineOperationType.RemoveFile -> {
|
||||
R.string.offline_operations_worker_notification_remove_conflict_text
|
||||
}
|
||||
|
||||
is OfflineOperationType.RenameFile -> {
|
||||
R.string.offline_operations_worker_notification_rename_conflict_text
|
||||
}
|
||||
|
||||
is OfflineOperationType.CreateFile -> {
|
||||
R.string.offline_operations_worker_notification_create_file_conflict_text
|
||||
}
|
||||
|
||||
else -> {
|
||||
R.string.offline_operations_worker_notification_create_folder_conflict_text
|
||||
}
|
||||
}
|
||||
|
||||
return context.getString(resId, filename)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.nextcloud.android.lib.resources.recommendations.Recommendation
|
||||
import com.owncloud.android.datamodel.FileDataStorageManager
|
||||
import com.owncloud.android.datamodel.OCFile
|
||||
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
|
||||
|
||||
@Entity(tableName = ProviderTableMeta.RECOMMENDED_FILE_TABLE_NAME)
|
||||
data class RecommendedFileEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ProviderTableMeta._ID)
|
||||
val id: Long,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_NAME)
|
||||
val name: String,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_DIRECTORY)
|
||||
val directory: String,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_EXTENSIONS)
|
||||
val extension: String,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_MIME_TYPE)
|
||||
val mimeType: String,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_HAS_PREVIEW)
|
||||
val hasPreview: Boolean,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_REASON)
|
||||
val reason: String,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_TIMESTAMP)
|
||||
val timestamp: Long,
|
||||
|
||||
@ColumnInfo(name = ProviderTableMeta.RECOMMENDED_FILE_ACCOUNT_NAME)
|
||||
val accountName: String?
|
||||
)
|
||||
|
||||
fun ArrayList<Recommendation>.toEntity(accountName: String): List<RecommendedFileEntity> = this.map { recommendation ->
|
||||
RecommendedFileEntity(
|
||||
id = recommendation.id,
|
||||
name = recommendation.name,
|
||||
directory = recommendation.directory,
|
||||
extension = recommendation.extension,
|
||||
mimeType = recommendation.mimeType,
|
||||
hasPreview = recommendation.hasPreview,
|
||||
reason = recommendation.reason,
|
||||
timestamp = recommendation.timestamp,
|
||||
accountName = accountName
|
||||
)
|
||||
}
|
||||
|
||||
fun List<RecommendedFileEntity>.toOCFile(storageManager: FileDataStorageManager): ArrayList<OCFile> =
|
||||
mapNotNull { entity ->
|
||||
entity.id.let {
|
||||
storageManager.getFileByLocalId(it).apply {
|
||||
this?.reason = entity.reason
|
||||
this?.setIsRecommendedFile(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toCollection(ArrayList())
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
@ -54,5 +54,11 @@ data class ShareEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.OCSHARES_SHARE_LINK)
|
||||
val shareLink: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.OCSHARES_SHARE_LABEL)
|
||||
val shareLabel: String?
|
||||
val shareLabel: String?,
|
||||
@ColumnInfo(name = ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_LIMIT)
|
||||
val downloadLimitLimit: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.OCSHARES_DOWNLOADLIMIT_COUNT)
|
||||
val downloadLimitCount: Int?,
|
||||
@ColumnInfo(name = ProviderTableMeta.OCSHARES_ATTRIBUTES)
|
||||
val attributes: String?
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,20 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.nextcloud.utils.autoRename.AutoRename
|
||||
import com.owncloud.android.datamodel.UploadsStorageManager
|
||||
import com.owncloud.android.db.OCUpload
|
||||
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
|
||||
import com.owncloud.android.db.UploadResult
|
||||
import com.owncloud.android.files.services.NameCollisionPolicy
|
||||
import com.owncloud.android.lib.resources.status.OCCapability
|
||||
|
||||
@Entity(tableName = ProviderTableMeta.UPLOADS_TABLE_NAME)
|
||||
data class UploadEntity(
|
||||
|
|
@ -48,3 +54,27 @@ data class UploadEntity(
|
|||
@ColumnInfo(name = ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN)
|
||||
val folderUnlockToken: String?
|
||||
)
|
||||
|
||||
fun UploadEntity.toOCUpload(capability: OCCapability? = null): OCUpload {
|
||||
val localPath = localPath
|
||||
var remotePath = remotePath
|
||||
if (capability != null && remotePath != null) {
|
||||
remotePath = AutoRename.rename(remotePath, capability)
|
||||
}
|
||||
val upload = OCUpload(localPath, remotePath, accountName)
|
||||
|
||||
fileSize?.let { upload.fileSize = it }
|
||||
id?.let { upload.uploadId = it.toLong() }
|
||||
status?.let { upload.uploadStatus = UploadsStorageManager.UploadStatus.fromValue(it) }
|
||||
localBehaviour?.let { upload.localAction = it }
|
||||
nameCollisionPolicy?.let { upload.nameCollisionPolicy = NameCollisionPolicy.deserialize(it) }
|
||||
isCreateRemoteFolder?.let { upload.isCreateRemoteFolder = it == 1 }
|
||||
uploadEndTimestamp?.let { upload.uploadEndTimestamp = it.toLong() }
|
||||
lastResult?.let { upload.lastResult = UploadResult.fromValue(it) }
|
||||
createdBy?.let { upload.createdBy = it }
|
||||
isWifiOnly?.let { upload.isUseWifiOnly = it == 1 }
|
||||
isWhileChargingOnly?.let { upload.isWhileChargingOnly = it == 1 }
|
||||
folderUnlockToken?.let { upload.folderUnlockToken = it }
|
||||
|
||||
return upload
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.entity
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.migrations
|
||||
|
||||
import androidx.room.DeleteColumn
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.nextcloud.client.database.migrations.model.SQLiteColumnType
|
||||
|
||||
object DatabaseMigrationUtil {
|
||||
|
||||
|
|
@ -17,6 +19,32 @@ object DatabaseMigrationUtil {
|
|||
const val TYPE_INTEGER_PRIMARY_KEY = "INTEGER PRIMARY KEY"
|
||||
const val KEYWORD_NOT_NULL = "NOT NULL"
|
||||
|
||||
fun addColumnIfNotExists(
|
||||
db: SupportSQLiteDatabase,
|
||||
tableName: String,
|
||||
columnName: String,
|
||||
columnType: SQLiteColumnType
|
||||
) {
|
||||
val cursor = db.query("PRAGMA table_info($tableName)")
|
||||
var columnExists = false
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val nameIndex = cursor.getColumnIndex("name")
|
||||
if (nameIndex != -1) {
|
||||
val existingColumnName = cursor.getString(nameIndex)
|
||||
if (existingColumnName == columnName) {
|
||||
columnExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor.close()
|
||||
|
||||
if (!columnExists) {
|
||||
db.execSQL("ALTER TABLE $tableName ADD COLUMN `$columnName` ${columnType.value}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to add or remove columns from a table
|
||||
*
|
||||
|
|
@ -46,11 +74,7 @@ object DatabaseMigrationUtil {
|
|||
/**
|
||||
* Utility method to create a new table with the given columns
|
||||
*/
|
||||
private fun createNewTable(
|
||||
database: SupportSQLiteDatabase,
|
||||
newTableName: String,
|
||||
columns: Map<String, String>
|
||||
) {
|
||||
private fun createNewTable(database: SupportSQLiteDatabase, newTableName: String, columns: Map<String, String>) {
|
||||
val columnsString = columns.entries.joinToString(",") { "${it.key} ${it.value}" }
|
||||
database.execSQL("CREATE TABLE $newTableName ($columnsString)")
|
||||
}
|
||||
|
|
@ -80,11 +104,7 @@ object DatabaseMigrationUtil {
|
|||
/**
|
||||
* Utility method to replace an old table with a new one, essentially deleting the old one and renaming the new one
|
||||
*/
|
||||
private fun replaceTable(
|
||||
database: SupportSQLiteDatabase,
|
||||
tableName: String,
|
||||
newTableTempName: String
|
||||
) {
|
||||
private fun replaceTable(database: SupportSQLiteDatabase, tableName: String, newTableTempName: String) {
|
||||
database.execSQL("DROP TABLE $tableName")
|
||||
database.execSQL("ALTER TABLE $newTableTempName RENAME TO $tableName")
|
||||
}
|
||||
|
|
@ -98,4 +118,12 @@ object DatabaseMigrationUtil {
|
|||
super.onPostMigrate(db)
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteColumn.Entries(
|
||||
DeleteColumn(
|
||||
tableName = "offline_operations",
|
||||
columnName = "offline_operations_parent_path"
|
||||
)
|
||||
)
|
||||
class DeleteColumnSpec : AutoMigrationSpec
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.migrations;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.nextcloud.client.database.migrations.model.SQLiteColumnType
|
||||
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
val MIGRATION_88_89 = object : Migration(88, 89) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
DatabaseMigrationUtil.addColumnIfNotExists(
|
||||
database,
|
||||
ProviderTableMeta.FILE_TABLE_NAME,
|
||||
ProviderTableMeta.FILE_UPLOADED,
|
||||
SQLiteColumnType.INTEGER_DEFAULT_NULL
|
||||
)
|
||||
DatabaseMigrationUtil.addColumnIfNotExists(
|
||||
database,
|
||||
ProviderTableMeta.CAPABILITIES_TABLE_NAME,
|
||||
ProviderTableMeta.CAPABILITIES_NOTES_FOLDER_PATH,
|
||||
SQLiteColumnType.TEXT_DEFAULT_NULL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.database.migrations
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.migrations.model
|
||||
|
||||
enum class SQLiteColumnType(val value: String) {
|
||||
INTEGER_DEFAULT_NULL("INTEGER DEFAULT NULL"),
|
||||
TEXT_DEFAULT_NULL("TEXT DEFAULT NULL")
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.typeAdapter
|
||||
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonDeserializer
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSerializationContext
|
||||
import com.google.gson.JsonSerializer
|
||||
import com.nextcloud.model.OfflineOperationRawType
|
||||
import com.nextcloud.model.OfflineOperationType
|
||||
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class OfflineOperationTypeAdapter :
|
||||
JsonSerializer<OfflineOperationType>,
|
||||
JsonDeserializer<OfflineOperationType> {
|
||||
|
||||
override fun serialize(
|
||||
src: OfflineOperationType?,
|
||||
typeOfSrc: Type?,
|
||||
context: JsonSerializationContext?
|
||||
): JsonElement {
|
||||
val jsonObject = JsonObject()
|
||||
jsonObject.addProperty("type", src?.javaClass?.simpleName)
|
||||
when (src) {
|
||||
is OfflineOperationType.CreateFolder -> {
|
||||
jsonObject.addProperty("type", src.type)
|
||||
jsonObject.addProperty("path", src.path)
|
||||
}
|
||||
|
||||
is OfflineOperationType.CreateFile -> {
|
||||
jsonObject.addProperty("type", src.type)
|
||||
jsonObject.addProperty("localPath", src.localPath)
|
||||
jsonObject.addProperty("remotePath", src.remotePath)
|
||||
jsonObject.addProperty("mimeType", src.mimeType)
|
||||
}
|
||||
|
||||
is OfflineOperationType.RenameFile -> {
|
||||
jsonObject.addProperty("type", src.type)
|
||||
jsonObject.addProperty("ocFileId", src.ocFileId)
|
||||
jsonObject.addProperty("newName", src.newName)
|
||||
}
|
||||
|
||||
is OfflineOperationType.RemoveFile -> {
|
||||
jsonObject.addProperty("type", src.type)
|
||||
jsonObject.addProperty("path", src.path)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
return jsonObject
|
||||
}
|
||||
|
||||
override fun deserialize(
|
||||
json: JsonElement?,
|
||||
typeOfT: Type?,
|
||||
context: JsonDeserializationContext?
|
||||
): OfflineOperationType? {
|
||||
val jsonObject = json?.asJsonObject ?: return null
|
||||
val type = jsonObject.get("type")?.asString
|
||||
return when (type) {
|
||||
OfflineOperationRawType.CreateFolder.name -> OfflineOperationType.CreateFolder(
|
||||
jsonObject.get("type").asString,
|
||||
jsonObject.get("path").asString
|
||||
)
|
||||
|
||||
OfflineOperationRawType.CreateFile.name -> OfflineOperationType.CreateFile(
|
||||
jsonObject.get("type").asString,
|
||||
jsonObject.get("localPath").asString,
|
||||
jsonObject.get("remotePath").asString,
|
||||
jsonObject.get("mimeType").asString
|
||||
)
|
||||
|
||||
OfflineOperationRawType.RenameFile.name -> OfflineOperationType.RenameFile(
|
||||
jsonObject.get("type").asString,
|
||||
jsonObject.get("ocFileId").asLong,
|
||||
jsonObject.get("newName").asString
|
||||
)
|
||||
|
||||
OfflineOperationRawType.RemoveFile.name -> OfflineOperationType.RemoveFile(
|
||||
jsonObject.get("type").asString,
|
||||
jsonObject.get("path").asString
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.database.typeConverter
|
||||
|
||||
import androidx.room.ProvidedTypeConverter
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.Gson
|
||||
import com.nextcloud.model.OfflineOperationType
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.nextcloud.client.database.typeAdapter.OfflineOperationTypeAdapter
|
||||
|
||||
@ProvidedTypeConverter
|
||||
class OfflineOperationTypeConverter {
|
||||
|
||||
private val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(OfflineOperationType::class.java, OfflineOperationTypeAdapter())
|
||||
.create()
|
||||
|
||||
@TypeConverter
|
||||
fun fromOfflineOperationType(type: OfflineOperationType?): String? = gson.toJson(type)
|
||||
|
||||
@TypeConverter
|
||||
fun toOfflineOperationType(type: String?): OfflineOperationType? =
|
||||
gson.fromJson(type, OfflineOperationType::class.java)
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.device
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.device
|
||||
|
||||
|
|
@ -12,11 +12,10 @@ import android.os.Build
|
|||
import java.util.Locale
|
||||
|
||||
class DeviceInfo {
|
||||
val vendor: String = Build.MANUFACTURER.toLowerCase(Locale.ROOT)
|
||||
val vendor: String = Build.MANUFACTURER.lowercase(Locale.ROOT)
|
||||
val apiLevel: Int = Build.VERSION.SDK_INT
|
||||
val androidVersion = Build.VERSION.RELEASE
|
||||
|
||||
fun hasCamera(context: Context): Boolean {
|
||||
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
||||
}
|
||||
fun hasCamera(context: Context): Boolean =
|
||||
context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky
|
||||
* SPDX-FileCopyrightText: 2019 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.device
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.device
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.device
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.di;
|
||||
|
|
@ -16,7 +16,10 @@ import com.nextcloud.client.device.DeviceModule;
|
|||
import com.nextcloud.client.integrations.IntegrationsModule;
|
||||
import com.nextcloud.client.jobs.JobsModule;
|
||||
import com.nextcloud.client.jobs.download.FileDownloadHelper;
|
||||
import com.nextcloud.client.jobs.offlineOperations.receiver.OfflineOperationReceiver;
|
||||
import com.nextcloud.client.jobs.upload.FileUploadBroadcastReceiver;
|
||||
import com.nextcloud.client.jobs.upload.FileUploadHelper;
|
||||
import com.nextcloud.client.media.BackgroundPlayerService;
|
||||
import com.nextcloud.client.network.NetworkModule;
|
||||
import com.nextcloud.client.onboarding.OnboardingModule;
|
||||
import com.nextcloud.client.preferences.PreferencesModule;
|
||||
|
|
@ -27,6 +30,8 @@ import com.owncloud.android.ui.whatsnew.ProgressIndicator;
|
|||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Component;
|
||||
import dagger.android.support.AndroidSupportInjectionModule;
|
||||
|
|
@ -46,7 +51,7 @@ import dagger.android.support.AndroidSupportInjectionModule;
|
|||
ThemeModule.class,
|
||||
DatabaseModule.class,
|
||||
DispatcherModule.class,
|
||||
VariantModule.class
|
||||
VariantModule.class,
|
||||
})
|
||||
@Singleton
|
||||
public interface AppComponent {
|
||||
|
|
@ -55,6 +60,9 @@ public interface AppComponent {
|
|||
|
||||
void inject(MediaControlView mediaControlView);
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
void inject(BackgroundPlayerService backgroundPlayerService);
|
||||
|
||||
void inject(ThemeableSwitchPreference switchPreference);
|
||||
|
||||
void inject(FileUploadHelper fileUploadHelper);
|
||||
|
|
@ -63,6 +71,10 @@ public interface AppComponent {
|
|||
|
||||
void inject(ProgressIndicator progressIndicator);
|
||||
|
||||
void inject(FileUploadBroadcastReceiver fileUploadBroadcastReceiver);
|
||||
|
||||
void inject(OfflineOperationReceiver offlineOperationReceiver);
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.di;
|
||||
|
|
@ -28,6 +28,7 @@ import com.nextcloud.client.core.ClockImpl;
|
|||
import com.nextcloud.client.core.ThreadPoolAsyncRunner;
|
||||
import com.nextcloud.client.database.dao.ArbitraryDataDao;
|
||||
import com.nextcloud.client.device.DeviceInfo;
|
||||
import com.nextcloud.client.jobs.operation.FileOperationHelper;
|
||||
import com.nextcloud.client.logger.FileLogHandler;
|
||||
import com.nextcloud.client.logger.Logger;
|
||||
import com.nextcloud.client.logger.LoggerImpl;
|
||||
|
|
@ -55,6 +56,7 @@ import com.owncloud.android.ui.activities.data.activities.RemoteActivitiesReposi
|
|||
import com.owncloud.android.ui.activities.data.files.FilesRepository;
|
||||
import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
|
||||
import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository;
|
||||
import com.owncloud.android.ui.dialog.setupEncryption.CertificateValidator;
|
||||
import com.owncloud.android.utils.theme.ViewThemeUtils;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
|
@ -249,10 +251,21 @@ class AppModule {
|
|||
return new PassCodeManager(preferences, clock);
|
||||
}
|
||||
|
||||
@Provides
|
||||
FileOperationHelper fileOperationHelper(CurrentAccountProvider currentAccountProvider, Context context) {
|
||||
return new FileOperationHelper(currentAccountProvider.getUser(), context, fileDataStorageManager(currentAccountProvider, context));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
UsersAndGroupsSearchConfig userAndGroupSearchConfig() {
|
||||
return new UsersAndGroupsSearchConfig();
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CertificateValidator certificateValidator() {
|
||||
return new CertificateValidator();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di;
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ import com.nextcloud.client.jobs.transfer.FileTransferService;
|
|||
import com.nextcloud.client.jobs.upload.FileUploadHelper;
|
||||
import com.nextcloud.client.logger.ui.LogsActivity;
|
||||
import com.nextcloud.client.logger.ui.LogsViewModel;
|
||||
import com.nextcloud.client.media.BackgroundPlayerService;
|
||||
import com.nextcloud.client.media.PlayerService;
|
||||
import com.nextcloud.client.migrations.Migrations;
|
||||
import com.nextcloud.client.onboarding.FirstRunActivity;
|
||||
|
|
@ -24,11 +26,15 @@ import com.nextcloud.client.onboarding.WhatsNewActivity;
|
|||
import com.nextcloud.client.widget.DashboardWidgetConfigurationActivity;
|
||||
import com.nextcloud.client.widget.DashboardWidgetProvider;
|
||||
import com.nextcloud.client.widget.DashboardWidgetService;
|
||||
import com.nextcloud.receiver.NetworkChangeReceiver;
|
||||
import com.nextcloud.ui.ChooseAccountDialogFragment;
|
||||
import com.nextcloud.ui.ChooseStorageLocationDialogFragment;
|
||||
import com.nextcloud.ui.ImageDetailFragment;
|
||||
import com.nextcloud.ui.SetStatusDialogFragment;
|
||||
import com.nextcloud.ui.SetOnlineStatusBottomSheet;
|
||||
import com.nextcloud.ui.SetStatusMessageBottomSheet;
|
||||
import com.nextcloud.ui.composeActivity.ComposeActivity;
|
||||
import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
|
||||
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsBottomSheet;
|
||||
import com.nmc.android.ui.LauncherActivity;
|
||||
import com.owncloud.android.MainApp;
|
||||
import com.owncloud.android.authentication.AuthenticatorActivity;
|
||||
|
|
@ -54,6 +60,7 @@ import com.owncloud.android.ui.activity.FileActivity;
|
|||
import com.owncloud.android.ui.activity.FileDisplayActivity;
|
||||
import com.owncloud.android.ui.activity.FilePickerActivity;
|
||||
import com.owncloud.android.ui.activity.FolderPickerActivity;
|
||||
import com.owncloud.android.ui.activity.InternalTwoWaySyncActivity;
|
||||
import com.owncloud.android.ui.activity.ManageAccountsActivity;
|
||||
import com.owncloud.android.ui.activity.ManageSpaceActivity;
|
||||
import com.owncloud.android.ui.activity.NotificationsActivity;
|
||||
|
|
@ -83,16 +90,16 @@ import com.owncloud.android.ui.dialog.LocalStoragePathPickerDialogFragment;
|
|||
import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
|
||||
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.RenamePublicShareDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.SendFilesDialog;
|
||||
import com.owncloud.android.ui.dialog.SendShareDialog;
|
||||
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
|
||||
import com.owncloud.android.ui.dialog.StoragePermissionDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
|
||||
import com.owncloud.android.ui.dialog.TermsOfServiceDialog;
|
||||
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
|
||||
import com.owncloud.android.ui.fragment.ExtendedListFragment;
|
||||
import com.owncloud.android.ui.fragment.FeatureFragment;
|
||||
import com.owncloud.android.ui.fragment.FileDetailActivitiesFragment;
|
||||
|
|
@ -121,6 +128,8 @@ import com.owncloud.android.ui.preview.PreviewTextStringFragment;
|
|||
import com.owncloud.android.ui.preview.pdf.PreviewPdfFragment;
|
||||
import com.owncloud.android.ui.trashbin.TrashbinActivity;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import dagger.Module;
|
||||
import dagger.android.ContributesAndroidInjector;
|
||||
|
||||
|
|
@ -219,6 +228,9 @@ abstract class ComponentsModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract TrashbinActivity trashbinActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract TrashbinFileActionsBottomSheet trashbinFileActionsBottomSheet();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract UploadFilesActivity uploadFilesActivity();
|
||||
|
||||
|
|
@ -289,7 +301,7 @@ abstract class ComponentsModule {
|
|||
abstract ChooseAccountDialogFragment chooseAccountDialogFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SetStatusDialogFragment setStatusDialogFragment();
|
||||
abstract SetOnlineStatusBottomSheet setOnlineStatusBottomSheet();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract PreviewTextFileFragment previewTextFileFragment();
|
||||
|
|
@ -312,6 +324,9 @@ abstract class ComponentsModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract BootupBroadcastReceiver bootupBroadcastReceiver();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NetworkChangeReceiver networkChangeReceiver();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NotificationWork.NotificationReceiver notificationWorkBroadcastReceiver();
|
||||
|
||||
|
|
@ -399,15 +414,15 @@ abstract class ComponentsModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract RemoveFilesDialogFragment removeFilesDialogFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract RenamePublicShareDialogFragment renamePublicShareDialogFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SendShareDialog sendShareDialog();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SetupEncryptionDialogFragment setupEncryptionDialogFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ChooseStorageLocationDialogFragment chooseStorageLocationDialogFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SharePasswordDialogFragment sharePasswordDialogFragment();
|
||||
|
||||
|
|
@ -476,4 +491,18 @@ abstract class ComponentsModule {
|
|||
|
||||
@ContributesAndroidInjector
|
||||
abstract TestJob testJob();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract InternalTwoWaySyncActivity internalTwoWaySyncActivity();
|
||||
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@ContributesAndroidInjector
|
||||
abstract BackgroundPlayerService backgroundPlayerService();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract TermsOfServiceDialog termsOfServiceDialog();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SetStatusMessageBottomSheet setStatusMessageBottomSheet();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
@ -12,11 +12,7 @@ import androidx.fragment.app.FragmentManager
|
|||
import dagger.android.support.AndroidSupportInjection
|
||||
|
||||
internal class FragmentInjector : FragmentManager.FragmentLifecycleCallbacks() {
|
||||
override fun onFragmentPreAttached(
|
||||
fragmentManager: FragmentManager,
|
||||
fragment: Fragment,
|
||||
context: Context
|
||||
) {
|
||||
override fun onFragmentPreAttached(fragmentManager: FragmentManager, fragment: Fragment, context: Context) {
|
||||
super.onFragmentPreAttached(fragmentManager, fragment, context)
|
||||
if (fragment is Injectable) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.di;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
package com.nextcloud.client.di;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
@ -27,19 +27,14 @@ internal abstract class ThemeModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun themeColorUtils(): ThemeColorUtils {
|
||||
return ThemeColorUtils()
|
||||
}
|
||||
fun themeColorUtils(): ThemeColorUtils = ThemeColorUtils()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun themeUtils(): ThemeUtils {
|
||||
return ThemeUtils()
|
||||
}
|
||||
fun themeUtils(): ThemeUtils = ThemeUtils()
|
||||
|
||||
@Provides
|
||||
fun provideMaterialSchemes(materialSchemesProvider: MaterialSchemesProvider): MaterialSchemes {
|
||||
return materialSchemesProvider.getMaterialSchemesForCurrentUser()
|
||||
}
|
||||
fun provideMaterialSchemes(materialSchemesProvider: MaterialSchemesProvider): MaterialSchemes =
|
||||
materialSchemesProvider.getMaterialSchemesForCurrentUser()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 TSI-mc <surinder.kumar@t-systems.com>
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.di
|
||||
|
||||
|
|
@ -13,6 +14,7 @@ import com.nextcloud.client.etm.EtmViewModel
|
|||
import com.nextcloud.client.logger.ui.LogsViewModel
|
||||
import com.nextcloud.ui.fileactions.FileActionsViewModel
|
||||
import com.owncloud.android.ui.preview.pdf.PreviewPdfViewModel
|
||||
import com.nextcloud.ui.trashbinFileActions.TrashbinFileActionsViewModel
|
||||
import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
|
@ -50,6 +52,11 @@ abstract class ViewModelModule {
|
|||
@ViewModelKey(DocumentScanViewModel::class)
|
||||
abstract fun documentScanViewModel(vm: DocumentScanViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(TrashbinFileActionsViewModel::class)
|
||||
abstract fun trashbinFileActionsViewModel(vm: TrashbinFileActionsViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Nextcloud - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.documentscan
|
||||
|
||||
|
|
@ -21,9 +21,8 @@ abstract class AppScanOptionalFeature {
|
|||
*/
|
||||
@Suppress("unused") // used only in some variants
|
||||
object Stub : AppScanOptionalFeature() {
|
||||
override fun getScanContract(): ActivityResultContract<Unit, String?> {
|
||||
override fun getScanContract(): ActivityResultContract<Unit, String?> =
|
||||
throw UnsupportedOperationException("Document scan is not available")
|
||||
}
|
||||
|
||||
override val isAvailable = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.documentscan
|
||||
|
||||
|
|
@ -28,9 +28,7 @@ class DocumentPageListAdapter :
|
|||
holder.bind(currentList[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return currentList.size
|
||||
}
|
||||
override fun getItemCount(): Int = currentList.size
|
||||
|
||||
class DocumentPageViewHolder(val binding: DocumentPageItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(imagePath: String) {
|
||||
|
|
@ -39,10 +37,8 @@ class DocumentPageListAdapter :
|
|||
}
|
||||
|
||||
private class DiffItemCallback : DiffUtil.ItemCallback<String>() {
|
||||
override fun areItemsTheSame(oldItem: String, newItem: String) =
|
||||
oldItem == newItem
|
||||
override fun areItemsTheSame(oldItem: String, newItem: String) = oldItem == newItem
|
||||
|
||||
override fun areContentsTheSame(oldItem: String, newItem: String) =
|
||||
oldItem == newItem
|
||||
override fun areContentsTheSame(oldItem: String, newItem: String) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.documentscan
|
||||
|
||||
|
|
@ -27,7 +27,9 @@ import com.owncloud.android.ui.activity.ToolbarActivity
|
|||
import com.owncloud.android.utils.theme.ViewThemeUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
class DocumentScanActivity : ToolbarActivity(), Injectable {
|
||||
class DocumentScanActivity :
|
||||
ToolbarActivity(),
|
||||
Injectable {
|
||||
|
||||
@Inject
|
||||
lateinit var vmFactory: ViewModelFactory
|
||||
|
|
@ -96,18 +98,16 @@ class DocumentScanActivity : ToolbarActivity(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
return when (menuItem.itemId) {
|
||||
R.id.action_save -> {
|
||||
viewModel.onClickDone()
|
||||
true
|
||||
}
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_save -> {
|
||||
viewModel.onClickDone()
|
||||
true
|
||||
}
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.documentscan
|
||||
|
||||
|
|
@ -46,15 +46,11 @@ class DocumentScanViewModel @Inject constructor(
|
|||
get() = pageList.isEmpty()
|
||||
}
|
||||
|
||||
class NormalState(
|
||||
pageList: List<String> = emptyList(),
|
||||
val shouldRequestScan: Boolean = false
|
||||
) : BaseState(pageList)
|
||||
class NormalState(pageList: List<String> = emptyList(), val shouldRequestScan: Boolean = false) :
|
||||
BaseState(pageList)
|
||||
|
||||
class RequestExportState(
|
||||
pageList: List<String> = emptyList(),
|
||||
val shouldRequestExportType: Boolean = true
|
||||
) : BaseState(pageList)
|
||||
class RequestExportState(pageList: List<String> = emptyList(), val shouldRequestExportType: Boolean = true) :
|
||||
BaseState(pageList)
|
||||
|
||||
object DoneState : UIState
|
||||
object CanceledState : UIState
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.documentscan
|
||||
|
||||
|
|
@ -22,40 +22,30 @@ class GeneratePDFUseCase @Inject constructor(private val logger: Logger) {
|
|||
* @param imagePaths list of image paths
|
||||
* @return `true` if the PDF was generated successfully, `false` otherwise
|
||||
*/
|
||||
fun execute(imagePaths: List<String>, filePath: String): Boolean {
|
||||
return if (imagePaths.isEmpty() || filePath.isBlank()) {
|
||||
logger.w(TAG, "Invalid parameters: imagePaths: $imagePaths, filePath: $filePath")
|
||||
false
|
||||
} else {
|
||||
val document = PdfDocument()
|
||||
fillDocumentPages(document, imagePaths)
|
||||
writePdfToFile(filePath, document)
|
||||
}
|
||||
fun execute(imagePaths: List<String>, filePath: String): Boolean = if (imagePaths.isEmpty() || filePath.isBlank()) {
|
||||
logger.w(TAG, "Invalid parameters: imagePaths: $imagePaths, filePath: $filePath")
|
||||
false
|
||||
} else {
|
||||
val document = PdfDocument()
|
||||
fillDocumentPages(document, imagePaths)
|
||||
writePdfToFile(filePath, document)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if the PDF was generated successfully, `false` otherwise
|
||||
*/
|
||||
private fun writePdfToFile(
|
||||
filePath: String,
|
||||
document: PdfDocument
|
||||
): Boolean {
|
||||
return try {
|
||||
val fileOutputStream = FileOutputStream(filePath)
|
||||
document.writeTo(fileOutputStream)
|
||||
fileOutputStream.close()
|
||||
document.close()
|
||||
true
|
||||
} catch (ex: IOException) {
|
||||
logger.e(TAG, "Error generating PDF", ex)
|
||||
false
|
||||
}
|
||||
private fun writePdfToFile(filePath: String, document: PdfDocument): Boolean = try {
|
||||
val fileOutputStream = FileOutputStream(filePath)
|
||||
document.writeTo(fileOutputStream)
|
||||
fileOutputStream.close()
|
||||
document.close()
|
||||
true
|
||||
} catch (ex: IOException) {
|
||||
logger.e(TAG, "Error generating PDF", ex)
|
||||
false
|
||||
}
|
||||
|
||||
private fun fillDocumentPages(
|
||||
document: PdfDocument,
|
||||
imagePaths: List<String>
|
||||
) {
|
||||
private fun fillDocumentPages(document: PdfDocument, imagePaths: List<String>) {
|
||||
imagePaths.forEach { path ->
|
||||
val bitmap = BitmapFactory.decodeFile(path)
|
||||
val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, 1).create()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2022 Tobias Kaminsky <tobias@kaminsky.me>
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.documentscan
|
||||
|
||||
|
|
@ -112,7 +112,8 @@ class GeneratePdfFromImagesWork(
|
|||
user,
|
||||
arrayOf(pdfPath),
|
||||
arrayOf(uploadPath),
|
||||
FileUploadWorker.LOCAL_BEHAVIOUR_DELETE, // MIME type will be detected from file name
|
||||
// MIME type will be detected from file name
|
||||
FileUploadWorker.LOCAL_BEHAVIOUR_DELETE,
|
||||
true,
|
||||
UploadFileOperation.CREATED_BY_USER,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* SPDX-FileCopyrightText: 2023 ZetaTom
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
|
||||
*/
|
||||
package com.nextcloud.client.editimage
|
||||
|
||||
|
|
@ -15,7 +15,6 @@ import android.view.Menu
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
|
@ -59,9 +58,7 @@ class EditImageActivity :
|
|||
MimeType.HEIC
|
||||
)
|
||||
|
||||
fun canBePreviewed(file: OCFile): Boolean {
|
||||
return file.mimeType in supportedMimeTypes
|
||||
}
|
||||
fun canBePreviewed(file: OCFile): Boolean = file.mimeType in supportedMimeTypes
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -82,7 +79,6 @@ class EditImageActivity :
|
|||
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
|
||||
windowInsetsController.hide(WindowInsetsCompat.Type.statusBars())
|
||||
|
||||
window.statusBarColor = ContextCompat.getColor(this, R.color.black)
|
||||
window.navigationBarColor = getColor(R.color.black)
|
||||
|
||||
setupCropper()
|
||||
|
|
@ -129,9 +125,7 @@ class EditImageActivity :
|
|||
}
|
||||
menu?.findItem(R.id.custom_menu_placeholder_item)?.apply {
|
||||
icon = saveIcon
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
contentDescription = getString(R.string.common_save)
|
||||
}
|
||||
contentDescription = getString(R.string.common_save)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue