Compare commits

...

4 Commits

41 changed files with 6179 additions and 203 deletions

View File

@ -130,6 +130,7 @@ public final class me/proton/core/accountmanager/data/db/AccountManagerDatabaseM
public final fun getMIGRATION_41_42 ()Landroidx/room/migration/Migration;
public final fun getMIGRATION_42_43 ()Landroidx/room/migration/Migration;
public final fun getMIGRATION_43_44 ()Landroidx/room/migration/Migration;
public final fun getMIGRATION_44_45 ()Landroidx/room/migration/Migration;
public final fun getMIGRATION_4_5 ()Landroidx/room/migration/Migration;
public final fun getMIGRATION_5_6 ()Landroidx/room/migration/Migration;
public final fun getMIGRATION_6_7 ()Landroidx/room/migration/Migration;

View File

@ -182,7 +182,7 @@ abstract class AccountManagerDatabase :
companion object {
const val name = "db-account-manager"
const val version = 44
const val version = 45
val migrations = listOf(
AccountManagerDatabaseMigrations.MIGRATION_1_2,
@ -228,6 +228,7 @@ abstract class AccountManagerDatabase :
AccountManagerDatabaseMigrations.MIGRATION_41_42,
AccountManagerDatabaseMigrations.MIGRATION_42_43,
AccountManagerDatabaseMigrations.MIGRATION_43_44,
AccountManagerDatabaseMigrations.MIGRATION_44_45,
)
fun databaseBuilder(context: Context): Builder<AccountManagerDatabase> =

View File

@ -314,4 +314,10 @@ object AccountManagerDatabaseMigrations {
PaymentDatabase.MIGRATION_1.migrate(database)
}
}
val MIGRATION_44_45 = object : Migration(44, 45) {
override fun migrate(db: SupportSQLiteDatabase) {
UserSettingsDatabase.MIGRATION_6.migrate(db)
}
}
}

View File

@ -80,23 +80,8 @@ class AccountSettingsViewModelTest : CoroutinesTest by CoroutinesTest() {
type = Type.Proton
)
private val userSettings = UserSettings(
userId = userId,
email = null,
phone = null,
private val userSettings = UserSettings.nil(userId).copy(
password = PasswordSetting(1, null),
twoFA = null,
news = null,
locale = null,
logAuth = null,
density = null,
weekStart = null,
dateFormat = null,
timeFormat = null,
earlyAccess = null,
deviceRecovery = null,
telemetry = null,
crashReports = null,
)
@BeforeTest

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61bde9744db09eaadf658a0e7a5da813de14418313fb16253f47b27646672a57
size 21735
oid sha256:4187a7b1ef2c284d91c35e76205e0b41cb0ed34694ab5f32d67d1f5defc6034b
size 21203

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:73d94be80f3f6627b6d17f8d07e11f6bb20d310166c3bc832ba41677ee2565a3
size 22086
oid sha256:94bf097bcf81485ef39890abe4b198831bc70e4132410e361fc504a69e7ce6f1
size 21482

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:49bfa1b70bbc235a3e70a4cdc4790d87ef629d0c1f23633cf1529091f81b01a5
size 25493
oid sha256:06c91ed443ae2733d6d329f9cdce9ee4b717944a249633bd9971a846670688c1
size 24990

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4429e403bca8f279704c9bddf7a59852819ea563ab60d8b6080d35e605b50eb3
size 25913
oid sha256:b912aa295b17483045cee6cbaa46eadb56786e89307712b4465e74cbc46dc25e
size 25429

View File

@ -29,7 +29,7 @@
<string name="account_settings_header">Account settings</string>
<string name="account_settings_item_header">User</string>
<string name="account_settings_list_header">Account</string>
<string name="account_settings_list_item_password_header">Password management</string>
<string name="account_settings_list_item_password_header">Change password</string>
<string name="account_settings_list_item_password_hint_grace">Password reset pending</string>
<string name="account_settings_list_item_password_hint_insecure">Password reset ready</string>
<string name="account_settings_list_item_recovery_header">Recovery email</string>

File diff suppressed because it is too large Load Diff

View File

@ -182,7 +182,7 @@ abstract class AppDatabase :
companion object {
const val name = "db-account-manager"
const val version = 44
const val version = 45
val migrations = listOf(
AppDatabaseMigrations.MIGRATION_1_2,
@ -228,6 +228,7 @@ abstract class AppDatabase :
AppDatabaseMigrations.MIGRATION_41_42,
AppDatabaseMigrations.MIGRATION_42_43,
AppDatabaseMigrations.MIGRATION_43_44,
AppDatabaseMigrations.MIGRATION_44_45
)
fun buildDatabase(context: Context): AppDatabase =

View File

@ -315,4 +315,10 @@ object AppDatabaseMigrations {
PaymentDatabase.MIGRATION_1.migrate(database)
}
}
val MIGRATION_44_45 = object : Migration(44, 45) {
override fun migrate(db: SupportSQLiteDatabase) {
UserSettingsDatabase.MIGRATION_6.migrate(db)
}
}
}

View File

