diff --git a/app/src/main/java/org/futo/circles/App.kt b/app/src/main/java/org/futo/circles/App.kt
index 7c94023e5e469d37d502c4cf81d8bb90c3ec61c5..644f369d1942c39bc4cb5aaf0251e6a5d3a17183 100644
--- a/app/src/main/java/org/futo/circles/App.kt
+++ b/app/src/main/java/org/futo/circles/App.kt
@@ -90,6 +90,7 @@ class App : Application() {
             override fun onResume(owner: LifecycleOwner) {
                 fcmHelper.onEnterForeground()
                 MatrixSessionProvider.currentSession?.syncService()?.stopAnyBackgroundSync()
+                NetworkObserver.updateConnectionState(applicationContext)
             }
 
             override fun onPause(owner: LifecycleOwner) {
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt
index 67baafdd534cbeb85c59b6c6419431c88fe1fbf8..6964f2aa995f7f759baebdf3f9d5f54cc1d8a7e3 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt
+++ b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt
@@ -16,6 +16,7 @@ import com.google.android.material.badge.ExperimentalBadgeUtils
 import dagger.hilt.android.AndroidEntryPoint
 import org.futo.circles.R
 import org.futo.circles.core.base.fragment.BaseFullscreenDialogFragment
+import org.futo.circles.core.extensions.dpToPx
 import org.futo.circles.core.extensions.getCurrentUserPowerLevel
 import org.futo.circles.core.extensions.isCurrentUserAbleToPost
 import org.futo.circles.core.extensions.observeData
@@ -40,6 +41,7 @@ import org.futo.circles.model.CreatePostContent
 import org.futo.circles.model.EndPoll
 import org.futo.circles.model.IgnoreSender
 import org.futo.circles.model.RemovePost
+import org.futo.circles.model.TextPostContent
 import org.futo.circles.view.CreatePostViewListener
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@@ -113,7 +115,11 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli
     private fun setupViews() {
         binding.rvTimeline.apply {
             adapter = listAdapter
-            getRecyclerView().isNestedScrollingEnabled = false
+            getRecyclerView().apply {
+                isNestedScrollingEnabled = false
+                clipToPadding = false
+                setPadding(paddingLeft, paddingTop, paddingRight, context.dpToPx(70))
+            }
             addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
             addPageEndListener { viewModel.loadMore() }
         }
@@ -223,6 +229,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli
     }
 
     override fun onReply(roomId: String, eventId: String) {
+        if (isThread) return
         navigator.navigateToThread(roomId, eventId)
     }
 
diff --git a/app/src/main/java/org/futo/circles/view/CreatePostView.kt b/app/src/main/java/org/futo/circles/view/CreatePostView.kt
index 648e41d7eb1754472f6e8beee003e1ec640aa74a..c0b071e343e9d8f4e19167700dd17d4040ec0761 100644
--- a/app/src/main/java/org/futo/circles/view/CreatePostView.kt
+++ b/app/src/main/java/org/futo/circles/view/CreatePostView.kt
@@ -66,6 +66,13 @@ class CreatePostView(
                 if (isUserAbleToPost)
                     if (dy > 0) gone() else if (dy < 0) visible()
             }
+
+            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+                super.onScrollStateChanged(recyclerView, newState)
+                if (!recyclerView.canScrollVertically(1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
+                    if (isUserAbleToPost) visible()
+                }
+            }
         })
     }
 
