diff --git a/data/persistence-test/src/commonMain/kotlin/com/wire/kalium/persistence/config/TestUserConfigStorage.kt b/data/persistence-test/src/commonMain/kotlin/com/wire/kalium/persistence/config/TestUserConfigStorage.kt index e3a0d54d5cb..d7485a29f89 100644 --- a/data/persistence-test/src/commonMain/kotlin/com/wire/kalium/persistence/config/TestUserConfigStorage.kt +++ b/data/persistence-test/src/commonMain/kotlin/com/wire/kalium/persistence/config/TestUserConfigStorage.kt @@ -18,8 +18,172 @@ package com.wire.kalium.persistence.config import com.russhwolf.settings.MapSettings +import com.wire.kalium.persistence.dao.SupportedProtocolEntity import com.wire.kalium.persistence.kmmSettings.KaliumPreferencesSettings +import kotlinx.coroutines.flow.Flow fun inMemoryUserConfigStorage(): UserConfigStorage = UserConfigStorageImpl( KaliumPreferencesSettings(MapSettings()) ) + +@Suppress("TooManyFunctions") +class FakeUserConfigStorage( + val backingStorage: MutableMap = mutableMapOf() +) : UserConfigStorage { + override suspend fun persistAppLockStatus( + isEnforced: Boolean, + inactivityTimeoutSecs: Int, + isStatusChanged: Boolean? + ) { + backingStorage["appLockStatus"] = AppLockConfigEntity( + inactivityTimeoutSecs = 60, + enforceAppLock = true, + isStatusChanged = false, + ) + } + + override suspend fun appLockStatus(): AppLockConfigEntity? { + return backingStorage["appLockStatus"] as? AppLockConfigEntity + } + + override fun appLockFlow(): Flow { + throw NotImplementedError() + } + + override suspend fun setTeamAppLockAsNotified() { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistFileSharingStatus(status: Boolean, isStatusChanged: Boolean?) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isFileSharingEnabled(): IsFileSharingEnabledEntity? { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override fun isFileSharingEnabledFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun setFileSharingAsNotified() { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override fun isClassifiedDomainsEnabledFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistClassifiedDomainsStatus(status: Boolean, classifiedDomains: List) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistSecondFactorPasswordChallengeStatus(isRequired: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isSecondFactorPasswordChallengeRequired(): Boolean { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistDefaultProtocol(protocol: SupportedProtocolEntity) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun defaultProtocol(): SupportedProtocolEntity { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun enableMLS(enabled: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isMLSEnabled(): Boolean { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun setE2EISettings(settingEntity: E2EISettingsEntity?) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun getE2EISettings(): E2EISettingsEntity? { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override fun e2EISettingsFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistConferenceCalling(enabled: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isConferenceCallingEnabled(): Boolean { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isConferenceCallingEnabledFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun shouldUseSftForOneOnOneCalls(): Boolean { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun areReadReceiptsEnabled(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistReadReceipts(enabled: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isTypingIndicatorEnabled(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistTypingIndicator(enabled: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistGuestRoomLinkFeatureFlag(status: Boolean, isStatusChanged: Boolean?) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isGuestRoomLinkEnabled(): IsGuestRoomLinkEnabledEntity? { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override fun isGuestRoomLinkEnabledFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun isScreenshotCensoringEnabledFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun persistScreenshotCensoring(enabled: Boolean) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun setIfAbsentE2EINotificationTime(timeStamp: Long) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun getE2EINotificationTime(): Long? { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun e2EINotificationTimeFlow(): Flow { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + + override suspend fun updateE2EINotificationTime(timeStamp: Long) { + throw NotImplementedError("Implement your fake logic if needed using the map provided.") + } + +} diff --git a/data/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt b/data/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..ecb3bbbea4f --- /dev/null +++ b/data/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt @@ -0,0 +1,52 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.config + +import android.content.Context +import com.wire.kalium.persistence.dao.UserIDEntity +import com.wire.kalium.persistence.kmmSettings.EncryptedSettingsPlatformParam +import com.wire.kalium.persistence.kmmSettings.KaliumPreferencesSettings +import com.wire.kalium.persistence.kmmSettings.SettingOptions +import com.wire.kalium.persistence.kmmSettings.buildSettings + +@Suppress("DEPRECATION") +@Deprecated( + "Scheduled for removal in future versions, User KMM Settings are now replaced by database implementation." + + "Just kept for migration purposes.", + ReplaceWith("No replacement available"), +) +actual class UserConfigStorageFactory actual constructor() { + /** + * Creates a [UserConfigStorage] instance for Android. + * @param userId The user ID entity + * @param shouldEncryptData Whether to encrypt the data + * @param platformParam Must be an Android [Context] + */ + actual fun create( + userId: UserIDEntity, + shouldEncryptData: Boolean, + platformParam: Any + ): UserConfigStorage { + require(platformParam is Context) { "platformParam must be an Android Context" } + val settings = buildSettings( + SettingOptions.UserSettings(shouldEncryptData, userId), + EncryptedSettingsPlatformParam(platformParam) + ) + return UserConfigStorageImpl(KaliumPreferencesSettings(settings)) + } +} diff --git a/data/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt b/data/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt deleted file mode 100644 index 282d2e4eacb..00000000000 --- a/data/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.kalium.persistence.kmmSettings - -import android.content.Context -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorage -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorageImpl -import com.wire.kalium.persistence.config.UserConfigStorage -import com.wire.kalium.persistence.config.UserConfigStorageImpl -import com.wire.kalium.persistence.dao.UserIDEntity - -actual class UserPrefBuilder( - userId: UserIDEntity, - context: Context, - shouldEncryptData: Boolean = true -) { - private val encryptedSettingsHolder = - KaliumPreferencesSettings( - buildSettings( - SettingOptions.UserSettings(shouldEncryptData = shouldEncryptData, userIDEntity = userId), - EncryptedSettingsPlatformParam(context) - ) - ) - - actual val lastRetrievedNotificationEventStorage: LastRetrievedNotificationEventStorage - get() = LastRetrievedNotificationEventStorageImpl(encryptedSettingsHolder) - - actual val userConfigStorage: UserConfigStorage = UserConfigStorageImpl(encryptedSettingsHolder) - - actual fun clear() { - encryptedSettingsHolder.nuke() - } -} diff --git a/data/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt b/data/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..0f28f35101b --- /dev/null +++ b/data/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt @@ -0,0 +1,51 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.config + +import com.wire.kalium.persistence.dao.UserIDEntity +import com.wire.kalium.persistence.kmmSettings.EncryptedSettingsPlatformParam +import com.wire.kalium.persistence.kmmSettings.KaliumPreferencesSettings +import com.wire.kalium.persistence.kmmSettings.SettingOptions +import com.wire.kalium.persistence.kmmSettings.buildSettings + +@Suppress("DEPRECATION") +@Deprecated( + "Scheduled for removal in future versions, User KMM Settings are now replaced by database implementation." + + "Just kept for migration purposes.", + ReplaceWith("No replacement available"), +) +actual class UserConfigStorageFactory actual constructor() { + /** + * Creates a [UserConfigStorage] instance for Apple platforms. + * @param userId The user ID entity + * @param shouldEncryptData Whether to encrypt the data (ignored on Apple, uses Keychain) + * @param platformParam Must be a [String] representing the service name for Keychain + */ + actual fun create( + userId: UserIDEntity, + shouldEncryptData: Boolean, + platformParam: Any + ): UserConfigStorage { + require(platformParam is String) { "platformParam must be a String (service name)" } + val settings = buildSettings( + SettingOptions.UserSettings(shouldEncryptData, userId), + EncryptedSettingsPlatformParam(platformParam) + ) + return UserConfigStorageImpl(KaliumPreferencesSettings(settings)) + } +} diff --git a/data/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt b/data/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt deleted file mode 100644 index ba66be6f495..00000000000 --- a/data/persistence/src/appleMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.kalium.persistence.kmmSettings - -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorage -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorageImpl -import com.wire.kalium.persistence.config.UserConfigStorage -import com.wire.kalium.persistence.config.UserConfigStorageImpl -import com.wire.kalium.persistence.dao.UserIDEntity - -actual class UserPrefBuilder( - userId: UserIDEntity, - rootPath: String, - shouldEncryptData: Boolean = true -) { - - private val kaliumPreferences = - KaliumPreferencesSettings( - buildSettings(SettingOptions.UserSettings(shouldEncryptData, userId), EncryptedSettingsPlatformParam(rootPath)) - ) - - actual val lastRetrievedNotificationEventStorage: LastRetrievedNotificationEventStorage - get() = LastRetrievedNotificationEventStorageImpl(kaliumPreferences) - - actual fun clear() { - kaliumPreferences.nuke() - } - - actual val userConfigStorage: UserConfigStorage = - UserConfigStorageImpl(kaliumPreferences) - -} diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/client/LastRetrievedNotificationEventStorage.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/client/LastRetrievedNotificationEventStorage.kt deleted file mode 100644 index da2586ee5f5..00000000000 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/client/LastRetrievedNotificationEventStorage.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.kalium.persistence.client - -import com.wire.kalium.persistence.kmmSettings.KaliumPreferences - -interface LastRetrievedNotificationEventStorage { - /** - * to save the id of the last event retrieved from the notifications stream - */ - fun saveEvent(eventId: String) - - /** - * get the id of the last saved event if one exists - */ - fun getLastEventId(): String? -} - -internal class LastRetrievedNotificationEventStorageImpl internal constructor( - private val kaliumPreferences: KaliumPreferences -) : LastRetrievedNotificationEventStorage { - - override fun saveEvent(eventId: String) { - kaliumPreferences.putString( - LAST_NOTIFICATION_STREAM_EVENT_ID, - eventId - ) - } - - override fun getLastEventId(): String? = - kaliumPreferences.getString(LAST_NOTIFICATION_STREAM_EVENT_ID) - - private companion object { - const val LAST_NOTIFICATION_STREAM_EVENT_ID = "last_notification_stream_event_id" - } -} diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt index 202b4d190c5..be651841497 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorage.kt @@ -18,6 +18,22 @@ package com.wire.kalium.persistence.config +import com.wire.kalium.persistence.config.UserConfigStorage.Companion.DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE +import com.wire.kalium.persistence.config.UserConfigStorage.Companion.DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.APP_LOCK +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.DEFAULT_PROTOCOL +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.E2EI_NOTIFICATION_TIME +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.E2EI_SETTINGS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_CLASSIFIED_DOMAINS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_CONFERENCE_CALLING +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_MLS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_READ_RECEIPTS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_SCREENSHOT_CENSORING +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_TYPING_INDICATOR +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.FILE_SHARING +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.GUEST_ROOM_LINK +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.USE_SFT_FOR_ONE_ON_ONE_CALLS import com.wire.kalium.persistence.dao.SupportedProtocolEntity import com.wire.kalium.persistence.kmmSettings.KaliumPreferences import com.wire.kalium.util.time.Second @@ -32,13 +48,14 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlin.time.Duration +@Deprecated("Use UserPrefsDAO instead. This will be removed in future versions.", ReplaceWith("UserPrefsDAO")) @Suppress("TooManyFunctions") interface UserConfigStorage { /** * save flag from the user settings to enforce and disable App Lock */ - fun persistAppLockStatus( + suspend fun persistAppLockStatus( isEnforced: Boolean, inactivityTimeoutSecs: Second, isStatusChanged: Boolean? @@ -47,42 +64,42 @@ interface UserConfigStorage { /** * get the saved flag to know if App Lock is enforced or not */ - fun appLockStatus(): AppLockConfigEntity? + suspend fun appLockStatus(): AppLockConfigEntity? /** * returns a Flow of the saved App Lock status */ fun appLockFlow(): Flow - fun setTeamAppLockAsNotified() + suspend fun setTeamAppLockAsNotified() /** * Save flag from the file sharing api, and if the status changes */ - fun persistFileSharingStatus(status: Boolean, isStatusChanged: Boolean?) + suspend fun persistFileSharingStatus(status: Boolean, isStatusChanged: Boolean?) /** * Get the saved flag that been saved to know if the file sharing is enabled or not with the flag * to know if there was a status change */ - fun isFileSharingEnabled(): IsFileSharingEnabledEntity? + suspend fun isFileSharingEnabled(): IsFileSharingEnabledEntity? /** * Returns the Flow of file sharing status */ fun isFileSharingEnabledFlow(): Flow - fun setFileSharingAsNotified() + suspend fun setFileSharingAsNotified() /** * Returns a Flow containing the status and list of classified domains */ - fun isClassifiedDomainsEnabledFlow(): Flow + fun isClassifiedDomainsEnabledFlow(): Flow /** *Save the flag and list of trusted domains */ - fun persistClassifiedDomainsStatus(status: Boolean, classifiedDomains: List) + suspend fun persistClassifiedDomainsStatus(status: Boolean, classifiedDomains: List) /** * Saves the flag that indicates whether a 2FA challenge is @@ -90,7 +107,7 @@ interface UserConfigStorage { * Login, Create Account, Register Client, etc. * @see isSecondFactorPasswordChallengeRequired */ - fun persistSecondFactorPasswordChallengeStatus(isRequired: Boolean) + suspend fun persistSecondFactorPasswordChallengeStatus(isRequired: Boolean) /** * Checks if the 2FA challenge is @@ -98,37 +115,37 @@ interface UserConfigStorage { * Login, Create Account, Register Client, etc. * @see persistSecondFactorPasswordChallengeStatus */ - fun isSecondFactorPasswordChallengeRequired(): Boolean + suspend fun isSecondFactorPasswordChallengeRequired(): Boolean /** * Save default protocol to use */ - fun persistDefaultProtocol(protocol: SupportedProtocolEntity) + suspend fun persistDefaultProtocol(protocol: SupportedProtocolEntity) /** * Gets default protocol to use. Defaults to PROTEUS if not default protocol has been saved. */ - fun defaultProtocol(): SupportedProtocolEntity + suspend fun defaultProtocol(): SupportedProtocolEntity /** * Save flag from the user settings to enable and disable MLS */ - fun enableMLS(enabled: Boolean) + suspend fun enableMLS(enabled: Boolean) /** * Get the saved flag to know if MLS enabled or not */ - fun isMLSEnabled(): Boolean + suspend fun isMLSEnabled(): Boolean /** * Save MLSE2EISetting */ - fun setE2EISettings(settingEntity: E2EISettingsEntity?) + suspend fun setE2EISettings(settingEntity: E2EISettingsEntity?) /** * Get MLSE2EISetting */ - fun getE2EISettings(): E2EISettingsEntity? + suspend fun getE2EISettings(): E2EISettingsEntity? /** * Get Flow of the saved MLSE2EISetting @@ -138,51 +155,73 @@ interface UserConfigStorage { /** * Save flag from user settings to enable or disable Conference Calling */ - fun persistConferenceCalling(enabled: Boolean) + suspend fun persistConferenceCalling(enabled: Boolean) /** * Get the saved flag to know if Conference Calling is enabled or not */ - fun isConferenceCallingEnabled(): Boolean + suspend fun isConferenceCallingEnabled(): Boolean /** * Get a flow of saved flag to know if conference calling is enabled or not */ - fun isConferenceCallingEnabledFlow(): Flow + suspend fun isConferenceCallingEnabledFlow(): Flow - fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) + suspend fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) - fun shouldUseSftForOneOnOneCalls(): Boolean + suspend fun shouldUseSftForOneOnOneCalls(): Boolean /** * Get the saved flag to know whether user's Read Receipts are currently enabled or not */ - fun areReadReceiptsEnabled(): Flow + suspend fun areReadReceiptsEnabled(): Flow /** * Persist the flag to indicate if user's Read Receipts are enabled or not. */ - fun persistReadReceipts(enabled: Boolean) + suspend fun persistReadReceipts(enabled: Boolean) /** * Get the saved global flag to know whether user's typing indicator is currently enabled or not. */ - fun isTypingIndicatorEnabled(): Flow + suspend fun isTypingIndicatorEnabled(): Flow /** * Persist the flag to indicate whether user's typing indicator global flag is enabled or not. */ - fun persistTypingIndicator(enabled: Boolean) + suspend fun persistTypingIndicator(enabled: Boolean) - fun persistGuestRoomLinkFeatureFlag(status: Boolean, isStatusChanged: Boolean?) - fun isGuestRoomLinkEnabled(): IsGuestRoomLinkEnabledEntity? + suspend fun persistGuestRoomLinkFeatureFlag(status: Boolean, isStatusChanged: Boolean?) + suspend fun isGuestRoomLinkEnabled(): IsGuestRoomLinkEnabledEntity? fun isGuestRoomLinkEnabledFlow(): Flow - fun isScreenshotCensoringEnabledFlow(): Flow - fun persistScreenshotCensoring(enabled: Boolean) - fun setIfAbsentE2EINotificationTime(timeStamp: Long) - fun getE2EINotificationTime(): Long? - fun e2EINotificationTimeFlow(): Flow - fun updateE2EINotificationTime(timeStamp: Long) + suspend fun isScreenshotCensoringEnabledFlow(): Flow + suspend fun persistScreenshotCensoring(enabled: Boolean) + suspend fun setIfAbsentE2EINotificationTime(timeStamp: Long) + suspend fun getE2EINotificationTime(): Long? + suspend fun e2EINotificationTimeFlow(): Flow + suspend fun updateE2EINotificationTime(timeStamp: Long) + + enum class UserPreferences(val key: String) { + FILE_SHARING("file_sharing"), + GUEST_ROOM_LINK("guest_room_link"), + ENABLE_CLASSIFIED_DOMAINS("enable_classified_domains"), + ENABLE_MLS("enable_mls"), + E2EI_SETTINGS("end_to_end_identity_settings"), + E2EI_NOTIFICATION_TIME("end_to_end_identity_notification_time"), + ENABLE_CONFERENCE_CALLING("enable_conference_calling"), + USE_SFT_FOR_ONE_ON_ONE_CALLS("use_sft_for_one_on_one_calls"), + ENABLE_READ_RECEIPTS("enable_read_receipts"), + REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE("require_second_factor_password_challenge"), + ENABLE_SCREENSHOT_CENSORING("enable_screenshot_censoring"), + ENABLE_TYPING_INDICATOR("enable_typing_indicator"), + APP_LOCK("app_lock"), + DEFAULT_PROTOCOL("default_protocol") + } + + companion object { + const val DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE = false + const val DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE = false + } } @Serializable @@ -278,8 +317,9 @@ data class WireCellsConfigEntity( @Serializable val teamQuotaBytes: Long?, ) +@Deprecated("Use UserPrefsDAO instead. This will be removed in future versions.", ReplaceWith("UserPrefsDAO")) @Suppress("TooManyFunctions") -class UserConfigStorageImpl( +class UserConfigStorageImpl constructor( private val kaliumPreferences: KaliumPreferences ) : UserConfigStorage { @@ -349,13 +389,13 @@ class UserConfigStorageImpl( onBufferOverflow = BufferOverflow.DROP_OLDEST ) - override fun persistAppLockStatus( + override suspend fun persistAppLockStatus( isEnforced: Boolean, inactivityTimeoutSecs: Second, isStatusChanged: Boolean? ) { kaliumPreferences.putSerializable( - APP_LOCK, + APP_LOCK.key, AppLockConfigEntity(inactivityTimeoutSecs, isEnforced, isStatusChanged), AppLockConfigEntity.serializer(), ).also { @@ -363,13 +403,13 @@ class UserConfigStorageImpl( } } - override fun setTeamAppLockAsNotified() { + override suspend fun setTeamAppLockAsNotified() { val newValue = - kaliumPreferences.getSerializable(APP_LOCK, AppLockConfigEntity.serializer()) + kaliumPreferences.getSerializable(APP_LOCK.key, AppLockConfigEntity.serializer()) ?.copy(isStatusChanged = false) ?: return kaliumPreferences.putSerializable( - APP_LOCK, + APP_LOCK.key, newValue, AppLockConfigEntity.serializer() ).also { @@ -377,8 +417,8 @@ class UserConfigStorageImpl( } } - override fun appLockStatus(): AppLockConfigEntity? = - kaliumPreferences.getSerializable(APP_LOCK, AppLockConfigEntity.serializer()) + override suspend fun appLockStatus(): AppLockConfigEntity? = + kaliumPreferences.getSerializable(APP_LOCK.key, AppLockConfigEntity.serializer()) override fun appLockFlow(): Flow = appLockFlow.map { appLockStatus() @@ -386,12 +426,12 @@ class UserConfigStorageImpl( emit(appLockStatus()) } - override fun persistFileSharingStatus( + override suspend fun persistFileSharingStatus( status: Boolean, isStatusChanged: Boolean? ) { kaliumPreferences.putSerializable( - FILE_SHARING, + FILE_SHARING.key, IsFileSharingEnabledEntity(status, isStatusChanged), IsFileSharingEnabledEntity.serializer() ).also { @@ -399,8 +439,8 @@ class UserConfigStorageImpl( } } - override fun isFileSharingEnabled(): IsFileSharingEnabledEntity? = - kaliumPreferences.getSerializable(FILE_SHARING, IsFileSharingEnabledEntity.serializer()) + override suspend fun isFileSharingEnabled(): IsFileSharingEnabledEntity? = + kaliumPreferences.getSerializable(FILE_SHARING.key, IsFileSharingEnabledEntity.serializer()) override fun isFileSharingEnabledFlow(): Flow = isFileSharingEnabledFlow @@ -408,13 +448,13 @@ class UserConfigStorageImpl( .onStart { emit(isFileSharingEnabled()) } .distinctUntilChanged() - override fun setFileSharingAsNotified() { + override suspend fun setFileSharingAsNotified() { val newValue = - kaliumPreferences.getSerializable(FILE_SHARING, IsFileSharingEnabledEntity.serializer()) + kaliumPreferences.getSerializable(FILE_SHARING.key, IsFileSharingEnabledEntity.serializer()) ?.copy(isStatusChanged = false) ?: return kaliumPreferences.putSerializable( - FILE_SHARING, + FILE_SHARING.key, newValue, IsFileSharingEnabledEntity.serializer() ).also { @@ -422,26 +462,26 @@ class UserConfigStorageImpl( } } - override fun isClassifiedDomainsEnabledFlow(): Flow { + override fun isClassifiedDomainsEnabledFlow(): Flow { return isClassifiedDomainsEnabledFlow .map { kaliumPreferences.getSerializable( - ENABLE_CLASSIFIED_DOMAINS, + ENABLE_CLASSIFIED_DOMAINS.key, ClassifiedDomainsEntity.serializer() )!! }.onStart { emit( kaliumPreferences.getSerializable( - ENABLE_CLASSIFIED_DOMAINS, + ENABLE_CLASSIFIED_DOMAINS.key, ClassifiedDomainsEntity.serializer() )!! ) }.distinctUntilChanged() } - override fun persistClassifiedDomainsStatus(status: Boolean, classifiedDomains: List) { + override suspend fun persistClassifiedDomainsStatus(status: Boolean, classifiedDomains: List) { kaliumPreferences.putSerializable( - ENABLE_CLASSIFIED_DOMAINS, + ENABLE_CLASSIFIED_DOMAINS.key, ClassifiedDomainsEntity(status, classifiedDomains), ClassifiedDomainsEntity.serializer() ).also { @@ -449,33 +489,33 @@ class UserConfigStorageImpl( } } - override fun persistSecondFactorPasswordChallengeStatus(isRequired: Boolean) { - kaliumPreferences.putBoolean(REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE, isRequired) + override suspend fun persistSecondFactorPasswordChallengeStatus(isRequired: Boolean) { + kaliumPreferences.putBoolean(REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE.key, isRequired) } - override fun isSecondFactorPasswordChallengeRequired(): Boolean = - kaliumPreferences.getBoolean(REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE, false) + override suspend fun isSecondFactorPasswordChallengeRequired(): Boolean = + kaliumPreferences.getBoolean(REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE.key, false) - override fun persistDefaultProtocol(protocol: SupportedProtocolEntity) { - kaliumPreferences.putString(DEFAULT_PROTOCOL, protocol.name) + override suspend fun persistDefaultProtocol(protocol: SupportedProtocolEntity) { + kaliumPreferences.putString(DEFAULT_PROTOCOL.key, protocol.name) } - override fun defaultProtocol(): SupportedProtocolEntity = - kaliumPreferences.getString(DEFAULT_PROTOCOL)?.let { SupportedProtocolEntity.valueOf(it) } + override suspend fun defaultProtocol(): SupportedProtocolEntity = + kaliumPreferences.getString(DEFAULT_PROTOCOL.key)?.let { SupportedProtocolEntity.valueOf(it) } ?: SupportedProtocolEntity.PROTEUS - override fun enableMLS(enabled: Boolean) { - kaliumPreferences.putBoolean(ENABLE_MLS, enabled) + override suspend fun enableMLS(enabled: Boolean) { + kaliumPreferences.putBoolean(ENABLE_MLS.key, enabled) } - override fun isMLSEnabled(): Boolean = kaliumPreferences.getBoolean(ENABLE_MLS, false) + override suspend fun isMLSEnabled(): Boolean = kaliumPreferences.getBoolean(ENABLE_MLS.key, false) - override fun setE2EISettings(settingEntity: E2EISettingsEntity?) { + override suspend fun setE2EISettings(settingEntity: E2EISettingsEntity?) { if (settingEntity == null) { - kaliumPreferences.remove(E2EI_SETTINGS) + kaliumPreferences.remove(E2EI_SETTINGS.key) } else { kaliumPreferences.putSerializable( - E2EI_SETTINGS, + E2EI_SETTINGS.key, settingEntity, E2EISettingsEntity.serializer() ).also { @@ -484,8 +524,8 @@ class UserConfigStorageImpl( } } - override fun getE2EISettings(): E2EISettingsEntity? { - return kaliumPreferences.getSerializable(E2EI_SETTINGS, E2EISettingsEntity.serializer()) + override suspend fun getE2EISettings(): E2EISettingsEntity? { + return kaliumPreferences.getSerializable(E2EI_SETTINGS.key, E2EISettingsEntity.serializer()) } override fun e2EISettingsFlow(): Flow = e2EIFlow @@ -493,79 +533,79 @@ class UserConfigStorageImpl( .onStart { emit(getE2EISettings()) } .distinctUntilChanged() - override fun setIfAbsentE2EINotificationTime(timeStamp: Long) { + override suspend fun setIfAbsentE2EINotificationTime(timeStamp: Long) { getE2EINotificationTime().let { current -> if (current == null || current <= 0) - kaliumPreferences.putLong(E2EI_NOTIFICATION_TIME, timeStamp).also { e2EINotificationFlow.tryEmit(Unit) } + kaliumPreferences.putLong(E2EI_NOTIFICATION_TIME.key, timeStamp).also { e2EINotificationFlow.tryEmit(Unit) } } } - override fun updateE2EINotificationTime(timeStamp: Long) { - kaliumPreferences.putLong(E2EI_NOTIFICATION_TIME, timeStamp).also { e2EINotificationFlow.tryEmit(Unit) } + override suspend fun updateE2EINotificationTime(timeStamp: Long) { + kaliumPreferences.putLong(E2EI_NOTIFICATION_TIME.key, timeStamp).also { e2EINotificationFlow.tryEmit(Unit) } } - override fun getE2EINotificationTime(): Long? { - return kaliumPreferences.getLong(E2EI_NOTIFICATION_TIME) + override suspend fun getE2EINotificationTime(): Long? { + return kaliumPreferences.getLong(E2EI_NOTIFICATION_TIME.key) } - override fun e2EINotificationTimeFlow(): Flow = e2EINotificationFlow + override suspend fun e2EINotificationTimeFlow(): Flow = e2EINotificationFlow .map { getE2EINotificationTime() } .onStart { emit(getE2EINotificationTime()) } .distinctUntilChanged() - override fun persistConferenceCalling(enabled: Boolean) { - kaliumPreferences.putBoolean(ENABLE_CONFERENCE_CALLING, enabled) + override suspend fun persistConferenceCalling(enabled: Boolean) { + kaliumPreferences.putBoolean(ENABLE_CONFERENCE_CALLING.key, enabled) conferenceCallingEnabledFlow.tryEmit(Unit) } - override fun isConferenceCallingEnabled(): Boolean = + override suspend fun isConferenceCallingEnabled(): Boolean = kaliumPreferences.getBoolean( - ENABLE_CONFERENCE_CALLING, + ENABLE_CONFERENCE_CALLING.key, DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE ) - override fun isConferenceCallingEnabledFlow(): Flow = conferenceCallingEnabledFlow + override suspend fun isConferenceCallingEnabledFlow(): Flow = conferenceCallingEnabledFlow .map { isConferenceCallingEnabled() } .onStart { emit(isConferenceCallingEnabled()) } - override fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) { - kaliumPreferences.putBoolean(USE_SFT_FOR_ONE_ON_ONE_CALLS, shouldUse) + override suspend fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) { + kaliumPreferences.putBoolean(USE_SFT_FOR_ONE_ON_ONE_CALLS.key, shouldUse) } - override fun shouldUseSftForOneOnOneCalls(): Boolean = + override suspend fun shouldUseSftForOneOnOneCalls(): Boolean = kaliumPreferences.getBoolean( - USE_SFT_FOR_ONE_ON_ONE_CALLS, + USE_SFT_FOR_ONE_ON_ONE_CALLS.key, DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE ) - override fun areReadReceiptsEnabled(): Flow = areReadReceiptsEnabledFlow - .map { kaliumPreferences.getBoolean(ENABLE_READ_RECEIPTS, true) } - .onStart { emit(kaliumPreferences.getBoolean(ENABLE_READ_RECEIPTS, true)) } + override suspend fun areReadReceiptsEnabled(): Flow = areReadReceiptsEnabledFlow + .map { kaliumPreferences.getBoolean(ENABLE_READ_RECEIPTS.key, true) } + .onStart { emit(kaliumPreferences.getBoolean(ENABLE_READ_RECEIPTS.key, true)) } .distinctUntilChanged() - override fun persistReadReceipts(enabled: Boolean) { - kaliumPreferences.putBoolean(ENABLE_READ_RECEIPTS, enabled).also { + override suspend fun persistReadReceipts(enabled: Boolean) { + kaliumPreferences.putBoolean(ENABLE_READ_RECEIPTS.key, enabled).also { areReadReceiptsEnabledFlow.tryEmit(Unit) } } - override fun isTypingIndicatorEnabled(): Flow = isTypingIndicatorEnabledFlow - .map { kaliumPreferences.getBoolean(ENABLE_TYPING_INDICATOR, true) } - .onStart { emit(kaliumPreferences.getBoolean(ENABLE_TYPING_INDICATOR, true)) } + override suspend fun isTypingIndicatorEnabled(): Flow = isTypingIndicatorEnabledFlow + .map { kaliumPreferences.getBoolean(ENABLE_TYPING_INDICATOR.key, true) } + .onStart { emit(kaliumPreferences.getBoolean(ENABLE_TYPING_INDICATOR.key, true)) } .distinctUntilChanged() - override fun persistTypingIndicator(enabled: Boolean) { - kaliumPreferences.putBoolean(ENABLE_TYPING_INDICATOR, enabled).also { + override suspend fun persistTypingIndicator(enabled: Boolean) { + kaliumPreferences.putBoolean(ENABLE_TYPING_INDICATOR.key, enabled).also { isTypingIndicatorEnabledFlow.tryEmit(Unit) } } - override fun persistGuestRoomLinkFeatureFlag( + override suspend fun persistGuestRoomLinkFeatureFlag( status: Boolean, isStatusChanged: Boolean? ) { kaliumPreferences.putSerializable( - GUEST_ROOM_LINK, + GUEST_ROOM_LINK.key, IsGuestRoomLinkEnabledEntity(status, isStatusChanged), IsGuestRoomLinkEnabledEntity.serializer() ).also { @@ -573,9 +613,9 @@ class UserConfigStorageImpl( } } - override fun isGuestRoomLinkEnabled(): IsGuestRoomLinkEnabledEntity? = + override suspend fun isGuestRoomLinkEnabled(): IsGuestRoomLinkEnabledEntity? = kaliumPreferences.getSerializable( - GUEST_ROOM_LINK, + GUEST_ROOM_LINK.key, IsGuestRoomLinkEnabledEntity.serializer() ) @@ -585,35 +625,15 @@ class UserConfigStorageImpl( .onStart { emit(isGuestRoomLinkEnabled()) } .distinctUntilChanged() - override fun isScreenshotCensoringEnabledFlow(): Flow = + override suspend fun isScreenshotCensoringEnabledFlow(): Flow = isScreenshotCensoringEnabledFlow - .map { kaliumPreferences.getBoolean(ENABLE_SCREENSHOT_CENSORING, false) } - .onStart { emit(kaliumPreferences.getBoolean(ENABLE_SCREENSHOT_CENSORING, false)) } + .map { kaliumPreferences.getBoolean(ENABLE_SCREENSHOT_CENSORING.key, false) } + .onStart { emit(kaliumPreferences.getBoolean(ENABLE_SCREENSHOT_CENSORING.key, false)) } .distinctUntilChanged() - override fun persistScreenshotCensoring(enabled: Boolean) { - kaliumPreferences.putBoolean(ENABLE_SCREENSHOT_CENSORING, enabled).also { + override suspend fun persistScreenshotCensoring(enabled: Boolean) { + kaliumPreferences.putBoolean(ENABLE_SCREENSHOT_CENSORING.key, enabled).also { isScreenshotCensoringEnabledFlow.tryEmit(Unit) } } - - private companion object { - const val FILE_SHARING = "file_sharing" - const val GUEST_ROOM_LINK = "guest_room_link" - const val ENABLE_CLASSIFIED_DOMAINS = "enable_classified_domains" - const val ENABLE_MLS = "enable_mls" - const val E2EI_SETTINGS = "end_to_end_identity_settings" - const val E2EI_NOTIFICATION_TIME = "end_to_end_identity_notification_time" - const val ENABLE_CONFERENCE_CALLING = "enable_conference_calling" - const val USE_SFT_FOR_ONE_ON_ONE_CALLS = "use_sft_for_one_on_one_calls" - const val ENABLE_READ_RECEIPTS = "enable_read_receipts" - const val DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE = false - const val DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE = false - const val REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE = - "require_second_factor_password_challenge" - const val ENABLE_SCREENSHOT_CENSORING = "enable_screenshot_censoring" - const val ENABLE_TYPING_INDICATOR = "enable_typing_indicator" - const val APP_LOCK = "app_lock" - const val DEFAULT_PROTOCOL = "default_protocol" - } } diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..ffc37a1bd03 --- /dev/null +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt @@ -0,0 +1,39 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.config + +import com.wire.kalium.persistence.dao.UserIDEntity + +/** + * Factory for creating [UserConfigStorage] instances. + * This is used during migration from SharedPreferences to database storage. + * Creating the storage only when needed avoids creating SharedPreferences + * when migration is not required. + */ +@Deprecated( + "Scheduled for removal in future versions, User KMM Settings are now replaced by database implementation." + + "Just kept for migration purposes.", + ReplaceWith("No replacement available"), +) +expect class UserConfigStorageFactory() { + fun create( + userId: UserIDEntity, + shouldEncryptData: Boolean, + platformParam: Any + ): UserConfigStorage +} diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserConfigDAO.kt similarity index 98% rename from data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt rename to data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserConfigDAO.kt index d789b1e361f..d5a5a003e6c 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UserConfigDAO.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserConfigDAO.kt @@ -1,6 +1,6 @@ /* * Wire - * Copyright (C) 2024 Wire Swiss GmbH + * Copyright (C) 2026 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,15 +18,13 @@ @file:Suppress("TooManyFunctions") -package com.wire.kalium.persistence.dao.unread +package com.wire.kalium.persistence.dao import com.wire.kalium.persistence.config.LastPreKey import com.wire.kalium.persistence.config.LegalHoldRequestEntity import com.wire.kalium.persistence.config.MLSMigrationEntity import com.wire.kalium.persistence.config.TeamSettingsSelfDeletionStatusEntity import com.wire.kalium.persistence.config.WireCellsConfigEntity -import com.wire.kalium.persistence.dao.MetadataDAO -import com.wire.kalium.persistence.dao.SupportedProtocolEntity import com.wire.kalium.persistence.model.SupportedCipherSuiteEntity import io.mockative.Mockable import kotlinx.coroutines.flow.Flow diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserPrefsDAO.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserPrefsDAO.kt new file mode 100644 index 00000000000..77a1c56e962 --- /dev/null +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserPrefsDAO.kt @@ -0,0 +1,261 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.dao + +import com.wire.kalium.persistence.config.AppLockConfigEntity +import com.wire.kalium.persistence.config.ClassifiedDomainsEntity +import com.wire.kalium.persistence.config.E2EISettingsEntity +import com.wire.kalium.persistence.config.IsFileSharingEnabledEntity +import com.wire.kalium.persistence.config.IsGuestRoomLinkEnabledEntity +import com.wire.kalium.persistence.config.UserConfigStorage +import com.wire.kalium.persistence.config.UserConfigStorage.Companion.DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE +import com.wire.kalium.persistence.config.UserConfigStorage.Companion.DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.APP_LOCK +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.DEFAULT_PROTOCOL +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.E2EI_NOTIFICATION_TIME +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.E2EI_SETTINGS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_CLASSIFIED_DOMAINS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_CONFERENCE_CALLING +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_MLS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_READ_RECEIPTS +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_SCREENSHOT_CENSORING +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.ENABLE_TYPING_INDICATOR +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.FILE_SHARING +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.GUEST_ROOM_LINK +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE +import com.wire.kalium.persistence.config.UserConfigStorage.UserPreferences.USE_SFT_FOR_ONE_ON_ONE_CALLS +import com.wire.kalium.util.time.Second +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +@Suppress("TooManyFunctions") +internal class UserPrefsDAO( + private val metadataDAO: MetadataDAO, +) : UserConfigStorage { + override suspend fun persistAppLockStatus( + isEnforced: Boolean, + inactivityTimeoutSecs: Second, + isStatusChanged: Boolean? + ) { + metadataDAO.putSerializable( + APP_LOCK.key, + AppLockConfigEntity(inactivityTimeoutSecs, isEnforced, isStatusChanged), + AppLockConfigEntity.serializer(), + ) + } + + override suspend fun appLockStatus(): AppLockConfigEntity? { + return metadataDAO.getSerializable(APP_LOCK.key, AppLockConfigEntity.serializer()) + } + + override fun appLockFlow(): Flow { + return metadataDAO.observeSerializable(APP_LOCK.key, AppLockConfigEntity.serializer()) + } + + override suspend fun setTeamAppLockAsNotified() { + val newValue = + metadataDAO.getSerializable(APP_LOCK.key, AppLockConfigEntity.serializer()) + ?.copy(isStatusChanged = false) + ?: return + metadataDAO.putSerializable( + APP_LOCK.key, + newValue, + AppLockConfigEntity.serializer() + ) + } + + override suspend fun persistFileSharingStatus(status: Boolean, isStatusChanged: Boolean?) { + metadataDAO.putSerializable( + FILE_SHARING.key, + IsFileSharingEnabledEntity(status, isStatusChanged), + IsFileSharingEnabledEntity.serializer() + ) + } + + override suspend fun isFileSharingEnabled(): IsFileSharingEnabledEntity? { + return metadataDAO.getSerializable(FILE_SHARING.key, IsFileSharingEnabledEntity.serializer()) + } + + override fun isFileSharingEnabledFlow(): Flow { + return metadataDAO.observeSerializable(FILE_SHARING.key, IsFileSharingEnabledEntity.serializer()) + } + + override suspend fun setFileSharingAsNotified() { + val newValue = + metadataDAO.getSerializable(FILE_SHARING.key, IsFileSharingEnabledEntity.serializer()) + ?.copy(isStatusChanged = false) + ?: return + metadataDAO.putSerializable( + FILE_SHARING.key, + newValue, + IsFileSharingEnabledEntity.serializer() + ) + } + + override fun isClassifiedDomainsEnabledFlow(): Flow { + return metadataDAO.observeSerializable(ENABLE_CLASSIFIED_DOMAINS.key, ClassifiedDomainsEntity.serializer()) + } + + override suspend fun persistClassifiedDomainsStatus(status: Boolean, classifiedDomains: List) { + metadataDAO.putSerializable( + ENABLE_CLASSIFIED_DOMAINS.key, + ClassifiedDomainsEntity(status, classifiedDomains), + ClassifiedDomainsEntity.serializer() + ) + } + + override suspend fun persistSecondFactorPasswordChallengeStatus(isRequired: Boolean) { + metadataDAO.insertValue(value = isRequired.toString(), key = REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE.key) + } + + override suspend fun isSecondFactorPasswordChallengeRequired(): Boolean { + return metadataDAO.valueByKey(REQUIRE_SECOND_FACTOR_PASSWORD_CHALLENGE.key)?.toBoolean() ?: false + } + + override suspend fun persistDefaultProtocol(protocol: SupportedProtocolEntity) { + metadataDAO.insertValue(value = protocol.name, key = DEFAULT_PROTOCOL.key) + } + + override suspend fun defaultProtocol(): SupportedProtocolEntity { + return metadataDAO.valueByKey(DEFAULT_PROTOCOL.key)?.let { SupportedProtocolEntity.valueOf(it) } + ?: SupportedProtocolEntity.PROTEUS + } + + override suspend fun enableMLS(enabled: Boolean) { + metadataDAO.insertValue(value = enabled.toString(), key = ENABLE_MLS.key) + } + + override suspend fun isMLSEnabled(): Boolean { + return metadataDAO.valueByKey(ENABLE_MLS.key)?.toBoolean() ?: false + } + + override suspend fun setE2EISettings(settingEntity: E2EISettingsEntity?) { + if (settingEntity == null) { + metadataDAO.deleteValue(E2EI_SETTINGS.key) + } else { + metadataDAO.putSerializable( + E2EI_SETTINGS.key, + settingEntity, + E2EISettingsEntity.serializer() + ) + } + } + + override suspend fun getE2EISettings(): E2EISettingsEntity? { + return metadataDAO.getSerializable(E2EI_SETTINGS.key, E2EISettingsEntity.serializer()) + } + + override fun e2EISettingsFlow(): Flow { + return metadataDAO.observeSerializable(E2EI_SETTINGS.key, E2EISettingsEntity.serializer()) + } + + override suspend fun persistConferenceCalling(enabled: Boolean) { + metadataDAO.insertValue(value = enabled.toString(), key = ENABLE_CONFERENCE_CALLING.key) + } + + override suspend fun isConferenceCallingEnabled(): Boolean { + return metadataDAO.valueByKey(ENABLE_CONFERENCE_CALLING.key)?.toBoolean() ?: DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE + } + + override suspend fun isConferenceCallingEnabledFlow(): Flow { + return metadataDAO.valueByKeyFlow(ENABLE_CONFERENCE_CALLING.key).map { value -> + value?.toBoolean() ?: DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE + } + } + + override suspend fun persistUseSftForOneOnOneCalls(shouldUse: Boolean) { + metadataDAO.insertValue(value = shouldUse.toString(), key = USE_SFT_FOR_ONE_ON_ONE_CALLS.key) + } + + override suspend fun shouldUseSftForOneOnOneCalls(): Boolean { + return metadataDAO.valueByKey(USE_SFT_FOR_ONE_ON_ONE_CALLS.key)?.toBoolean() ?: DEFAULT_USE_SFT_FOR_ONE_ON_ONE_CALLS_VALUE + } + + override suspend fun areReadReceiptsEnabled(): Flow { + return metadataDAO.valueByKeyFlow(ENABLE_READ_RECEIPTS.key).map { + it?.toBoolean() ?: true + } + } + + override suspend fun persistReadReceipts(enabled: Boolean) { + metadataDAO.insertValue(value = enabled.toString(), key = ENABLE_READ_RECEIPTS.key) + } + + override suspend fun isTypingIndicatorEnabled(): Flow { + return metadataDAO.valueByKeyFlow(ENABLE_TYPING_INDICATOR.key).map { + it?.toBoolean() ?: true + } + } + + override suspend fun persistTypingIndicator(enabled: Boolean) { + metadataDAO.insertValue(value = enabled.toString(), key = ENABLE_TYPING_INDICATOR.key) + } + + override suspend fun persistGuestRoomLinkFeatureFlag(status: Boolean, isStatusChanged: Boolean?) { + metadataDAO.putSerializable( + GUEST_ROOM_LINK.key, + IsGuestRoomLinkEnabledEntity(status, isStatusChanged), + IsGuestRoomLinkEnabledEntity.serializer() + ) + } + + override suspend fun isGuestRoomLinkEnabled(): IsGuestRoomLinkEnabledEntity? { + return metadataDAO.getSerializable( + GUEST_ROOM_LINK.key, + IsGuestRoomLinkEnabledEntity.serializer() + ) + } + + override fun isGuestRoomLinkEnabledFlow(): Flow { + return metadataDAO.observeSerializable( + GUEST_ROOM_LINK.key, + IsGuestRoomLinkEnabledEntity.serializer() + ) + } + + override suspend fun isScreenshotCensoringEnabledFlow(): Flow { + return metadataDAO.valueByKeyFlow(ENABLE_SCREENSHOT_CENSORING.key).map { + it?.toBoolean() ?: false + } + } + + override suspend fun persistScreenshotCensoring(enabled: Boolean) { + metadataDAO.insertValue(value = enabled.toString(), key = ENABLE_SCREENSHOT_CENSORING.key) + } + + override suspend fun setIfAbsentE2EINotificationTime(timeStamp: Long) { + getE2EINotificationTime().let { current -> + if (current == null || current <= 0) + metadataDAO.insertValue(value = timeStamp.toString(), key = E2EI_NOTIFICATION_TIME.key) + } + } + + override suspend fun getE2EINotificationTime(): Long? { + return metadataDAO.valueByKey(E2EI_NOTIFICATION_TIME.key)?.toLong() + } + + override suspend fun e2EINotificationTimeFlow(): Flow { + return metadataDAO.valueByKeyFlow(E2EI_NOTIFICATION_TIME.key).map { + it?.toLong() + } + } + + override suspend fun updateE2EINotificationTime(timeStamp: Long) { + metadataDAO.insertValue(value = timeStamp.toString(), key = E2EI_NOTIFICATION_TIME.key) + } +} diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapper.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UnreadContentMapper.kt similarity index 89% rename from data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapper.kt rename to data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UnreadContentMapper.kt index 7a10829747e..933e988ec22 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapper.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/unread/UnreadContentMapper.kt @@ -1,6 +1,6 @@ /* * Wire - * Copyright (C) 2024 Wire Swiss GmbH + * Copyright (C) 2026 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,12 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ - -package com.wire.kalium.persistence.dao +package com.wire.kalium.persistence.dao.unread import com.wire.kalium.persistence.dao.message.UnreadContentCountEntity import com.wire.kalium.persistence.util.JsonSerializer -import kotlinx.serialization.decodeFromString object UnreadContentMapper { diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt index a23ba3fd155..670f47a4822 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt @@ -29,6 +29,7 @@ import com.wire.kalium.persistence.backup.DatabaseImporter import com.wire.kalium.persistence.backup.DatabaseImporterImpl import com.wire.kalium.persistence.backup.ObfuscatedCopyExporter import com.wire.kalium.persistence.cache.FlowCache +import com.wire.kalium.persistence.config.UserConfigStorage import com.wire.kalium.persistence.dao.ConnectionDAO import com.wire.kalium.persistence.dao.ConnectionDAOImpl import com.wire.kalium.persistence.dao.ConversationIDEntity @@ -42,10 +43,13 @@ import com.wire.kalium.persistence.dao.ServiceDAO import com.wire.kalium.persistence.dao.ServiceDAOImpl import com.wire.kalium.persistence.dao.TeamDAO import com.wire.kalium.persistence.dao.TeamDAOImpl +import com.wire.kalium.persistence.dao.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAOImpl import com.wire.kalium.persistence.dao.UserDAO import com.wire.kalium.persistence.dao.UserDAOImpl import com.wire.kalium.persistence.dao.UserDetailsEntity import com.wire.kalium.persistence.dao.UserIDEntity +import com.wire.kalium.persistence.dao.UserPrefsDAO import com.wire.kalium.persistence.dao.asset.AssetDAO import com.wire.kalium.persistence.dao.asset.AssetDAOImpl import com.wire.kalium.persistence.dao.call.CallDAO @@ -84,8 +88,6 @@ import com.wire.kalium.persistence.dao.reaction.ReactionDAO import com.wire.kalium.persistence.dao.reaction.ReactionDAOImpl import com.wire.kalium.persistence.dao.receipt.ReceiptDAO import com.wire.kalium.persistence.dao.receipt.ReceiptDAOImpl -import com.wire.kalium.persistence.dao.unread.UserConfigDAO -import com.wire.kalium.persistence.dao.unread.UserConfigDAOImpl import com.wire.kalium.persistence.db.feeders.MentionsFeeder import com.wire.kalium.persistence.db.feeders.MessagesFeeder import com.wire.kalium.persistence.db.feeders.ReactionsFeeder @@ -204,8 +206,9 @@ class UserDatabaseBuilder internal constructor( val messageMetaDataDAO: MessageMetadataDAO get() = MessageMetadataDAOImpl(database.messageMetadataQueries, readDispatcher) - val userConfigDAO: UserConfigDAO - get() = UserConfigDAOImpl(metadataDAO) + val userConfigDAO: UserConfigDAO by lazy { UserConfigDAOImpl(metadataDAO) } + + val userPrefsDAO: UserConfigStorage by lazy { UserPrefsDAO(metadataDAO) } val connectionDAO: ConnectionDAO get() = ConnectionDAOImpl( diff --git a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigDAOTest.kt b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigDAOTest.kt index a1902160ce8..a5c48afde44 100644 --- a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigDAOTest.kt +++ b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigDAOTest.kt @@ -20,7 +20,7 @@ package com.wire.kalium.persistence.config import app.cash.turbine.test import com.wire.kalium.persistence.BaseDatabaseTest import com.wire.kalium.persistence.dao.UserIDEntity -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest diff --git a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigStorageTest.kt b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigStorageTest.kt index b1ddab9af78..d895ddbec6b 100644 --- a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigStorageTest.kt +++ b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/config/UserConfigStorageTest.kt @@ -24,7 +24,6 @@ import com.russhwolf.settings.Settings import com.wire.kalium.persistence.dao.SupportedProtocolEntity import com.wire.kalium.persistence.kmmSettings.KaliumPreferences import com.wire.kalium.persistence.kmmSettings.KaliumPreferencesSettings -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlin.test.AfterTest @@ -115,7 +114,7 @@ class UserConfigStorageTest { } @Test - fun givenGuestRoomLinkStatusIsSetToFalse_whenGettingItsValue_thenItShouldBeFalse() { + fun givenGuestRoomLinkStatusIsSetToFalse_whenGettingItsValue_thenItShouldBeFalse() = runTest { userConfigStorage.persistGuestRoomLinkFeatureFlag(status = false, isStatusChanged = false) userConfigStorage.isGuestRoomLinkEnabled()?.status?.let { assertFalse { it } @@ -123,7 +122,7 @@ class UserConfigStorageTest { } @Test - fun givenGuestRoomLinkStatusIsSetToTrue_whenGettingItsValue_thenItShouldBeTrue() { + fun givenGuestRoomLinkStatusIsSetToTrue_whenGettingItsValue_thenItShouldBeTrue() = runTest { userConfigStorage.persistGuestRoomLinkFeatureFlag(status = true, isStatusChanged = false) userConfigStorage.isGuestRoomLinkEnabled()?.status?.let { assertTrue { it } @@ -195,12 +194,12 @@ class UserConfigStorageTest { } @Test - fun givenDefaultProtocolIsNotSet_whenGettingItsValue_thenItShouldBeProteus() { + fun givenDefaultProtocolIsNotSet_whenGettingItsValue_thenItShouldBeProteus() = runTest { assertEquals(SupportedProtocolEntity.PROTEUS, userConfigStorage.defaultProtocol()) } @Test - fun givenDefaultProtocolIsSetToMls_whenGettingItsValue_thenItShouldBeMls() { + fun givenDefaultProtocolIsSetToMls_whenGettingItsValue_thenItShouldBeMls() = runTest { userConfigStorage.persistDefaultProtocol(SupportedProtocolEntity.MLS) assertEquals(SupportedProtocolEntity.MLS, userConfigStorage.defaultProtocol()) } diff --git a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapperTest.kt b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapperTest.kt index 5e94e7470d7..e9bfb31515e 100644 --- a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapperTest.kt +++ b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UnreadContentMapperTest.kt @@ -19,6 +19,7 @@ package com.wire.kalium.persistence.dao import com.wire.kalium.persistence.dao.message.MessageEntity +import com.wire.kalium.persistence.dao.unread.UnreadContentMapper import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals diff --git a/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserPrefsDAOTest.kt b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserPrefsDAOTest.kt new file mode 100644 index 00000000000..cce3490a258 --- /dev/null +++ b/data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserPrefsDAOTest.kt @@ -0,0 +1,287 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +package com.wire.kalium.persistence.dao + +import app.cash.turbine.test +import com.wire.kalium.persistence.config.AppLockConfigEntity +import com.wire.kalium.persistence.config.ClassifiedDomainsEntity +import com.wire.kalium.persistence.config.IsFileSharingEnabledEntity +import com.wire.kalium.persistence.config.IsGuestRoomLinkEnabledEntity +import io.mockative.any +import io.mockative.coEvery +import io.mockative.eq +import io.mockative.every +import io.mockative.mock +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +internal class UserPrefsDAOTest { + + @Test + fun givenAFileSharingStatusValue_whenCAllPersistItSaveAnd_thenCanRestoreTheValueLocally() = runTest { + val expected1 = IsFileSharingEnabledEntity(true, null) + val expected2 = IsFileSharingEnabledEntity(false, null) + val (_, userConfigStorage) = Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("file_sharing", expected1) + .arrange() + userConfigStorage.persistFileSharingStatus(true, null) + assertEquals(expected1, userConfigStorage.isFileSharingEnabled()) + + Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("file_sharing", expected2) + .arrange() + .let { (_, storage) -> + storage.persistFileSharingStatus(false, null) + assertEquals(expected2, storage.isFileSharingEnabled()) + } + } + + @Test + fun givenAClassifiedDomainsStatusValue_whenCAllPersistItSaveAndThenCanRestoreTheValueLocally() = runTest { + val expected = ClassifiedDomainsEntity(true, listOf("bella.com", "anta.wire")) + val (_, userConfigStorage) = Arrangement() + .withSerializablePutSucceeding() + .withSerializableObserveSucceeding("enable_classified_domains", expected) + .arrange() + userConfigStorage.persistClassifiedDomainsStatus(true, listOf("bella.com", "anta.wire")) + assertEquals( + expected, + userConfigStorage.isClassifiedDomainsEnabledFlow().first() + ) + } + + @Test + fun givenAClassifiedDomainsStatusNotExists_whenCAllPersistItSaveAndThenCanRestoreTheValueLocally() = runTest { + val (_, userConfigStorage) = Arrangement() + .withSerializableObserveReturningNull("enable_classified_domains") + .arrange() + + userConfigStorage.isClassifiedDomainsEnabledFlow().test { + val item = awaitItem() + assertEquals(null, item) + awaitComplete() + } + } + + @Test + fun givenAConferenceCallingStatusValue_whenPersistingIt_saveAndThenRestoreTheValueLocally() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withMetadataRetrieved("enable_conference_calling", "true") + .arrange() + userConfigStorage.persistConferenceCalling(true) + assertEquals( + true, + userConfigStorage.isConferenceCallingEnabled() + ) + } + + @Test + fun givenAReadReceiptsSetValue_whenPersistingIt_saveAndThenRestoreTheValueLocally() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withValueByKeyFlowSucceeding("enable_read_receipts", "true") + .arrange() + userConfigStorage.persistReadReceipts(true) + assertTrue(userConfigStorage.areReadReceiptsEnabled().first()) + } + + @Test + fun whenMarkingFileSharingAsNotified_thenIsChangedIsSetToFalse() = runTest { + val statusWithChanged = IsFileSharingEnabledEntity(true, true) + val statusWithoutChanged = IsFileSharingEnabledEntity(true, false) + val (_, userConfigStorage) = Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("file_sharing", statusWithChanged) + .arrange() + userConfigStorage.persistFileSharingStatus(true, true) + + Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("file_sharing", statusWithoutChanged) + .arrange() + .let { (_, storage) -> + storage.setFileSharingAsNotified() + assertEquals(statusWithoutChanged, storage.isFileSharingEnabled()) + } + } + + @Test + fun givenPasswordChallengeRequirementIsNotSet_whenGettingItsValue_thenItShouldBeFalseByDefault() = runTest { + val (_, userConfigStorage) = Arrangement() + .withNoMetadataRetrieved("require_second_factor_password_challenge") + .arrange() + assertFalse { + userConfigStorage.isSecondFactorPasswordChallengeRequired() + } + } + + @Test + fun givenPasswordChallengeRequirementIsSetToFalse_whenGettingItsValue_thenItShouldBeFalse() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withMetadataRetrieved("require_second_factor_password_challenge", "false") + .arrange() + userConfigStorage.persistSecondFactorPasswordChallengeStatus(false) + assertFalse { + userConfigStorage.isSecondFactorPasswordChallengeRequired() + } + } + + @Test + fun givenPasswordChallengeRequirementIsSetToTrue_whenGettingItsValue_thenItShouldBeTrue() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withMetadataRetrieved("require_second_factor_password_challenge", "true") + .arrange() + userConfigStorage.persistSecondFactorPasswordChallengeStatus(true) + assertTrue { + userConfigStorage.isSecondFactorPasswordChallengeRequired() + } + } + + @Test + fun givenGuestRoomLinkStatusIsSetToFalse_whenGettingItsValue_thenItShouldBeFalse() = runTest { + val expected = IsGuestRoomLinkEnabledEntity(false, false) + val (_, userConfigStorage) = Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("guest_room_link", expected) + .arrange() + userConfigStorage.persistGuestRoomLinkFeatureFlag(status = false, isStatusChanged = false) + userConfigStorage.isGuestRoomLinkEnabled()?.status?.let { + assertFalse { it } + } + } + + @Test + fun givenGuestRoomLinkStatusIsSetToTrue_whenGettingItsValue_thenItShouldBeTrue() = runTest { + val expected = IsGuestRoomLinkEnabledEntity(true, false) + val (_, userConfigStorage) = Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("guest_room_link", expected) + .arrange() + userConfigStorage.persistGuestRoomLinkFeatureFlag(status = true, isStatusChanged = false) + userConfigStorage.isGuestRoomLinkEnabled()?.status?.let { + assertTrue { it } + } + } + + @Test + fun givenScreenshotCensoringConfigIsSetToFalse_whenGettingItsValue_thenItShouldBeFalse() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withValueByKeyFlowSucceeding("enable_screenshot_censoring", "false") + .arrange() + userConfigStorage.persistScreenshotCensoring(enabled = false) + assertEquals(false, userConfigStorage.isScreenshotCensoringEnabledFlow().first()) + } + + @Test + fun givenScreenshotCensoringConfigIsSetToTrue_whenGettingItsValue_thenItShouldBeTrue() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withValueByKeyFlowSucceeding("enable_screenshot_censoring", "true") + .arrange() + userConfigStorage.persistScreenshotCensoring(enabled = true) + assertEquals(true, userConfigStorage.isScreenshotCensoringEnabledFlow().first()) + } + + @Test + fun givenAppLockConfig_whenStoring_thenItCanBeRead() = runTest { + val expected = AppLockConfigEntity( + enforceAppLock = true, + inactivityTimeoutSecs = 60, + isStatusChanged = false + ) + val (_, userConfigStorage) = Arrangement() + .withSerializablePutSucceeding() + .withSerializableGetSucceeding("app_lock", expected) + .arrange() + userConfigStorage.persistAppLockStatus( + expected.enforceAppLock, + expected.inactivityTimeoutSecs, + expected.isStatusChanged + ) + assertEquals(expected, userConfigStorage.appLockStatus()) + } + + @Test + fun givenDefaultProtocolIsNotSet_whenGettingItsValue_thenItShouldBeProteus() = runTest { + val (_, userConfigStorage) = Arrangement() + .withNoMetadataRetrieved("default_protocol") + .arrange() + assertEquals(SupportedProtocolEntity.PROTEUS, userConfigStorage.defaultProtocol()) + } + + @Test + fun givenDefaultProtocolIsSetToMls_whenGettingItsValue_thenItShouldBeMls() = runTest { + val (_, userConfigStorage) = Arrangement() + .withMetadataInserted() + .withMetadataRetrieved("default_protocol", "MLS") + .arrange() + userConfigStorage.persistDefaultProtocol(SupportedProtocolEntity.MLS) + assertEquals(SupportedProtocolEntity.MLS, userConfigStorage.defaultProtocol()) + } + + private class Arrangement { + val metadataDAO = mock(MetadataDAO::class) + + suspend fun withMetadataInserted() = apply { + coEvery { metadataDAO.insertValue(any(), any()) } returns Unit + } + + suspend fun withMetadataRetrieved(key: String, value: String?) = apply { + coEvery { metadataDAO.valueByKey(eq(key)) } returns value + } + + suspend fun withNoMetadataRetrieved(key: String) = apply { + coEvery { metadataDAO.valueByKey(eq(key)) } returns null + } + + suspend fun withSerializablePutSucceeding() = apply { + coEvery { metadataDAO.putSerializable(any(), any(), any()) } returns Unit + } + + suspend fun withSerializableGetSucceeding(key: String, value: Any?) = apply { + coEvery { metadataDAO.getSerializable(eq(key), any>()) } returns value + } + + fun withSerializableObserveSucceeding(key: String, value: Any?) = apply { + every { metadataDAO.observeSerializable(eq(key), any>()) } returns flowOf(value) + } + + fun withSerializableObserveReturningNull(key: String) = apply { + every { metadataDAO.observeSerializable(eq(key), any>()) } returns flowOf(null) + } + + suspend fun withValueByKeyFlowSucceeding(key: String, value: String?) = apply { + coEvery { metadataDAO.valueByKeyFlow(eq(key)) } returns flowOf(value) + } + + fun arrange() = this to UserPrefsDAO(metadataDAO) + + } +} diff --git a/data/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt b/data/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..f9aea6dd994 --- /dev/null +++ b/data/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.config + +import com.wire.kalium.persistence.dao.UserIDEntity + +@Suppress("DEPRECATION") +@Deprecated( + "Scheduled for removal in future versions, User KMM Settings are now replaced by database implementation." + + "Just kept for migration purposes.", + ReplaceWith("No replacement available"), +) +actual class UserConfigStorageFactory actual constructor() { + actual fun create( + userId: UserIDEntity, + shouldEncryptData: Boolean, + platformParam: Any + ): UserConfigStorage { + TODO("JS implementation not yet available") + } +} diff --git a/data/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt b/data/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..c3ffbf0421e --- /dev/null +++ b/data/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/config/UserConfigStorageFactory.kt @@ -0,0 +1,51 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.config + +import com.wire.kalium.persistence.dao.UserIDEntity +import com.wire.kalium.persistence.kmmSettings.EncryptedSettingsPlatformParam +import com.wire.kalium.persistence.kmmSettings.KaliumPreferencesSettings +import com.wire.kalium.persistence.kmmSettings.SettingOptions +import com.wire.kalium.persistence.kmmSettings.buildSettings + +@Suppress("DEPRECATION") +@Deprecated( + "Scheduled for removal in future versions, User KMM Settings are now replaced by database implementation." + + "Just kept for migration purposes.", + ReplaceWith("No replacement available"), +) +actual class UserConfigStorageFactory actual constructor() { + /** + * Creates a [UserConfigStorage] instance for JVM. + * @param userId The user ID entity + * @param shouldEncryptData Whether to encrypt the data + * @param platformParam Must be a [String] representing the root path + */ + actual fun create( + userId: UserIDEntity, + shouldEncryptData: Boolean, + platformParam: Any + ): UserConfigStorage { + require(platformParam is String) { "platformParam must be a String (root path)" } + val settings = buildSettings( + SettingOptions.UserSettings(shouldEncryptData, userId), + EncryptedSettingsPlatformParam(platformParam) + ) + return UserConfigStorageImpl(KaliumPreferencesSettings(settings)) + } +} diff --git a/data/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt b/data/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt deleted file mode 100644 index a891fcab7c9..00000000000 --- a/data/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.kalium.persistence.kmmSettings - -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorage -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorageImpl -import com.wire.kalium.persistence.config.UserConfigStorage -import com.wire.kalium.persistence.config.UserConfigStorageImpl -import com.wire.kalium.persistence.dao.UserIDEntity - -actual class UserPrefBuilder( - userId: UserIDEntity, - rootPath: String, - shouldEncryptData: Boolean = true -) { - - private val kaliumPref = - KaliumPreferencesSettings( - buildSettings( - SettingOptions.UserSettings(shouldEncryptData, userId), - EncryptedSettingsPlatformParam(rootPath) - ) - ) - - actual val lastRetrievedNotificationEventStorage: LastRetrievedNotificationEventStorage - get() = LastRetrievedNotificationEventStorageImpl(kaliumPref) - actual val userConfigStorage: UserConfigStorage = UserConfigStorageImpl(kaliumPref) - - actual fun clear() { - kaliumPref.nuke() - } - -} diff --git a/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/CellsScope.kt b/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/CellsScope.kt index 5b6b6f968b3..0528ea18bcd 100644 --- a/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/CellsScope.kt +++ b/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/CellsScope.kt @@ -126,7 +126,7 @@ import com.wire.kalium.persistence.dao.conversation.ConversationDAO import com.wire.kalium.persistence.dao.message.attachment.MessageAttachmentsDao import com.wire.kalium.persistence.dao.messageattachment.MessageAttachmentDraftDao import com.wire.kalium.persistence.dao.publiclink.PublicLinkDao -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO import io.ktor.client.HttpClient import io.ktor.client.plugins.HttpRedirect import kotlinx.coroutines.CoroutineScope diff --git a/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/data/CellConfigDataSource.kt b/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/data/CellConfigDataSource.kt index 1c526500c9f..bc35a410554 100644 --- a/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/data/CellConfigDataSource.kt +++ b/domain/cells/src/commonMain/kotlin/com/wire/kalium/cells/data/CellConfigDataSource.kt @@ -23,7 +23,7 @@ import com.wire.kalium.common.error.StorageFailure import com.wire.kalium.common.error.wrapStorageRequest import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.data.featureConfig.CollaboraEdition.Companion.fromString -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO internal class CellConfigDataSource( private val userConfigDAO: UserConfigDAO, diff --git a/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt b/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt index a225461422c..2806b963c90 100644 --- a/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt +++ b/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt @@ -22,7 +22,6 @@ import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.persistence.db.PlatformDatabaseData import com.wire.kalium.persistence.db.userDatabaseBuilder -import com.wire.kalium.persistence.kmmSettings.UserPrefBuilder import com.wire.kalium.util.KaliumDispatcherImpl internal actual class PlatformUserStorageProvider : UserStorageProvider() { @@ -33,7 +32,6 @@ internal actual class PlatformUserStorageProvider : UserStorageProvider() { dbInvalidationControlEnabled: Boolean ): UserStorage { val userIdEntity = userId.toDao() - val pref = UserPrefBuilder(userIdEntity, platformProperties.applicationContext, shouldEncryptData) val databasePassphrase = if (shouldEncryptData) { platformProperties.securityHelper.userDBSecret(userId) @@ -48,6 +46,6 @@ internal actual class PlatformUserStorageProvider : UserStorageProvider() { enableWAL = true, dbInvalidationControlEnabled = dbInvalidationControlEnabled ) - return UserStorage(database, pref) + return UserStorage(database) } } diff --git a/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt b/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..98e89894d4a --- /dev/null +++ b/logic/src/androidMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt @@ -0,0 +1,38 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.di + +import com.wire.kalium.logic.data.id.toDao +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.persistence.config.UserConfigStorage +import com.wire.kalium.persistence.config.UserConfigStorageFactory as PersistenceUserConfigStorageFactory + +@Suppress("DEPRECATION") +internal actual class UserConfigStorageFactory actual constructor() { + private val persistenceFactory = PersistenceUserConfigStorageFactory() + + actual fun create( + userId: UserId, + shouldEncryptData: Boolean, + platformProperties: PlatformUserStorageProperties + ): UserConfigStorage = persistenceFactory.create( + userId = userId.toDao(), + shouldEncryptData = shouldEncryptData, + platformParam = platformProperties.applicationContext + ) +} diff --git a/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt b/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt index 92ccebe529c..de5bdaa6108 100644 --- a/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt +++ b/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt @@ -23,7 +23,6 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.persistence.db.PlatformDatabaseData import com.wire.kalium.persistence.db.StorageData import com.wire.kalium.persistence.db.userDatabaseBuilder -import com.wire.kalium.persistence.kmmSettings.UserPrefBuilder import com.wire.kalium.util.KaliumDispatcherImpl internal actual class PlatformUserStorageProvider actual constructor() : UserStorageProvider() { @@ -34,7 +33,6 @@ internal actual class PlatformUserStorageProvider actual constructor() : UserSto dbInvalidationControlEnabled: Boolean ): UserStorage { val userIdEntity = userId.toDao() - val pref = UserPrefBuilder(userIdEntity, platformProperties.rootPath, shouldEncryptData) val database = userDatabaseBuilder( platformDatabaseData = PlatformDatabaseData(StorageData.FileBacked(platformProperties.rootStoragePath)), userId = userIdEntity, @@ -43,6 +41,6 @@ internal actual class PlatformUserStorageProvider actual constructor() : UserSto enableWAL = true, dbInvalidationControlEnabled = dbInvalidationControlEnabled ) - return UserStorage(database, pref) + return UserStorage(database) } } diff --git a/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt b/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..b63bdbfaa7b --- /dev/null +++ b/logic/src/appleMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt @@ -0,0 +1,38 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.di + +import com.wire.kalium.logic.data.id.toDao +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.persistence.config.UserConfigStorage +import com.wire.kalium.persistence.config.UserConfigStorageFactory as PersistenceUserConfigStorageFactory + +@Suppress("DEPRECATION") +internal actual class UserConfigStorageFactory actual constructor() { + private val persistenceFactory = PersistenceUserConfigStorageFactory() + + actual fun create( + userId: UserId, + shouldEncryptData: Boolean, + platformProperties: PlatformUserStorageProperties + ): UserConfigStorage = persistenceFactory.create( + userId = userId.toDao(), + shouldEncryptData = shouldEncryptData, + platformParam = platformProperties.rootPath + ) +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt index 558a82e8f0a..32be9370af7 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/configuration/UserConfigRepository.kt @@ -23,7 +23,6 @@ import com.wire.kalium.common.error.StorageFailure import com.wire.kalium.common.error.wrapFlowStorageRequest import com.wire.kalium.common.error.wrapStorageRequest import com.wire.kalium.common.functional.Either -import com.wire.kalium.common.functional.getOrNull import com.wire.kalium.common.functional.isLeft import com.wire.kalium.common.functional.map import com.wire.kalium.common.functional.mapRight @@ -48,7 +47,7 @@ import com.wire.kalium.persistence.config.IsFileSharingEnabledEntity import com.wire.kalium.persistence.config.TeamSettingsSelfDeletionStatusEntity import com.wire.kalium.persistence.config.UserConfigStorage import com.wire.kalium.persistence.config.WireCellsConfigEntity -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO import com.wire.kalium.persistence.model.SupportedCipherSuiteEntity import com.wire.kalium.util.DateTimeUtil import io.mockative.Mockable @@ -64,54 +63,54 @@ import kotlin.time.Duration.Companion.seconds @Suppress("TooManyFunctions") @Mockable internal interface UserConfigRepository { - fun setAppLockStatus( + suspend fun setAppLockStatus( isAppLocked: Boolean, timeout: Int, isStatusChanged: Boolean? ): Either - fun isTeamAppLockEnabled(): Either + suspend fun isTeamAppLockEnabled(): Either fun observeAppLockConfig(): Flow> - fun setTeamAppLockAsNotified(): Either - fun setFileSharingStatus( + suspend fun setTeamAppLockAsNotified(): Either + suspend fun setFileSharingStatus( status: Boolean, isStatusChanged: Boolean? ): Either - fun setFileSharingAsNotified(): Either - fun isFileSharingEnabled(): Either + suspend fun setFileSharingAsNotified(): Either + suspend fun isFileSharingEnabled(): Either fun isFileSharingEnabledFlow(): Flow> - fun setClassifiedDomainsStatus( + suspend fun setClassifiedDomainsStatus( enabled: Boolean, domains: List ): Either fun getClassifiedDomainsStatus(): Flow> suspend fun isMLSEnabled(): Either - fun setMLSEnabled(enabled: Boolean): Either - fun getE2EISettings(): Either + suspend fun setMLSEnabled(enabled: Boolean): Either + suspend fun getE2EISettings(): Either fun observeE2EISettings(): Flow> - fun setE2EISettings(setting: E2EISettings): Either - fun snoozeE2EINotification(duration: Duration): Either - fun setDefaultProtocol(protocol: SupportedProtocol): Either + suspend fun setE2EISettings(setting: E2EISettings): Either + suspend fun snoozeE2EINotification(duration: Duration): Either + suspend fun setDefaultProtocol(protocol: SupportedProtocol): Either suspend fun setSupportedCipherSuite(cipherSuite: SupportedCipherSuite): Either suspend fun getSupportedCipherSuite(): Either - fun getDefaultProtocol(): Either + suspend fun getDefaultProtocol(): Either suspend fun setSupportedProtocols(protocols: Set): Either suspend fun getSupportedProtocols(): Either> - fun setConferenceCallingEnabled(enabled: Boolean): Either - fun isConferenceCallingEnabled(): Either - fun observeConferenceCallingEnabled(): Flow> - fun setUseSFTForOneOnOneCalls(shouldUse: Boolean): Either + suspend fun setConferenceCallingEnabled(enabled: Boolean): Either + suspend fun isConferenceCallingEnabled(): Either + suspend fun observeConferenceCallingEnabled(): Flow> + suspend fun setUseSFTForOneOnOneCalls(shouldUse: Boolean): Either suspend fun shouldUseSFTForOneOnOneCalls(): Either - fun setSecondFactorPasswordChallengeStatus(isRequired: Boolean): Either - fun isSecondFactorPasswordChallengeRequired(): Either - fun isReadReceiptsEnabled(): Flow> - fun setReadReceiptsStatus(enabled: Boolean): Either - fun isTypingIndicatorEnabled(): Flow> - fun setTypingIndicatorStatus(enabled: Boolean): Either - fun setGuestRoomStatus(status: Boolean, isStatusChanged: Boolean?): Either - fun getGuestRoomLinkStatus(): Either + suspend fun setSecondFactorPasswordChallengeStatus(isRequired: Boolean): Either + suspend fun isSecondFactorPasswordChallengeRequired(): Either + suspend fun isReadReceiptsEnabled(): Flow> + suspend fun setReadReceiptsStatus(enabled: Boolean): Either + suspend fun isTypingIndicatorEnabled(): Flow> + suspend fun setTypingIndicatorStatus(enabled: Boolean): Either + suspend fun setGuestRoomStatus(status: Boolean, isStatusChanged: Boolean?): Either + suspend fun getGuestRoomLinkStatus(): Either fun observeGuestRoomLinkFeatureFlag(): Flow> suspend fun setScreenshotCensoringConfig(enabled: Boolean): Either suspend fun observeScreenshotCensoringConfig(): Flow> @@ -123,8 +122,8 @@ internal interface UserConfigRepository { suspend fun markTeamSettingsSelfDeletingMessagesStatusAsNotified(): Either suspend fun observeTeamSettingsSelfDeletingStatus(): Flow> - fun observeE2EINotificationTime(): Flow> - fun setE2EINotificationTime(instant: Instant): Either + suspend fun observeE2EINotificationTime(): Flow> + suspend fun setE2EINotificationTime(instant: Instant): Either suspend fun getMigrationConfiguration(): Either suspend fun setMigrationConfiguration(configuration: MLSMigrationModel): Either suspend fun setLegalHoldRequest( @@ -179,17 +178,17 @@ internal class UserConfigDataSource internal constructor( private val kaliumConfigs: KaliumConfigs ) : UserConfigRepository { - override fun setFileSharingStatus( + override suspend fun setFileSharingStatus( status: Boolean, isStatusChanged: Boolean? ): Either = wrapStorageRequest { userConfigStorage.persistFileSharingStatus(status, isStatusChanged) } - override fun setFileSharingAsNotified(): Either = wrapStorageRequest { + override suspend fun setFileSharingAsNotified(): Either = wrapStorageRequest { userConfigStorage.setFileSharingAsNotified() } - override fun isFileSharingEnabled(): Either { + override suspend fun isFileSharingEnabled(): Either { val serverSideConfig = wrapStorageRequest { userConfigStorage.isFileSharingEnabled() } val buildConfig = kaliumConfigs.fileRestrictionState return deriveFileSharingStatus(serverSideConfig, buildConfig) @@ -233,7 +232,7 @@ internal class UserConfigDataSource internal constructor( else -> error("Unknown file restriction state: buildConfig: $buildConfig , serverConfig: $serverSideConfig") } - override fun setClassifiedDomainsStatus(enabled: Boolean, domains: List) = + override suspend fun setClassifiedDomainsStatus(enabled: Boolean, domains: List) = wrapStorageRequest { userConfigStorage.persistClassifiedDomainsStatus(enabled, domains) } override fun getClassifiedDomainsStatus(): Flow> = @@ -247,10 +246,10 @@ internal class UserConfigDataSource internal constructor( wrapStorageRequest { userConfigStorage.isMLSEnabled() } } - override fun setMLSEnabled(enabled: Boolean): Either = + override suspend fun setMLSEnabled(enabled: Boolean): Either = wrapStorageRequest { userConfigStorage.enableMLS(enabled) } - override fun getE2EISettings(): Either = + override suspend fun getE2EISettings(): Either = wrapStorageRequest { userConfigStorage.getE2EISettings() } .map { E2EISettings.fromEntity(it) } @@ -259,18 +258,18 @@ internal class UserConfigDataSource internal constructor( .wrapStorageRequest() .mapRight { E2EISettings.fromEntity(it) } - override fun setE2EISettings(setting: E2EISettings): Either = + override suspend fun setE2EISettings(setting: E2EISettings): Either = wrapStorageRequest { userConfigStorage.setE2EISettings(setting.toEntity()) } - override fun observeE2EINotificationTime(): Flow> = + override suspend fun observeE2EINotificationTime(): Flow> = userConfigStorage.e2EINotificationTimeFlow() .wrapStorageRequest() .mapRight { Instant.fromEpochMilliseconds(it) } - override fun setE2EINotificationTime(instant: Instant): Either = + override suspend fun setE2EINotificationTime(instant: Instant): Either = wrapStorageRequest { userConfigStorage.setIfAbsentE2EINotificationTime(instant.toEpochMilliseconds()) } - override fun snoozeE2EINotification(duration: Duration): Either = + override suspend fun snoozeE2EINotification(duration: Duration): Either = wrapStorageRequest { val notifyUserAfterMs = DateTimeUtil.currentInstant().toEpochMilliseconds().plus(duration.inWholeMilliseconds) userConfigStorage.updateE2EINotificationTime(notifyUserAfterMs) @@ -283,10 +282,7 @@ internal class UserConfigDataSource internal constructor( } } - private fun getE2EINotificationTimeOrNull() = - wrapStorageRequest { userConfigStorage.getE2EINotificationTime() }.getOrNull() - - override fun setDefaultProtocol(protocol: SupportedProtocol): Either = + override suspend fun setDefaultProtocol(protocol: SupportedProtocol): Either = wrapStorageRequest { userConfigStorage.persistDefaultProtocol(protocol.toDao()) } override suspend fun setSupportedCipherSuite(cipherSuite: SupportedCipherSuite): Either = @@ -308,7 +304,7 @@ internal class UserConfigDataSource internal constructor( ) } - override fun getDefaultProtocol(): Either = + override suspend fun getDefaultProtocol(): Either = wrapStorageRequest { userConfigStorage.defaultProtocol().toModel() } override suspend fun setSupportedProtocols(protocols: Set): Either = @@ -317,20 +313,20 @@ internal class UserConfigDataSource internal constructor( override suspend fun getSupportedProtocols(): Either> = wrapStorageRequest { userConfigDAO.getSupportedProtocols()?.toModel() } - override fun setConferenceCallingEnabled(enabled: Boolean): Either = + override suspend fun setConferenceCallingEnabled(enabled: Boolean): Either = wrapStorageRequest { userConfigStorage.persistConferenceCalling(enabled) } - override fun isConferenceCallingEnabled(): Either = + override suspend fun isConferenceCallingEnabled(): Either = wrapStorageRequest { userConfigStorage.isConferenceCallingEnabled() } - override fun observeConferenceCallingEnabled(): Flow> = + override suspend fun observeConferenceCallingEnabled(): Flow> = userConfigStorage.isConferenceCallingEnabledFlow().wrapStorageRequest() - override fun setUseSFTForOneOnOneCalls(shouldUse: Boolean): Either = wrapStorageRequest { + override suspend fun setUseSFTForOneOnOneCalls(shouldUse: Boolean): Either = wrapStorageRequest { userConfigStorage.persistUseSftForOneOnOneCalls(shouldUse) } @@ -340,33 +336,33 @@ internal class UserConfigDataSource internal constructor( } } - override fun setSecondFactorPasswordChallengeStatus(isRequired: Boolean): Either = + override suspend fun setSecondFactorPasswordChallengeStatus(isRequired: Boolean): Either = wrapStorageRequest { userConfigStorage.persistSecondFactorPasswordChallengeStatus(isRequired) } - override fun isSecondFactorPasswordChallengeRequired(): Either = + override suspend fun isSecondFactorPasswordChallengeRequired(): Either = wrapStorageRequest { userConfigStorage.isSecondFactorPasswordChallengeRequired() } - override fun isReadReceiptsEnabled(): Flow> = + override suspend fun isReadReceiptsEnabled(): Flow> = userConfigStorage.areReadReceiptsEnabled().wrapStorageRequest() - override fun setReadReceiptsStatus(enabled: Boolean): Either = + override suspend fun setReadReceiptsStatus(enabled: Boolean): Either = wrapStorageRequest { userConfigStorage.persistReadReceipts(enabled) } - override fun isTypingIndicatorEnabled(): Flow> = + override suspend fun isTypingIndicatorEnabled(): Flow> = userConfigStorage.isTypingIndicatorEnabled().wrapStorageRequest() - override fun setTypingIndicatorStatus(enabled: Boolean): Either = + override suspend fun setTypingIndicatorStatus(enabled: Boolean): Either = wrapStorageRequest { userConfigStorage.persistTypingIndicator(enabled) } - override fun setGuestRoomStatus( + override suspend fun setGuestRoomStatus( status: Boolean, isStatusChanged: Boolean? ): Either = @@ -374,7 +370,7 @@ internal class UserConfigDataSource internal constructor( userConfigStorage.persistGuestRoomLinkFeatureFlag(status, isStatusChanged) } - override fun getGuestRoomLinkStatus(): Either = + override suspend fun getGuestRoomLinkStatus(): Either = wrapStorageRequest { userConfigStorage.isGuestRoomLinkEnabled() }.map { with(it) { GuestRoomLinkStatus(status, isStatusChanged) } } @@ -434,7 +430,7 @@ internal class UserConfigDataSource internal constructor( override suspend fun observeScreenshotCensoringConfig(): Flow> = userConfigStorage.isScreenshotCensoringEnabledFlow().wrapStorageRequest() - override fun setAppLockStatus( + override suspend fun setAppLockStatus( isAppLocked: Boolean, timeout: Int, isStatusChanged: Boolean? @@ -460,7 +456,7 @@ internal class UserConfigDataSource internal constructor( } } - override fun isTeamAppLockEnabled(): Either = + override suspend fun isTeamAppLockEnabled(): Either = wrapStorageRequest { userConfigStorage.appLockStatus() }.map { @@ -471,7 +467,7 @@ internal class UserConfigDataSource internal constructor( ) } - override fun setTeamAppLockAsNotified(): Either = wrapStorageRequest { + override suspend fun setTeamAppLockAsNotified(): Either = wrapStorageRequest { userConfigStorage.setTeamAppLockAsNotified() } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt index a66aaa8ae3c..fa56d1d7bf5 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepository.kt @@ -97,7 +97,7 @@ internal interface E2EIRepository { suspend fun initiateMLSClient(certificateChain: String): Either suspend fun nukeE2EIClient() suspend fun fetchFederationCertificates(): Either - fun discoveryUrl(): Either + suspend fun discoveryUrl(): Either } @Suppress("LongParameterList") @@ -384,7 +384,7 @@ internal class E2EIRepositoryImpl( }) }) - override fun discoveryUrl() = + override suspend fun discoveryUrl() = userConfigRepository.getE2EISettings().fold({ E2EIFailure.MissingTeamSettings.left() }, { settings -> diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/team/TeamRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/team/TeamRepository.kt index 32005d7d5af..bc74b7efcb4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/team/TeamRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/team/TeamRepository.kt @@ -45,7 +45,7 @@ import com.wire.kalium.persistence.dao.ServiceDAO import com.wire.kalium.persistence.dao.TeamDAO import com.wire.kalium.persistence.dao.UserDAO import com.wire.kalium.persistence.dao.message.LocalId -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO import io.mockative.Mockable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/data/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt similarity index 55% rename from data/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt rename to logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt index 6666485d928..e79086d03b7 100644 --- a/data/persistence/src/jsMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt @@ -1,6 +1,6 @@ /* * Wire - * Copyright (C) 2024 Wire Swiss GmbH + * Copyright (C) 2026 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,20 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ +package com.wire.kalium.logic.di -package com.wire.kalium.persistence.kmmSettings - -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorage +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.persistence.config.UserConfigStorage -actual class UserPrefBuilder { - actual val lastRetrievedNotificationEventStorage: LastRetrievedNotificationEventStorage - get() = TODO("Not yet implemented") - - actual fun clear() { - TODO("Not yet implemented") - } - - actual val userConfigStorage: UserConfigStorage - get() = TODO("Not yet implemented") +/** + * Factory for creating [UserConfigStorage] lazily. + * This is used during migration from SharedPreferences to database storage. + * Creating the storage only when needed avoids creating SharedPreferences + * when migration is not required. + */ +internal expect class UserConfigStorageFactory() { + fun create( + userId: UserId, + shouldEncryptData: Boolean, + platformProperties: PlatformUserStorageProperties + ): UserConfigStorage } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserStorageProvider.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserStorageProvider.kt index 35aac86edb5..cfa02b41c58 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserStorageProvider.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/di/UserStorageProvider.kt @@ -21,9 +21,8 @@ package com.wire.kalium.logic.di import co.touchlab.stately.collections.ConcurrentMutableMap import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.persistence.db.UserDatabaseBuilder -import com.wire.kalium.persistence.kmmSettings.UserPrefBuilder -internal data class UserStorage(val database: UserDatabaseBuilder, val preferences: UserPrefBuilder) +internal data class UserStorage(val database: UserDatabaseBuilder) internal abstract class UserStorageProvider { private val inMemoryUserStorage: ConcurrentMutableMap = ConcurrentMutableMap() internal fun getOrCreate( diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index 1460b30a35e..a5c95a5f07d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -194,6 +194,7 @@ import com.wire.kalium.logic.data.user.UserRepository import com.wire.kalium.logic.di.MapperProvider import com.wire.kalium.logic.di.PlatformUserStorageProperties import com.wire.kalium.logic.di.RootPathsProvider +import com.wire.kalium.logic.di.UserConfigStorageFactory import com.wire.kalium.logic.di.UserStorageProvider import com.wire.kalium.logic.feature.analytics.AnalyticsIdentifierManager import com.wire.kalium.logic.feature.analytics.GetAnalyticsContactsDataUseCase @@ -558,7 +559,7 @@ public class UserSessionScope internal constructor( private val userSessionScopeProvider: UserSessionScopeProvider, userStorageProvider: UserStorageProvider, private val clientConfig: ClientConfig, - platformUserStorageProperties: PlatformUserStorageProperties, + private val platformUserStorageProperties: PlatformUserStorageProperties, networkStateObserver: NetworkStateObserver, private val logoutCallback: LogoutCallback, ) : CoroutineScope { @@ -707,7 +708,7 @@ public class UserSessionScope internal constructor( internal val userConfigRepository: UserConfigRepository get() = UserConfigDataSource( - userStorage.preferences.userConfigStorage, + userStorage.database.userPrefsDAO, userStorage.database.userConfigDAO, kaliumConfigs ) @@ -1299,7 +1300,14 @@ public class UserSessionScope internal constructor( get() = SlowSyncRecoveryHandlerImpl(logout) private val syncMigrationStepsProvider: () -> SyncMigrationStepsProvider = { - SyncMigrationStepsProviderImpl(lazy { accountRepository }, selfTeamId) + SyncMigrationStepsProviderImpl( + accountRepository = lazy { accountRepository }, + selfTeamIdProvider = selfTeamId, + oldUserConfigStorage = lazy { + UserConfigStorageFactory().create(userId, kaliumConfigs.shouldEncryptData, platformUserStorageProperties) + }, + newUserConfigStorage = lazy { userStorage.database.userPrefsDAO } + ) } private val slowSyncManager: SlowSyncManager by lazy { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCase.kt index ebbc81286e7..1d00fca7daf 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCase.kt @@ -25,13 +25,13 @@ import com.wire.kalium.logic.configuration.UserConfigRepository * e.g. after showing a dialog, or a toast etc. */ public interface MarkTeamAppLockStatusAsNotifiedUseCase { - public operator fun invoke() + public suspend operator fun invoke() } internal class MarkTeamAppLockStatusAsNotifiedUseCaseImpl internal constructor( private val userConfigRepository: UserConfigRepository ) : MarkTeamAppLockStatusAsNotifiedUseCase { - override operator fun invoke() { + override suspend operator fun invoke() { userConfigRepository.setTeamAppLockAsNotified() } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/ClearUserDataUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/ClearUserDataUseCase.kt index 58f6d507d39..3334f940281 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/ClearUserDataUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/auth/ClearUserDataUseCase.kt @@ -39,7 +39,5 @@ internal class ClearUserDataUseCaseImpl internal constructor( private fun clearUserStorage() { userStorage.database.nuke() - // exclude clientId clear from this step - userStorage.preferences.clear() } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCase.kt index 66b71413098..e64daa912de 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCase.kt @@ -67,7 +67,7 @@ internal class IsEligibleToStartCallUseCaseImpl( else -> ConferenceCallingResult.Disabled.Unavailable } - private fun isConferenceCallingEnabled(): Boolean = userConfigRepository + private suspend fun isConferenceCallingEnabled(): Boolean = userConfigRepository .isConferenceCallingEnabled() .fold({ DEFAULT_CONFERENCE_CALLING_ENABLED_VALUE diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandler.kt index 902a90bb81e..89ee19af3be 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandler.kt @@ -28,7 +28,7 @@ import com.wire.kalium.common.functional.nullableFold internal class AppLockConfigHandler internal constructor( private val userConfigRepository: UserConfigRepository ) { - fun handle(appLockConfig: AppLockModel): Either { + suspend fun handle(appLockConfig: AppLockModel): Either { val isStatusChanged = userConfigRepository.isTeamAppLockEnabled().nullableFold( { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ClassifiedDomainsConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ClassifiedDomainsConfigHandler.kt index afc2a234d2b..67726d31065 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ClassifiedDomainsConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ClassifiedDomainsConfigHandler.kt @@ -26,7 +26,7 @@ import com.wire.kalium.common.functional.Either internal class ClassifiedDomainsConfigHandler internal constructor( private val userConfigRepository: UserConfigRepository ) { - fun handle(classifiedDomainsConfig: ClassifiedDomainsModel): Either { + suspend fun handle(classifiedDomainsConfig: ClassifiedDomainsModel): Either { val classifiedDomainsEnabled = classifiedDomainsConfig.status == Status.ENABLED return userConfigRepository.setClassifiedDomainsStatus(classifiedDomainsEnabled, classifiedDomainsConfig.config.domains) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt index 64fd7e761fd..e497afa88a4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandler.kt @@ -27,7 +27,7 @@ import com.wire.kalium.common.functional.flatMap internal class ConferenceCallingConfigHandler( private val userConfigRepository: UserConfigRepository ) { - internal fun handle(conferenceCallingConfig: ConferenceCallingModel): Either { + internal suspend fun handle(conferenceCallingConfig: ConferenceCallingModel): Either { val conferenceCallingEnabled = conferenceCallingConfig.status == Status.ENABLED val result = userConfigRepository.setConferenceCallingEnabled(conferenceCallingEnabled).flatMap { userConfigRepository.setUseSFTForOneOnOneCalls(conferenceCallingConfig.useSFTForOneOnOneCalls) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/E2EIConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/E2EIConfigHandler.kt index 014c2a7760e..e61b11bdf16 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/E2EIConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/E2EIConfigHandler.kt @@ -30,12 +30,12 @@ import kotlin.time.toDuration internal class E2EIConfigHandler(private val userConfigRepository: UserConfigRepository) { - internal fun handle(e2eiConfig: E2EIModel): Either { + internal suspend fun handle(e2eiConfig: E2EIModel): Either { setSettingsIfNeeded(e2eiConfig) return userConfigRepository.setE2EINotificationTime(DateTimeUtil.currentInstant()) } - private fun setSettingsIfNeeded(e2eiConfig: E2EIModel) { + private suspend fun setSettingsIfNeeded(e2eiConfig: E2EIModel) { val gracePeriodEnd = e2eiConfig.config.verificationExpirationSeconds .toDuration(DurationUnit.SECONDS) .let { DateTimeUtil.currentInstant().plus(it) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/FileSharingConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/FileSharingConfigHandler.kt index 9ab34aaddcd..1b980297aaa 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/FileSharingConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/FileSharingConfigHandler.kt @@ -27,10 +27,9 @@ import com.wire.kalium.common.functional.Either internal class FileSharingConfigHandler( private val userConfigRepository: UserConfigRepository, ) { - internal fun handle(fileSharingConfig: ConfigsStatusModel): Either { + internal suspend fun handle(fileSharingConfig: ConfigsStatusModel): Either { val newStatus: Boolean = fileSharingConfig.status == Status.ENABLED - val currentStatus = userConfigRepository.isFileSharingEnabled() - val isStatusChanged: Boolean = when (currentStatus) { + val isStatusChanged: Boolean = when (val currentStatus = userConfigRepository.isFileSharingEnabled()) { is Either.Left -> false is Either.Right -> { when (currentStatus.value.state) { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/GuestRoomConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/GuestRoomConfigHandler.kt index ba9ba6f4040..71cb3f0587b 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/GuestRoomConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/GuestRoomConfigHandler.kt @@ -29,7 +29,7 @@ internal class GuestRoomConfigHandler( private val userConfigRepository: UserConfigRepository, private val kaliumConfigs: KaliumConfigs ) { - internal fun handle(guestRoomConfig: ConfigsStatusModel): Either = + internal suspend fun handle(guestRoomConfig: ConfigsStatusModel): Either = if (!kaliumConfigs.guestRoomLink) { userConfigRepository.setGuestRoomStatus(false, null) } else { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/SecondFactorPasswordChallengeConfigHandler.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/SecondFactorPasswordChallengeConfigHandler.kt index 8cb9cdaf1e3..1815dc9e8b4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/SecondFactorPasswordChallengeConfigHandler.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/SecondFactorPasswordChallengeConfigHandler.kt @@ -26,7 +26,7 @@ import com.wire.kalium.common.functional.Either internal class SecondFactorPasswordChallengeConfigHandler( private val userConfigRepository: UserConfigRepository ) { - internal fun handle(secondFactorPasswordChallengeConfig: ConfigsStatusModel): Either { + internal suspend fun handle(secondFactorPasswordChallengeConfig: ConfigsStatusModel): Either { val isRequired = secondFactorPasswordChallengeConfig.status == Status.ENABLED return userConfigRepository.setSecondFactorPasswordChallengeStatus(isRequired) } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCase.kt index 2942236a0be..b3bb998ce86 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCase.kt @@ -30,14 +30,14 @@ public interface GetDefaultProtocolUseCase { /** * @return [SupportedProtocol.MLS] or [SupportedProtocol.PROTEUS] */ - public operator fun invoke(): SupportedProtocol + public suspend operator fun invoke(): SupportedProtocol } internal class GetDefaultProtocolUseCaseImpl( private val userConfigRepository: UserConfigRepository ) : GetDefaultProtocolUseCase { - override fun invoke(): SupportedProtocol = + override suspend fun invoke(): SupportedProtocol = userConfigRepository.getDefaultProtocol().fold({ SupportedProtocol.PROTEUS }, { supportedProtocol -> diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/IsFileSharingEnabledUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/IsFileSharingEnabledUseCase.kt index e12cd6975a9..9080cbe25f2 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/IsFileSharingEnabledUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/IsFileSharingEnabledUseCase.kt @@ -28,14 +28,14 @@ import com.wire.kalium.common.functional.fold */ public interface IsFileSharingEnabledUseCase { - public operator fun invoke(): FileSharingStatus + public suspend operator fun invoke(): FileSharingStatus } internal class IsFileSharingEnabledUseCaseImpl( private val userConfigRepository: UserConfigRepository ) : IsFileSharingEnabledUseCase { - override operator fun invoke(): FileSharingStatus = + override suspend operator fun invoke(): FileSharingStatus = userConfigRepository.isFileSharingEnabled() .fold({ FileSharingStatus(FileSharingStatus.Value.Disabled, false) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/ObserveE2EIRequiredUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/ObserveE2EIRequiredUseCase.kt index 3abfb6925ac..160cfbc1a03 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/ObserveE2EIRequiredUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/ObserveE2EIRequiredUseCase.kt @@ -51,7 +51,7 @@ public interface ObserveE2EIRequiredUseCase { /** * @return [Flow] of [E2EIRequiredResult] */ - public operator fun invoke(): Flow + public suspend operator fun invoke(): Flow } internal class ObserveE2EIRequiredUseCaseImpl( @@ -64,7 +64,7 @@ internal class ObserveE2EIRequiredUseCaseImpl( ) : ObserveE2EIRequiredUseCase { @OptIn(ExperimentalCoroutinesApi::class) - override fun invoke(): Flow { + override suspend fun invoke(): Flow { if (!featureSupport.isMLSSupported) return flowOf(E2EIRequiredResult.NotRequired) return userConfigRepository diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCase.kt index 90f87ce61f0..96766d2aba4 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCase.kt @@ -25,13 +25,13 @@ import com.wire.kalium.common.functional.onSuccess * Mark Guest Link Feature Flag state as not changed */ public interface MarkGuestLinkFeatureFlagAsNotChangedUseCase { - public operator fun invoke() + public suspend operator fun invoke() } internal class MarkGuestLinkFeatureFlagAsNotChangedUseCaseImpl internal constructor( private val userConfigRepository: UserConfigRepository ) : MarkGuestLinkFeatureFlagAsNotChangedUseCase { - override operator fun invoke() { + override suspend operator fun invoke() { userConfigRepository.getGuestRoomLinkStatus().onSuccess { it.isStatusChanged?.let { isEnabled -> userConfigRepository.setGuestRoomStatus(isEnabled, false) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiver.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiver.kt index 1fe3871d550..de3af0fea1e 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiver.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiver.kt @@ -59,7 +59,7 @@ internal class UserPropertiesEventReceiverImpl internal constructor( } } - private fun handleReadReceiptMode( + private suspend fun handleReadReceiptMode( event: Event.UserProperty.ReadReceiptModeSet ): Either { val logger = kaliumLogger.createEventProcessingLogger(event) @@ -69,7 +69,7 @@ internal class UserPropertiesEventReceiverImpl internal constructor( .onFailure { logger.logFailure(it) } } - private fun handleTypingIndicatorMode( + private suspend fun handleTypingIndicatorMode( event: Event.UserProperty.TypingIndicatorModeSet ): Either { val logger = kaliumLogger.createEventProcessingLogger(event) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt index e1b85ae4a6a..76b9096c572 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/SlowSyncManager.kt @@ -66,7 +66,7 @@ internal interface SlowSyncManager { * Useful when a new step is added to Slow Sync, or when we fix some bug in Slow Sync, * and we'd like to get all users to take advantage of the fix. */ - const val CURRENT_VERSION = 10 + const val CURRENT_VERSION = 11 // because we already had version 9, the next version should be 10 val MIN_RETRY_DELAY = 1.seconds diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProvider.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProvider.kt index e136ce37280..b90e2f5092c 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProvider.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProvider.kt @@ -20,7 +20,9 @@ package com.wire.kalium.logic.sync.slow.migration import com.wire.kalium.logic.data.id.SelfTeamIdProvider import com.wire.kalium.logic.data.user.AccountRepository import com.wire.kalium.logic.sync.slow.migration.steps.SyncMigrationStep +import com.wire.kalium.logic.sync.slow.migration.steps.SyncMigrationStep_10_11 import com.wire.kalium.logic.sync.slow.migration.steps.SyncMigrationStep_6_7 +import com.wire.kalium.persistence.config.UserConfigStorage import io.mockative.Mockable @Mockable @@ -31,11 +33,14 @@ internal interface SyncMigrationStepsProvider { @Suppress("MagicNumber") internal class SyncMigrationStepsProviderImpl( accountRepository: Lazy, - selfTeamIdProvider: SelfTeamIdProvider + selfTeamIdProvider: SelfTeamIdProvider, + oldUserConfigStorage: Lazy, + newUserConfigStorage: Lazy ) : SyncMigrationStepsProvider { private val steps = mapOf( - 7 to lazy { SyncMigrationStep_6_7(accountRepository, selfTeamIdProvider) } + 7 to lazy { SyncMigrationStep_6_7(accountRepository, selfTeamIdProvider) }, + 11 to lazy { SyncMigrationStep_10_11(oldUserConfigStorage, newUserConfigStorage) } ) override fun getMigrationSteps(fromVersion: Int, toVersion: Int): List { diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/steps/SyncMigrationStep_10_11.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/steps/SyncMigrationStep_10_11.kt new file mode 100644 index 00000000000..cffd2424b6d --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/slow/migration/steps/SyncMigrationStep_10_11.kt @@ -0,0 +1,70 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.sync.slow.migration.steps + +import com.wire.kalium.common.error.CoreFailure +import com.wire.kalium.common.functional.Either +import com.wire.kalium.common.functional.right +import com.wire.kalium.common.logger.kaliumLogger +import com.wire.kalium.persistence.config.UserConfigStorage +import kotlinx.coroutines.flow.first + +@Suppress("ClassNaming", "MagicNumber") +internal class SyncMigrationStep_10_11( + private val oldUserConfigStorage: Lazy, + private val newUserConfigStorage: Lazy +) : SyncMigrationStep { + + override val version: Int = 11 + + @Suppress("TooGenericExceptionCaught") + override suspend fun invoke(): Either { + return try { + migrateLocallyManagedPreferences() + Unit.right() + } catch (e: Exception) { + // Log but don't fail - preferences will be set by user in case of failure. + kaliumLogger.w("Migration 10->11 failed, continuing: ${e.message}") + Unit.right() + } + } + + private suspend fun migrateLocallyManagedPreferences() { + oldUserConfigStorage.value.areReadReceiptsEnabled().first().let { isEnabled -> + newUserConfigStorage.value.persistReadReceipts(isEnabled) + kaliumLogger.d("Migrated read receipts: $isEnabled") + } + + oldUserConfigStorage.value.isTypingIndicatorEnabled().first().let { isEnabled -> + newUserConfigStorage.value.persistTypingIndicator(isEnabled) + kaliumLogger.d("Migrated typing indicator: $isEnabled") + } + + oldUserConfigStorage.value.isScreenshotCensoringEnabledFlow().first().let { isEnabled -> + newUserConfigStorage.value.persistScreenshotCensoring(isEnabled) + kaliumLogger.d("Migrated screenshot censoring: $isEnabled") + } + + oldUserConfigStorage.value.getE2EINotificationTime()?.let { timestamp -> + if (timestamp > 0) { + newUserConfigStorage.value.updateE2EINotificationTime(timestamp) + kaliumLogger.d("Migrated E2EI notification time: $timestamp") + } + } + } +} diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt index 60e5c170ea7..24a4a52f918 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/CertificateRevocationListRepositoryTest.kt @@ -18,11 +18,11 @@ package com.wire.kalium.logic.data.e2ei import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either +import com.wire.kalium.common.functional.right import com.wire.kalium.logic.configuration.E2EISettings import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.e2ei.CertificateRevocationListRepositoryDataSource.Companion.CRL_LIST_KEY -import com.wire.kalium.common.functional.Either -import com.wire.kalium.common.functional.right import com.wire.kalium.network.api.base.unbound.acme.ACMEApi import com.wire.kalium.network.utils.NetworkResponse import com.wire.kalium.persistence.config.CRLUrlExpirationList @@ -30,11 +30,9 @@ import com.wire.kalium.persistence.config.CRLWithExpiration import com.wire.kalium.persistence.dao.MetadataDAO import dev.mokkery.MockMode import dev.mokkery.answering.returns -import dev.mokkery.every import dev.mokkery.everySuspend import dev.mokkery.matcher.any import dev.mokkery.mock -import dev.mokkery.verify import dev.mokkery.verifySuspend import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -123,7 +121,7 @@ class CertificateRevocationListRepositoryTest { crlRepository.getClientDomainCRL(DUMMY_URL2) - verify { arrangement.userConfigRepository.getE2EISettings() } + verifySuspend { arrangement.userConfigRepository.getE2EISettings() } verifySuspend { arrangement.acmeApi.getClientDomainCRL(DUMMY_URL2, DUMMY_URL) } } @@ -137,7 +135,7 @@ class CertificateRevocationListRepositoryTest { crlRepository.getClientDomainCRL(DUMMY_URL2) - verify { arrangement.userConfigRepository.getE2EISettings() } + verifySuspend { arrangement.userConfigRepository.getE2EISettings() } verifySuspend { arrangement.acmeApi.getClientDomainCRL(DUMMY_URL2, null) @@ -153,7 +151,7 @@ class CertificateRevocationListRepositoryTest { crlRepository.getClientDomainCRL(DUMMY_URL2) - verify { arrangement.userConfigRepository.getE2EISettings() } + verifySuspend { arrangement.userConfigRepository.getE2EISettings() } verifySuspend { arrangement.acmeApi.getClientDomainCRL(DUMMY_URL2, null) @@ -196,11 +194,16 @@ class CertificateRevocationListRepositoryTest { } fun withE2EISettings(result: Either = E2EI_SETTINGS.right()) = apply { - every { userConfigRepository.getE2EISettings() } returns result + everySuspend { userConfigRepository.getE2EISettings() } returns result } fun withClientDomainCRL() = apply { - everySuspend { acmeApi.getClientDomainCRL(any(), any()) } returns NetworkResponse.Success("some_response".encodeToByteArray(), mapOf(), 200) + everySuspend { + acmeApi.getClientDomainCRL( + any(), + any() + ) + } returns NetworkResponse.Success("some_response".encodeToByteArray(), mapOf(), 200) } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt index e47c0a56711..f12b5ec00d2 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/E2EIRepositoryTest.kt @@ -62,11 +62,9 @@ import io.mockative.any import io.mockative.coEvery import io.mockative.coVerify import io.mockative.eq -import io.mockative.every import io.mockative.mock import io.mockative.once import io.mockative.time -import io.mockative.verify import kotlinx.coroutines.test.runTest import kotlinx.datetime.Instant import kotlin.test.Test @@ -941,7 +939,7 @@ class E2EIRepositoryTest { } @Test - fun givenE2EIIsDisabled_whenCallingDiscoveryUrl_thenItFailWithDisabled() { + fun givenE2EIIsDisabled_whenCallingDiscoveryUrl_thenItFailWithDisabled() = runTest { val (arrangement, e2eiRepository) = Arrangement() .withGettingE2EISettingsReturns(Either.Right(E2EISettings(false, null, Instant.DISTANT_FUTURE, false, null))) .arrange() @@ -950,13 +948,13 @@ class E2EIRepositoryTest { assertIs(it) } - verify { + coVerify { arrangement.userConfigRepository.getE2EISettings() }.wasInvoked(once) } @Test - fun givenE2EIIsEnabledAndDiscoveryUrlIsNull_whenCallingDiscoveryUrl_thenItFailWithMissingDiscoveryUrl() { + fun givenE2EIIsEnabledAndDiscoveryUrlIsNull_whenCallingDiscoveryUrl_thenItFailWithMissingDiscoveryUrl() = runTest { val (arrangement, e2eiRepository) = Arrangement() .withGettingE2EISettingsReturns(Either.Right(E2EISettings(true, null, Instant.DISTANT_FUTURE, false, null))) .arrange() @@ -965,13 +963,13 @@ class E2EIRepositoryTest { assertIs(it) } - verify { + coVerify { arrangement.userConfigRepository.getE2EISettings() }.wasInvoked(once) } @Test - fun givenE2EIIsEnabledAndDiscoveryUrlIsNotNull_whenCallingDiscoveryUrl_thenItSucceed() { + fun givenE2EIIsEnabledAndDiscoveryUrlIsNotNull_whenCallingDiscoveryUrl_thenItSucceed() = runTest { val (arrangement, e2eiRepository) = Arrangement() .withGettingE2EISettingsReturns(Either.Right(E2EISettings(true, RANDOM_URL, Instant.DISTANT_FUTURE, false, null))) .arrange() @@ -980,7 +978,7 @@ class E2EIRepositoryTest { assertEquals(RANDOM_URL, it) } - verify { + coVerify { arrangement.userConfigRepository.getE2EISettings() }.wasInvoked(once) } @@ -1095,8 +1093,8 @@ class E2EIRepositoryTest { }.returns(Either.Right(mlsClient)) } - fun withGettingE2EISettingsReturns(result: Either) = apply { - every { + suspend fun withGettingE2EISettingsReturns(result: Either) = apply { + coEvery { userConfigRepository.getE2EISettings() }.returns(result) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/RevocationListCheckerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/RevocationListCheckerTest.kt index ff2ff4e2b8d..f0c9405ca8f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/RevocationListCheckerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/e2ei/RevocationListCheckerTest.kt @@ -146,7 +146,7 @@ class RevocationListCheckerTest { userConfigRepository.isMLSEnabled() }.returns(result.right()) - every { + coEvery { userConfigRepository.getE2EISettings() }.returns(E2EISettings(true, DUMMY_URL, DateTimeUtil.currentInstant(), false, null).right()) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/properties/UserPropertyRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/properties/UserPropertyRepositoryTest.kt index 0d47002c349..e9034a41e41 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/properties/UserPropertyRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/properties/UserPropertyRepositoryTest.kt @@ -19,21 +19,19 @@ package com.wire.kalium.logic.data.properties import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.framework.TestUser -import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.util.shouldSucceed -import com.wire.kalium.network.api.base.authenticated.properties.PropertiesApi import com.wire.kalium.network.api.authenticated.properties.PropertyKey +import com.wire.kalium.network.api.base.authenticated.properties.PropertiesApi import com.wire.kalium.network.utils.NetworkResponse import io.mockative.any -import io.mockative.eq import io.mockative.coEvery import io.mockative.coVerify -import io.mockative.every +import io.mockative.eq import io.mockative.mock import io.mockative.once -import io.mockative.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -55,7 +53,7 @@ class UserPropertyRepositoryTest { arrangement.propertiesApi.setProperty(any(), any()) }.wasInvoked(once) - verify { + coVerify { arrangement.userConfigRepository.setReadReceiptsStatus(eq(true)) }.wasInvoked(once) } @@ -74,7 +72,7 @@ class UserPropertyRepositoryTest { arrangement.propertiesApi.deleteProperty(any()) }.wasInvoked(once) - verify { + coVerify { arrangement.userConfigRepository.setReadReceiptsStatus(eq(false)) }.wasInvoked(once) } @@ -88,7 +86,7 @@ class UserPropertyRepositoryTest { val result = repository.getReadReceiptsStatus() assertFalse(result) - verify { + coVerify { arrangement.userConfigRepository.isReadReceiptsEnabled() }.wasInvoked(exactly = once) } @@ -113,14 +111,14 @@ class UserPropertyRepositoryTest { }.returns(NetworkResponse.Success(Unit, mapOf(), 200)) } - fun withUpdateReadReceiptsLocallySuccess() = apply { - every { + suspend fun withUpdateReadReceiptsLocallySuccess() = apply { + coEvery { userConfigRepository.setReadReceiptsStatus(any()) }.returns(Either.Right(Unit)) } - fun withNullReadReceiptsStatus() = apply { - every { + suspend fun withNullReadReceiptsStatus() = apply { + coEvery { userConfigRepository.isReadReceiptsEnabled() }.returns(flowOf(Either.Left(StorageFailure.DataNotFound))) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/team/TeamRepositoryTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/team/TeamRepositoryTest.kt index f7792ff502a..7086d1a6f0d 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/team/TeamRepositoryTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/data/team/TeamRepositoryTest.kt @@ -47,7 +47,7 @@ import com.wire.kalium.persistence.dao.ServiceDAO import com.wire.kalium.persistence.dao.TeamDAO import com.wire.kalium.persistence.dao.TeamEntity import com.wire.kalium.persistence.dao.UserDAO -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO import io.mockative.any import io.mockative.coEvery import io.mockative.coVerify diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCaseTest.kt index 896b4ff4aea..bbbbac836b6 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/applock/MarkTeamAppLockStatusAsNotifiedUseCaseTest.kt @@ -17,25 +17,26 @@ */ package com.wire.kalium.logic.feature.applock -import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.common.functional.Either -import io.mockative.every +import com.wire.kalium.logic.configuration.UserConfigRepository +import io.mockative.coEvery +import io.mockative.coVerify import io.mockative.mock import io.mockative.once -import io.mockative.verify +import kotlinx.coroutines.test.runTest import kotlin.test.Test internal class MarkTeamAppLockStatusAsNotifiedUseCaseTest { @Test - fun givenAppLockStatusChanged_whenMarkingAsNotified_thenSetAppLockAsNotified() { + fun givenAppLockStatusChanged_whenMarkingAsNotified_thenSetAppLockAsNotified() = runTest { val (arrangement, useCase) = Arrangement() .withSuccess() .arrange() useCase.invoke() - verify { + coVerify { arrangement.userConfigRepository.setTeamAppLockAsNotified() }.wasInvoked(once) } @@ -48,8 +49,8 @@ internal class MarkTeamAppLockStatusAsNotifiedUseCaseTest { userConfigRepository = userConfigRepository ) - fun withSuccess() = apply { - every { + suspend fun withSuccess() = apply { + coEvery { userConfigRepository.setTeamAppLockAsNotified() }.returns(Either.Right(Unit)) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCaseTest.kt index 22315e51e2a..f60aede22e8 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/IsEligibleToStartCallUseCaseTest.kt @@ -19,14 +19,13 @@ package com.wire.kalium.logic.feature.call.usecase import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.call.CallRepository import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.id.ConversationId -import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.test_util.TestKaliumDispatcher import io.mockative.coEvery -import io.mockative.every import io.mockative.mock import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest @@ -35,9 +34,9 @@ import kotlin.test.assertEquals internal class IsEligibleToStartCallUseCaseTest { - val userConfigRepository = mock(UserConfigRepository::class) + val userConfigRepository = mock(UserConfigRepository::class) - val callRepository = mock(CallRepository::class) + val callRepository = mock(CallRepository::class) private lateinit var isEligibleToStartCall: IsEligibleToStartCallUseCase @@ -58,7 +57,7 @@ internal class IsEligibleToStartCallUseCaseTest { callRepository.establishedCallConversationId() }.returns(null) - every { + coEvery { userConfigRepository.isConferenceCallingEnabled() }.returns(Either.Left(StorageFailure.Generic(Throwable("error")))) @@ -80,7 +79,7 @@ internal class IsEligibleToStartCallUseCaseTest { callRepository.establishedCallConversationId() }.returns(null) - every { + coEvery { userConfigRepository.isConferenceCallingEnabled() }.returns(Either.Left(StorageFailure.Generic(Throwable("error")))) @@ -102,7 +101,7 @@ internal class IsEligibleToStartCallUseCaseTest { callRepository.establishedCallConversationId() }.returns(establishedCallConversationId) - every { + coEvery { userConfigRepository.isConferenceCallingEnabled() }.returns(Either.Left(StorageFailure.Generic(Throwable("error")))) @@ -124,7 +123,7 @@ internal class IsEligibleToStartCallUseCaseTest { callRepository.establishedCallConversationId() }.returns(establishedCallConversationId) - every { + coEvery { userConfigRepository.isConferenceCallingEnabled() }.returns(Either.Right(true)) @@ -146,7 +145,7 @@ internal class IsEligibleToStartCallUseCaseTest { callRepository.establishedCallConversationId() }.returns(conversationId) - every { + coEvery { userConfigRepository.isConferenceCallingEnabled() }.returns(Either.Right(true)) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveConferenceCallingEnabledUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveConferenceCallingEnabledUseCaseTest.kt index 9f2bb4929ee..48f625cd085 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveConferenceCallingEnabledUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/usecase/ObserveConferenceCallingEnabledUseCaseTest.kt @@ -20,6 +20,7 @@ package com.wire.kalium.logic.feature.call.usecase import app.cash.turbine.test import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.common.functional.Either +import io.mockative.coEvery import io.mockative.every import io.mockative.mock import kotlinx.coroutines.flow.asFlow @@ -114,8 +115,8 @@ class ObserveConferenceCallingEnabledUseCaseTest { val userConfigRepository = mock(UserConfigRepository::class) - fun withDefaultValue(values: List) = apply { - every { + suspend fun withDefaultValue(values: List) = apply { + coEvery { userConfigRepository.observeConferenceCallingEnabled() }.returns(values.map { Either.Right(it) }.asFlow()) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/MarkEnablingE2EIAsNotifiedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/MarkEnablingE2EIAsNotifiedUseCaseTest.kt index eb045e8f936..3f5a91359c3 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/MarkEnablingE2EIAsNotifiedUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/MarkEnablingE2EIAsNotifiedUseCaseTest.kt @@ -17,16 +17,17 @@ */ package com.wire.kalium.logic.feature.client +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.feature.user.MarkEnablingE2EIAsNotifiedUseCase import com.wire.kalium.logic.feature.user.MarkEnablingE2EIAsNotifiedUseCaseImpl -import com.wire.kalium.common.functional.Either import io.mockative.any +import io.mockative.coEvery import io.mockative.coVerify import io.mockative.eq -import io.mockative.every import io.mockative.mock import io.mockative.once +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.time.Duration @@ -96,9 +97,11 @@ class MarkEnablingE2EIAsNotifiedUseCaseTest { val userConfigRepository = mock(UserConfigRepository::class) init { - every { - userConfigRepository.snoozeE2EINotification(any()) - }.returns(Either.Right(Unit)) + runBlocking { + coEvery { + userConfigRepository.snoozeE2EINotification(any()) + }.returns(Either.Right(Unit)) + } } private var markMLSE2EIEnableChangeAsNotified: MarkEnablingE2EIAsNotifiedUseCase = diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ObserveE2EIRequiredUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ObserveE2EIRequiredUseCaseTest.kt index 48ca8f6b432..139ca19f64c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ObserveE2EIRequiredUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/ObserveE2EIRequiredUseCaseTest.kt @@ -18,15 +18,13 @@ package com.wire.kalium.logic.feature.client import app.cash.turbine.test -import com.wire.kalium.common.error.CoreFailure -import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.E2EISettings import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.conversation.ClientId import com.wire.kalium.logic.data.id.CurrentClientIdProvider import com.wire.kalium.logic.data.id.QualifiedClientID import com.wire.kalium.logic.feature.e2ei.MLSClientE2EIStatus -import com.wire.kalium.logic.feature.e2ei.MLSClientIdentity import com.wire.kalium.logic.feature.e2ei.usecase.GetMLSClientIdentityResult import com.wire.kalium.logic.feature.e2ei.usecase.GetMLSClientIdentityUseCase import com.wire.kalium.logic.feature.user.E2EIRequiredResult @@ -37,14 +35,13 @@ import com.wire.kalium.logic.framework.TestClient import com.wire.kalium.logic.framework.TestMLSClientIdentity.getMLSClientIdentityWithE2EI import com.wire.kalium.logic.framework.TestMLSClientIdentity.getMLSClientIdentityWithOutE2EI import com.wire.kalium.logic.framework.TestUser -import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.test_util.TestKaliumDispatcher import com.wire.kalium.util.DateTimeUtil import io.mockative.any import io.mockative.coEvery +import io.mockative.coVerify import io.mockative.every import io.mockative.mock -import io.mockative.verify import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf @@ -181,7 +178,7 @@ class ObserveE2EIRequiredUseCaseTest { awaitComplete() } - verify { + coVerify { arrangement.userConfigRepository.observeE2EINotificationTime() }.wasNotInvoked() } @@ -317,7 +314,7 @@ class ObserveE2EIRequiredUseCaseTest { } private class Arrangement(testDispatcher: CoroutineDispatcher = UnconfinedTestDispatcher()) { - val userConfigRepository = mock(UserConfigRepository::class) + val userConfigRepository = mock(UserConfigRepository::class) val featureSupport = mock(FeatureSupport::class) val e2eiCertificate = mock(GetMLSClientIdentityUseCase::class) val currentClientIdProvider = mock(CurrentClientIdProvider::class) @@ -330,8 +327,8 @@ class ObserveE2EIRequiredUseCaseTest { .returns(flowOf(Either.Right(setting))) } - fun withE2EINotificationTime(instant: Instant) = apply { - every { userConfigRepository.observeE2EINotificationTime() } + suspend fun withE2EINotificationTime(instant: Instant) = apply { + coEvery { userConfigRepository.observeE2EINotificationTime() } .returns(flowOf(Either.Right(instant))) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt index 4a366fa85ad..f9aa0e6bfdd 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/client/RegisterMLSClientUseCaseTest.kt @@ -161,8 +161,8 @@ class RegisterMLSClientUseCaseTest { val keyPackageLimitsProvider = mock(KeyPackageLimitsProvider::class) val userConfigRepository = mock(UserConfigRepository::class) - fun withGettingE2EISettingsReturns(result: Either) = apply { - every { + suspend fun withGettingE2EISettingsReturns(result: Either) = apply { + coEvery { userConfigRepository.getE2EISettings() }.returns(result) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt index 94de13a75f0..f50320ab09f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/SyncFeatureConfigsUseCaseTest.kt @@ -67,7 +67,7 @@ import com.wire.kalium.logic.util.shouldSucceed import com.wire.kalium.persistence.TestUserDatabase import com.wire.kalium.persistence.config.inMemoryUserConfigStorage import com.wire.kalium.persistence.dao.SupportedProtocolEntity -import com.wire.kalium.persistence.dao.unread.UserConfigDAO +import com.wire.kalium.persistence.dao.UserConfigDAO import com.wire.kalium.util.DateTimeUtil import io.mockative.any import io.mockative.coEvery @@ -908,7 +908,7 @@ class SyncFeatureConfigsUseCaseTest { }.returns(result) } - fun withLocalSharingEnabledReturning( + suspend fun withLocalSharingEnabledReturning( status: Boolean, isStatusChanged: Boolean? ) = apply { @@ -918,7 +918,7 @@ class SyncFeatureConfigsUseCaseTest { ) } - fun withGuestRoomLinkEnabledReturning(guestRoomLinkStatus: GuestRoomLinkStatus) = apply { + suspend fun withGuestRoomLinkEnabledReturning(guestRoomLinkStatus: GuestRoomLinkStatus) = apply { inMemoryStorage.persistGuestRoomLinkFeatureFlag( guestRoomLinkStatus.isGuestRoomLinkEnabled ?: false, guestRoomLinkStatus.isStatusChanged diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandlerTest.kt index 3a5e22d1047..ecefd33ee3c 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/AppLockConfigHandlerTest.kt @@ -18,24 +18,26 @@ package com.wire.kalium.logic.feature.featureConfig.handler import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.AppLockTeamConfig import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.featureConfig.AppLockModel import com.wire.kalium.logic.data.featureConfig.Status -import com.wire.kalium.common.functional.Either import io.mockative.any +import io.mockative.coEvery +import io.mockative.coVerify import io.mockative.eq -import io.mockative.every import io.mockative.mock import io.mockative.once -import io.mockative.verify +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.time.Duration.Companion.seconds internal class AppLockConfigHandlerTest { @Test - fun givenConfigRepositoryReturnsFailureWithStatusDisabled_whenHandlingTheEvent_ThenSetAppLockWithStatusChangedFalse() { + fun givenConfigRepositoryReturnsFailureWithStatusDisabled_whenHandlingTheEvent_ThenSetAppLockWithStatusChangedFalse() = runTest { val appLockModel = AppLockModel(Status.DISABLED, 20) val (arrangement, appLockConfigHandler) = Arrangement() .withUserConfigRepositoryFailure() @@ -43,11 +45,11 @@ internal class AppLockConfigHandlerTest { appLockConfigHandler.handle(appLockModel) - verify { + coVerify { arrangement.userConfigRepository.isTeamAppLockEnabled() }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setAppLockStatus( eq(appLockModel.status.toBoolean()), eq(appLockModel.inactivityTimeoutSecs), @@ -57,7 +59,7 @@ internal class AppLockConfigHandlerTest { } @Test - fun givenConfigRepositoryReturnsFailureWithStatusEnabled_whenHandlingTheEvent_ThenSetAppLockWithStatusChangedTrue() { + fun givenConfigRepositoryReturnsFailureWithStatusEnabled_whenHandlingTheEvent_ThenSetAppLockWithStatusChangedTrue() = runTest { val appLockModel = AppLockModel(Status.ENABLED, 20) val (arrangement, appLockConfigHandler) = Arrangement() .withUserConfigRepositoryFailure() @@ -65,11 +67,11 @@ internal class AppLockConfigHandlerTest { appLockConfigHandler.handle(appLockModel) - verify { + coVerify { arrangement.userConfigRepository.isTeamAppLockEnabled() }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setAppLockStatus( eq(appLockModel.status.toBoolean()), eq(appLockModel.inactivityTimeoutSecs), @@ -79,7 +81,7 @@ internal class AppLockConfigHandlerTest { } @Test - fun givenNewStatusSameAsCurrent_whenHandlingTheEvent_ThenSetAppLockWithOldStatusChangedValue() { + fun givenNewStatusSameAsCurrent_whenHandlingTheEvent_ThenSetAppLockWithOldStatusChangedValue() = runTest { val appLockModel = AppLockModel(Status.ENABLED, 44) val (arrangement, appLockConfigHandler) = Arrangement() .withAppLocked() @@ -87,11 +89,11 @@ internal class AppLockConfigHandlerTest { appLockConfigHandler.handle(appLockModel) - verify { + coVerify { arrangement.userConfigRepository.isTeamAppLockEnabled() }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setAppLockStatus( eq(appLockModel.status.toBoolean()), eq(appLockModel.inactivityTimeoutSecs), @@ -101,7 +103,7 @@ internal class AppLockConfigHandlerTest { } @Test - fun givenNewStatusDifferentThenCurrent_whenHandlingTheEvent_ThenSetAppLockWithStatusChangedTrue() { + fun givenNewStatusDifferentThenCurrent_whenHandlingTheEvent_ThenSetAppLockWithStatusChangedTrue() = runTest { val appLockModel = AppLockModel(Status.ENABLED, 20) val (arrangement, appLockConfigHandler) = Arrangement() .withAppNotLocked() @@ -109,11 +111,11 @@ internal class AppLockConfigHandlerTest { appLockConfigHandler.handle(appLockModel) - verify { + coVerify { arrangement.userConfigRepository.isTeamAppLockEnabled() }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setAppLockStatus( eq(appLockModel.status.toBoolean()), eq(appLockModel.inactivityTimeoutSecs), @@ -133,25 +135,27 @@ internal class AppLockConfigHandlerTest { } init { - every { - userConfigRepository.setAppLockStatus(any(), any(), any()) - }.returns(Either.Right(Unit)) + runBlocking { + coEvery { + userConfigRepository.setAppLockStatus(any(), any(), any()) + }.returns(Either.Right(Unit)) + } } - fun withUserConfigRepositoryFailure() = apply { - every { + suspend fun withUserConfigRepositoryFailure() = apply { + coEvery { userConfigRepository.isTeamAppLockEnabled() }.returns(Either.Left(StorageFailure.DataNotFound)) } - fun withAppLocked() = apply { - every { + suspend fun withAppLocked() = apply { + coEvery { userConfigRepository.isTeamAppLockEnabled() }.returns(Either.Right(appLockTeamConfigEnabled)) } - fun withAppNotLocked() = apply { - every { + suspend fun withAppNotLocked() = apply { + coEvery { userConfigRepository.isTeamAppLockEnabled() }.returns(Either.Right(appLockTeamConfigDisabled)) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt index d9fe746a6cf..2e0f33aa602 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/featureConfig/handler/ConferenceCallingConfigHandlerTest.kt @@ -18,24 +18,26 @@ package com.wire.kalium.logic.feature.featureConfig.handler import com.wire.kalium.common.error.StorageFailure -import com.wire.kalium.logic.configuration.UserConfigRepository -import com.wire.kalium.logic.data.featureConfig.ConferenceCallingModel -import com.wire.kalium.logic.data.featureConfig.Status import com.wire.kalium.common.functional.Either import com.wire.kalium.common.functional.isLeft import com.wire.kalium.common.functional.isRight +import com.wire.kalium.logic.configuration.UserConfigRepository +import com.wire.kalium.logic.data.featureConfig.ConferenceCallingModel +import com.wire.kalium.logic.data.featureConfig.Status import io.mockative.any -import io.mockative.every +import io.mockative.coEvery +import io.mockative.coVerify import io.mockative.mock import io.mockative.once -import io.mockative.verify +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertTrue class ConferenceCallingConfigHandlerTest { @Test - fun givenUserConfigRepositoryFailureForConferenceCallingEnabled_whenHandlingTheEvent_ThenReturnFailure() { + fun givenUserConfigRepositoryFailureForConferenceCallingEnabled_whenHandlingTheEvent_ThenReturnFailure() = runTest { val conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false) val (arrangement, conferenceCallingConfigHandler) = Arrangement() .withSetConferenceCallingEnabledFailure() @@ -43,11 +45,11 @@ class ConferenceCallingConfigHandlerTest { val result = conferenceCallingConfigHandler.handle(conferenceCallingModel) - verify { + coVerify { arrangement.userConfigRepository.setConferenceCallingEnabled(conferenceCallingModel.status.toBoolean()) }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setUseSFTForOneOnOneCalls(any()) }.wasNotInvoked() @@ -55,7 +57,7 @@ class ConferenceCallingConfigHandlerTest { } @Test - fun givenUserConfigRepositoryFailureForUseSFTForOneOnOneCalls_whenHandlingTheEvent_ThenReturnFailure() { + fun givenUserConfigRepositoryFailureForUseSFTForOneOnOneCalls_whenHandlingTheEvent_ThenReturnFailure() = runTest { val conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false) val (arrangement, conferenceCallingConfigHandler) = Arrangement() .withSetConferenceCallingEnabledSuccess() @@ -64,11 +66,11 @@ class ConferenceCallingConfigHandlerTest { val result = conferenceCallingConfigHandler.handle(conferenceCallingModel) - verify { + coVerify { arrangement.userConfigRepository.setConferenceCallingEnabled(conferenceCallingModel.status.toBoolean()) }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setUseSFTForOneOnOneCalls(any()) }.wasInvoked(exactly = once) @@ -76,7 +78,7 @@ class ConferenceCallingConfigHandlerTest { } @Test - fun givenUserConfigRepositorySuccess_whenHandlingTheEvent_ThenReturnUnit() { + fun givenUserConfigRepositorySuccess_whenHandlingTheEvent_ThenReturnUnit() = runTest { val conferenceCallingModel = ConferenceCallingModel(Status.ENABLED, false) val (arrangement, conferenceCallingConfigHandler) = Arrangement() .withSetConferenceCallingEnabledSuccess() @@ -85,11 +87,11 @@ class ConferenceCallingConfigHandlerTest { val result = conferenceCallingConfigHandler.handle(conferenceCallingModel) - verify { + coVerify { arrangement.userConfigRepository.setConferenceCallingEnabled(conferenceCallingModel.status.toBoolean()) }.wasInvoked(exactly = once) - verify { + coVerify { arrangement.userConfigRepository.setUseSFTForOneOnOneCalls(any()) }.wasInvoked(exactly = once) @@ -107,31 +109,34 @@ class ConferenceCallingConfigHandlerTest { } init { - every { - userConfigRepository.setAppLockStatus(any(), any(), any()) - }.returns(Either.Right(Unit)) + runBlocking { + coEvery { + userConfigRepository.setAppLockStatus(any(), any(), any()) + }.returns(Either.Right(Unit)) + } + } - fun withSetConferenceCallingEnabledFailure() = apply { - every { + suspend fun withSetConferenceCallingEnabledFailure() = apply { + coEvery { userConfigRepository.setConferenceCallingEnabled(any()) }.returns(Either.Left(StorageFailure.DataNotFound)) } - fun withSetConferenceCallingEnabledSuccess() = apply { - every { + suspend fun withSetConferenceCallingEnabledSuccess() = apply { + coEvery { userConfigRepository.setConferenceCallingEnabled(any()) }.returns(Either.Right(Unit)) } - fun withSetUseSFTForOneOnOneCallsFailure() = apply { - every { + suspend fun withSetUseSFTForOneOnOneCallsFailure() = apply { + coEvery { userConfigRepository.setUseSFTForOneOnOneCalls(any()) }.returns(Either.Left(StorageFailure.DataNotFound)) } - fun withSetUseSFTForOneOnOneCallsSuccess() = apply { - every { + suspend fun withSetUseSFTForOneOnOneCallsSuccess() = apply { + coEvery { userConfigRepository.setUseSFTForOneOnOneCalls(any()) }.returns(Either.Right(Unit)) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigrationWorkerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigrationWorkerTest.kt index ae645d992f6..22307b6e36f 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigrationWorkerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/mlsmigration/MLSMigrationWorkerTest.kt @@ -253,9 +253,9 @@ class MLSMigrationWorkerTest { init { runBlocking { coEvery { featureConfigRepository.getFeatureConfigs() }.returns(FeatureConfigTest.newModel().right()) - every { userConfigRepository.setMLSEnabled(any()) }.returns(Unit.right()) + coEvery { userConfigRepository.setMLSEnabled(any()) }.returns(Unit.right()) coEvery { userConfigRepository.getSupportedProtocols() }.returns(NOT_FOUND_FAILURE) - every { userConfigRepository.setDefaultProtocol(any()) }.returns(Unit.right()) + coEvery { userConfigRepository.setDefaultProtocol(any()) }.returns(Unit.right()) coEvery { userConfigRepository.setSupportedProtocols(any>()) }.returns(Unit.right()) coEvery { userConfigRepository.setSupportedCipherSuite(any()) }.returns(Unit.right()) coEvery { userConfigRepository.setMigrationConfiguration(any()) }.returns(Unit.right()) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/search/IsFederationSearchAllowedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/search/IsFederationSearchAllowedUseCaseTest.kt index cf3daa6ab7a..a3aaeffed93 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/search/IsFederationSearchAllowedUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/search/IsFederationSearchAllowedUseCaseTest.kt @@ -18,6 +18,7 @@ package com.wire.kalium.logic.feature.search import com.wire.kalium.common.error.CoreFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.data.mls.MLSPublicKeys import com.wire.kalium.logic.data.mlspublickeys.MLSPublicKeysRepository import com.wire.kalium.logic.data.user.SupportedProtocol @@ -25,12 +26,10 @@ import com.wire.kalium.logic.feature.conversation.GetConversationProtocolInfoUse import com.wire.kalium.logic.feature.user.GetDefaultProtocolUseCase import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.framework.TestConversation.PROTEUS_PROTOCOL_INFO -import com.wire.kalium.common.functional.Either import com.wire.kalium.util.KaliumDispatcherImpl import io.mockative.any import io.mockative.coEvery import io.mockative.coVerify -import io.mockative.every import io.mockative.mock import io.mockative.once import kotlinx.coroutines.test.runTest @@ -126,8 +125,8 @@ class IsFederationSearchAllowedUseCaseTest { ) ) - fun withDefaultProtocol(protocol: SupportedProtocol) = apply { - every { getDefaultProtocol.invoke() }.returns(protocol) + suspend fun withDefaultProtocol(protocol: SupportedProtocol) = apply { + coEvery { getDefaultProtocol.invoke() }.returns(protocol) } suspend fun withConversationProtocolInfo(protocolInfo: GetConversationProtocolInfoUseCase.Result) = apply { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/session/IsFileSharingEnabledUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/session/IsFileSharingEnabledUseCaseTest.kt index 2486d74f8b6..4df84c578ca 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/session/IsFileSharingEnabledUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/session/IsFileSharingEnabledUseCaseTest.kt @@ -19,14 +19,14 @@ package com.wire.kalium.logic.feature.session import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.FileSharingStatus import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.feature.user.IsFileSharingEnabledUseCaseImpl -import com.wire.kalium.common.functional.Either -import io.mockative.every +import io.mockative.coEvery +import io.mockative.coVerify import io.mockative.mock import io.mockative.once -import io.mockative.verify import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -44,7 +44,7 @@ class IsFileSharingEnabledUseCaseTest { val actual = isFileSharingEnabledUseCase.invoke() assertEquals(expectedValue, actual) - verify { + coVerify { arrangement.userConfigRepository.isFileSharingEnabled() }.wasInvoked(exactly = once) } @@ -60,7 +60,7 @@ class IsFileSharingEnabledUseCaseTest { // When isFileSharingEnabledUseCase.invoke() - verify { + coVerify { arrangement.userConfigRepository.isFileSharingEnabled() }.wasInvoked(exactly = once) } @@ -71,16 +71,16 @@ class IsFileSharingEnabledUseCaseTest { val isFileSharingEnabledUseCase = IsFileSharingEnabledUseCaseImpl(userConfigRepository) - fun withSuccessfulResponse(expectedValue: FileSharingStatus): Arrangement { - every { + suspend fun withSuccessfulResponse(expectedValue: FileSharingStatus): Arrangement { + coEvery { userConfigRepository.isFileSharingEnabled() }.returns(Either.Right(expectedValue)) return this } - fun withIsFileSharingEnabledErrorResponse(storageFailure: StorageFailure): Arrangement { - every { + suspend fun withIsFileSharingEnabledErrorResponse(storageFailure: StorageFailure): Arrangement { + coEvery { userConfigRepository.isFileSharingEnabled() }.returns(Either.Left(storageFailure)) return this diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCaseTest.kt index fc14ac4c5ec..ee75a0c7aa9 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/GetDefaultProtocolUseCaseTest.kt @@ -18,10 +18,10 @@ package com.wire.kalium.logic.feature.user import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.user.SupportedProtocol -import com.wire.kalium.common.functional.Either -import io.mockative.every +import io.mockative.coEvery import io.mockative.mock import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -79,20 +79,20 @@ class GetDefaultProtocolUseCaseTest { userConfigRepository = userConfigRepository ) - fun withReturningSuccessProteusProtocol() = apply { - every { + suspend fun withReturningSuccessProteusProtocol() = apply { + coEvery { userConfigRepository.getDefaultProtocol() }.returns(Either.Right(SupportedProtocol.PROTEUS)) } - fun withReturningSuccessMLSProtocol() = apply { - every { + suspend fun withReturningSuccessMLSProtocol() = apply { + coEvery { userConfigRepository.getDefaultProtocol() }.returns(Either.Right(SupportedProtocol.MLS)) } - fun withReturningError() = apply { - every { + suspend fun withReturningError() = apply { + coEvery { userConfigRepository.getDefaultProtocol() }.returns(Either.Left(StorageFailure.Generic(Throwable()))) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCaseTest.kt index 5df96313ab8..dc08e598040 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/user/guestroomlink/MarkGuestLinkFeatureFlagAsNotChangedUseCaseTest.kt @@ -19,15 +19,16 @@ package com.wire.kalium.logic.feature.user.guestroomlink import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.GuestRoomLinkStatus import com.wire.kalium.logic.configuration.UserConfigRepository -import com.wire.kalium.common.functional.Either import io.mockative.any +import io.mockative.coEvery +import io.mockative.coVerify import io.mockative.eq -import io.mockative.every import io.mockative.mock import io.mockative.once -import io.mockative.verify +import kotlinx.coroutines.test.runTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -43,37 +44,37 @@ internal class MarkGuestLinkFeatureFlagAsNotChangedUseCaseTest { } @Test - fun givenRepositoryReturnsFailure_whenInvokingUseCase_thenDoNotUpdateGuestStatus() { - every { + fun givenRepositoryReturnsFailure_whenInvokingUseCase_thenDoNotUpdateGuestStatus() = runTest { + coEvery { userConfigRepository.getGuestRoomLinkStatus() }.returns(Either.Left(StorageFailure.DataNotFound)) markGuestLinkFeatureFlagAsNotChanged() - verify { + coVerify { userConfigRepository.getGuestRoomLinkStatus() }.wasInvoked(exactly = once) - verify { + coVerify { userConfigRepository.setGuestRoomStatus(any(), eq(false)) }.wasNotInvoked() } @Test - fun givenRepositoryReturnsSuccess_whenInvokingUseCase_thenUpdateGuestStatus() { - every { + fun givenRepositoryReturnsSuccess_whenInvokingUseCase_thenUpdateGuestStatus() = runTest { + coEvery { userConfigRepository.getGuestRoomLinkStatus() }.returns(Either.Right(GuestRoomLinkStatus(isGuestRoomLinkEnabled = true, isStatusChanged = false))) - every { + coEvery { userConfigRepository.setGuestRoomStatus(status = false, isStatusChanged = false) }.returns(Either.Right(Unit)) markGuestLinkFeatureFlagAsNotChanged() - verify { + coVerify { userConfigRepository.getGuestRoomLinkStatus() }.wasInvoked(exactly = once) - verify { + coVerify { userConfigRepository.setGuestRoomStatus(any(), eq(false)) }.wasInvoked(once) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt index 95f794ff02e..1133ff1746a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/FeatureConfigEventReceiverTest.kt @@ -53,11 +53,9 @@ import io.mockative.any import io.mockative.coEvery import io.mockative.coVerify import io.mockative.eq -import io.mockative.every import io.mockative.matches import io.mockative.mock import io.mockative.once -import io.mockative.verify import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlin.test.Test @@ -79,7 +77,7 @@ class FeatureConfigEventReceiverTest { deliveryInfo = TestEvent.liveDeliveryInfo ) - verify { + coVerify { arrangement.userConfigRepository.setFileSharingStatus(eq(true), eq(true)) }.wasInvoked(once) } @@ -97,7 +95,7 @@ class FeatureConfigEventReceiverTest { deliveryInfo = TestEvent.liveDeliveryInfo ) - verify { + coVerify { arrangement.userConfigRepository.setFileSharingStatus(eq(false), eq(true)) }.wasInvoked(once) } @@ -115,7 +113,7 @@ class FeatureConfigEventReceiverTest { deliveryInfo = TestEvent.liveDeliveryInfo ) - verify { + coVerify { arrangement.userConfigRepository.setFileSharingStatus(eq(false), eq(false)) }.wasInvoked(once) } @@ -133,11 +131,11 @@ class FeatureConfigEventReceiverTest { TestEvent.liveDeliveryInfo ) - verify { + coVerify { arrangement.userConfigRepository.setConferenceCallingEnabled(eq(true)) }.wasInvoked(once) - verify { + coVerify { arrangement.userConfigRepository.setUseSFTForOneOnOneCalls(eq(false)) }.wasInvoked(once) } @@ -155,11 +153,11 @@ class FeatureConfigEventReceiverTest { deliveryInfo = TestEvent.liveDeliveryInfo ) - verify { + coVerify { arrangement.userConfigRepository.setConferenceCallingEnabled(eq(false)) }.wasInvoked(once) - verify { + coVerify { arrangement.userConfigRepository.setUseSFTForOneOnOneCalls(eq(true)) }.wasNotInvoked() } @@ -359,26 +357,26 @@ class FeatureConfigEventReceiverTest { ) } - fun withSettingFileSharingEnabledSuccessful() = apply { - every { + suspend fun withSettingFileSharingEnabledSuccessful() = apply { + coEvery { userConfigRepository.setFileSharingStatus(any(), any()) }.returns(Either.Right(Unit)) } - fun withSettingConferenceCallingEnabledSuccessful() = apply { - every { + suspend fun withSettingConferenceCallingEnabledSuccessful() = apply { + coEvery { userConfigRepository.setConferenceCallingEnabled(any()) }.returns(Either.Right(Unit)) } - fun withSetUseSFTForOneOnOneCallsSuccessful() = apply { - every { + suspend fun withSetUseSFTForOneOnOneCallsSuccessful() = apply { + coEvery { userConfigRepository.setUseSFTForOneOnOneCalls(any()) }.returns(Either.Right(Unit)) } - fun withIsFileSharingEnabled(result: Either) = apply { - every { + suspend fun withIsFileSharingEnabled(result: Either) = apply { + coEvery { userConfigRepository.isFileSharingEnabled() }.returns(result) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiverTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiverTest.kt index 609b90038de..f1fc1894b7a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiverTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/UserPropertiesEventReceiverTest.kt @@ -46,7 +46,7 @@ class UserPropertiesEventReceiverTest { eventReceiver.onEvent(arrangement.transactionContext, event, TestEvent.liveDeliveryInfo) - verify { + coVerify { arrangement.userConfigRepository.setReadReceiptsStatus(any()) }.wasInvoked(exactly = once) } @@ -75,8 +75,8 @@ class UserPropertiesEventReceiverTest { conversationFolderRepository = conversationFolderRepository ) - fun withUpdateReadReceiptsSuccess() = apply { - every { + suspend fun withUpdateReadReceiptsSuccess() = apply { + coEvery { userConfigRepository.setReadReceiptsStatus(any()) }.returns(Either.Right(Unit)) } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt index 944042be407..cbd12558621 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/ApplicationMessageHandlerTest.kt @@ -373,8 +373,8 @@ class ApplicationMessageHandlerTest { }.returns(result) } - fun withFileSharingEnabled() = apply { - every { + suspend fun withFileSharingEnabled() = apply { + coEvery { userConfigRepository.isFileSharingEnabled() }.returns( Either.Right( diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProviderTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProviderTest.kt index 2652c3010e8..df77c76259e 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProviderTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/slow/migration/SyncMigrationStepsProviderTest.kt @@ -17,11 +17,14 @@ */ package com.wire.kalium.logic.sync.slow.migration +import com.wire.kalium.logic.sync.slow.migration.steps.SyncMigrationStep_10_11 import com.wire.kalium.logic.sync.slow.migration.steps.SyncMigrationStep_6_7 import com.wire.kalium.logic.util.arrangement.provider.SelfTeamIdProviderArrangement import com.wire.kalium.logic.util.arrangement.provider.SelfTeamIdProviderArrangementImpl import com.wire.kalium.logic.util.arrangement.repository.AccountRepositoryArrangement import com.wire.kalium.logic.util.arrangement.repository.AccountRepositoryArrangementImpl +import com.wire.kalium.persistence.config.FakeUserConfigStorage +import com.wire.kalium.persistence.config.inMemoryUserConfigStorage import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -34,8 +37,13 @@ class SyncMigrationStepsProviderTest { val (_, provider) = Arrangement().arrange() provider.getMigrationSteps(Int.MIN_VALUE, Int.MAX_VALUE).also { - assertIs(it.first()) - assertEquals(7, it.first().version) + val firstManualMigration = it.first() + val lastManualMigration = it.last() + assertIs(firstManualMigration) + assertEquals(7, firstManualMigration.version) + + assertIs(lastManualMigration) + assertEquals(11, lastManualMigration.version) } } @@ -53,8 +61,10 @@ class SyncMigrationStepsProviderTest { SelfTeamIdProviderArrangement by SelfTeamIdProviderArrangementImpl() { private val provider: SyncMigrationStepsProvider = SyncMigrationStepsProviderImpl( - lazy { accountRepository }, - selfTeamIdProvider + accountRepository = lazy { accountRepository }, + selfTeamIdProvider = selfTeamIdProvider, + oldUserConfigStorage = lazy { inMemoryUserConfigStorage() }, + newUserConfigStorage = lazy { FakeUserConfigStorage() } ) fun arrange(block: Arrangement.() -> Unit = { }) = apply(block).let { this to provider } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/UserConfigRepositoryArrangement.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/UserConfigRepositoryArrangement.kt index d16fe793687..aadfdb7baee 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/UserConfigRepositoryArrangement.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/util/arrangement/repository/UserConfigRepositoryArrangement.kt @@ -18,15 +18,14 @@ package com.wire.kalium.logic.util.arrangement.repository import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either +import com.wire.kalium.common.functional.right import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.featureConfig.MLSMigrationModel import com.wire.kalium.logic.data.mls.SupportedCipherSuite import com.wire.kalium.logic.data.user.SupportedProtocol -import com.wire.kalium.common.functional.Either -import com.wire.kalium.common.functional.right import io.mockative.any import io.mockative.coEvery -import io.mockative.every import io.mockative.mock import kotlinx.coroutines.flow.flowOf @@ -35,9 +34,9 @@ internal interface UserConfigRepositoryArrangement { suspend fun withGetSupportedProtocolsReturning(result: Either>) suspend fun withSetSupportedProtocolsSuccessful() - fun withSetDefaultProtocolSuccessful() - fun withGetDefaultProtocolReturning(result: Either) - fun withSetMLSEnabledSuccessful() + suspend fun withSetDefaultProtocolSuccessful() + suspend fun withGetDefaultProtocolReturning(result: Either) + suspend fun withSetMLSEnabledSuccessful() suspend fun withGetMLSEnabledReturning(result: Either) suspend fun withSetMigrationConfigurationSuccessful() suspend fun withGetMigrationConfigurationReturning(result: Either) @@ -70,18 +69,18 @@ internal class UserConfigRepositoryArrangementImpl : UserConfigRepositoryArrange }.returns(Either.Right(Unit)) } - override fun withSetDefaultProtocolSuccessful() { - every { + override suspend fun withSetDefaultProtocolSuccessful() { + coEvery { userConfigRepository.setDefaultProtocol(any()) }.returns(Either.Right(Unit)) } - override fun withGetDefaultProtocolReturning(result: Either) { - every { userConfigRepository.getDefaultProtocol() }.returns(result) + override suspend fun withGetDefaultProtocolReturning(result: Either) { + coEvery { userConfigRepository.getDefaultProtocol() }.returns(result) } - override fun withSetMLSEnabledSuccessful() { - every { + override suspend fun withSetMLSEnabledSuccessful() { + coEvery { userConfigRepository.setMLSEnabled(any()) }.returns(Either.Right(Unit)) } @@ -145,6 +144,6 @@ internal class UserConfigRepositoryArrangementImpl : UserConfigRepositoryArrange } override suspend fun withConferenceCallingEnabled(result: Boolean) { - every { userConfigRepository.isConferenceCallingEnabled() }.returns(result.right()) + coEvery { userConfigRepository.isConferenceCallingEnabled() }.returns(result.right()) } } diff --git a/logic/src/jsMain/kotlin/com.wire.kalium.logic/di/PlatformUserStorageProperties.kt b/logic/src/jsMain/kotlin/com.wire.kalium.logic/di/PlatformUserStorageProperties.kt new file mode 100644 index 00000000000..49821efe556 --- /dev/null +++ b/logic/src/jsMain/kotlin/com.wire.kalium.logic/di/PlatformUserStorageProperties.kt @@ -0,0 +1,20 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.di + +internal actual class PlatformUserStorageProperties diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt b/logic/src/jsMain/kotlin/com.wire.kalium.logic/di/UserConfigStorageFactory.kt similarity index 62% rename from data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt rename to logic/src/jsMain/kotlin/com.wire.kalium.logic/di/UserConfigStorageFactory.kt index d94cfeaad41..097a3cf7df7 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/kmmSettings/UserPrefBuilder.kt +++ b/logic/src/jsMain/kotlin/com.wire.kalium.logic/di/UserConfigStorageFactory.kt @@ -1,6 +1,6 @@ /* * Wire - * Copyright (C) 2024 Wire Swiss GmbH + * Copyright (C) 2026 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,14 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ +package com.wire.kalium.logic.di -package com.wire.kalium.persistence.kmmSettings - -import com.wire.kalium.persistence.client.LastRetrievedNotificationEventStorage +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.persistence.config.UserConfigStorage -expect class UserPrefBuilder { - val lastRetrievedNotificationEventStorage: LastRetrievedNotificationEventStorage - val userConfigStorage: UserConfigStorage - fun clear() +@Suppress("DEPRECATION") +internal actual class UserConfigStorageFactory actual constructor() { + actual fun create( + userId: UserId, + shouldEncryptData: Boolean, + platformProperties: PlatformUserStorageProperties + ): UserConfigStorage { + TODO("JS implementation not yet available") + } } diff --git a/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt b/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt index 44e00dfc68b..1c9073e0ffa 100644 --- a/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt +++ b/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/PlatformUserStorageProvider.kt @@ -24,7 +24,6 @@ import com.wire.kalium.persistence.db.PlatformDatabaseData import com.wire.kalium.persistence.db.StorageData import com.wire.kalium.persistence.db.inMemoryDatabase import com.wire.kalium.persistence.db.userDatabaseBuilder -import com.wire.kalium.persistence.kmmSettings.UserPrefBuilder import com.wire.kalium.util.KaliumDispatcherImpl internal actual class PlatformUserStorageProvider : UserStorageProvider() { @@ -35,7 +34,6 @@ internal actual class PlatformUserStorageProvider : UserStorageProvider() { dbInvalidationControlEnabled: Boolean ): UserStorage { val userIdEntity = userId.toDao() - val pref = UserPrefBuilder(userIdEntity, platformProperties.rootPath, shouldEncryptData) val databaseInfo = platformProperties.databaseInfo val database = when (databaseInfo) { @@ -55,6 +53,6 @@ internal actual class PlatformUserStorageProvider : UserStorageProvider() { inMemoryDatabase(userIdEntity, KaliumDispatcherImpl.io) } } - return UserStorage(database, pref) + return UserStorage(database) } } diff --git a/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt b/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt new file mode 100644 index 00000000000..b63bdbfaa7b --- /dev/null +++ b/logic/src/jvmMain/kotlin/com/wire/kalium/logic/di/UserConfigStorageFactory.kt @@ -0,0 +1,38 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.logic.di + +import com.wire.kalium.logic.data.id.toDao +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.persistence.config.UserConfigStorage +import com.wire.kalium.persistence.config.UserConfigStorageFactory as PersistenceUserConfigStorageFactory + +@Suppress("DEPRECATION") +internal actual class UserConfigStorageFactory actual constructor() { + private val persistenceFactory = PersistenceUserConfigStorageFactory() + + actual fun create( + userId: UserId, + shouldEncryptData: Boolean, + platformProperties: PlatformUserStorageProperties + ): UserConfigStorage = persistenceFactory.create( + userId = userId.toDao(), + shouldEncryptData = shouldEncryptData, + platformParam = platformProperties.rootPath + ) +} diff --git a/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt b/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt index f6ba3c9e011..240b79c6e06 100644 --- a/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt +++ b/logic/src/jvmTest/kotlin/com/wire/kalium/logic/sync/receiver/asset/AssetMessageHandlerTest.kt @@ -18,6 +18,7 @@ package com.wire.kalium.logic.sync.receiver.asset import com.wire.kalium.common.error.StorageFailure +import com.wire.kalium.common.functional.Either import com.wire.kalium.logic.configuration.FileSharingStatus import com.wire.kalium.logic.configuration.UserConfigRepository import com.wire.kalium.logic.data.conversation.ClientId @@ -32,7 +33,6 @@ import com.wire.kalium.logic.data.message.hasValidData import com.wire.kalium.logic.data.message.hasValidRemoteData import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.asset.ValidateAssetFileTypeUseCase -import com.wire.kalium.common.functional.Either import com.wire.kalium.util.time.UNIX_FIRST_DATE import io.mockative.any import io.mockative.coEvery @@ -476,8 +476,8 @@ class AssetMessageHandlerTest { }.returns(result) } - fun withSuccessfulFileSharingFlag(value: FileSharingStatus.Value) = apply { - every { + suspend fun withSuccessfulFileSharingFlag(value: FileSharingStatus.Value) = apply { + coEvery { userConfigRepository.isFileSharingEnabled() }.returns(Either.Right(FileSharingStatus(state = value, isStatusChanged = false))) }