diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
index c87f21d7ac..f8472319fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
@@ -60,7 +60,11 @@ data class MatrixConfiguration(
/**
* RoomDisplayNameFallbackProvider to provide default room display name.
*/
- val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider
+ val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider,
+ /**
+ * Thread messages default enable/disabled value
+ */
+ val threadMessagesEnabledDefault: Boolean = false,
) {
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt
index 700b94a985..65c98ab872 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/lightweight/LightweightSettingsStorage.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.lightweight
import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
+import org.matrix.android.sdk.api.MatrixConfiguration
import javax.inject.Inject
/**
@@ -27,7 +28,10 @@ import javax.inject.Inject
* not for large data sets
*/
-class LightweightSettingsStorage @Inject constructor(context: Context) {
+class LightweightSettingsStorage @Inject constructor(
+ context: Context,
+ val matrixConfiguration: MatrixConfiguration
+) {
private val sdkDefaultPrefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
@@ -38,7 +42,7 @@ class LightweightSettingsStorage @Inject constructor(context: Context) {
}
fun areThreadMessagesEnabled(): Boolean {
- return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, false)
+ return sdkDefaultPrefs.getBoolean(MATRIX_SDK_SETTINGS_THREAD_MESSAGES_ENABLED, matrixConfiguration.threadMessagesEnabledDefault)
}
companion object {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 1bbf54a788..ac3ae3df91 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -75,7 +75,7 @@ internal class SyncResponseHandler @Inject constructor(
suspend fun handleResponse(syncResponse: SyncResponse,
fromToken: String?,
reporter: ProgressReporter?) {
- val isInitialSync = fromToken == null
+ var isInitialSync = fromToken == null
Timber.v("Start handling sync, is InitialSync: $isInitialSync")
measureTimeMillis {
diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 0121ee9ae7..40fc68bbae 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -36,8 +36,9 @@
+ false
-
+
diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
index a5575ef536..211bc3987f 100644
--- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
@@ -46,6 +46,7 @@ import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.SharedPrefPinCodeStore
import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider
+import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.ui.SharedPreferencesUiStateRepository
import im.vector.app.features.ui.UiStateRepository
import kotlinx.coroutines.CoroutineScope
@@ -113,10 +114,13 @@ object VectorStaticModule {
}
@Provides
- fun providesMatrixConfiguration(vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
+ fun providesMatrixConfiguration(
+ vectorPreferences: VectorPreferences,
+ vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
return MatrixConfiguration(
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
- roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider
+ roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider,
+ threadMessagesEnabledDefault = vectorPreferences.areThreadMessagesEnabled()
)
}
diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt
index 33b735551c..42bd2318b3 100644
--- a/vector/src/main/java/im/vector/app/features/MainActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt
@@ -241,7 +241,7 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity
// We have a session.
// Check it can be opened
if (sessionHolder.getActiveSession().isOpenable) {
- HomeActivity.newIntent(this)
+ HomeActivity.newIntent(this, existingSession = true)
} else {
// The token is still invalid
navigator.softLogout(this)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 964fb6f365..1bd61c0f9e 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -90,6 +90,7 @@ import javax.inject.Inject
data class HomeActivityArgs(
val clearNotification: Boolean,
val accountCreation: Boolean,
+ val existingSession: Boolean = false,
val inviteNotificationRoomId: String? = null
) : Parcelable
@@ -253,6 +254,8 @@ class HomeActivity :
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
HomeActivityViewEvents.ShowAnalyticsOptIn -> handleShowAnalyticsOptIn()
+ HomeActivityViewEvents.NotifyUserForThreadsMigration -> handleNotifyUserForThreadsMigration()
+ is HomeActivityViewEvents.MigrateThreads -> migrateThreadsIfNeeded(it.checkSession)
}.exhaustive
}
homeActivityViewModel.onEach { renderState(it) }
@@ -269,6 +272,49 @@ class HomeActivity :
navigator.openAnalyticsOptIn(this)
}
+ /**
+ * Migrating from old threads io.element.thread to new m.thread needs an initial sync to
+ * sync and display existing messages appropriately
+ */
+ private fun migrateThreadsIfNeeded(checkSession: Boolean) {
+
+ if (checkSession) {
+ // We should check session to ensure we will only clear cache if needed
+ val args = intent.getParcelableExtra(Mavericks.KEY_ARG)
+ if (args?.existingSession == true) {
+ // existingSession --> Will be true only if we came from an existing active session
+ Timber.i("----> Migrating threads from an existing session..")
+ handleThreadsMigration()
+ } else {
+ // We came from a new session and not an existing one,
+ // so there is no need to migrate threads while an initial synced performed
+ Timber.i("----> No thread migration needed, we are ok")
+ vectorPreferences.threadsMigrated()
+ }
+ } else {
+ // Proceed with migration
+ handleThreadsMigration()
+ }
+ }
+
+ /**
+ * Clear cache and restart to invoke an initial sync for threads migration
+ */
+ private fun handleThreadsMigration() {
+ Timber.i("----> Threads Migration detected, clearing cache and sync...")
+ vectorPreferences.threadsMigrated()
+ MainActivity.restartApp(this, MainActivityArgs(clearCache = true))
+ }
+
+ private fun handleNotifyUserForThreadsMigration() {
+ MaterialAlertDialogBuilder(this)
+ .setTitle("Threads, no longer experimental")
+ .setMessage("All \uD83C\uDF89 \uD83C\uDF89 threads created during experimental period will\n\n now be rendered as regular replies. This will be an one-off transition, as threads are now part of the matrix specification")
+ .setCancelable(true)
+ .setPositiveButton(R.string.ok) { _, _ -> }
+ .show()
+ }
+
private fun handleIntent(intent: Intent?) {
intent?.dataString?.let { deepLink ->
val resolvedLink = when {
@@ -546,11 +592,13 @@ class HomeActivity :
fun newIntent(context: Context,
clearNotification: Boolean = false,
accountCreation: Boolean = false,
+ existingSession: Boolean = false,
inviteNotificationRoomId: String? = null
): Intent {
val args = HomeActivityArgs(
clearNotification = clearNotification,
accountCreation = accountCreation,
+ existingSession = existingSession,
inviteNotificationRoomId = inviteNotificationRoomId
)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
index adc44a57bd..e301967884 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt
@@ -25,4 +25,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
object PromptToEnableSessionPush : HomeActivityViewEvents
object ShowAnalyticsOptIn : HomeActivityViewEvents
+ object NotifyUserForThreadsMigration : HomeActivityViewEvents
+ data class MigrateThreads(val checkSession: Boolean) : HomeActivityViewEvents
+
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index 35c112b63a..bb248965e4 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -51,6 +51,7 @@ import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
import org.matrix.android.sdk.internal.util.awaitCallback
import timber.log.Timber
import kotlin.coroutines.Continuation
@@ -62,6 +63,7 @@ class HomeActivityViewModel @AssistedInject constructor(
private val activeSessionHolder: ActiveSessionHolder,
private val reAuthHelper: ReAuthHelper,
private val analyticsStore: AnalyticsStore,
+ private val lightweightSettingsStorage: LightweightSettingsStorage,
private val vectorPreferences: VectorPreferences
) : VectorViewModel(initialState) {
@@ -84,6 +86,7 @@ class HomeActivityViewModel @AssistedInject constructor(
checkSessionPushIsOn()
observeCrossSigningReset()
observeAnalytics()
+ initThreadsMigration()
}
private fun observeAnalytics() {
@@ -130,6 +133,49 @@ class HomeActivityViewModel @AssistedInject constructor(
.launchIn(viewModelScope)
}
+ /**
+ * Handle threads migration. The migration includes:
+ * - Notify users that had io.element.thread enabled from labs
+ * - Re-Enable m.thread to those users (that they had enabled labs threads)
+ * - Handle migration when threads are enabled by default
+ */
+ private fun initThreadsMigration() {
+
+ // Notify users
+ if (vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.areThreadMessagesEnabled()) {
+ Timber.i("----> Notify users about threads")
+ // Notify the user if needed that we migrated to support m.thread
+ // instead of io.element.thread so old thread messages will be displayed as normal timeline messages
+ _viewEvents.post(HomeActivityViewEvents.NotifyUserForThreadsMigration)
+ vectorPreferences.userNotifiedAboutThreads()
+ return
+ }
+
+ // Migrate users with enabled lab settings
+ if (vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.shouldMigrateThreads()) {
+ Timber.i("----> Migrate threads with enabled labs")
+ // If user had io.element.thread enabled then enable the new thread support,
+ // clear cache to sync messages appropriately
+ vectorPreferences.setThreadMessagesEnabled()
+ lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
+ // Clear Cache
+ _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = false))
+ return
+ }
+ // Enable all users
+ // When we would to enable threads for all
+ // if(vectorPreferences.shouldMigrateThreads) -->
+ // vectorPreferences.setThreadMessagesEnabled() &&
+ // lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
+ if(vectorPreferences.shouldMigrateThreads() && vectorPreferences.areThreadMessagesEnabled()){
+ Timber.i("----> Try to migrate threads")
+ _viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = true))
+ return
+ }
+
+
+ }
+
private fun observeInitialSync() {
val session = activeSessionHolder.getSafeActiveSession() ?: return
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index 352c5768fb..d263dc1cbb 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -201,7 +201,13 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE"
private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
- const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
+
+ // This key will be used to identify clients with the old thread support enabled io.element.thread
+ const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
+
+ // This key will be used to identify clients with the new thread support enabled m.thread
+ const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL"
+ const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED"
// Possible values for TAKE_PHOTO_VIDEO_MODE
const val TAKE_PHOTO_VIDEO_MODE_ALWAYS_ASK = 0
@@ -1006,7 +1012,55 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true)
}
+ /**
+ * Indicates whether or not thread messages are enabled
+ */
fun areThreadMessagesEnabled(): Boolean {
- return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, false)
+ return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, getDefault(R.bool.settings_labs_thread_messages_default))
+ }
+
+ /**
+ * Manually sets thread messages enabled, useful for migrating users from io.element.thread
+ */
+ fun setThreadMessagesEnabled() {
+ defaultPrefs
+ .edit()
+ .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES, true)
+ .apply()
+ }
+ /**
+ * Indicates whether or not the user will be notified about the new thread support
+ * We should notify the user only if he had old thread support enabled
+ */
+ fun shouldNotifyUserAboutThreads(): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false)
+ }
+
+ /**
+ * Indicates that the user have been notified about threads migration
+ */
+ fun userNotifiedAboutThreads() {
+ defaultPrefs
+ .edit()
+ .putBoolean(SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS, false)
+ .apply()
+ }
+
+ /**
+ * Indicates whether or not we should clear cache for threads migration.
+ * Default value is true, for fresh installs and updates
+ */
+ fun shouldMigrateThreads(): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, true)
+ }
+
+ /**
+ * Indicates that there no longer threads migration needed
+ */
+ fun threadsMigrated() {
+ defaultPrefs
+ .edit()
+ .putBoolean(SETTINGS_THREAD_MESSAGES_SYNCED, false)
+ .apply()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
index 118e820f84..683c1e1dd2 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt
@@ -42,6 +42,7 @@ class VectorSettingsLabsFragment @Inject constructor(
// clear cache
findPreference(VectorPreferences.SETTINGS_LABS_ENABLE_THREAD_MESSAGES)?.let {
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ vectorPreferences.threadsMigrated() // Manual actions should disable the ato enable mechanism
lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
displayLoadingView()
MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = true))
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index 73193edfd5..5144f6fe1f 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -52,8 +52,8 @@