Skip to content

Commit 4334eca

Browse files
Merge pull request #15894 from nextcloud/backport/15756/stable-3.34
[stable-3.34] fix: handle slow internet connection better for fetching shares
2 parents 74f08a3 + 7f29183 commit 4334eca

File tree

13 files changed

+197
-117
lines changed

13 files changed

+197
-117
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ android {
116116
applicationId = "com.nextcloud.client"
117117
minSdk = 27
118118
targetSdk = 35
119-
compileSdk = 35
119+
compileSdk = 36
120120

121121
buildConfigField "boolean", "CI", ciBuild.toString()
122122
buildConfigField "boolean", "RUNTIME_PERF_ANALYSIS", perfAnalysis.toString()

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
@@ -707,6 +707,14 @@ open class ExtendedListFragment :
707707
true
708708
)
709709
}
710+
EmptyListState.ERROR -> {
711+
setMessageForEmptyList(
712+
R.string.file_list_error_headline,
713+
R.string.file_list_error_description,
714+
R.drawable.ic_no_internet,
715+
false
716+
)
717+
}
710718
else -> {
711719
setMessageForEmptyList(
712720
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;
@@ -356,8 +356,8 @@ public void onDetach() {
356356
setOnRefreshListener(null);
357357
mContainerActivity = null;
358358

359-
if (remoteOperationAsyncTask != null) {
360-
remoteOperationAsyncTask.cancel(true);
359+
if (searchTask != null) {
360+
searchTask.cancel();
361361
}
362362
super.onDetach();
363363
}
@@ -1914,10 +1914,10 @@ protected void handleSearchEvent(SearchEvent event) {
19141914
return;
19151915
}
19161916

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

19231923
return;
@@ -1948,11 +1948,10 @@ protected void handleSearchEvent(SearchEvent event) {
19481948

19491949
final User currentUser = accountManager.getUser();
19501950

1951-
final RemoteOperation remoteOperation = getSearchRemoteOperation(currentUser, event);
1951+
final var remoteOperation = getSearchRemoteOperation(currentUser, event);
19521952

1953-
remoteOperationAsyncTask = new OCFileListSearchAsyncTask(mContainerActivity, this, remoteOperation, currentUser, event);
1954-
1955-
remoteOperationAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
1953+
searchTask = new OCFileListSearchTask(mContainerActivity, this, remoteOperation, currentUser, event, SharedListFragment.TASK_TIMEOUT);
1954+
searchTask.execute();
19561955
}
19571956

19581957

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>

0 commit comments

Comments
 (0)