diff --git a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt index 91367c729408..ab22b2c314fd 100644 --- a/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt +++ b/app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt @@ -138,30 +138,34 @@ class AvatarGroupLayout @JvmOverloads constructor( avatar: ImageView, viewThemeUtils: ViewThemeUtils ) { - // maybe federated share - val split = user.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val userId: String? = split[0] - val server = split[1] - - val url = "https://" + server + "/index.php/avatar/" + userId + "/" + - resources.getInteger(R.integer.file_avatar_px) - var placeholder: Drawable? - try { - placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius) + val split = user.split("@") + val userId = split.getOrNull(0) ?: user + val server = split.getOrNull(1) + + val url = if (server != null) { + "https://$server/index.php/avatar/$userId/${resources.getInteger(R.integer.file_avatar_px)}" + } else { + // fallback: no federated server, maybe use local avatar + null + } + + val placeholder: Drawable = try { + TextDrawable.createAvatarByUserId(userId, avatarRadius) } catch (e: Exception) { Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e) - placeholder = viewThemeUtils.platform.colorDrawable( - ResourcesCompat.getDrawable( - resources, - R.drawable.account_circle_white, - null - )!!, + viewThemeUtils.platform.colorDrawable( + ResourcesCompat + .getDrawable(resources, R.drawable.account_circle_white, null)!!, ContextCompat.getColor(context, R.color.black) ) } avatar.tag = null - loadCircularBitmapIntoImageView(context, url, avatar, placeholder) + if (url != null) { + loadCircularBitmapIntoImageView(context, url, avatar, placeholder) + } else { + avatar.setImageDrawable(placeholder) + } } override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt index 6224420f5256..e4eee22ef872 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt @@ -704,6 +704,14 @@ open class ExtendedListFragment : true ) } + EmptyListState.ERROR -> { + setMessageForEmptyList( + R.string.file_list_error_headline, + R.string.file_list_error_description, + R.drawable.ic_no_internet, + false + ) + } else -> { setMessageForEmptyList( R.string.file_list_empty_headline, diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 8623c5420cb4..30febe401ed6 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -225,7 +225,7 @@ public class OCFileListFragment extends ExtendedListFragment implements protected SearchType currentSearchType; protected boolean searchFragment; protected SearchEvent searchEvent; - protected AsyncTask remoteOperationAsyncTask; + private OCFileListSearchTask searchTask; protected String mLimitToMimeType; private FloatingActionButton mFabMain; public static boolean isMultipleFileSelectedForCopyOrMove = false; @@ -350,8 +350,8 @@ public void onDetach() { setOnRefreshListener(null); mContainerActivity = null; - if (remoteOperationAsyncTask != null) { - remoteOperationAsyncTask.cancel(true); + if (searchTask != null) { + searchTask.cancel(); } super.onDetach(); } @@ -1902,10 +1902,10 @@ protected void handleSearchEvent(SearchEvent event) { return; } - // avoid calling api multiple times if async task is already executing - if (remoteOperationAsyncTask != null && remoteOperationAsyncTask.getStatus() != AsyncTask.Status.FINISHED) { + // avoid calling api multiple times if task is already executing + if (searchTask != null && !searchTask.isFinished()) { if (searchEvent != null) { - Log_OC.d(TAG, "OCFileListSearchAsyncTask already running skipping new api call for search event: " + searchEvent.getSearchType()); + Log_OC.d(TAG, "OCFileListSearchTask already running skipping new api call for search event: " + searchEvent.getSearchType()); } return; @@ -1936,11 +1936,10 @@ protected void handleSearchEvent(SearchEvent event) { final User currentUser = accountManager.getUser(); - final RemoteOperation remoteOperation = getSearchRemoteOperation(currentUser, event); + final var remoteOperation = getSearchRemoteOperation(currentUser, event); - remoteOperationAsyncTask = new OCFileListSearchAsyncTask(mContainerActivity, this, remoteOperation, currentUser, event); - - remoteOperationAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + searchTask = new OCFileListSearchTask(mContainerActivity, this, remoteOperation, currentUser, event, SharedListFragment.TASK_TIMEOUT); + searchTask.execute(); } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt deleted file mode 100644 index 75f12306f6bb..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2022 Álvaro Brey - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -package com.owncloud.android.ui.fragment - -import android.annotation.SuppressLint -import android.os.AsyncTask -import android.os.Handler -import android.os.Looper -import com.nextcloud.client.account.User -import com.owncloud.android.datamodel.FileDataStorageManager -import com.owncloud.android.lib.common.operations.RemoteOperation -import com.owncloud.android.lib.common.operations.RemoteOperationResult -import com.owncloud.android.ui.events.SearchEvent -import java.lang.ref.WeakReference - -class OCFileListSearchAsyncTask( - containerActivity: FileFragment.ContainerActivity, - fragment: OCFileListFragment, - private val remoteOperation: RemoteOperation>, - private val currentUser: User, - private val event: SearchEvent -) : AsyncTask() { - private val activityReference: WeakReference = WeakReference(containerActivity) - private val fragmentReference: WeakReference = WeakReference(fragment) - - private val fileDataStorageManager: FileDataStorageManager? - get() = activityReference.get()?.storageManager - - private fun RemoteOperationResult.hasSuccessfulResult() = this.isSuccess && this.resultData != null - - override fun onPreExecute() { - fragmentReference.get()?.let { fragment -> - Handler(Looper.getMainLooper()).post { - fragment.setEmptyListMessage(EmptyListState.LOADING) - } - } - } - - override fun doInBackground(vararg voids: Void): Boolean { - val fragment = fragmentReference.get() - if (fragment?.context == null || isCancelled) { - return java.lang.Boolean.FALSE - } - - fragment.setTitle() - lateinit var remoteOperationResult: RemoteOperationResult> - try { - remoteOperationResult = remoteOperation.execute(currentUser, fragment.context) - } catch (_: UnsupportedOperationException) { - remoteOperationResult = remoteOperation.executeNextcloudClient(currentUser, fragment.requireContext()) - } - - if (remoteOperationResult.hasSuccessfulResult() && !isCancelled && fragment.searchFragment) { - fragment.searchEvent = event - if (remoteOperationResult.resultData.isNullOrEmpty()) { - fragment.setEmptyView(event) - } else { - fragment.adapter.setData( - remoteOperationResult.resultData, - fragment.currentSearchType, - fileDataStorageManager, - fragment.mFile, - true - ) - } - } - return remoteOperationResult.isSuccess - } - - @SuppressLint("NotifyDataSetChanged") - override fun onPostExecute(bool: Boolean) { - fragmentReference.get()?.let { fragment -> - if (!isCancelled) { - fragment.adapter.notifyDataSetChanged() - } - } - } -} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt new file mode 100644 index 000000000000..5aa41ffeb8f7 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -0,0 +1,95 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-FileCopyrightText: 2022 Álvaro Brey + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.fragment + +import android.annotation.SuppressLint +import androidx.lifecycle.lifecycleScope +import com.nextcloud.client.account.User +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.lib.common.operations.RemoteOperation +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.ui.events.SearchEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import java.lang.ref.WeakReference + +@Suppress("LongParameterList") +@SuppressLint("NotifyDataSetChanged") +class OCFileListSearchTask( + containerActivity: FileFragment.ContainerActivity, + fragment: OCFileListFragment, + private val remoteOperation: RemoteOperation>, + private val currentUser: User, + private val event: SearchEvent, + private val taskTimeout: Long +) { + private val activityReference: WeakReference = WeakReference(containerActivity) + private val fragmentReference: WeakReference = WeakReference(fragment) + + private val fileDataStorageManager: FileDataStorageManager? + get() = activityReference.get()?.storageManager + + private fun RemoteOperationResult.hasSuccessfulResult() = this.isSuccess && this.resultData != null + + private var job: Job? = null + + @Suppress("TooGenericExceptionCaught", "DEPRECATION") + fun execute() { + fragmentReference.get()?.let { fragment -> + job = fragment.lifecycleScope.launch(Dispatchers.IO) { + val result = withTimeoutOrNull(taskTimeout) { + if (!isActive) { + false + } else { + fragment.setTitle() + lateinit var remoteOperationResult: RemoteOperationResult> + try { + remoteOperationResult = remoteOperation.execute(currentUser, fragment.requireContext()) + } catch (_: Exception) { + remoteOperationResult = + remoteOperation.executeNextcloudClient(currentUser, fragment.requireContext()) + } + + if (remoteOperationResult.hasSuccessfulResult() && isActive && fragment.searchFragment) { + fragment.searchEvent = event + if (remoteOperationResult.resultData.isNullOrEmpty()) { + fragment.setEmptyView(event) + } else { + fragment.adapter.setData( + remoteOperationResult.resultData, + fragment.currentSearchType, + fileDataStorageManager, + fragment.mFile, + true + ) + } + } + remoteOperationResult.isSuccess + } + } ?: false + + withContext(Dispatchers.Main) { + if (result && isActive) { + fragment.adapter.notifyDataSetChanged() + } else { + fragment.setEmptyListMessage(EmptyListState.ERROR) + } + } + } + } + } + + fun cancel() = job?.cancel(null) + + fun isFinished(): Boolean = job?.isCompleted == true +} diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt b/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt index 36c1e6f6ace0..4e27e0222e17 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt @@ -32,5 +32,6 @@ enum class EmptyListState : Parcelable { ADD_FOLDER, ONLY_ON_DEVICE, LOCAL_FILE_LIST_EMPTY_FILE, - LOCAL_FILE_LIST_EMPTY_FOLDER + LOCAL_FILE_LIST_EMPTY_FOLDER, + ERROR } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt index 6c8692129e22..c146a574450c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt @@ -14,6 +14,7 @@ import androidx.lifecycle.lifecycleScope import com.nextcloud.client.account.User import com.nextcloud.client.di.Injectable import com.nextcloud.client.logger.Logger +import com.nextcloud.common.SessionTimeOut import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.operations.RemoteOperation @@ -68,7 +69,7 @@ class SharedListFragment : } override fun getSearchRemoteOperation(currentUser: User?, event: SearchEvent?): RemoteOperation<*> = - GetSharesRemoteOperation() + GetSharesRemoteOperation(false, SessionTimeOut(TASK_TIMEOUT, TASK_TIMEOUT)) @Suppress("DEPRECATION") private suspend fun fetchFileData(partialFile: OCFile): OCFile? = withContext(Dispatchers.IO) { @@ -185,5 +186,6 @@ class SharedListFragment : companion object { private val SHARED_TAG = SharedListFragment::class.java.simpleName + const val TASK_TIMEOUT = 120_000 } } diff --git a/app/src/main/res/drawable/ic_no_internet.xml b/app/src/main/res/drawable/ic_no_internet.xml new file mode 100644 index 000000000000..623ec3333316 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_internet.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9813534437da..0008ca46bf2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1454,4 +1454,7 @@ Clear Set message Error setting status message! + + Poor connection + Check your internet connection or try again later diff --git a/build.gradle b/build.gradle index a199fbcfcd95..9546ab28c301 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ */ buildscript { ext { - androidLibraryVersion ="3546bd82fc" + androidLibraryVersion ="72d739551d" androidCommonLibraryVersion = "0.28.0" androidPluginVersion = '8.13.0' androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a2be80a32d49..426792c2c0e7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -16198,6 +16198,14 @@ + + + + + + + + @@ -16270,6 +16278,14 @@ + + + + + + + +