@ -541,7 +541,7 @@ public final class me/proton/core/usersettings/data/api/response/UpdateUserSetti
public final class me/proton/core/usersettings/data/api/response/UserSettingsResponse {
public static final field Companion Lme/proton/core/usersettings/data/api/response/UserSettingsResponse$Companion;
public fun <init> (Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/PasswordResponse;Lme/proton/core/usersettings/data/api/response/TwoFAResponse;ILjava/lang/String;IIIIIIIII)V
public fun <init> (Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/PasswordResponse;Lme/proton/core/usersettings/data/api/response/TwoFAResponse;ILjava/lang/String;IIIIIIIIII)V
public final fun component1 ()Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;
public final fun component10 ()I
public final fun component11 ()I
@ -549,6 +549,7 @@ public final class me/proton/core/usersettings/data/api/response/UserSettingsRes
public final fun component13 ()I
public final fun component14 ()I
public final fun component15 ()I
public final fun component16 ()I
public final fun component2 ()Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;
public final fun component3 ()Lme/proton/core/usersettings/data/api/response/PasswordResponse;
public final fun component4 ()Lme/proton/core/usersettings/data/api/response/TwoFAResponse;
@ -557,8 +558,8 @@ public final class me/proton/core/usersettings/data/api/response/UserSettingsRes
public final fun component7 ()I
public final fun component8 ()I
public final fun component9 ()I
public final fun copy (Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/PasswordResponse;Lme/proton/core/usersettings/data/api/response/TwoFAResponse;ILjava/lang/String;IIIIIIIII)Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;
public static synthetic fun copy$default (Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/PasswordResponse;Lme/proton/core/usersettings/data/api/response/TwoFAResponse;ILjava/lang/String;IIIIIIIIIILjava/lang/Object;)Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;
public final fun copy (Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/PasswordResponse;Lme/proton/core/usersettings/data/api/response/TwoFAResponse;ILjava/lang/String;IIIIIIIIII)Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;
public static synthetic fun copy$default (Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;Lme/proton/core/usersettings/data/api/response/PasswordResponse;Lme/proton/core/usersettings/data/api/response/TwoFAResponse;ILjava/lang/String;IIIIIIIIIIILjava/lang/Object;)Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;
public fun equals (Ljava/lang/Object;)Z
public final fun getCrashReports ()I
public final fun getDateFormat ()I
@ -571,6 +572,7 @@ public final class me/proton/core/usersettings/data/api/response/UserSettingsRes
public final fun getNews ()I
public final fun getPassword ()Lme/proton/core/usersettings/data/api/response/PasswordResponse;
public final fun getPhone ()Lme/proton/core/usersettings/data/api/response/RecoverySettingResponse;
public final fun getSessionAccountRecovery ()I
public final fun getTelemetry ()I
public final fun getTimeFormat ()I
public final fun getTwoFA ()Lme/proton/core/usersettings/data/api/response/TwoFAResponse;
@ -591,6 +593,7 @@ public final class me/proton/core/usersettings/data/api/response/UserSettingsRes
}
public final class me/proton/core/usersettings/data/api/response/UserSettingsResponse$Companion {
public final fun nil ()Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
@ -624,6 +627,7 @@ public final class me/proton/core/usersettings/data/db/UserSettingsDatabase$Comp
public final fun getMIGRATION_3 ()Lme/proton/core/data/room/db/migration/DatabaseMigration;
public final fun getMIGRATION_4 ()Lme/proton/core/data/room/db/migration/DatabaseMigration;
public final fun getMIGRATION_5 ()Lme/proton/core/data/room/db/migration/DatabaseMigration;
public final fun getMIGRATION_6 ()Lme/proton/core/data/room/db/migration/DatabaseMigration;
}
public final class me/proton/core/usersettings/data/db/UserSettingsLocalDataSourceImpl : me/proton/core/usersettings/domain/repository/UserSettingsLocalDataSource {
@ -826,7 +830,8 @@ public final class me/proton/core/usersettings/data/entity/TwoFAEntity {
}
public final class me/proton/core/usersettings/data/entity/UserSettingsEntity {
public fun <init> (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/PasswordEntity;Lme/proton/core/usersettings/data/entity/TwoFAEntity;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public static final field Companion Lme/proton/core/usersettings/data/entity/UserSettingsEntity$Companion;
public fun <init> (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/PasswordEntity;Lme/proton/core/usersettings/data/entity/TwoFAEntity;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public final fun component1 ()Lme/proton/core/domain/entity/UserId;
public final fun component10 ()Ljava/lang/Integer;
public final fun component11 ()Ljava/lang/Integer;
@ -835,6 +840,7 @@ public final class me/proton/core/usersettings/data/entity/UserSettingsEntity {
public final fun component14 ()Ljava/lang/Boolean;
public final fun component15 ()Ljava/lang/Boolean;
public final fun component16 ()Ljava/lang/Boolean;
public final fun component17 ()Ljava/lang/Boolean;
public final fun component2 ()Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;
public final fun component3 ()Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;
public final fun component4 ()Lme/proton/core/usersettings/data/entity/PasswordEntity;
@ -843,8 +849,8 @@ public final class me/proton/core/usersettings/data/entity/UserSettingsEntity {
public final fun component7 ()Ljava/lang/String;
public final fun component8 ()Ljava/lang/Integer;
public final fun component9 ()Ljava/lang/Integer;
public final fun copy (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/PasswordEntity;Lme/proton/core/usersettings/data/entity/TwoFAEntity;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lme/proton/core/usersettings/data/entity/UserSettingsEntity;
public static synthetic fun copy$default (Lme/proton/core/usersettings/data/entity/UserSettingsEntity;Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/PasswordEntity;Lme/proton/core/usersettings/data/entity/TwoFAEntity;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lme/proton/core/usersettings/data/entity/UserSettingsEntity;
public final fun copy (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/PasswordEntity;Lme/proton/core/usersettings/data/entity/TwoFAEntity;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lme/proton/core/usersettings/data/entity/UserSettingsEntity;
public static synthetic fun copy$default (Lme/proton/core/usersettings/data/entity/UserSettingsEntity;Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;Lme/proton/core/usersettings/data/entity/PasswordEntity;Lme/proton/core/usersettings/data/entity/TwoFAEntity;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lme/proton/core/usersettings/data/entity/UserSettingsEntity;
public fun equals (Ljava/lang/Object;)Z
public final fun getCrashReports ()Ljava/lang/Boolean;
public final fun getDateFormat ()Ljava/lang/Integer;
@ -857,6 +863,7 @@ public final class me/proton/core/usersettings/data/entity/UserSettingsEntity {
public final fun getNews ()Ljava/lang/Integer;
public final fun getPassword ()Lme/proton/core/usersettings/data/entity/PasswordEntity;
public final fun getPhone ()Lme/proton/core/usersettings/data/entity/RecoverySettingEntity;
public final fun getSessionAccountRecovery ()Ljava/lang/Boolean;
public final fun getTelemetry ()Ljava/lang/Boolean;
public final fun getTimeFormat ()Ljava/lang/Integer;
public final fun getTwoFA ()Lme/proton/core/usersettings/data/entity/TwoFAEntity;
@ -866,6 +873,10 @@ public final class me/proton/core/usersettings/data/entity/UserSettingsEntity {
public fun toString ()Ljava/lang/String;
}
public final class me/proton/core/usersettings/data/entity/UserSettingsEntity$Companion {
public final fun nil (Lme/proton/core/domain/entity/UserId;)Lme/proton/core/usersettings/data/entity/UserSettingsEntity;
}
public final class me/proton/core/usersettings/data/extension/UserSettingsMapperKt {
public static final fun toUserSettings (Lme/proton/core/usersettings/data/api/response/UserSettingsResponse;Lme/proton/core/domain/entity/UserId;)Lme/proton/core/usersettings/domain/entity/UserSettings;
}

View File

@ -29,8 +29,8 @@ protonBuild {
}
protonCoverage {
branchCoveragePercentage.set(59)
lineCoveragePercentage.set(81)
branchCoveragePercentage.set(56)
lineCoveragePercentage.set(85)
}
publishOption.shouldBePublishedAsLib = true

View File

@ -129,22 +129,5 @@ class UserSettingsRemoteDataSourceImpl @Inject constructor(
is UserSettingsProperty.Telemetry -> updateTelemetry(UpdateTelemetryRequest(property.value.toInt()))
}.exhaustive
private fun makeUserSettingsForCredentialLess(userId: UserId) = UserSettings(
userId = userId,
email = null,
phone = null,
password = PasswordSetting(null, null),
twoFA = null,
news = null,
locale = null,
logAuth = null,
density = null,
weekStart = null,
dateFormat = null,
timeFormat = null,
earlyAccess = null,
deviceRecovery = null,
telemetry = null,
crashReports = null,
)
private fun makeUserSettingsForCredentialLess(userId: UserId) = UserSettings.nil(userId)
}

View File

@ -53,7 +53,30 @@ data class UserSettingsResponse(
val telemetry: Int,
@SerialName("CrashReports")
val crashReports: Int,
)
@SerialName("SessionAccountRecovery")
val sessionAccountRecovery: Int,
) {
companion object {
fun nil(): UserSettingsResponse = UserSettingsResponse(
email = null,
phone = null,
password = PasswordResponse(0, null),
twoFA = TwoFAResponse(0, 0, null),
news = 0,
locale = "en",
logAuth = 0,
density = 0,
weekStart = 0,
dateFormat = 0,
timeFormat = 0,
earlyAccess = 0,
deviceRecovery = 0,
telemetry = 0,
crashReports = 0,
sessionAccountRecovery = 0,
)
}
}
@Serializable
data class RecoverySettingResponse(

View File

@ -108,5 +108,15 @@ interface UserSettingsDatabase : Database {
)
}
}
val MIGRATION_6 = object : DatabaseMigration {
override fun migrate(database: SupportSQLiteDatabase) {
database.addTableColumn(
table = "UserSettingsEntity",
column = "sessionAccountRecovery",
type = "INTEGER"
)
}
}
}
}

View File

@ -24,6 +24,9 @@ import androidx.room.ForeignKey
import kotlinx.serialization.Serializable
import me.proton.core.domain.entity.UserId
import me.proton.core.user.data.entity.UserEntity
import me.proton.core.usersettings.data.extension.toEntity
import me.proton.core.usersettings.domain.entity.PasswordSetting
import me.proton.core.usersettings.domain.entity.UserSettings
@Entity(
primaryKeys = ["userId"],
@ -57,7 +60,12 @@ data class UserSettingsEntity(
val deviceRecovery: Boolean?,
val telemetry: Boolean?,
val crashReports: Boolean?,
)
val sessionAccountRecovery: Boolean?
) {
companion object {
fun nil(userId: UserId): UserSettingsEntity = UserSettings.nil(userId).toEntity()
}
}
data class RecoverySettingEntity(
val value: String?,

View File

@ -59,6 +59,7 @@ internal fun UserSettingsResponse.fromResponse(userId: UserId) = UserSettings(
deviceRecovery = deviceRecovery.toBooleanOrFalse(),
telemetry = telemetry.toBooleanOrFalse(),
crashReports = crashReports.toBooleanOrFalse(),
sessionAccountRecovery = sessionAccountRecovery.toBooleanOrFalse()
)
internal fun RecoverySettingResponse.fromResponse() = RecoverySetting(
@ -96,6 +97,7 @@ internal fun UserSettingsEntity.fromEntity() = UserSettings(
deviceRecovery = deviceRecovery,
telemetry = telemetry,
crashReports = crashReports,
sessionAccountRecovery = sessionAccountRecovery
)
internal fun UserSettings.toEntity() = UserSettingsEntity(
@ -114,7 +116,8 @@ internal fun UserSettings.toEntity() = UserSettingsEntity(
earlyAccess = earlyAccess,
deviceRecovery = deviceRecovery,
telemetry = telemetry,
crashReports = crashReports
crashReports = crashReports,
sessionAccountRecovery = sessionAccountRecovery
)
internal fun RecoverySettingEntity.fromEntity() = RecoverySetting(

View File

@ -140,13 +140,9 @@ class UserSettingsRemoteDataSourceImplTest {
assertTrue(response)
}
private val settingsResponse = UserSettingsResponse(
private val settingsResponse = UserSettingsResponse.nil().copy(
email = RecoverySettingResponse("test-email2", 1, notify = 1, reset = 1),
phone = null,
twoFA = null,
password = PasswordResponse(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = 1,
density = 1,
dateFormat = 1,

View File

@ -54,7 +54,6 @@ import me.proton.core.usersettings.data.api.response.UserSettingsResponse
import me.proton.core.usersettings.data.db.UserSettingsDatabase
import me.proton.core.usersettings.data.db.UserSettingsLocalDataSourceImpl
import me.proton.core.usersettings.data.db.dao.UserSettingsDao
import me.proton.core.usersettings.data.entity.PasswordEntity
import me.proton.core.usersettings.data.entity.UserSettingsEntity
import me.proton.core.usersettings.data.extension.fromEntity
import me.proton.core.usersettings.data.extension.fromResponse
@ -121,13 +120,9 @@ class UserSettingsRepositoryImplTest {
@Test
fun `user settings returns success`() = runTest(dispatcherProvider.Main) {
val settingsResponse = UserSettingsResponse(
val settingsResponse = UserSettingsResponse.nil().copy(
email = RecoverySettingResponse("test-email", 1, notify = 1, reset = 1),
phone = null,
twoFA = null,
password = PasswordResponse(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = 1,
density = 1,
dateFormat = 1,
@ -193,13 +188,9 @@ class UserSettingsRepositoryImplTest {
}
private fun setUpRecoveryEmailUpdateTest(srpServerProof: String) {
val settingsResponse = UserSettingsResponse(
val settingsResponse = UserSettingsResponse.nil().copy(
email = RecoverySettingResponse("test-email2", 1, notify = 1, reset = 1),
phone = null,
twoFA = null,
password = PasswordResponse(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = 1,
density = 1,
dateFormat = 1,
@ -261,13 +252,9 @@ class UserSettingsRepositoryImplTest {
}
private fun setUpUpdatePasswordTest(srpServerProof: String): Auth {
val settingsResponse = UserSettingsResponse(
val settingsResponse = UserSettingsResponse.nil().copy(
email = RecoverySettingResponse("test-email2", 1, notify = 1, reset = 1),
phone = null,
twoFA = null,
password = PasswordResponse(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = 1,
density = 1,
dateFormat = 1,
@ -299,13 +286,9 @@ class UserSettingsRepositoryImplTest {
@Test
fun `update crash reports returns success`() = runTest(dispatcherProvider.Main) {
// GIVEN
val settingsResponse = UserSettingsResponse(
val settingsResponse = UserSettingsResponse.nil().copy(
email = RecoverySettingResponse("test-email2", 1, notify = 1, reset = 1),
phone = null,
twoFA = null,
password = PasswordResponse(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = 1,
density = 1,
dateFormat = 1,
@ -341,13 +324,9 @@ class UserSettingsRepositoryImplTest {
@Test
fun `update telemetry returns success`() = runTest(dispatcherProvider.Main) {
// GIVEN
val settingsResponse = UserSettingsResponse(
val settingsResponse = UserSettingsResponse.nil().copy(
email = RecoverySettingResponse("test-email2", 1, notify = 1, reset = 1),
phone = null,
twoFA = null,
password = PasswordResponse(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = 1,
density = 1,
dateFormat = 1,
@ -402,24 +381,7 @@ class UserSettingsRepositoryImplTest {
@Test
fun localObjectIsReturnedForCredentialLess() = runTest(dispatcherProvider.Main) {
// GIVEN
val userSettingsEntity = UserSettingsEntity(
userId = UserId(testUserId),
email = null,
phone = null,
password = PasswordEntity(null, null),
twoFA = null,
news = null,
locale = null,
logAuth = null,
density = null,
weekStart = null,
dateFormat = null,
timeFormat = null,
earlyAccess = null,
deviceRecovery = null,
telemetry = null,
crashReports = null,
)
val userSettingsEntity = UserSettingsEntity.nil(UserId(testUserId))
every { userSettingsDao.observeByUserId(any()) } returns flowOf(userSettingsEntity)
coEvery { userRepository.getUser(any(), any()) } returns mockk {

View File

@ -155,7 +155,8 @@ public final class me/proton/core/usersettings/domain/entity/TwoFASetting {
}
public final class me/proton/core/usersettings/domain/entity/UserSettings {
public fun <init> (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/PasswordSetting;Lme/proton/core/usersettings/domain/entity/TwoFASetting;Ljava/lang/Integer;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public static final field Companion Lme/proton/core/usersettings/domain/entity/UserSettings$Companion;
public fun <init> (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/PasswordSetting;Lme/proton/core/usersettings/domain/entity/TwoFASetting;Ljava/lang/Integer;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)V
public final fun component1 ()Lme/proton/core/domain/entity/UserId;
public final fun component10 ()Lme/proton/core/domain/type/IntEnum;
public final fun component11 ()Lme/proton/core/domain/type/IntEnum;
@ -164,6 +165,7 @@ public final class me/proton/core/usersettings/domain/entity/UserSettings {
public final fun component14 ()Ljava/lang/Boolean;
public final fun component15 ()Ljava/lang/Boolean;
public final fun component16 ()Ljava/lang/Boolean;
public final fun component17 ()Ljava/lang/Boolean;
public final fun component2 ()Lme/proton/core/usersettings/domain/entity/RecoverySetting;
public final fun component3 ()Lme/proton/core/usersettings/domain/entity/RecoverySetting;
public final fun component4 ()Lme/proton/core/usersettings/domain/entity/PasswordSetting;
@ -172,8 +174,8 @@ public final class me/proton/core/usersettings/domain/entity/UserSettings {
public final fun component7 ()Ljava/lang/String;
public final fun component8 ()Lme/proton/core/domain/type/IntEnum;
public final fun component9 ()Lme/proton/core/domain/type/IntEnum;
public final fun copy (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/PasswordSetting;Lme/proton/core/usersettings/domain/entity/TwoFASetting;Ljava/lang/Integer;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lme/proton/core/usersettings/domain/entity/UserSettings;
public static synthetic fun copy$default (Lme/proton/core/usersettings/domain/entity/UserSettings;Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/PasswordSetting;Lme/proton/core/usersettings/domain/entity/TwoFASetting;Ljava/lang/Integer;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lme/proton/core/usersettings/domain/entity/UserSettings;
public final fun copy (Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/PasswordSetting;Lme/proton/core/usersettings/domain/entity/TwoFASetting;Ljava/lang/Integer;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lme/proton/core/usersettings/domain/entity/UserSettings;
public static synthetic fun copy$default (Lme/proton/core/usersettings/domain/entity/UserSettings;Lme/proton/core/domain/entity/UserId;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/RecoverySetting;Lme/proton/core/usersettings/domain/entity/PasswordSetting;Lme/proton/core/usersettings/domain/entity/TwoFASetting;Ljava/lang/Integer;Ljava/lang/String;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Lme/proton/core/domain/type/IntEnum;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lme/proton/core/usersettings/domain/entity/UserSettings;
public fun equals (Ljava/lang/Object;)Z
public final fun getCrashReports ()Ljava/lang/Boolean;
public final fun getDateFormat ()Lme/proton/core/domain/type/IntEnum;
@ -186,6 +188,7 @@ public final class me/proton/core/usersettings/domain/entity/UserSettings {
public final fun getNews ()Ljava/lang/Integer;
public final fun getPassword ()Lme/proton/core/usersettings/domain/entity/PasswordSetting;
public final fun getPhone ()Lme/proton/core/usersettings/domain/entity/RecoverySetting;
public final fun getSessionAccountRecovery ()Ljava/lang/Boolean;
public final fun getTelemetry ()Ljava/lang/Boolean;
public final fun getTimeFormat ()Lme/proton/core/domain/type/IntEnum;
public final fun getTwoFA ()Lme/proton/core/usersettings/domain/entity/TwoFASetting;
@ -195,6 +198,10 @@ public final class me/proton/core/usersettings/domain/entity/UserSettings {
public fun toString ()Ljava/lang/String;
}
public final class me/proton/core/usersettings/domain/entity/UserSettings$Companion {
public final fun nil (Lme/proton/core/domain/entity/UserId;)Lme/proton/core/usersettings/domain/entity/UserSettings;
}
public final class me/proton/core/usersettings/domain/entity/UserSettings$DateFormat : java/lang/Enum {
public static final field Companion Lme/proton/core/usersettings/domain/entity/UserSettings$DateFormat$Companion;
public static final field DayMonthYear Lme/proton/core/usersettings/domain/entity/UserSettings$DateFormat;
@ -366,6 +373,12 @@ public final class me/proton/core/usersettings/domain/usecase/GetUserSettings {
public final fun invoke (Lme/proton/core/network/domain/session/SessionId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/usersettings/domain/usecase/IsSessionAccountRecoveryEnabled {
public fun <init> (Lme/proton/core/usersettings/domain/usecase/GetUserSettings;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun invoke$default (Lme/proton/core/usersettings/domain/usecase/IsSessionAccountRecoveryEnabled;Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}
public final class me/proton/core/usersettings/domain/usecase/ObserveDeviceSettings {
public fun <init> (Lme/proton/core/usersettings/domain/repository/DeviceSettingsRepository;)V
public final fun invoke ()Lkotlinx/coroutines/flow/Flow;

View File

@ -28,8 +28,8 @@ protonBuild {
}
protonCoverage {
branchCoveragePercentage.set(44)
lineCoveragePercentage.set(73)
branchCoveragePercentage.set(52)
lineCoveragePercentage.set(77)
}
publishOption.shouldBePublishedAsLib = true

View File

@ -38,7 +38,30 @@ data class UserSettings(
val deviceRecovery: Boolean?,
val telemetry: Boolean?,
val crashReports: Boolean?,
val sessionAccountRecovery: Boolean?
) {
companion object {
fun nil(userId: UserId): UserSettings = UserSettings(
userId = userId,
email = null,
phone = null,
password = PasswordSetting(null, null),
twoFA = TwoFASetting(false, 0, null),
news = 0,
locale = "en",
logAuth = IntEnum(UserSettings.LogAuth.Disabled.value, UserSettings.LogAuth.Disabled),
density = IntEnum(UserSettings.Density.Comfortable.value, UserSettings.Density.Comfortable),
weekStart = IntEnum(UserSettings.WeekStart.Default.value, UserSettings.WeekStart.Default),
dateFormat = IntEnum(UserSettings.DateFormat.Default.value, UserSettings.DateFormat.Default),
timeFormat = IntEnum(UserSettings.TimeFormat.Default.value, UserSettings.TimeFormat.Default),
earlyAccess = false,
deviceRecovery = false,
telemetry = false,
crashReports = false,
sessionAccountRecovery = false,
)
}
enum class LogAuth(val value: Int) {
Disabled(0),
Basic(1),

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.domain.usecase
import me.proton.core.domain.entity.SessionUserId
import javax.inject.Inject
class IsSessionAccountRecoveryEnabled @Inject constructor(
private val getUserSettings: GetUserSettings
) {
suspend operator fun invoke(
sessionUserId: SessionUserId,
refresh: Boolean = false
): Boolean = getUserSettings(sessionUserId, refresh = refresh).sessionAccountRecovery ?: run {
when {
refresh -> null // Don't retry if user settings were already refreshed.
else -> getUserSettings(sessionUserId, refresh = true).sessionAccountRecovery
}
} ?: false
}

View File

@ -29,7 +29,6 @@ import me.proton.core.accountmanager.domain.AccountManager
import me.proton.core.accountmanager.domain.getAccounts
import me.proton.core.domain.entity.UserId
import me.proton.core.test.kotlin.coroutineScopeProvider
import me.proton.core.usersettings.domain.entity.PasswordSetting
import me.proton.core.usersettings.domain.entity.UserSettings
import me.proton.core.usersettings.domain.usecase.ObserveUserSettings
import org.junit.Assert.assertEquals
@ -96,22 +95,7 @@ class UsersSettingsHandlerTest {
details = AccountDetails(null, null)
)
private fun userSettings(userId: UserId, crashReports: Boolean?) = UserSettings(
userId = userId,
email = null,
phone = null,
password = PasswordSetting(null, null),
twoFA = null,
news = null,
locale = null,
logAuth = null,
density = null,
weekStart = null,
dateFormat = null,
timeFormat = null,
earlyAccess = null,
deviceRecovery = null,
telemetry = null,
private fun userSettings(userId: UserId, crashReports: Boolean?) = UserSettings.nil(userId).copy(
crashReports = crashReports
)
}

View File

@ -47,14 +47,9 @@ class GetUserSettingsTest {
// region test data
private val testSessionId = SessionId("test-session-id")
private val testUserId = UserId("test-user-id")
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, true, true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),

View File

@ -0,0 +1,93 @@
package me.proton.core.usersettings.domain.usecase
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import me.proton.core.domain.entity.UserId
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class IsSessionAccountRecoveryEnabledTest {
@MockK
private lateinit var getUserSettings: GetUserSettings
private lateinit var tested: IsSessionAccountRecoveryEnabled
private val testUserId = UserId("user-id")
@BeforeTest
fun setUp() {
MockKAnnotations.init(this)
tested = IsSessionAccountRecoveryEnabled(getUserSettings)
}
@Test
fun `sessionAccountRecovery is enabled`() = runTest {
// GIVEN
coEvery { getUserSettings(testUserId, false) } returns mockk {
every { sessionAccountRecovery } returns true
}
// WHEN
val result = tested(testUserId)
// THEN
assertTrue(result)
coVerify(exactly = 1) { getUserSettings(testUserId, false) }
}
@Test
fun `sessionAccountRecovery is initially null`() = runTest {
// GIVEN
coEvery { getUserSettings(testUserId, false) } returns mockk {
every { sessionAccountRecovery } returns null
}
coEvery { getUserSettings(testUserId, true) } returns mockk {
every { sessionAccountRecovery } returns true
}
// WHEN
val result = tested(testUserId)
// THEN
assertTrue(result)
coVerify(exactly = 1) { getUserSettings(testUserId, false) }
coVerify(exactly = 1) { getUserSettings(testUserId, true) }
}
@Test
fun `force refresh`() = runTest {
// GIVEN
coEvery { getUserSettings(testUserId, true) } returns mockk {
every { sessionAccountRecovery } returns null
}
// WHEN
val result = tested(testUserId, refresh = true)
// THEN
assertFalse(result)
coVerify(exactly = 1) { getUserSettings(testUserId, true) }
}
@Test
fun `sessionAccountRecovery is always null`() = runTest {
// GIVEN
coEvery { getUserSettings(testUserId, any()) } returns mockk {
every { sessionAccountRecovery } returns null
}
// WHEN
val result = tested(testUserId)
// THEN
assertFalse(result)
coVerify(exactly = 2) { getUserSettings(testUserId, any()) }
}
}

View File

@ -38,14 +38,9 @@ class PerformUpdateCrashReportsTest {
// region test data
private val testUserId = UserId("test-user-id")
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, notify = true, reset = true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),

View File

@ -97,14 +97,9 @@ class PerformUpdateLoginPasswordTest {
keys = emptyList()
)
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, notify = true, reset = true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),

View File

@ -67,14 +67,9 @@ class PerformUpdateRecoveryEmailTest {
every { email } returns null
}
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, notify = true, reset = true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),

View File

@ -38,14 +38,9 @@ class PerformUpdateTelemetryTest {
// region test data
private val testUserId = UserId("test-user-id")
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, notify = true, reset = true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),

View File

@ -82,6 +82,7 @@ public final class me/proton/core/usersettings/presentation/databinding/Activity
}
public final class me/proton/core/usersettings/presentation/databinding/FragmentPasswordManagementBinding : androidx/viewbinding/ViewBinding {
public final field accountPasswordNote Landroid/widget/TextView;
public final field accountRecoveryInfo Landroidx/compose/ui/platform/ComposeView;
public final field confirmNewLoginPasswordInput Lme/proton/core/presentation/ui/view/ProtonInput;
public final field confirmNewMailboxPasswordInput Lme/proton/core/presentation/ui/view/ProtonInput;
@ -350,7 +351,7 @@ public abstract interface class me/proton/core/usersettings/presentation/ui/Upda
public final class me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel : me/proton/core/presentation/viewmodel/ProtonViewModel, me/proton/core/observability/domain/ObservabilityContext {
public static final field $stable I
public fun <init> (Lme/proton/core/crypto/common/keystore/KeyStoreCrypto;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecovery;Lme/proton/core/usersettings/domain/usecase/ObserveUserSettings;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecoverySelfInitiated;Lme/proton/core/usersettings/domain/usecase/PerformUpdateLoginPassword;Lme/proton/core/usersettings/domain/usecase/PerformUpdateUserPassword;Lme/proton/core/usersettings/domain/usecase/PerformResetUserPassword;Lme/proton/core/accountrecovery/domain/IsAccountRecoveryResetEnabled;Lme/proton/core/observability/domain/ObservabilityManager;)V
public fun <init> (Lme/proton/core/crypto/common/keystore/KeyStoreCrypto;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecovery;Lme/proton/core/usersettings/domain/usecase/ObserveUserSettings;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecoverySelfInitiated;Lme/proton/core/usersettings/domain/usecase/PerformUpdateLoginPassword;Lme/proton/core/usersettings/domain/usecase/PerformUpdateUserPassword;Lme/proton/core/usersettings/domain/usecase/PerformResetUserPassword;Lme/proton/core/accountrecovery/domain/IsAccountRecoveryResetEnabled;Lme/proton/core/observability/domain/ObservabilityManager;Lme/proton/core/usersettings/domain/usecase/IsSessionAccountRecoveryEnabled;Lme/proton/core/domain/entity/Product;)V
public fun enqueueObservability (Lme/proton/core/observability/domain/metrics/ObservabilityData;)V
public fun enqueueObservability-KWTtemM (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public fun getObservabilityManager ()Lme/proton/core/observability/domain/ObservabilityManager;
@ -510,11 +511,11 @@ public final class me/proton/core/usersettings/presentation/viewmodel/PasswordMa
}
public final class me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel_Factory;
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel;
public static fun newInstance (Lme/proton/core/crypto/common/keystore/KeyStoreCrypto;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecovery;Lme/proton/core/usersettings/domain/usecase/ObserveUserSettings;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecoverySelfInitiated;Lme/proton/core/usersettings/domain/usecase/PerformUpdateLoginPassword;Lme/proton/core/usersettings/domain/usecase/PerformUpdateUserPassword;Lme/proton/core/usersettings/domain/usecase/PerformResetUserPassword;Lme/proton/core/accountrecovery/domain/IsAccountRecoveryResetEnabled;Lme/proton/core/observability/domain/ObservabilityManager;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel;
public static fun newInstance (Lme/proton/core/crypto/common/keystore/KeyStoreCrypto;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecovery;Lme/proton/core/usersettings/domain/usecase/ObserveUserSettings;Lme/proton/core/accountrecovery/domain/usecase/ObserveUserRecoverySelfInitiated;Lme/proton/core/usersettings/domain/usecase/PerformUpdateLoginPassword;Lme/proton/core/usersettings/domain/usecase/PerformUpdateUserPassword;Lme/proton/core/usersettings/domain/usecase/PerformResetUserPassword;Lme/proton/core/accountrecovery/domain/IsAccountRecoveryResetEnabled;Lme/proton/core/observability/domain/ObservabilityManager;Lme/proton/core/usersettings/domain/usecase/IsSessionAccountRecoveryEnabled;Lme/proton/core/domain/entity/Product;)Lme/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel;
}
public final class me/proton/core/usersettings/presentation/viewmodel/PasswordManagementViewModel_HiltModules {

View File

@ -30,8 +30,8 @@ protonBuild {
}
protonCoverage {
branchCoveragePercentage.set(26)
lineCoveragePercentage.set(55)
branchCoveragePercentage.set(40)
lineCoveragePercentage.set(56)
}
publishOption.shouldBePublishedAsLib = true

View File

@ -136,6 +136,7 @@ class PasswordManagementFragment :
binding.tabLayout.isVisible = it.loginPasswordAvailable && it.mailboxPasswordAvailable
binding.loginPasswordGroup.isVisible = binding.tabLayout.selectedTabPosition == 0
binding.mailboxPasswordGroup.isVisible = binding.tabLayout.selectedTabPosition == 1
binding.accountPasswordNote.isVisible = it.loginPasswordAvailable && it.mailboxPasswordAvailable
binding.dontKnowYourCurrentPassword.isVisible = it.recoveryResetAvailable
binding.currentLoginPasswordInput.isVisible = it.currentLoginPasswordNeeded

View File

@ -37,12 +37,14 @@ import me.proton.core.accountrecovery.domain.usecase.ObserveUserRecoverySelfInit
import me.proton.core.compose.viewmodel.stopTimeoutMillis
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.crypto.common.keystore.encrypt
import me.proton.core.domain.entity.Product
import me.proton.core.domain.entity.UserId
import me.proton.core.observability.domain.ObservabilityContext
import me.proton.core.observability.domain.ObservabilityManager
import me.proton.core.observability.domain.metrics.AccountRecoveryResetTotal
import me.proton.core.presentation.viewmodel.ProtonViewModel
import me.proton.core.user.domain.entity.UserRecovery
import me.proton.core.usersettings.domain.usecase.IsSessionAccountRecoveryEnabled
import me.proton.core.usersettings.domain.usecase.ObserveUserSettings
import me.proton.core.usersettings.domain.usecase.PerformUpdateLoginPassword
import me.proton.core.usersettings.domain.usecase.PerformUpdateUserPassword
@ -61,6 +63,8 @@ class PasswordManagementViewModel @Inject constructor(
private val performResetPassword: PerformResetUserPassword,
private val isAccountRecoveryResetEnabled: IsAccountRecoveryResetEnabled,
override val observabilityManager: ObservabilityManager,
private val isSessionAccountRecoveryEnabled: IsSessionAccountRecoveryEnabled,
private val product: Product
) : ProtonViewModel(), ObservabilityContext {
private var pendingUpdate: Action.UpdatePassword? = null
@ -89,7 +93,7 @@ class PasswordManagementViewModel @Inject constructor(
userId = userId,
loginPasswordAvailable = true,
mailboxPasswordAvailable = isMailboxPassword,
recoveryResetAvailable = isRecoveryResetEnabled && isRecoveryAvailable,
recoveryResetAvailable = recoveryResetAvailable(userId),
recoveryResetEnabled = isRecoveryResetEnabled,
currentLoginPasswordNeeded = !isRecoveryResetEnabled || !isRecoveryInsecure || !isSelfInitiated,
twoFactorEnabled = isTwoFactorEnabled,
@ -99,6 +103,9 @@ class PasswordManagementViewModel @Inject constructor(
currentUserId.emit(userId)
}
private suspend fun recoveryResetAvailable(userId: UserId): Boolean =
isRecoveryResetEnabled && isRecoveryAvailable && isSessionAccountRecoveryEnabled(userId)
private suspend fun updatePassword(action: Action.UpdatePassword): Flow<State> = when {
pendingUpdate == null && userSettings?.twoFA?.enabled == true -> {
pendingUpdate = action
@ -185,7 +192,7 @@ class PasswordManagementViewModel @Inject constructor(
private val userRecovery get() = currentUserRecovery.value
private val userSettings get() = currentUserSettings.value
private val isSelfInitiated get() = currentSelfInitiated.value
private val isMailboxPassword get() = userSettings?.password?.mode == 2
private val isMailboxPassword get() = userSettings?.password?.mode == 2 && product != Product.Vpn
private val isTwoFactorEnabled get() = userSettings?.twoFA?.enabled ?: false
private val isRecoveryResetEnabled get() = isAccountRecoveryResetEnabled(currentUserId.value)
private val isRecoveryInsecure get() = userRecovery?.state?.enum == UserRecovery.State.Insecure

View File

@ -75,37 +75,41 @@
tools:visibility="visible">
<TextView
style="@style/ProtonTextView.InputHeader"
android:id="@+id/account_password_note"
style="@style/Proton.Text.DefaultSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/default_gap"
android:text="@string/settings_password_management_change_login_password" />
android:text="@string/settings_password_management_change_login_password"
android:textColor="@color/text_weak"
android:visibility="gone"
tools:visibility="visible" />
<me.proton.core.presentation.ui.view.ProtonInput
android:id="@+id/currentLoginPasswordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_login_password_hint_current"
android:inputType="textPassword"
app:actionMode="password_toggle"
app:label="@string/settings_login_password_hint_current"
app:passwordClearable="false" />
<me.proton.core.presentation.ui.view.ProtonInput
android:id="@+id/newLoginPasswordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_login_password_hint_new"
android:inputType="textPassword"
app:actionMode="password_toggle"
app:label="@string/settings_login_password_hint_new"
app:passwordClearable="false" />
<me.proton.core.presentation.ui.view.ProtonInput
android:id="@+id/confirmNewLoginPasswordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_login_password_hint_new_confirm"
android:inputType="textPassword"
app:actionMode="password_toggle"
app:label="@string/settings_login_password_hint_new_confirm"
app:passwordClearable="false" />
<me.proton.core.presentation.ui.view.ProtonProgressButton
@ -128,37 +132,38 @@
tools:visibility="gone">
<TextView
style="@style/ProtonTextView.InputHeader"
style="@style/Proton.Text.DefaultSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/default_gap"
android:text="@string/settings_password_management_change_mailbox_password" />
android:text="@string/settings_password_management_change_mailbox_password"
android:textColor="@color/text_weak" />
<me.proton.core.presentation.ui.view.ProtonInput
android:id="@+id/currentMailboxPasswordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_login_password_hint_current"
android:inputType="textPassword"
app:actionMode="password_toggle"
app:label="@string/settings_mailbox_password_hint_current_account_password"
app:passwordClearable="false" />
<me.proton.core.presentation.ui.view.ProtonInput
android:id="@+id/newMailboxPasswordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_mailbox_password_hint_new"
android:inputType="textPassword"
app:actionMode="password_toggle"
app:label="@string/settings_mailbox_password_hint_new"
app:passwordClearable="false" />
<me.proton.core.presentation.ui.view.ProtonInput
android:id="@+id/confirmNewMailboxPasswordInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_mailbox_password_hint_new_confirm"
android:inputType="textPassword"
app:actionMode="password_toggle"
app:label="@string/settings_mailbox_password_hint_new_confirm"
app:passwordClearable="false" />
<me.proton.core.presentation.ui.view.ProtonProgressButton
@ -175,7 +180,9 @@
style="@style/ProtonButton.Borderless.Text.NoProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_password_management_start_recovery" />
android:text="@string/settings_password_management_start_recovery"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>

View File

@ -32,16 +32,17 @@
<string name="settings_validation_email">Email address is invalid.</string>
<string name="settings_general_error">An error occurred.</string>
<!-- login and mailbox password settings -->
<string name="settings_password_management_tab_login_password">Login password</string>
<string name="settings_password_management_tab_mailbox_password">Mailbox password</string>
<string name="settings_password_management_change_login_password">Change password</string>
<string name="settings_password_management_change_mailbox_password">Change mailbox password</string>
<string name="settings_password_management_header">Password management</string>
<string name="settings_login_password_hint_current">Current login password</string>
<string name="settings_login_password_hint_new">New login password</string>
<string name="settings_login_password_hint_new_confirm">Confirm new login password</string>
<string name="settings_mailbox_password_hint_new">New mailbox password</string>
<string name="settings_mailbox_password_hint_new_confirm">Confirm new mailbox password</string>
<string name="settings_password_management_tab_login_password">Account password</string>
<string name="settings_password_management_tab_mailbox_password">Encryption password</string>
<string name="settings_password_management_change_login_password">In two-password mode, the account password is used to sign in to your Proton Account.</string>
<string name="settings_password_management_change_mailbox_password">In two-password mode, the encryption password is used to encrypt and decrypt your data.</string>
<string name="settings_password_management_header">Change password</string>
<string name="settings_login_password_hint_current">Current password</string>
<string name="settings_login_password_hint_new">New password</string>
<string name="settings_login_password_hint_new_confirm">Confirm new password</string>
<string name="settings_mailbox_password_hint_current_account_password">Current account password</string>
<string name="settings_mailbox_password_hint_new">New encryption password</string>
<string name="settings_mailbox_password_hint_new_confirm">Confirm new encryption password</string>
<string name="settings_change_password_error">A change password error has occurred.</string>
<string name="settings_recovery_email_success">Recovery email address updated</string>
<string name="settings_password_management_success">Password updated</string>

View File

@ -28,24 +28,23 @@ import me.proton.core.accountrecovery.domain.IsAccountRecoveryResetEnabled
import me.proton.core.accountrecovery.domain.usecase.ObserveUserRecovery
import me.proton.core.accountrecovery.domain.usecase.ObserveUserRecoverySelfInitiated
import me.proton.core.crypto.common.keystore.KeyStoreCrypto
import me.proton.core.domain.entity.Product
import me.proton.core.domain.entity.UserId
import me.proton.core.observability.domain.ObservabilityManager
import me.proton.core.test.android.ArchTest
import me.proton.core.test.kotlin.CoroutinesTest
import me.proton.core.test.kotlin.assertIs
import me.proton.core.test.kotlin.flowTest
import me.proton.core.user.domain.entity.Type
import me.proton.core.user.domain.entity.User
import me.proton.core.user.domain.repository.UserRepository
import me.proton.core.user.domain.usecase.ObserveUser
import me.proton.core.usersettings.domain.entity.PasswordSetting
import me.proton.core.usersettings.domain.entity.RecoverySetting
import me.proton.core.usersettings.domain.entity.TwoFASetting
import me.proton.core.usersettings.domain.entity.UserSettings
import me.proton.core.usersettings.domain.usecase.IsSessionAccountRecoveryEnabled
import me.proton.core.usersettings.domain.usecase.ObserveUserSettings
import me.proton.core.usersettings.domain.usecase.PerformResetUserPassword
import me.proton.core.usersettings.domain.usecase.PerformUpdateLoginPassword
import me.proton.core.usersettings.domain.usecase.PerformUpdateUserPassword
import me.proton.core.usersettings.domain.usecase.PerformResetUserPassword
import me.proton.core.usersettings.presentation.viewmodel.PasswordManagementViewModel.Action.ObserveState
import me.proton.core.usersettings.presentation.viewmodel.PasswordManagementViewModel.Action.UpdatePassword
import me.proton.core.usersettings.presentation.viewmodel.PasswordManagementViewModel.PasswordType.Both
@ -55,6 +54,7 @@ import me.proton.core.usersettings.presentation.viewmodel.PasswordManagementView
import org.junit.Before
import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertTrue
class PasswordManagementViewModelTest : ArchTest by ArchTest(), CoroutinesTest by CoroutinesTest() {
@ -68,6 +68,7 @@ class PasswordManagementViewModelTest : ArchTest by ArchTest(), CoroutinesTest b
private val isAccountRecoveryResetEnabled = mockk<IsAccountRecoveryResetEnabled>()
private val observabilityManager = mockk<ObservabilityManager>()
private val keyStoreCrypto = mockk<KeyStoreCrypto>()
private val isSessionAccountRecoveryEnabled = mockk<IsSessionAccountRecoveryEnabled>()
// endregion
// region test data
@ -76,14 +77,9 @@ class PasswordManagementViewModelTest : ArchTest by ArchTest(), CoroutinesTest b
private val testPassword = "test-password"
private val testNewPassword = "test-new-password"
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, notify = true, reset = true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),
@ -134,7 +130,9 @@ class PasswordManagementViewModelTest : ArchTest by ArchTest(), CoroutinesTest b
performUpdateMailboxPassword,
performResetUserPassword,
isAccountRecoveryResetEnabled,
observabilityManager
observabilityManager,
isSessionAccountRecoveryEnabled,
Product.Mail
)
}
@ -336,4 +334,22 @@ class PasswordManagementViewModelTest : ArchTest by ArchTest(), CoroutinesTest b
assertIs<State.TwoFactorNeeded>(awaitItem())
}
}
@Test
fun `recovery password reset available`() = coroutinesTest {
// GIVEN
coEvery { isAccountRecoveryResetEnabled.invoke(testUserId) } returns true
coEvery { isSessionAccountRecoveryEnabled.invoke(testUserId) } returns true
viewModel.state.test {
// WHEN
viewModel.perform(ObserveState(testUserId))
// THEN
assertIs<State.Idle>(awaitItem())
val result = assertIs<State.ChangePassword>(awaitItem())
assertTrue(result.recoveryResetAvailable)
coVerify { isSessionAccountRecoveryEnabled(testUserId, false) }
}
}
}

View File

@ -54,14 +54,9 @@ class UpdateRecoveryEmailViewModelTest : ArchTest by ArchTest(), CoroutinesTest
private val testUsername = "test-username"
private val testPassword: EncryptedString = "test-password"
private val testUserSettingsResponse = UserSettings(
userId = testUserId,
private val testUserSettingsResponse = UserSettings.nil(testUserId).copy(
email = RecoverySetting("test-email", 1, notify = true, reset = true),
phone = null,
twoFA = null,
password = PasswordSetting(mode = 1, expirationTime = null),
news = 0,
locale = "en",
logAuth = UserSettings.LogAuth.enumOf(1),
density = UserSettings.Density.enumOf(1),
dateFormat = UserSettings.DateFormat.enumOf(1),