diff --git a/auth/build.gradle b/auth/build.gradle
index cb98c75473ba0fcb1e9031a7073febae10229f23..1bd1a82c631f20adea4f4fc8766e6850881b720a 100644
--- a/auth/build.gradle
+++ b/auth/build.gradle
@@ -70,10 +70,10 @@ dependencies {
     implementation 'com.nulab-inc:zxcvbn:1.9.0'
 
     //Subscriptions
-    gplayImplementation 'com.android.billingclient:billing-ktx:6.2.1'
+    gplayImplementation 'com.android.billingclient:billing-ktx:7.0.0'
 
     //PasswordManager
-    def credentials_version = '1.3.0-alpha03'
+    def credentials_version = '1.3.0-alpha04'
     gplayImplementation "androidx.credentials:credentials:$credentials_version"
     gplayImplementation "androidx.credentials:credentials-play-services-auth:$credentials_version"
 
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/create/CreatePassPhraseDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/create/CreatePassPhraseDataSource.kt
index 9dc6c754bad907b60df768099265a5be0d9b2405..56baa3c5186d25bbb88480b831078ce0a5748bd1 100644
--- a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/create/CreatePassPhraseDataSource.kt
+++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/create/CreatePassPhraseDataSource.kt
@@ -31,8 +31,8 @@ class CreatePassPhraseDataSource @Inject constructor(
         val backupCreationInfo =
             keysBackupService.prepareKeysBackupVersion(keyBackupPrivateKey, null)
         createKeyBackup(backupCreationInfo)
-        val keyData = ssssDataSource.storeBsSpekeKeyIntoSSSS(keyBackupPrivateKey)
-        crossSigningDataSource.initCrossSigningIfNeed(keyData.keySpec)
+        val keySpec = ssssDataSource.storeBsSpekeKeyIntoSSSS(keyBackupPrivateKey)
+        crossSigningDataSource.initCrossSigningIfNeed(keySpec)
         loadingLiveData.postValue(LoadingData(isLoading = false))
     }
 
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt
index fcdd46dd01766e4535130bacbb3a4c3895d69e41..b51db190d3a9808187d309ccc00c0167e57d0b14 100644
--- a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt
+++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt
@@ -7,20 +7,22 @@ import androidx.lifecycle.MutableLiveData
 import dagger.hilt.android.qualifiers.ApplicationContext
 import org.futo.circles.auth.R
 import org.futo.circles.auth.feature.cross_signing.CrossSigningDataSource
-import org.futo.circles.auth.model.KeyData
+import org.futo.circles.auth.model.SecretKeyData
 import org.futo.circles.core.model.LoadingData
+import org.futo.circles.core.provider.KeyStoreProvider
 import org.futo.circles.core.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.listeners.StepProgressListener
-import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
 import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
 import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
+import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
 import javax.inject.Inject
 
 class RestoreBackupDataSource @Inject constructor(
     @ApplicationContext private val context: Context,
     private val ssssDataSource: SSSSDataSource,
-    private val crossSigningDataSource: CrossSigningDataSource
+    private val crossSigningDataSource: CrossSigningDataSource,
+    private val keyStoreProvider: KeyStoreProvider
 ) {
 
     val loadingLiveData = MutableLiveData(LoadingData(isLoading = false))
@@ -75,8 +77,12 @@ class RestoreBackupDataSource @Inject constructor(
 
     suspend fun restoreWithBsSpekeKey() {
         try {
-            val keyData = ssssDataSource.getBsSpekeRecoveryKey(progressObserver)
+            val keyData = ssssDataSource.getBsSpekeSecretKeyData(progressObserver)
             restoreKeysWithRecoveryKey(keyData)
+            keyStoreProvider.storeBsSpekePrivateKey(
+                (keyData.keySpec as RawBytesKeySpec).privateKey,
+                keyData.keyId
+            )
         } catch (e: Throwable) {
             loadingLiveData.postValue(LoadingData(isLoading = false))
             throw e
@@ -87,7 +93,7 @@ class RestoreBackupDataSource @Inject constructor(
     suspend fun restoreKeysWithPassPhase(passphrase: String) {
         try {
             val keyData =
-                ssssDataSource.getRecoveryKeyFromPassphrase(passphrase, progressObserver)
+                ssssDataSource.getSecretKeyDataFromPassphrase(passphrase, progressObserver)
             restoreKeysWithRecoveryKey(keyData)
         } catch (e: Throwable) {
             loadingLiveData.postValue(LoadingData(isLoading = false))
@@ -96,18 +102,18 @@ class RestoreBackupDataSource @Inject constructor(
         loadingLiveData.postValue(LoadingData(isLoading = false))
     }
 
-    private suspend fun restoreKeysWithRecoveryKey(keyData: KeyData) {
+    private suspend fun restoreKeysWithRecoveryKey(secretKeyData: SecretKeyData) {
         val keysBackupService = getKeysBackupService()
         try {
             val keyVersion = getKeysVersion(keysBackupService)
             keysBackupService.restoreKeysWithRecoveryKey(
                 keyVersion,
-                BackupRecoveryKey.fromBase58(keyData.recoveryKey),
+                secretKeyData.getBackupRecoveryKey(),
                 null,
                 MatrixSessionProvider.currentSession?.myUserId,
                 progressObserver
             )
-            crossSigningDataSource.configureCrossSigning(keyData.keySpec)
+            crossSigningDataSource.configureCrossSigning(secretKeyData.keySpec)
             keysBackupService.trustKeysBackupVersion(keyVersion, true)
         } catch (e: Throwable) {
             loadingLiveData.postValue(LoadingData(isLoading = false))
@@ -118,7 +124,7 @@ class RestoreBackupDataSource @Inject constructor(
 
     suspend fun restoreKeysWithRawKey(rawKey: String) {
         try {
-            val keyData = ssssDataSource.getRecoveryKeyFromFileKey(rawKey, progressObserver)
+            val keyData = ssssDataSource.getSecretKeyDataKeyFromFileKey(rawKey, progressObserver)
             restoreKeysWithRecoveryKey(keyData)
         } catch (e: Throwable) {
             loadingLiveData.postValue(LoadingData(isLoading = false))
@@ -130,7 +136,7 @@ class RestoreBackupDataSource @Inject constructor(
     suspend fun restoreKeysWithRecoveryKey(uri: Uri) {
         try {
             val key = readRecoveryKeyFile(uri)
-            val keyData = ssssDataSource.getRecoveryKeyFromFileKey(key, progressObserver)
+            val keyData = ssssDataSource.getSecretKeyDataKeyFromFileKey(key, progressObserver)
             restoreKeysWithRecoveryKey(keyData)
         } catch (e: Throwable) {
             loadingLiveData.postValue(LoadingData(isLoading = false))
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/SSSSDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/SSSSDataSource.kt
index 142ffd9c2589611e7785cd3fca601bc81297cb3a..3b02df39af3182bfa78853d5536c248705ba3684 100644
--- a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/SSSSDataSource.kt
+++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/SSSSDataSource.kt
@@ -1,14 +1,13 @@
 package org.futo.circles.auth.feature.pass_phrase.restore
 
 import org.futo.circles.auth.bsspeke.BSSpekeClientProvider
-import org.futo.circles.auth.model.KeyData
+import org.futo.circles.auth.model.SecretKeyData
+import org.futo.circles.core.provider.KeyStoreProvider
 import org.futo.circles.core.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.listeners.ProgressListener
 import org.matrix.android.sdk.api.listeners.StepProgressListener
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
-import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
-import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
 import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
 import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
 import org.matrix.android.sdk.api.session.securestorage.KeyInfo
@@ -16,13 +15,15 @@ import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
 import org.matrix.android.sdk.api.session.securestorage.KeyRef
 import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
 import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
-import org.matrix.android.sdk.api.util.fromBase64
+import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
 import org.matrix.android.sdk.api.util.toBase64NoPadding
 import javax.inject.Inject
 
-class SSSSDataSource @Inject constructor() {
+class SSSSDataSource @Inject constructor(
+    private val keyStoreProvider: KeyStoreProvider
+) {
 
-    suspend fun storeBsSpekeKeyIntoSSSS(keyBackupPrivateKey: ByteArray): KeyData {
+    suspend fun storeBsSpekeKeyIntoSSSS(keyBackupPrivateKey: ByteArray): SsssKeySpec {
         val session = MatrixSessionProvider.getSessionOrThrow()
         val bsSpekeClient = BSSpekeClientProvider.getClientOrThrow()
         val keyId = bsSpekeClient.generateKeyId()
@@ -30,10 +31,10 @@ class SSSSDataSource @Inject constructor() {
         val keyInfo = session.sharedSecretStorageService()
             .generateBsSpekeKeyInfo(keyId, key, EmptyKeySigner())
         storeSecret(session, keyBackupPrivateKey, keyInfo)
-        return KeyData(keyInfo.recoveryKey, keyInfo.keySpec)
+        return keyInfo.keySpec
     }
 
-    suspend fun getBsSpekeRecoveryKey(progressObserver: StepProgressListener): KeyData {
+    suspend fun getBsSpekeSecretKeyData(progressObserver: StepProgressListener): SecretKeyData {
         progressObserver.onStepProgress(
             StepProgressListener.Step.ComputingKey(0, 0)
         )
@@ -41,10 +42,11 @@ class SSSSDataSource @Inject constructor() {
         val keySpec = RawBytesKeySpec(
             BSSpekeClientProvider.getClientOrThrow().generateHashKey()
         )
+
         val secret = getSecret(keyInfo, keySpec)
             ?: throw Exception("Backup could not be decrypted with this passphrase")
 
-        return KeyData(computeRecoveryKey(secret.fromBase64()), keySpec)
+        return SecretKeyData(keyInfo.id, secret, keySpec)
     }
 
     suspend fun replaceBsSpeke4SKey() {
@@ -57,10 +59,10 @@ class SSSSDataSource @Inject constructor() {
         storeBsSpekeKeyIntoSSSS(secret)
     }
 
-    suspend fun getRecoveryKeyFromPassphrase(
+    suspend fun getSecretKeyDataFromPassphrase(
         passphrase: String,
         progressObserver: StepProgressListener
-    ): KeyData {
+    ): SecretKeyData {
         val keyInfo = getKeyInfo()
 
         progressObserver.onStepProgress(
@@ -82,13 +84,13 @@ class SSSSDataSource @Inject constructor() {
         val secret = getSecret(keyInfo, keySpec)
             ?: throw Exception("Backup could not be decrypted with this passphrase")
 
-        return KeyData(computeRecoveryKey(secret.fromBase64()), keySpec)
+        return SecretKeyData(keyInfo.id, secret, keySpec)
     }
 
-    suspend fun getRecoveryKeyFromFileKey(
+    suspend fun getSecretKeyDataKeyFromFileKey(
         recoveryKey: String,
         progressObserver: StepProgressListener
-    ): KeyData {
+    ): SecretKeyData {
         val keyInfo = getKeyInfo()
 
         progressObserver.onStepProgress(
@@ -100,7 +102,7 @@ class SSSSDataSource @Inject constructor() {
         val secret = getSecret(keyInfo, keySpec)
             ?: throw Exception("Backup could not be decrypted with this recovery key")
 
-        return KeyData(computeRecoveryKey(secret.fromBase64()), keySpec)
+        return SecretKeyData(keyInfo.id, secret, keySpec)
     }
 
     private suspend fun storeSecret(
@@ -115,6 +117,11 @@ class SSSSDataSource @Inject constructor() {
         )
         session.cryptoService().keysBackupService()
             .onSecretKeyGossip(keyBackupPrivateKey.toBase64NoPadding())
+
+        keyStoreProvider.storeBsSpekePrivateKey(
+            (keyInfo.keySpec as RawBytesKeySpec).privateKey,
+            keyInfo.keyId
+        )
         session.sharedSecretStorageService().setDefaultKey(keyInfo.keyId)
     }
 
diff --git a/auth/src/main/java/org/futo/circles/auth/model/KeyData.kt b/auth/src/main/java/org/futo/circles/auth/model/KeyData.kt
deleted file mode 100644
index a565dd9d4e711904bf12cac02669656b81034699..0000000000000000000000000000000000000000
--- a/auth/src/main/java/org/futo/circles/auth/model/KeyData.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.futo.circles.auth.model
-
-import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
-
-data class KeyData(
-    val recoveryKey: String,
-    val keySpec: SsssKeySpec
-)
\ No newline at end of file
diff --git a/auth/src/main/java/org/futo/circles/auth/model/SecretKeyData.kt b/auth/src/main/java/org/futo/circles/auth/model/SecretKeyData.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c1bf051cb10dbe59de05cbf0b3439717cc235c8b
--- /dev/null
+++ b/auth/src/main/java/org/futo/circles/auth/model/SecretKeyData.kt
@@ -0,0 +1,18 @@
+package org.futo.circles.auth.model
+
+import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupRecoveryKey
+import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
+import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
+import org.matrix.android.sdk.api.util.fromBase64
+
+data class SecretKeyData(
+    val keyId: String,
+    val secretBase64: String,
+    val keySpec: SsssKeySpec
+) {
+    fun getBackupRecoveryKey(): BackupRecoveryKey {
+        val recoveryBase58 = computeRecoveryKey(secretBase64.fromBase64())
+        return BackupRecoveryKey.fromBase58(recoveryBase58)
+    }
+
+}
\ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
index a4ba3eacb2b2283927ee7aaf93acaac58e3b54b0..5f4cb10d735bf1110a725f3b46a82882c44227a5 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -55,7 +55,7 @@ android {
 
 dependencies {
     api 'androidx.appcompat:appcompat:1.6.1'
-    api 'com.google.android.material:material:1.11.0'
+    api 'com.google.android.material:material:1.12.0'
     api 'androidx.recyclerview:recyclerview:1.3.2'
     api "androidx.autofill:autofill:1.1.0"
 
@@ -63,7 +63,7 @@ dependencies {
     api 'androidx.core:core-ktx:1.13.1'
 
     //androidx lifecycle
-    def lifecycle_version = "2.7.0"
+    def lifecycle_version = "2.8.0"
     api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
     api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
     api "androidx.lifecycle:lifecycle-process:$lifecycle_version"
@@ -80,12 +80,12 @@ dependencies {
     kapt "com.google.dagger:hilt-compiler:$rootProject.ext.hilt_version"
 
     //Matrix release
-    api('org.futo.gitlab.circles:matrix-android-sdk:v1.6.10.35@aar') {
+    api('org.futo.gitlab.circles:matrix-android-sdk:v1.6.10.38@aar') {
         transitive = true
     }
 
     //Matrix mavenLocal testing
-    //api "org.futo.gitlab.circles:matrix-android-sdk:0.1"
+    //api "org.futo.gitlab.circles:matrix-android-sdk:0.1.57"
 
     //Retrofit2
     def retrofit_version = '2.11.0'
@@ -117,7 +117,7 @@ dependencies {
     api "io.noties.markwon:linkify:$markwon_version"
     api "io.noties.markwon:ext-strikethrough:$markwon_version"
     api "io.noties.markwon:ext-tasklist:$markwon_version"
-    api 'io.element.android:wysiwyg:2.37.0'
+    api 'io.element.android:wysiwyg:2.37.2'
 
     //ExoPlayer
     def exoplayer_version = '1.3.1'
@@ -138,9 +138,10 @@ dependencies {
     implementation 'androidx.browser:browser:1.8.0'
 
     //Firebase
-    gplayImplementation "com.google.firebase:firebase-crashlytics-ktx:18.6.4"
-    gplayImplementation "com.google.firebase:firebase-analytics-ktx:21.6.2"
-    gplayImplementation "com.google.firebase:firebase-messaging-ktx:23.4.1"
+    gplayImplementation "com.google.firebase:firebase-crashlytics-ktx:19.0.0"
+    gplayImplementation "com.google.firebase:firebase-analytics-ktx:22.0.0"
+    gplayImplementation "com.google.firebase:firebase-messaging-ktx:24.0.0"
+    gplayImplementation 'com.google.android.gms:play-services-base:18.4.0'
 
     testImplementation 'junit:junit:4.13.2'
     androidTestImplementation 'androidx.test.ext:junit:1.1.5'
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index 3de53f08d1a3637f60e5f0f887e94cc3d855ba8a..9af6326419d89b2b53a9a7e44760504d81ed99ec 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.VIBRATE" />
 </manifest>
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt b/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt
index 175028eafd3cce4ee9b751aa760f079bf4c3ae95..e0d6dde162e6bbfd6476071d80cdfb774a1f502c 100644
--- a/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt
+++ b/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt
@@ -21,10 +21,14 @@ object NetworkObserver {
     fun isConnected() = internetConnectionFlow.value
 
     fun register(context: Context) {
-        internetConnectionFlow.value = isConnectedToInternet(context)
+        updateConnectionState(context)
         setInternetConnectionObserver(context) { internetConnectionFlow.value = it }
     }
 
+    fun updateConnectionState(context: Context) {
+        internetConnectionFlow.value = isConnectedToInternet(context)
+    }
+
     fun observe(lifecycleOwner: LifecycleOwner, onConnectionChanged: (Boolean) -> Unit) {
         lifecycleOwner.lifecycleScope.launch {
             lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
diff --git a/core/src/main/java/org/futo/circles/core/feature/autocomplete/AutocompletePopup.kt b/core/src/main/java/org/futo/circles/core/feature/autocomplete/AutocompletePopup.kt
index cd239fb63d1b3e21d6dbb7b6a53d4c39ae1d25e1..a5b181ac69a42213900d1d9dd40c6283892e2e25 100644
--- a/core/src/main/java/org/futo/circles/core/feature/autocomplete/AutocompletePopup.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/autocomplete/AutocompletePopup.kt
@@ -3,14 +3,11 @@ package org.futo.circles.core.feature.autocomplete
 import android.content.Context
 import android.graphics.Rect
 import android.graphics.drawable.Drawable
-import android.os.Build
 import android.view.Gravity
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.widget.PopupWindow
-import androidx.annotation.StyleRes
-import androidx.core.view.ViewCompat
 import androidx.core.widget.PopupWindowCompat
 import kotlin.math.min
 
@@ -110,7 +107,7 @@ internal class AutocompletePopup(private val mContext: Context) {
         }
 
     fun setElevation(elevationPx: Float) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mPopup.elevation = elevationPx
+        mPopup.elevation = elevationPx
     }
 
     var isOutsideTouchable: Boolean
@@ -155,24 +152,6 @@ internal class AutocompletePopup(private val mContext: Context) {
         mPopup.setBackgroundDrawable(d)
     }
 
-    @get:StyleRes
-    @get:Suppress("unused")
-    @set:Suppress("unused")
-    var animationStyle: Int
-        /**
-         * Returns the animation style that will be used when the popup window is
-         * shown or dismissed.
-         * @return Animation style that will be used.
-         */
-        get() = mPopup.animationStyle
-        /**
-         * Set an animation style to use when the popup window is shown or dismissed.
-         * @param animationStyle Animation style to use.
-         */
-        set(animationStyle) {
-            mPopup.animationStyle = animationStyle
-        }
-
     /**
      * Sets the popup's anchor view. This popup will always be positioned relative to
      * the anchor view when shown.
@@ -262,7 +241,7 @@ internal class AutocompletePopup(private val mContext: Context) {
      * will recalculate the popup's size and position.
      */
     fun show() {
-        if (!ViewCompat.isAttachedToWindow(anchorView!!)) return
+        if (anchorView!!.isAttachedToWindow.not()) return
         val height = buildDropDown()
         val noInputMethod = isInputMethodNotNeeded
         val mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL
@@ -285,8 +264,7 @@ internal class AutocompletePopup(private val mContext: Context) {
             // for any value you do not want to update.
 
             // Width.
-            var widthSpec: Int
-            widthSpec = if (width == ViewGroup.LayoutParams.MATCH_PARENT) {
+            var widthSpec: Int = if (width == ViewGroup.LayoutParams.MATCH_PARENT) {
                 -1
             } else if (width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                 anchorView!!.width
@@ -297,8 +275,7 @@ internal class AutocompletePopup(private val mContext: Context) {
             widthSpec = if (widthSpec < 0) -1 else widthSpec
 
             // Height.
-            var heightSpec: Int
-            heightSpec = if (this.height == ViewGroup.LayoutParams.MATCH_PARENT) {
+            var heightSpec: Int = if (this.height == ViewGroup.LayoutParams.MATCH_PARENT) {
                 if (noInputMethod) height else ViewGroup.LayoutParams.MATCH_PARENT
             } else if (this.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                 height
@@ -316,8 +293,7 @@ internal class AutocompletePopup(private val mContext: Context) {
                 mPopup.update(anchorView, mHorizontalOffset, mVerticalOffset, widthSpec, heightSpec)
             }
         } else {
-            var widthSpec: Int
-            widthSpec = if (width == ViewGroup.LayoutParams.MATCH_PARENT) {
+            var widthSpec: Int = if (width == ViewGroup.LayoutParams.MATCH_PARENT) {
                 ViewGroup.LayoutParams.MATCH_PARENT
             } else if (width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                 anchorView!!.width
@@ -325,8 +301,7 @@ internal class AutocompletePopup(private val mContext: Context) {
                 width
             }
             widthSpec = Math.min(widthSpec, mMaxWidth)
-            var heightSpec: Int
-            heightSpec = if (this.height == ViewGroup.LayoutParams.MATCH_PARENT) {
+            var heightSpec: Int = if (this.height == ViewGroup.LayoutParams.MATCH_PARENT) {
                 ViewGroup.LayoutParams.MATCH_PARENT
             } else if (this.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                 height
@@ -380,7 +355,7 @@ internal class AutocompletePopup(private val mContext: Context) {
          * @return `true` if the popup is currently showing, `false` otherwise.
          */
         get() = mPopup.isShowing
-    val isInputMethodNotNeeded: Boolean
+    private val isInputMethodNotNeeded: Boolean
         /**
          * @return `true` if this popup is configured to assume the user does not need
          * to interact with the IME while it is showing, `false` otherwise.
@@ -433,12 +408,9 @@ internal class AutocompletePopup(private val mContext: Context) {
 
         // Redefine dimensions taking into account maxWidth and maxHeight.
         val ignoreBottomDecorations = mPopup.inputMethodMode == PopupWindow.INPUT_METHOD_NOT_NEEDED
-        val maxContentHeight =
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) mPopup.getMaxAvailableHeight(
-                anchorView!!, mVerticalOffset, ignoreBottomDecorations
-            ) else mPopup.getMaxAvailableHeight(
-                anchorView!!, mVerticalOffset
-            )
+        val maxContentHeight = mPopup.getMaxAvailableHeight(
+            anchorView!!, mVerticalOffset, ignoreBottomDecorations
+        )
         val maxContentWidth = mContext.resources.displayMetrics.widthPixels - paddingHoriz
         mMaxHeight = min(maxContentHeight + paddingVert, mUserMaxHeight)
         mMaxWidth = min(maxContentWidth + paddingHoriz, mUserMaxWidth)
@@ -471,6 +443,6 @@ internal class AutocompletePopup(private val mContext: Context) {
         if (viewHeight > 0) {
             otherHeights += paddingVert + mView!!.paddingTop + mView!!.paddingBottom
         }
-        return Math.min(viewHeight + otherHeights, mMaxHeight)
+        return min(viewHeight + otherHeights, mMaxHeight)
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt b/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..d292d9e640fd6a5d7037bad02f992dc9f2fcf0c3
--- /dev/null
+++ b/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt
@@ -0,0 +1,81 @@
+package org.futo.circles.core.provider
+
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import android.util.Base64
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import java.security.KeyStore
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.SecretKey
+import javax.crypto.spec.GCMParameterSpec
+import javax.inject.Inject
+
+
+class KeyStoreProvider @Inject constructor(
+    private val preferencesProvider: PreferencesProvider
+) {
+    private val keyStore
+        get() = KeyStore.getInstance(ANDROID_KEYS_TORE).apply { load(null) }
+
+    fun storeBsSpekePrivateKey(keyBytes: ByteArray, keyId: String) {
+        tryOrNull {
+            val alias = "$ORG_FUTO_SSSS_KEY_PREFIX.$keyId"
+            val masterKey = getOrGenerateMasterKeyIfNotExist(alias)
+                ?: throw IllegalArgumentException("Failed to get master key")
+            val encrypted = encryptData(keyBytes, masterKey)
+            preferencesProvider.storeBSspekeEncryptedPrivateKeyBase64(alias, encrypted)
+        }
+    }
+
+    fun getBsSpekePrivateKey(keyId: String): ByteArray? = tryOrNull {
+        val alias = "$ORG_FUTO_SSSS_KEY_PREFIX.$keyId"
+        val masterKey = getOrGenerateMasterKeyIfNotExist(alias)
+            ?: throw IllegalArgumentException("Failed to get master key")
+        val encrypted = preferencesProvider.getBSspekeEncryptedPrivateKeyBase64(alias)
+            ?: throw IllegalArgumentException("Not saved in preferences $alias")
+        decryptData(encrypted, masterKey)
+    }
+
+    private fun getOrGenerateMasterKeyIfNotExist(alias: String): SecretKey? {
+        if (!keyStore.containsAlias(alias)) {
+            KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYS_TORE).apply {
+                init(
+                    KeyGenParameterSpec.Builder(
+                        alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
+                    )
+                        .setKeySize(KEY_SIZE)
+                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                        .build()
+                )
+                generateKey()
+            }
+        }
+        return keyStore.getKey(alias, null) as? SecretKey
+    }
+
+    private fun encryptData(data: ByteArray, masterKey: SecretKey): String {
+        val cipher = Cipher.getInstance(AES_NO_PADDING)
+        cipher.init(Cipher.ENCRYPT_MODE, masterKey)
+        val ivString = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
+        val encryptedBytes: ByteArray = cipher.doFinal(data)
+        return ivString + IV_SEPARATOR + Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
+    }
+
+    private fun decryptData(encryptedData: String, masterKey: SecretKey): ByteArray {
+        val (iv64, encrypted64) = encryptedData.split(IV_SEPARATOR)
+        val cipher = Cipher.getInstance(AES_NO_PADDING)
+        val spec = GCMParameterSpec(KEY_SIZE, Base64.decode(iv64, Base64.DEFAULT))
+        cipher.init(Cipher.DECRYPT_MODE, masterKey, spec)
+        return cipher.doFinal(Base64.decode(encrypted64, Base64.DEFAULT))
+    }
+
+    companion object {
+        private const val AES_NO_PADDING = "AES/GCM/NoPadding"
+        private const val IV_SEPARATOR = "]"
+        private const val KEY_SIZE = 128
+        private const val ANDROID_KEYS_TORE = "AndroidKeyStore"
+        private const val ORG_FUTO_SSSS_KEY_PREFIX = "org.futo.ssss.key"
+    }
+}
diff --git a/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt b/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt
index 0be15e3a30d79d65e19254235a2eb961e332b530..5ba08a31f400e2f5de1e7ad76f3fecd0ef39e9f5 100644
--- a/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt
+++ b/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt
@@ -95,6 +95,15 @@ class PreferencesProvider @Inject constructor(
         getSharedPreferences().edit(true) { putStringSet(NOT_RESTORED_SESSION, set) }
     }
 
+    fun getBSspekeEncryptedPrivateKeyBase64(alias: String): String? {
+        return getSharedPreferences().getString(alias, null)
+    }
+
+    fun storeBSspekeEncryptedPrivateKeyBase64(alias: String, key: String) {
+        getSharedPreferences().edit { putString(alias, key) }
+    }
+
+
     companion object {
         private const val PREFERENCES_NAME = "circles_preferences"
         private const val DEV_MODE_KEY = "developer_mode"
diff --git a/gallery/build.gradle b/gallery/build.gradle
index 445bdc29aa4fda3e756c8c2d7537e92479efeabf..2f1f082c0bd6488183cdbe5900b9ebdcc33392bb 100644
--- a/gallery/build.gradle
+++ b/gallery/build.gradle
@@ -14,8 +14,6 @@ android {
         minSdk rootProject.ext.min_sdk_version
         targetSdk rootProject.ext.sdk_version
 
-        missingDimensionStrategy "store", "gplay", "fdroid"
-
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles "consumer-rules.pro"
     }
@@ -30,6 +28,16 @@ android {
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
         }
     }
+    def flavor_dimension_name = "store"
+    flavorDimensions.add(flavor_dimension_name)
+    productFlavors {
+        gplay {
+            dimension flavor_dimension_name
+        }
+        fdroid {
+            dimension flavor_dimension_name
+        }
+    }
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_17
         targetCompatibility JavaVersion.VERSION_17
@@ -63,25 +71,36 @@ kapt {
     correctErrorTypes = true
 }
 
-
 afterEvaluate {
-    publishing.publications.create("release", MavenPublication) {
-        groupId rootProject.ext.modules_groupId
-        artifactId "gallery"
-        version rootProject.ext.modules_version
+    android.libraryVariants.each { variant ->
+        if (variant.buildType.name != "release") {
+            return
+        }
+        publishing.publications.create(variant.name, MavenPublication) {
+            groupId rootProject.ext.modules_groupId
+            artifactId "gallery_${variant.flavorName}"
+            version rootProject.ext.modules_version
 
-        pom.withXml {
-            def dependenciesNode = asNode().appendNode('dependencies')
-            ext.addDependency = { Dependency dep, String scope ->
-                if (dep.group == null || dep.name == null || dep.name == "unspecified" || dep.version == "unspecified") return
-                final dependencyNode = dependenciesNode.appendNode('dependency')
-                dependencyNode.appendNode('groupId', dep.group)
-                dependencyNode.appendNode('artifactId', dep.name)
-                dependencyNode.appendNode('version', dep.version)
-                dependencyNode.appendNode('scope', scope)
+            pom.withXml {
+                def dependenciesNode = asNode().appendNode('dependencies')
+                ext.addDependency = { Dependency dep, String scope ->
+                    if (dep.group == null || dep.name == null || dep.name == "unspecified" || dep.version == "unspecified") return
+                    final dependencyNode = dependenciesNode.appendNode('dependency')
+                    dependencyNode.appendNode('groupId', dep.group)
+                    dependencyNode.appendNode('artifactId', dep.name)
+                    dependencyNode.appendNode('version', dep.version)
+                    dependencyNode.appendNode('scope', scope)
+                }
+                configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
+                configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
+                if (variant.flavorName == "gplay") {
+                    configurations.gplayApi.getDependencies().each { dep -> addDependency(dep, "compile") }
+                    configurations.gplayImplementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
+                } else if (variant.flavorName == "fdroid") {
+                    configurations.fdroidApi.getDependencies().each { dep -> addDependency(dep, "compile") }
+                    configurations.fdroidImplementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
+                }
             }
-            configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
-            configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
         }
     }
 }
\ No newline at end of file
diff --git a/jitpack.yml b/jitpack.yml
index 4f0cb798b8152401a753aff2aa502d542416fafc..39f456d05dfd577f223c6d3d4f85428326a56eee 100644
--- a/jitpack.yml
+++ b/jitpack.yml
@@ -1,15 +1,26 @@
 jdk:
   - openjdk11
 install:
+  - GROUP_ID="-DgroupId=org.futo.gitlab.circles"
+  - VERSION="-Dversion=1.0.12"
+  - PACKAGING="-Dpackaging=aar"
+
   - FILE_AUTH_GPLAY="-Dfile=auth-gplay-release.aar"
-  - mvn install:install-file $FILE_AUTH_GPLAY -DgroupId=org.futo.gitlab.circles -DartifactId=auth_gplay -Dversion=1.0.12 -Dpackaging=aar -DpomFile=pom_auth_gplay.xml
+  - mvn install:install-file $FILE_AUTH_GPLAY $GROUP_ID -DartifactId=auth_gplay $VERSION $PACKAGING -DpomFile=pom_auth_gplay.xml
   - FILE_AUTH_FDROID="-Dfile=auth-fdroid-release.aar"
-  - mvn install:install-file $FILE_AUTH_FDROID -DgroupId=org.futo.gitlab.circles -DartifactId=auth_fdroid -Dversion=1.0.12 -Dpackaging=aar -DpomFile=pom_auth_fdroid.xml
+  - mvn install:install-file $FILE_AUTH_FDROID $GROUP_ID -DartifactId=auth_fdroid $VERSION $PACKAGING -DpomFile=pom_auth_fdroid.xml
+
   - FILE_CORE_GPLAY="-Dfile=core-gplay-release.aar"
-  - mvn install:install-file $FILE_CORE_GPLAY -DgroupId=org.futo.gitlab.circles -DartifactId=core_gplay -Dversion=1.0.12 -Dpackaging=aar -DpomFile=pom_core_gplay.xml
+  - mvn install:install-file $FILE_CORE_GPLAY $GROUP_ID -DartifactId=core_gplay $VERSION $PACKAGING -DpomFile=pom_core_gplay.xml
   - FILE_CORE_FDROID="-Dfile=core-fdroid-release.aar"
-  - mvn install:install-file $FILE_CORE_FDROID -DgroupId=org.futo.gitlab.circles -DartifactId=core_fdroid -Dversion=1.0.12 -Dpackaging=aar -DpomFile=pom_core_fdroid.xml
-  - FILE_GALLERY="-Dfile=gallery-release.aar"
-  - mvn install:install-file $FILE_GALLERY -DgroupId=org.futo.gitlab.circles -DartifactId=gallery -Dversion=1.0.12 -Dpackaging=aar -DpomFile=pom_gallery.xml
-  - FILE_SETTINGS="-Dfile=settings-release.aar"
-  - mvn install:install-file $FILE_SETTINGS -DgroupId=org.futo.gitlab.circles -DartifactId=settings -Dversion=1.0.12 -Dpackaging=aar -DpomFile=pom_settings.xml
+  - mvn install:install-file $FILE_CORE_FDROID $GROUP_ID -DartifactId=core_fdroid $VERSION $PACKAGING -DpomFile=pom_core_fdroid.xml
+
+  - FILE_GALLERY_GPLAY="-Dfile=gallery-gplay-release.aar"
+  - mvn install:install-file $FILE_GALLERY_GPLAY $GROUP_ID -DartifactId=gallery_gplay $VERSION $PACKAGING -DpomFile=pom_gallery_gplay.xml
+  - FILE_GALLERY_FDROID="-Dfile=gallery-fdroid-release.aar"
+  - mvn install:install-file $FILE_GALLERY_FDROID $GROUP_ID -DartifactId=gallery_fdroid $VERSION $PACKAGING -DpomFile=pom_gallery_fdroid.xml
+
+  - FILE_SETTINGS_GPLAY="-Dfile=settings-gplay-release.aar"
+  - mvn install:install-file $FILE_SETTINGS_GPLAY $GROUP_ID -DartifactId=settings_gplay $VERSION $PACKAGING -DpomFile=pom_settings_gplay.xml
+  - FILE_SETTINGS_FDROID="-Dfile=settings-fdroid-release.aar"
+  - mvn install:install-file $FILE_SETTINGS_FDROID $GROUP_ID -DartifactId=settings_fdroid $VERSION $PACKAGING -DpomFile=pom_settings_fdroid.xml
diff --git a/modules_release.sh b/modules_release.sh
index 39cb7c9ec2df6b49074c3261a76c1f252462f5c1..46e77f5404d04cd6c40c074ce7799915d188c987 100644
--- a/modules_release.sh
+++ b/modules_release.sh
@@ -24,24 +24,39 @@ awk -v version="$version_name" '/-Dversion=/ {gsub(/-Dversion=[^ ]+/, "-Dversion
 # Generate POM file for release publication
 ./gradlew :core:generatePomFileForGplayReleasePublication
 ./gradlew :core:generatePomFileForFdroidReleasePublication
-./gradlew :gallery:generatePomFileForReleasePublication
+
+./gradlew :gallery:generatePomFileForGplayReleasePublication
+./gradlew :gallery:generatePomFileForFdroidReleasePublication
+
 ./gradlew :auth:generatePomFileForGplayReleasePublication
 ./gradlew :auth:generatePomFileForFdroidReleasePublication
-./gradlew :settings:generatePomFileForReleasePublication
+
+./gradlew :settings:generatePomFileForGplayReleasePublication
+./gradlew :settings:generatePomFileForFdroidReleasePublication
 
 
 # Move .aar to the root directory
 mv core/build/outputs/aar/core-fdroid-release.aar core-fdroid-release.aar
 mv core/build/outputs/aar/core-gplay-release.aar core-gplay-release.aar
-mv gallery/build/outputs/aar/gallery-release.aar gallery-release.aar
+
+mv gallery/build/outputs/aar/gallery-fdroid-release.aar gallery-fdroid-release.aar
+mv gallery/build/outputs/aar/gallery-gplay-release.aar gallery-gplay-release.aar
+
 mv auth/build/outputs/aar/auth-fdroid-release.aar auth-fdroid-release.aar
 mv auth/build/outputs/aar/auth-gplay-release.aar auth-gplay-release.aar
-mv settings/build/outputs/aar/settings-release.aar settings-release.aar
+
+mv settings/build/outputs/aar/settings-fdroid-release.aar settings-fdroid-release.aar
+mv settings/build/outputs/aar/settings-gplay-release.aar settings-gplay-release.aar
 
 # Move poms to the root directory and rename
 mv core/build/publications/gplayRelease/pom-default.xml pom_core_gplay.xml
 mv core/build/publications/fdroidRelease/pom-default.xml pom_core_fdroid.xml
-mv gallery/build/publications/release/pom-default.xml pom_gallery.xml
+
+mv gallery/build/publications/gplayRelease/pom-default.xml pom_gallery_gplay.xml
+mv gallery/build/publications/fdroidRelease/pom-default.xml pom_gallery_fdroid.xml
+
 mv auth/build/publications/gplayRelease/pom-default.xml pom_auth_gplay.xml
 mv auth/build/publications/fdroidRelease/pom-default.xml pom_auth_fdroid.xml
-mv settings/build/publications/release/pom-default.xml pom_settings.xml
\ No newline at end of file
+
+mv settings/build/publications/gplayRelease/pom-default.xml pom_settings_gplay.xml
+mv settings/build/publications/fdroidRelease/pom-default.xml pom_settings_fdroid.xml
\ No newline at end of file
diff --git a/modules_release_clean.sh b/modules_release_clean.sh
index 76e3c949fe71141677ffb6c9a7368bba130677a0..bf9ddd201214c10943f254ee75085f3dbacc27b4 100755
--- a/modules_release_clean.sh
+++ b/modules_release_clean.sh
@@ -2,14 +2,24 @@
 # Remove .aar and pom.xml from the root directory if they exist
 rm -f auth-gplay-release.aar
 rm -f auth-fdroid-release.aar
+
 rm -f core-gplay-release.aar
 rm -f core-fdroid-release.aar
-rm -f gallery-release.aar
-rm -f settings-release.aar
+
+rm -f gallery-gplay-release.aar
+rm -f gallery-fdroid-release.aar
+
+rm -f settings-gplay-release.aar
+rm -f settings-fdroid-release.aar
 
 rm -f pom_auth_gplay.xml
 rm -f pom_auth_fdroid.xml
+
 rm -f pom_core_gplay.xml
 rm -f pom_core_fdroid.xml
-rm -f pom_gallery.xml
-rm -f pom_settings.xml
\ No newline at end of file
+
+rm -f pom_gallery_gplay.xml
+rm -f pom_gallery_fdroid.xml
+
+rm -f pom_settings_gplay.xml
+rm -f pom_settings_fdroid.xml
\ No newline at end of file
diff --git a/settings/build.gradle b/settings/build.gradle
index 7aa6fc1865f065f645bdf6b02d2636780c0bfefd..5c1a826dcf14bd24399add263f0444bd23c4f087 100644
--- a/settings/build.gradle
+++ b/settings/build.gradle
@@ -14,8 +14,6 @@ android {
         minSdk rootProject.ext.min_sdk_version
         targetSdk rootProject.ext.sdk_version
 
-        missingDimensionStrategy "store", "gplay", "fdroid"
-
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         consumerProguardFiles "consumer-rules.pro"
     }
@@ -30,6 +28,16 @@ android {
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
         }
     }
+    def flavor_dimension_name = "store"
+    flavorDimensions.add(flavor_dimension_name)
+    productFlavors {
+        gplay {
+            dimension flavor_dimension_name
+        }
+        fdroid {
+            dimension flavor_dimension_name
+        }
+    }
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_17
         targetCompatibility JavaVersion.VERSION_17
@@ -64,25 +72,36 @@ kapt {
     correctErrorTypes = true
 }
 
-
 afterEvaluate {
-    publishing.publications.create("release", MavenPublication) {
-        groupId rootProject.ext.modules_groupId
-        artifactId "settings"
-        version rootProject.ext.modules_version
+    android.libraryVariants.each { variant ->
+        if (variant.buildType.name != "release") {
+            return
+        }
+        publishing.publications.create(variant.name, MavenPublication) {
+            groupId rootProject.ext.modules_groupId
+            artifactId "settings_${variant.flavorName}"
+            version rootProject.ext.modules_version
 
-        pom.withXml {
-            def dependenciesNode = asNode().appendNode('dependencies')
-            ext.addDependency = { Dependency dep, String scope ->
-                if (dep.group == null || dep.name == null || dep.name == "unspecified" || dep.version == "unspecified") return
-                final dependencyNode = dependenciesNode.appendNode('dependency')
-                dependencyNode.appendNode('groupId', dep.group)
-                dependencyNode.appendNode('artifactId', dep.name)
-                dependencyNode.appendNode('version', dep.version)
-                dependencyNode.appendNode('scope', scope)
+            pom.withXml {
+                def dependenciesNode = asNode().appendNode('dependencies')
+                ext.addDependency = { Dependency dep, String scope ->
+                    if (dep.group == null || dep.name == null || dep.name == "unspecified" || dep.version == "unspecified") return
+                    final dependencyNode = dependenciesNode.appendNode('dependency')
+                    dependencyNode.appendNode('groupId', dep.group)
+                    dependencyNode.appendNode('artifactId', dep.name)
+                    dependencyNode.appendNode('version', dep.version)
+                    dependencyNode.appendNode('scope', scope)
+                }
+                configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
+                configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
+                if (variant.flavorName == "gplay") {
+                    configurations.gplayApi.getDependencies().each { dep -> addDependency(dep, "compile") }
+                    configurations.gplayImplementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
+                } else if (variant.flavorName == "fdroid") {
+                    configurations.fdroidApi.getDependencies().each { dep -> addDependency(dep, "compile") }
+                    configurations.fdroidImplementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
+                }
             }
-            configurations.api.getDependencies().each { dep -> addDependency(dep, "compile") }
-            configurations.implementation.getDependencies().each { dep -> addDependency(dep, "runtime") }
         }
     }
 }
\ No newline at end of file
diff --git a/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsDataSource.kt b/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsDataSource.kt
index 200e7957bacd98a395381d66b13eb088d16d646f..f3f8a057aeac6c75cc27d1bc7b0be625a30e80cc 100644
--- a/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsDataSource.kt
+++ b/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsDataSource.kt
@@ -19,8 +19,6 @@ import org.futo.circles.settings.model.ActiveSession
 import org.futo.circles.settings.model.ActiveSessionListItem
 import org.futo.circles.settings.model.SessionHeader
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
-import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
-import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 class ActiveSessionsDataSource @Inject constructor(
@@ -36,56 +34,54 @@ class ActiveSessionsDataSource @Inject constructor(
         MutableStateFlow(mutableSetOf())
 
 
+    suspend fun refreshDevicesList() {
+        session.cryptoService().downloadKeysIfNeeded(listOf(session.myUserId), true)
+    }
+
     fun getActiveSessionsFlow(): Flow<List<ActiveSessionListItem>> {
         return combine(
-            session.cryptoService().getMyDevicesInfoLive().asFlow(),
             session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow(),
             itemsWithVisibleOptionsFlow
-        ) { infoList, cryptoList, devicesWithVisibleOptions ->
-            buildList(infoList, cryptoList, devicesWithVisibleOptions)
+        ) { cryptoList, devicesWithVisibleOptions ->
+            buildList(cryptoList, devicesWithVisibleOptions)
         }.flowOn(Dispatchers.IO).distinctUntilChanged()
     }
 
 
     private fun buildList(
-        infoList: List<DeviceInfo>,
         cryptoList: List<CryptoDeviceInfo>,
         sessionsWithVisibleOptions: Set<String>
     ): List<ActiveSessionListItem> {
-        val devicesList = infoList.mapNotNull { deviceInfo ->
-            val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
-            cryptoDeviceInfo?.let { deviceInfo to it }
-        }.sortedByDescending { it.first.lastSeenTs }
-
-        val currentSession =
-            devicesList.firstOrNull { it.second.deviceId == MatrixSessionProvider.currentSession?.sessionParams?.deviceId }
-                ?: return emptyList()
-        val otherSessions = devicesList.toMutableList().apply { remove(currentSession) }
-            .filter { !isSessionInactive(it.first.lastSeenTs) }
+        val currentSession = cryptoList.firstOrNull {
+            it.deviceId == MatrixSessionProvider.currentSession?.sessionParams?.deviceId
+        } ?: return emptyList()
+
+        val otherSessions = cryptoList.toMutableList().apply { remove(currentSession) }
+
         val isCurrentSessionVerified =
-            currentSession.second.trustLevel?.isCrossSigningVerified() == true
+            currentSession.trustLevel?.isCrossSigningVerified() == true
 
         val sessionsList =
             mutableListOf<ActiveSessionListItem>(SessionHeader(context.getString(R.string.current_session)))
         sessionsList.add(
             ActiveSession(
-                deviceInfo = currentSession.first,
-                cryptoDeviceInfo = currentSession.second,
+                cryptoDeviceInfo = currentSession,
                 canVerify = !isCurrentSessionVerified && otherSessions.isNotEmpty(),
                 isResetKeysVisible = !isCurrentSessionVerified,
-                isOptionsVisible = sessionsWithVisibleOptions.contains(currentSession.second.deviceId)
+                isOptionsVisible = sessionsWithVisibleOptions.contains(currentSession.deviceId)
             )
         )
         if (otherSessions.isNotEmpty()) {
             sessionsList.add(SessionHeader(context.getString(R.string.other_sessions)))
-            sessionsList.addAll(otherSessions.map {
-                ActiveSession(
-                    deviceInfo = it.first,
-                    cryptoDeviceInfo = it.second,
-                    canVerify = isCurrentSessionVerified && it.second.trustLevel?.isCrossSigningVerified() != true,
-                    isResetKeysVisible = false,
-                    isOptionsVisible = sessionsWithVisibleOptions.contains(it.second.deviceId)
-                )
+            sessionsList.addAll(otherSessions.mapNotNull {
+                if (!it.isDehydrated) {
+                    ActiveSession(
+                        cryptoDeviceInfo = it,
+                        canVerify = isCurrentSessionVerified && it.trustLevel?.isCrossSigningVerified() != true,
+                        isResetKeysVisible = false,
+                        isOptionsVisible = sessionsWithVisibleOptions.contains(it.deviceId)
+                    )
+                } else null
             }
             )
         }
@@ -101,16 +97,4 @@ class ActiveSessionsDataSource @Inject constructor(
             .initializeCrossSigning(authConfirmationProvider)
     }
 
-    private fun isSessionInactive(lastSeenTsMillis: Long?): Boolean =
-        if (lastSeenTsMillis == null || lastSeenTsMillis <= 0) {
-            false
-        } else {
-            val diffMilliseconds = System.currentTimeMillis() - lastSeenTsMillis
-            diffMilliseconds >= TimeUnit.DAYS.toMillis(SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS)
-        }
-
-    companion object {
-        private const val SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS = 30L
-    }
-
 }
\ No newline at end of file
diff --git a/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsViewModel.kt b/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsViewModel.kt
index b2f3db9872916ca33d15bbb1b35d96324bdc109d..c2e296fe7b48a36d1ef23ef1392cd90836835198 100644
--- a/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsViewModel.kt
+++ b/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/ActiveSessionsViewModel.kt
@@ -41,6 +41,9 @@ class ActiveSessionsViewModel @Inject constructor(
         }
     }
 
+    init {
+        launchBg { dataSource.refreshDevicesList() }
+    }
 
     fun onSessionClicked(deviceId: String) {
         dataSource.toggleOptionsVisibilityFor(deviceId)
diff --git a/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/list/ActiveSessionsViewHolder.kt b/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/list/ActiveSessionsViewHolder.kt
index ee21a65a2a440f6759f0044120db81e0a269ac5a..9ca96e7dd45de7bb0d133c7c719f4c0df8198e32 100644
--- a/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/list/ActiveSessionsViewHolder.kt
+++ b/settings/src/main/java/org/futo/circles/settings/feature/active_sessions/list/ActiveSessionsViewHolder.kt
@@ -31,7 +31,7 @@ class SessionItemViewHolder(
         with(binding) {
             lRoot.setOnClickListener { activeSessionClickListener.onItemClicked(data.id) }
 
-            tvDeviceName.text = data.deviceInfo.displayName ?: data.id
+            tvDeviceName.text = data.cryptoDeviceInfo.displayName() ?: data.id
             tvDeviceId.text = data.cryptoDeviceInfo.deviceId
             vInfo.setData(data, activeSessionClickListener)
             vInfo.setIsVisible(data.isOptionsVisible)
diff --git a/settings/src/main/java/org/futo/circles/settings/model/ActiveSessionListItem.kt b/settings/src/main/java/org/futo/circles/settings/model/ActiveSessionListItem.kt
index f2ae29c621a2a003cab9d2fb785a4ce12783744b..5956757eb90a43be51f6130bd68097ba38621aa5 100644
--- a/settings/src/main/java/org/futo/circles/settings/model/ActiveSessionListItem.kt
+++ b/settings/src/main/java/org/futo/circles/settings/model/ActiveSessionListItem.kt
@@ -3,7 +3,6 @@ package org.futo.circles.settings.model
 import org.futo.circles.core.base.list.IdEntity
 import org.futo.circles.core.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
-import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 
 sealed class ActiveSessionListItem : IdEntity<String>
 
@@ -14,7 +13,6 @@ data class SessionHeader(
 }
 
 data class ActiveSession(
-    val deviceInfo: DeviceInfo,
     val cryptoDeviceInfo: CryptoDeviceInfo,
     val canVerify: Boolean,
     val isResetKeysVisible: Boolean,