Skip to content

Commit 0710c82

Browse files
Merge pull request #15756 from nextcloud/fix/handle-slow-internet-connection-better-for-fetching-shares
fix: handle slow internet connection better for fetching shares
2 parents ed4b7ad + d31db26 commit 0710c82

File tree

11 files changed

+182
-115
lines changed

11 files changed

+182
-115
lines changed

app/src/main/java/com/owncloud/android/ui/AvatarGroupLayout.kt

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -138,30 +138,34 @@ class AvatarGroupLayout @JvmOverloads constructor(
138138
avatar: ImageView,
139139
viewThemeUtils: ViewThemeUtils
140140
) {
141-
// maybe federated share
142-
val split = user.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
143-
val userId: String? = split[0]
144-
val server = split[1]
145-
146-
val url = "https://" + server + "/index.php/avatar/" + userId + "/" +
147-
resources.getInteger(R.integer.file_avatar_px)
148-
var placeholder: Drawable?
149-
try {
150-
placeholder = TextDrawable.createAvatarByUserId(userId, avatarRadius)
141+
val split = user.split("@")
142+
val userId = split.getOrNull(0) ?: user
143+
val server = split.getOrNull(1)
144+
145+
val url = if (server != null) {
146+
"https://$server/index.php/avatar/$userId/${resources.getInteger(R.integer.file_avatar_px)}"
147+
} else {
148+
// fallback: no federated server, maybe use local avatar
149+
null
150+
}
151+
152+
val placeholder: Drawable = try {
153+
TextDrawable.createAvatarByUserId(userId, avatarRadius)
151154
} catch (e: Exception) {
152155
Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e)
153-
placeholder = viewThemeUtils.platform.colorDrawable(
154-
ResourcesCompat.getDrawable(
155-
resources,
156-
R.drawable.account_circle_white,
157-
null
158-
)!!,
156+
viewThemeUtils.platform.colorDrawable(
157+
ResourcesCompat
158+
.getDrawable(resources, R.drawable.account_circle_white, null)!!,
159159
ContextCompat.getColor(context, R.color.black)
160160
)
161161
}
162162

163163
avatar.tag = null
164-
loadCircularBitmapIntoImageView(context, url, avatar, placeholder)
164+
if (url != null) {
165+
loadCircularBitmapIntoImageView(context, url, avatar, placeholder)
166+
} else {
167+
avatar.setImageDrawable(placeholder)
168+
}
165169
}
166170

167171
override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) {

app/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,14 @@ open class ExtendedListFragment :
704704
true
705705
)
706706
}
707+
EmptyListState.ERROR -> {
708+
setMessageForEmptyList(
709+
R.string.file_list_error_headline,
710+
R.string.file_list_error_description,
711+
R.drawable.ic_no_internet,
712+
false
713+
)
714+
}
707715
else -> {
708716
setMessageForEmptyList(
709717
R.string.file_list_empty_headline,

app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
225225
protected SearchType currentSearchType;
226226
protected boolean searchFragment;
227227
protected SearchEvent searchEvent;
228-
protected AsyncTask<Void, Void, Boolean> remoteOperationAsyncTask;
228+
private OCFileListSearchTask searchTask;
229229
protected String mLimitToMimeType;
230230
private FloatingActionButton mFabMain;
231231
public static boolean isMultipleFileSelectedForCopyOrMove = false;
@@ -350,8 +350,8 @@ public void onDetach() {
350350
setOnRefreshListener(null);
351351
mContainerActivity = null;
352352

353-
if (remoteOperationAsyncTask != null) {
354-
remoteOperationAsyncTask.cancel(true);
353+
if (searchTask != null) {
354+
searchTask.cancel();
355355
}
356356
super.onDetach();
357357
}
@@ -1907,10 +1907,10 @@ protected void handleSearchEvent(SearchEvent event) {
19071907
return;
19081908
}
19091909

1910-
// avoid calling api multiple times if async task is already executing
1911-
if (remoteOperationAsyncTask != null && remoteOperationAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
1910+
// avoid calling api multiple times if task is already executing
1911+
if (searchTask != null && !searchTask.isFinished()) {
19121912
if (searchEvent != null) {
1913-
Log_OC.d(TAG, "OCFileListSearchAsyncTask already running skipping new api call for search event: " + searchEvent.getSearchType());
1913+
Log_OC.d(TAG, "OCFileListSearchTask already running skipping new api call for search event: " + searchEvent.getSearchType());
19141914
}
19151915

19161916
return;
@@ -1941,11 +1941,10 @@ protected void handleSearchEvent(SearchEvent event) {
19411941

19421942
final User currentUser = accountManager.getUser();
19431943

1944-
final RemoteOperation remoteOperation = getSearchRemoteOperation(currentUser, event);
1944+
final var remoteOperation = getSearchRemoteOperation(currentUser, event);
19451945

1946-
remoteOperationAsyncTask = new OCFileListSearchAsyncTask(mContainerActivity, this, remoteOperation, currentUser, event);
1947-
1948-
remoteOperationAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1946+
searchTask = new OCFileListSearchTask(mContainerActivity, this, remoteOperation, currentUser, event, SharedListFragment.TASK_TIMEOUT);
1947+
searchTask.execute();
19491948
}
19501949

19511950

app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchAsyncTask.kt

Lines changed: 0 additions & 83 deletions
This file was deleted.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 Alper Ozturk <[email protected]>
5+
* SPDX-FileCopyrightText: 2022 Álvaro Brey <[email protected]>
6+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
package com.owncloud.android.ui.fragment
10+
11+
import android.annotation.SuppressLint
12+
import androidx.lifecycle.lifecycleScope
13+
import com.nextcloud.client.account.User
14+
import com.owncloud.android.datamodel.FileDataStorageManager
15+
import com.owncloud.android.lib.common.operations.RemoteOperation
16+
import com.owncloud.android.lib.common.operations.RemoteOperationResult
17+
import com.owncloud.android.ui.events.SearchEvent
18+
import kotlinx.coroutines.Dispatchers
19+
import kotlinx.coroutines.Job
20+
import kotlinx.coroutines.isActive
21+
import kotlinx.coroutines.launch
22+
import kotlinx.coroutines.withContext
23+
import kotlinx.coroutines.withTimeoutOrNull
24+
import java.lang.ref.WeakReference
25+
26+
@Suppress("LongParameterList")
27+
@SuppressLint("NotifyDataSetChanged")
28+
class OCFileListSearchTask(
29+
containerActivity: FileFragment.ContainerActivity,
30+
fragment: OCFileListFragment,
31+
private val remoteOperation: RemoteOperation<List<Any>>,
32+
private val currentUser: User,
33+
private val event: SearchEvent,
34+
private val taskTimeout: Long
35+
) {
36+
private val activityReference: WeakReference<FileFragment.ContainerActivity> = WeakReference(containerActivity)
37+
private val fragmentReference: WeakReference<OCFileListFragment> = WeakReference(fragment)
38+
39+
private val fileDataStorageManager: FileDataStorageManager?
40+
get() = activityReference.get()?.storageManager
41+
42+
private fun RemoteOperationResult<out Any>.hasSuccessfulResult() = this.isSuccess && this.resultData != null
43+
44+
private var job: Job? = null
45+
46+
@Suppress("TooGenericExceptionCaught", "DEPRECATION")
47+
fun execute() {
48+
fragmentReference.get()?.let { fragment ->
49+
job = fragment.lifecycleScope.launch(Dispatchers.IO) {
50+
val result = withTimeoutOrNull(taskTimeout) {
51+
if (!isActive) {
52+
false
53+
} else {
54+
fragment.setTitle()
55+
lateinit var remoteOperationResult: RemoteOperationResult<List<Any>>
56+
try {
57+
remoteOperationResult = remoteOperation.execute(currentUser, fragment.requireContext())
58+
} catch (_: Exception) {
59+
remoteOperationResult =
60+
remoteOperation.executeNextcloudClient(currentUser, fragment.requireContext())
61+
}
62+
63+
if (remoteOperationResult.hasSuccessfulResult() && isActive && fragment.searchFragment) {
64+
fragment.searchEvent = event
65+
if (remoteOperationResult.resultData.isNullOrEmpty()) {
66+
fragment.setEmptyView(event)
67+
} else {
68+
fragment.adapter.setData(
69+
remoteOperationResult.resultData,
70+
fragment.currentSearchType,
71+
fileDataStorageManager,
72+
fragment.mFile,
73+
true
74+
)
75+
}
76+
}
77+
remoteOperationResult.isSuccess
78+
}
79+
} ?: false
80+
81+
withContext(Dispatchers.Main) {
82+
if (result && isActive) {
83+
fragment.adapter.notifyDataSetChanged()
84+
} else {
85+
fragment.setEmptyListMessage(EmptyListState.ERROR)
86+
}
87+
}
88+
}
89+
}
90+
}
91+
92+
fun cancel() = job?.cancel(null)
93+
94+
fun isFinished(): Boolean = job?.isCompleted == true
95+
}

app/src/main/java/com/owncloud/android/ui/fragment/SearchType.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ enum class EmptyListState : Parcelable {
3232
ADD_FOLDER,
3333
ONLY_ON_DEVICE,
3434
LOCAL_FILE_LIST_EMPTY_FILE,
35-
LOCAL_FILE_LIST_EMPTY_FOLDER
35+
LOCAL_FILE_LIST_EMPTY_FOLDER,
36+
ERROR
3637
}

app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.lifecycle.lifecycleScope
1414
import com.nextcloud.client.account.User
1515
import com.nextcloud.client.di.Injectable
1616
import com.nextcloud.client.logger.Logger
17+
import com.nextcloud.common.SessionTimeOut
1718
import com.owncloud.android.R
1819
import com.owncloud.android.datamodel.OCFile
1920
import com.owncloud.android.lib.common.operations.RemoteOperation
@@ -68,7 +69,7 @@ class SharedListFragment :
6869
}
6970

7071
override fun getSearchRemoteOperation(currentUser: User?, event: SearchEvent?): RemoteOperation<*> =
71-
GetSharesRemoteOperation()
72+
GetSharesRemoteOperation(false, SessionTimeOut(TASK_TIMEOUT, TASK_TIMEOUT))
7273

7374
@Suppress("DEPRECATION")
7475
private suspend fun fetchFileData(partialFile: OCFile): OCFile? = withContext(Dispatchers.IO) {
@@ -185,5 +186,6 @@ class SharedListFragment :
185186

186187
companion object {
187188
private val SHARED_TAG = SharedListFragment::class.java.simpleName
189+
const val TASK_TIMEOUT = 120_000
188190
}
189191
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!--
2+
~ SPDX-FileCopyrightText: 2025 Alper Ozturk <[email protected]>
3+
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
4+
~ SPDX-License-Identifier: Apache-2.0
5+
-->
6+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
7+
android:width="24dp"
8+
android:height="24dp"
9+
android:tint="#626364"
10+
android:viewportWidth="960"
11+
android:viewportHeight="960">
12+
13+
<path
14+
android:fillColor="@android:color/white"
15+
android:pathData="M73,424L2,353Q99,259 222.5,209.5Q346,160 480,160Q614,160 737.5,209.5Q861,259 958,353L887,424Q805,345 700,302.5Q595,260 480,260Q365,260 260,302.5Q155,345 73,424ZM480,800Q447,800 423.5,776.5Q400,753 400,720Q400,687 423.5,663.5Q447,640 480,640Q513,640 536.5,663.5Q560,687 560,720Q560,753 536.5,776.5Q513,800 480,800ZM298,651L228,580Q279,532 344,506Q409,480 480,480Q521,480 560.5,488.5Q600,497 636,514Q619,531 603,552.5Q587,574 574,597Q551,589 527.5,584.5Q504,580 480,580Q429,580 382.5,598Q336,616 298,651ZM186,538L116,467Q190,396 284,358Q378,320 481,320Q584,320 677.5,357.5Q771,395 845,466L830,482Q810,479 793.5,474.5Q777,470 760,470Q746,470 731.5,473.5Q717,477 701,482Q651,452 595,436Q539,420 480,420Q397,420 321.5,450.5Q246,481 186,538ZM760,800Q743,800 731.5,788.5Q720,777 720,760Q720,743 731.5,731.5Q743,720 760,720Q777,720 788.5,731.5Q800,743 800,760Q800,777 788.5,788.5Q777,800 760,800ZM720,680L720,540L800,540L800,680L720,680Z" />
16+
17+
</vector>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
<string name="bottom_navigation_menu_favorites_label">Favorites</string>
1111
<string name="bottom_navigation_menu_assistant_label">Assistant</string>
1212
<string name="bottom_navigation_menu_media_label">Media</string>
13-
14-
13+
<string name="file_list_error_headline">Poor connection</string>
14+
<string name="file_list_error_description">Check your internet connection or try again later</string>
1515
<string name="about_android">%1$s Android app</string>
1616
<string name="about_version">version %1$s</string>
1717
<string name="about_version_with_build">version %1$s, build #%2$s</string>

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
androidCommonLibraryVersion = "0.29.0"
66
androidGifDrawableVersion = "1.2.29"
77
androidImageCropperVersion = "4.6.0"
8-
androidLibraryVersion = "3546bd82fc"
8+
androidLibraryVersion = "8c77f600ac942f6eb4e3c4447143fc949d34acc6"
99
androidPluginVersion = '8.13.0'
1010
androidsvgVersion = "1.4"
1111
androidxMediaVersion = "1.5.1"

0 commit comments

Comments
 (0)