diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 7c75f36a3b..d9f94ba27b 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -794,6 +794,7 @@
Shows all threads you’ve participated in
Keep discussions organized with threads
Threads help keep your conversations on-topic and easy to track.
+ You\'re homeserver does not support listing threads yet.
Tip: Long tap a message and use “%s”.
From a Thread
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
index 5b41ddaaec..165dcf079e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt
@@ -25,6 +25,9 @@ import java.io.IOException
import java.net.UnknownHostException
import javax.net.ssl.HttpsURLConnection
+fun Throwable.is400() = this is Failure.ServerError &&
+ httpCode == HttpsURLConnection.HTTP_BAD_REQUEST
+
fun Throwable.is401() = this is Failure.ServerError &&
httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED && /* 401 */
error.code == MatrixError.M_UNAUTHORIZED
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt
index 5d4d67a65e..e3c5deeee7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/threads/FetchThreadsResult.kt
@@ -19,5 +19,4 @@ package org.matrix.android.sdk.api.session.room.threads
sealed class FetchThreadsResult {
data class ShouldFetchMore(val nextBatch: String) : FetchThreadsResult()
object ReachedEnd : FetchThreadsResult()
- object Failed : FetchThreadsResult()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt
new file mode 100644
index 0000000000..7dda460a5e
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewActions.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.threads.list.viewmodel
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed interface ThreadListViewActions : VectorViewModelAction {
+ object TryAgain : ThreadListViewActions
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt
new file mode 100644
index 0000000000..3e9af034f4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewEvents.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.threads.list.viewmodel
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed interface ThreadListViewEvents : VectorViewEvents {
+ data class ShowError(val throwable: Throwable) : ThreadListViewEvents
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt
index 7124727bb7..f31f19849c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt
@@ -27,8 +27,6 @@ import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import im.vector.app.core.platform.EmptyAction
-import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
@@ -52,7 +50,7 @@ class ThreadListViewModel @AssistedInject constructor(
@Assisted val initialState: ThreadListViewState,
private val analyticsTracker: AnalyticsTracker,
private val session: Session,
-) : VectorViewModel(initialState) {
+) : VectorViewModel(initialState) {
private val room = session.getRoom(initialState.roomId)
@@ -93,7 +91,17 @@ class ThreadListViewModel @AssistedInject constructor(
fetchAndObserveThreads()
}
- override fun handle(action: EmptyAction) {}
+ override fun handle(action: ThreadListViewActions) {
+ when (action) {
+ ThreadListViewActions.TryAgain -> handleTryAgain()
+ }
+ }
+
+ private fun handleTryAgain() {
+ viewModelScope.launch {
+ fetchNextPage()
+ }
+ }
/**
* Observing thread list with respect to homeserver capabilities.
@@ -181,21 +189,23 @@ class ThreadListViewModel @AssistedInject constructor(
true -> ThreadFilter.PARTICIPATED
false -> ThreadFilter.ALL
}
- room?.threadsService()?.fetchThreadList(
- nextBatchId = nextBatchId,
- limit = defaultPagedListConfig.pageSize,
- filter = filter,
- ).let { result ->
- when (result) {
- is FetchThreadsResult.ReachedEnd -> {
- hasReachedEnd = true
- }
- is FetchThreadsResult.ShouldFetchMore -> {
- nextBatchId = result.nextBatch
- }
- else -> {
+ try {
+ room?.threadsService()?.fetchThreadList(
+ nextBatchId = nextBatchId,
+ limit = defaultPagedListConfig.pageSize,
+ filter = filter,
+ )?.let { result ->
+ when (result) {
+ is FetchThreadsResult.ReachedEnd -> {
+ hasReachedEnd = true
+ }
+ is FetchThreadsResult.ShouldFetchMore -> {
+ nextBatchId = result.nextBatch
+ }
}
}
+ } catch (throwable: Throwable) {
+ _viewEvents.post(ThreadListViewEvents.ShowError(throwable))
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
index 318c250906..1e67941856 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
@@ -26,6 +26,7 @@ import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
@@ -41,10 +42,14 @@ import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListPagedController
+import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewActions
+import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewEvents
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
import im.vector.app.features.rageshake.BugReporter
import im.vector.app.features.rageshake.ReportType
+import org.matrix.android.sdk.api.failure.is400
+import org.matrix.android.sdk.api.failure.is404
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.MatrixItem
@@ -126,11 +131,45 @@ class ThreadListFragment :
views.threadListRecyclerView.configureWith(legacyThreadListController, TimelineItemAnimator(), hasFixedSize = false)
legacyThreadListController.listener = this
}
+ observeViewEvents()
+ }
+
+ private fun observeViewEvents() {
+ threadListViewModel.observeViewEvents {
+ when (it) {
+ is ThreadListViewEvents.ShowError -> handleShowError(it)
+ }
+ }
+ }
+
+ private fun handleShowError(event: ThreadListViewEvents.ShowError) {
+ val error = event.throwable
+ MaterialAlertDialogBuilder(requireActivity())
+ .setTitle(R.string.dialog_title_error)
+ .also {
+ if (error.is400() || error.is404()) {
+ // Outdated Homeserver
+ it.setMessage(R.string.thread_list_not_available)
+ it.setPositiveButton(R.string.ok) { _, _ ->
+ requireActivity().finish()
+ }
+ } else {
+ // Other error, can retry
+ // (Can happen on first request or on pagination request)
+ it.setMessage(errorFormatter.toHumanReadable(error))
+ it.setPositiveButton(R.string.ok, null)
+ it.setNegativeButton(R.string.global_retry) { _, _ ->
+ threadListViewModel.handle(ThreadListViewActions.TryAgain)
+ }
+ }
+ }
+ .show()
}
override fun onDestroyView() {
views.threadListRecyclerView.cleanup()
threadListController.listener = null
+ legacyThreadListController.listener = null
super.onDestroyView()
}