Merge branch 'develop' into renovate/com.posthog-posthog-android-3.x

This commit is contained in:
Benoit Marty 2025-02-27 10:15:24 +01:00 committed by GitHub
commit 70223bf350
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
883 changed files with 1927 additions and 1818 deletions

View file

@ -138,7 +138,7 @@ jobs:
- name: Build Fdroid Debug
run: ./gradlew :app:compileFdroidDebugKotlin $CI_GRADLE_ARG_PROPERTIES
- name: Run lint
run: ./gradlew :app:lintGplayDebug :app:lintFdroidDebug $CI_GRADLE_ARG_PROPERTIES
run: ./gradlew :app:lintGplayDebug :app:lintFdroidDebug lintDebug $CI_GRADLE_ARG_PROPERTIES --continue
- name: Upload reports
if: always()
uses: actions/upload-artifact@v4

View file

@ -26,6 +26,7 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.pushproviders.api.RegistrationFailure
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -60,6 +62,7 @@ class LoggedInPresenter @Inject constructor(
pushService.ignoreRegistrationError(matrixClient.sessionId)
}.collectAsState(initial = false)
val pusherRegistrationState = remember<MutableState<AsyncData<Unit>>> { mutableStateOf(AsyncData.Uninitialized) }
LaunchedEffect(Unit) { preloadAccountManagementUrl() }
LaunchedEffect(Unit) {
sessionVerificationService.sessionVerifiedStatus
.onEach { sessionVerifiedStatus ->
@ -202,4 +205,9 @@ class LoggedInPresenter @Inject constructor(
analyticsService.capture(CryptoSessionStateChange(changeRecoveryState, changeVerificationState))
}
}
private fun CoroutineScope.preloadAccountManagementUrl() = launch {
matrixClient.getAccountManagementUrl(AccountManagementAction.Profile)
matrixClient.getAccountManagementUrl(AccountManagementAction.SessionsList)
}
}

View file

@ -5,12 +5,11 @@
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.appnav.loggedin
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties
@ -19,6 +18,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncState
@ -45,8 +45,8 @@ import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@ -58,10 +58,7 @@ class LoggedInPresenterTest {
@Test
fun `present - initial state`() = runTest {
val presenter = createLoggedInPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
createLoggedInPresenter().test {
val initialState = awaitItem()
assertThat(initialState.showSyncSpinner).isFalse()
assertThat(initialState.pusherRegistrationState.isUninitialized()).isTrue()
@ -69,13 +66,32 @@ class LoggedInPresenterTest {
}
}
@Test
fun `present - ensure that account urls are preloaded`() = runTest {
val accountManagementUrlResult = lambdaRecorder<AccountManagementAction?, Result<String?>> { Result.success("aUrl") }
val matrixClient = FakeMatrixClient(
accountManagementUrlResult = accountManagementUrlResult,
)
createLoggedInPresenter(
matrixClient = matrixClient,
).test {
awaitItem()
advanceUntilIdle()
accountManagementUrlResult.assertions().isCalledExactly(2)
.withSequence(
listOf(value(AccountManagementAction.Profile)),
listOf(value(AccountManagementAction.SessionsList)),
)
}
}
@Test
fun `present - show sync spinner`() = runTest {
val roomListService = FakeRoomListService()
val presenter = createLoggedInPresenter(roomListService, SyncState.Running)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
createLoggedInPresenter(
syncState = SyncState.Running,
matrixClient = FakeMatrixClient(roomListService = roomListService),
).test {
val initialState = awaitItem()
assertThat(initialState.showSyncSpinner).isFalse()
roomListService.postSyncIndicator(RoomListService.SyncIndicator.Show)
@ -92,18 +108,18 @@ class LoggedInPresenterTest {
val verificationService = FakeSessionVerificationService()
val encryptionService = FakeEncryptionService()
val buildMeta = aBuildMeta()
val presenter = LoggedInPresenter(
matrixClient = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService),
LoggedInPresenter(
matrixClient = FakeMatrixClient(
roomListService = roomListService,
encryptionService = encryptionService,
),
syncService = FakeSyncService(initialSyncState = SyncState.Running),
pushService = FakePushService(),
sessionVerificationService = verificationService,
analyticsService = analyticsService,
encryptionService = encryptionService,
buildMeta = buildMeta,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
encryptionService.emitRecoveryState(RecoveryState.UNKNOWN)
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
verificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
@ -129,13 +145,10 @@ class LoggedInPresenterTest {
val verificationService = FakeSessionVerificationService(
initialSessionVerifiedStatus = SessionVerifiedStatus.NotVerified
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = verificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.AccountNotVerified::class.java)
@ -155,13 +168,13 @@ class LoggedInPresenterTest {
val pushService = createFakePushService(
registerWithLambda = lambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
matrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
),
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions()
@ -188,13 +201,13 @@ class LoggedInPresenterTest {
val pushService = createFakePushService(
registerWithLambda = lambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
matrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
),
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.isFailure()).isTrue()
lambda.assertions()
@ -233,13 +246,13 @@ class LoggedInPresenterTest {
currentPushProvider = { pushProvider },
registerWithLambda = lambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
matrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
),
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions()
@ -277,13 +290,13 @@ class LoggedInPresenterTest {
currentPushProvider = { pushProvider },
registerWithLambda = lambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
matrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
),
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions()
@ -317,13 +330,10 @@ class LoggedInPresenterTest {
currentPushProvider = { pushProvider },
registerWithLambda = lambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java)
@ -345,13 +355,10 @@ class LoggedInPresenterTest {
registerWithLambda = lambda,
setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.NoProvidersAvailable::class.java)
@ -394,13 +401,10 @@ class LoggedInPresenterTest {
registerWithLambda = lambda,
selectPushProviderLambda = selectPushProviderLambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.errorOrNull())
.isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java)
@ -445,13 +449,13 @@ class LoggedInPresenterTest {
pushProvider1 = pushProvider1,
registerWithLambda = lambda,
)
val presenter = createLoggedInPresenter(
createLoggedInPresenter(
pushService = pushService,
sessionVerificationService = sessionVerificationService,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
matrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
),
).test {
val finalState = awaitFirstItem()
assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue()
lambda.assertions().isCalledOnce()
@ -505,10 +509,9 @@ class LoggedInPresenterTest {
currentSlidingSyncVersionLambda = { Result.success(SlidingSyncVersion.Proxy) },
availableSlidingSyncVersionsLambda = { Result.success(listOf(SlidingSyncVersion.Native)) },
)
val presenter = createLoggedInPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
createLoggedInPresenter(
matrixClient = matrixClient,
).test {
val initialState = awaitItem()
assertThat(initialState.forceNativeSlidingSyncMigration).isFalse()
@ -525,13 +528,14 @@ class LoggedInPresenterTest {
assertThat(userInitiated).isTrue()
assertThat(ignoreSdkError).isTrue()
}
val matrixClient = FakeMatrixClient().apply {
val matrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
).apply {
this.logoutLambda = logoutLambda
}
val presenter = createLoggedInPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
createLoggedInPresenter(
matrixClient = matrixClient,
).test {
val initialState = awaitItem()
initialState.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync)
@ -547,14 +551,15 @@ class LoggedInPresenterTest {
return awaitItem()
}
private fun TestScope.createLoggedInPresenter(
roomListService: RoomListService = FakeRoomListService(),
private fun createLoggedInPresenter(
syncState: SyncState = SyncState.Running,
analyticsService: AnalyticsService = FakeAnalyticsService(),
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
encryptionService: EncryptionService = FakeEncryptionService(),
pushService: PushService = FakePushService(),
matrixClient: MatrixClient = FakeMatrixClient(roomListService = roomListService),
matrixClient: MatrixClient = FakeMatrixClient(
accountManagementUrlResult = { Result.success(null) },
),
buildMeta: BuildMeta = aBuildMeta(),
): LoggedInPresenter {
return LoggedInPresenter(

View file

@ -166,14 +166,17 @@ allprojects {
// Register quality check tasks.
tasks.register("runQualityChecks") {
dependsOn(":tests:konsist:testDebugUnitTest")
dependsOn(":app:lintGplayDebug")
project.subprojects {
// For some reason `findByName("lint")` doesn't work
tasks.findByPath("$path:lint")?.let { dependsOn(it) }
tasks.findByPath("$path:lintDebug")?.let { dependsOn(it) }
tasks.findByName("detekt")?.let { dependsOn(it) }
tasks.findByName("ktlintCheck")?.let { dependsOn(it) }
// tasks.findByName("buildHealth")?.let { dependsOn(it) }
}
dependsOn(":app:knitCheck")
// Make sure all checks run even if some fail
gradle.startParameter.isContinueOnFailure = true
}
// Make sure to delete old screenshots before recording new ones

View file

@ -100,7 +100,7 @@ internal fun IncomingCallScreen(
ActionButton(
size = 64.dp,
onClick = { onAnswer(notificationData) },
icon = CompoundIcons.VoiceCall(),
icon = CompoundIcons.VoiceCallSolid(),
title = stringResource(CommonStrings.action_accept),
backgroundColor = ElementTheme.colors.iconSuccessPrimary,
borderColor = ElementTheme.colors.borderSuccessSubtle

View file

@ -136,7 +136,7 @@ private fun ColumnScope.Buttons(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = CompoundIcons.Error(),
imageVector = CompoundIcons.ErrorSolid(),
tint = ElementTheme.colors.iconCriticalPrimary,
contentDescription = null,
modifier = Modifier.size(24.dp)

View file

@ -391,7 +391,7 @@ private fun VerifiedUserSendFailureView(
modifier = modifier
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 8.dp),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Error())),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ErrorSolid())),
trailingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChevronRight())),
headlineContent = {
Text(

View file

@ -68,7 +68,7 @@ fun TimelineEventTimestampView(
val isVerifiedUserSendFailure = event.localSendState is LocalEventSendState.Failed.VerifiedUser
Spacer(modifier = Modifier.width(2.dp))
Icon(
imageVector = CompoundIcons.Error(),
imageVector = CompoundIcons.ErrorSolid(),
contentDescription = stringResource(id = CommonStrings.common_sending_failed),
tint = tint,
modifier = Modifier

View file

@ -31,7 +31,7 @@ fun TimelineItemLegacyCallInviteView(
modifier = modifier,
) {
Icon(
imageVector = CompoundIcons.VoiceCall(),
imageVector = CompoundIcons.VoiceCallSolid(),
contentDescription = null,
tint = ElementTheme.colors.iconSecondary,
)

View file

@ -83,19 +83,19 @@ class NotificationSettingsPresenter @Inject constructor(
}
}
}
// List of Distributor names
val distributorNames = remember {
distributors.map { it.second.name }.toImmutableList()
// List of Distributors
val availableDistributors = remember {
distributors.map { it.second }.toImmutableList()
}
var currentDistributorName by remember { mutableStateOf<AsyncData<String>>(AsyncData.Uninitialized) }
var currentDistributor by remember { mutableStateOf<AsyncData<Distributor>>(AsyncData.Uninitialized) }
var refreshPushProvider by remember { mutableIntStateOf(0) }
LaunchedEffect(refreshPushProvider) {
val p = pushService.getCurrentPushProvider()
val name = p?.getCurrentDistributor(matrixClient.sessionId)?.name
currentDistributorName = if (name != null) {
AsyncData.Success(name)
val distributor = p?.getCurrentDistributor(matrixClient.sessionId)
currentDistributor = if (distributor != null) {
AsyncData.Success(distributor)
} else {
AsyncData.Failure(Exception("Failed to get current push provider"))
}
@ -108,24 +108,23 @@ class NotificationSettingsPresenter @Inject constructor(
) = launch {
showChangePushProviderDialog = false
data ?: return@launch
// No op if the value is the same.
if (data.second.name == currentDistributorName.dataOrNull()) return@launch
currentDistributorName = AsyncData.Loading(currentDistributorName.dataOrNull())
data.let { (pushProvider, distributor) ->
pushService.registerWith(
matrixClient = matrixClient,
pushProvider = pushProvider,
distributor = distributor
val (pushProvider, distributor) = data
// No op if the distributor is the same.
if (distributor == currentDistributor.dataOrNull()) return@launch
currentDistributor = AsyncData.Loading(currentDistributor.dataOrNull())
pushService.registerWith(
matrixClient = matrixClient,
pushProvider = pushProvider,
distributor = distributor
)
.fold(
{
refreshPushProvider++
},
{
currentDistributor = AsyncData.Failure(it)
}
)
.fold(
{
refreshPushProvider++
},
{
currentDistributorName = AsyncData.Failure(it)
}
)
}
}
fun handleEvents(event: NotificationSettingsEvents) {
@ -162,8 +161,8 @@ class NotificationSettingsPresenter @Inject constructor(
appNotificationsEnabled = appNotificationsEnabled.value
),
changeNotificationSettingAction = changeNotificationSettingAction.value,
currentPushDistributor = currentDistributorName,
availablePushDistributors = distributorNames,
currentPushDistributor = currentDistributor,
availablePushDistributors = availableDistributors,
showChangePushProviderDialog = showChangePushProviderDialog,
fullScreenIntentPermissionsState = key(refreshFullScreenIntentSettings) { fullScreenIntentPermissionsPresenter.present() },
eventSink = ::handleEvents

View file

@ -12,6 +12,7 @@ import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.pushproviders.api.Distributor
import kotlinx.collections.immutable.ImmutableList
@Immutable
@ -19,8 +20,8 @@ data class NotificationSettingsState(
val matrixSettings: MatrixSettings,
val appSettings: AppSettings,
val changeNotificationSettingAction: AsyncAction<Unit>,
val currentPushDistributor: AsyncData<String>,
val availablePushDistributors: ImmutableList<String>,
val currentPushDistributor: AsyncData<Distributor>,
val availablePushDistributors: ImmutableList<Distributor>,
val showChangePushProviderDialog: Boolean,
val fullScreenIntentPermissionsState: FullScreenIntentPermissionsState,
val eventSink: (NotificationSettingsEvents) -> Unit,

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.pushproviders.api.Distributor
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -24,11 +25,19 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider<Notifica
aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading),
aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
aValidNotificationSettingsState(
availablePushDistributors = listOf("Firebase"),
availablePushDistributors = listOf(aDistributor("Firebase")),
changeNotificationSettingAction = AsyncAction.Failure(Throwable("error")),
),
aValidNotificationSettingsState(availablePushDistributors = listOf("Firebase")),
aValidNotificationSettingsState(availablePushDistributors = listOf(aDistributor("Firebase"))),
aValidNotificationSettingsState(showChangePushProviderDialog = true),
aValidNotificationSettingsState(
availablePushDistributors = listOf(
aDistributor("Firebase"),
aDistributor("ntfy", "app.id1"),
aDistributor("ntfy", "app.id2"),
),
showChangePushProviderDialog = true,
),
aValidNotificationSettingsState(currentPushDistributor = AsyncData.Loading()),
aValidNotificationSettingsState(currentPushDistributor = AsyncData.Failure(Exception("Failed to change distributor"))),
aInvalidNotificationSettingsState(),
@ -45,8 +54,11 @@ fun aValidNotificationSettingsState(
inviteForMeNotificationsEnabled: Boolean = true,
systemNotificationsEnabled: Boolean = true,
appNotificationEnabled: Boolean = true,
currentPushDistributor: AsyncData<String> = AsyncData.Success("Firebase"),
availablePushDistributors: List<String> = listOf("Firebase", "ntfy"),
currentPushDistributor: AsyncData<Distributor> = AsyncData.Success(aDistributor("Firebase")),
availablePushDistributors: List<Distributor> = listOf(
aDistributor("Firebase"),
aDistributor("ntfy"),
),
showChangePushProviderDialog: Boolean = false,
fullScreenIntentPermissionsState: FullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
eventSink: (NotificationSettingsEvents) -> Unit = {},
@ -88,3 +100,11 @@ fun aInvalidNotificationSettingsState(
fullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
eventSink = eventSink,
)
fun aDistributor(
name: String = "Name",
value: String = "$name Value",
) = Distributor(
value = value,
name = name,
)

View file

@ -132,7 +132,7 @@ private fun NotificationSettingsContentView(
if (!state.fullScreenIntentPermissionsState.permissionGranted) {
PreferenceCategory {
PreferenceText(
icon = CompoundIcons.VoiceCall(),
icon = CompoundIcons.VoiceCallSolid(),
title = stringResource(id = R.string.full_screen_intent_banner_title),
subtitle = stringResource(R.string.full_screen_intent_banner_message),
onClick = {
@ -206,7 +206,7 @@ private fun NotificationSettingsContentView(
stringResource(id = CommonStrings.common_error)
)
is AsyncData.Success -> ListItemContent.Text(
state.currentPushDistributor.dataOrNull() ?: ""
state.currentPushDistributor.dataOrNull()?.name ?: ""
)
},
onClick = {
@ -219,8 +219,14 @@ private fun NotificationSettingsContentView(
if (state.showChangePushProviderDialog) {
SingleSelectionDialog(
title = stringResource(id = R.string.screen_advanced_settings_choose_distributor_dialog_title_android),
options = state.availablePushDistributors.map {
ListOption(title = it)
options = state.availablePushDistributors.map { distributor ->
// If there are several distributors with the same name, use the full name
val title = if (state.availablePushDistributors.count { it.name == distributor.name } > 1) {
distributor.fullName
} else {
distributor.name
}
ListOption(title = title)
}.toImmutableList(),
initialSelection = state.availablePushDistributors.indexOf(state.currentPushDistributor.dataOrNull()),
onSelectOption = { index ->

View file

@ -240,8 +240,11 @@ class NotificationSettingsPresenterTest {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
assertThat(initialState.currentPushDistributor).isEqualTo(AsyncData.Success("aDistributorName0"))
assertThat(initialState.availablePushDistributors).containsExactly("aDistributorName0", "aDistributorName1")
assertThat(initialState.currentPushDistributor).isEqualTo(AsyncData.Success(Distributor(value = "aDistributorValue0", name = "aDistributorName0")))
assertThat(initialState.availablePushDistributors).containsExactly(
Distributor(value = "aDistributorValue0", name = "aDistributorName0"),
Distributor(value = "aDistributorValue1", name = "aDistributorName1"),
)
initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider)
val withDialog = awaitItem()
assertThat(withDialog.showChangePushProviderDialog).isTrue()
@ -257,11 +260,35 @@ class NotificationSettingsPresenterTest {
assertThat(withNewProvider.currentPushDistributor).isInstanceOf(AsyncData.Loading::class.java)
skipItems(1)
val lastItem = awaitItem()
assertThat(lastItem.currentPushDistributor).isEqualTo(AsyncData.Success("aDistributorName1"))
assertThat(lastItem.currentPushDistributor).isEqualTo(AsyncData.Success(Distributor(value = "aDistributorValue1", name = "aDistributorName1")))
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - change push provider to the same value is no op`() = runTest {
val presenter = createNotificationSettingsPresenter(
pushService = createFakePushService(),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitLastSequentialItem()
assertThat(initialState.currentPushDistributor).isEqualTo(AsyncData.Success(Distributor(value = "aDistributorValue0", name = "aDistributorName0")))
assertThat(initialState.availablePushDistributors).containsExactly(
Distributor(value = "aDistributorValue0", name = "aDistributorName0"),
Distributor(value = "aDistributorValue1", name = "aDistributorName1"),
)
initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider)
assertThat(awaitItem().showChangePushProviderDialog).isTrue()
// Choose the same value (index 0)
initialState.eventSink(NotificationSettingsEvents.SetPushProvider(0))
val withNewProvider = awaitItem()
assertThat(withNewProvider.showChangePushProviderDialog).isFalse()
expectNoEvents()
}
}
@Test
fun `present - RefreshSystemNotificationsEnabled also refreshes fullScreenIntentState`() = runTest {
var lambdaResult = aFullScreenIntentPermissionsState(permissionGranted = false)

View file

@ -267,7 +267,7 @@ class NotificationSettingsViewTest {
state = aValidNotificationSettingsState(
eventSink = eventsRecorder,
showChangePushProviderDialog = true,
availablePushDistributors = listOf("P1", "P2")
availablePushDistributors = listOf(aDistributor("P1"), aDistributor("P2"))
),
)
rule.onNodeWithText("P2").performClick()

View file

@ -5,12 +5,11 @@
* Please see LICENSE files in the repository root for full details.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package io.element.android.features.preferences.impl.root
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider
@ -18,6 +17,7 @@ import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_USER_NAME
@ -27,6 +27,10 @@ import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@ -37,11 +41,16 @@ class PreferencesRootPresenterTest {
@Test
fun `present - initial state`() = runTest {
val matrixClient = FakeMatrixClient(canDeactivateAccountResult = { true })
val presenter = createPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val accountManagementUrlResult = lambdaRecorder<AccountManagementAction?, Result<String?>> { action ->
Result.success("$action url")
}
val matrixClient = FakeMatrixClient(
canDeactivateAccountResult = { true },
accountManagementUrlResult = accountManagementUrlResult,
)
createPresenter(
matrixClient = matrixClient,
).test {
val initialState = awaitItem()
assertThat(initialState.myUser).isEqualTo(
MatrixUser(
@ -71,19 +80,26 @@ class PreferencesRootPresenterTest {
assertThat(loadedState.canDeactivateAccount).isTrue()
assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState())
assertThat(loadedState.snackbarMessage).isNull()
skipItems(1)
val finalState = awaitItem()
accountManagementUrlResult.assertions().isCalledExactly(2)
.withSequence(
listOf(value(AccountManagementAction.Profile)),
listOf(value(AccountManagementAction.SessionsList)),
)
assertThat(finalState.accountManagementUrl).isEqualTo("Profile url")
assertThat(finalState.devicesManagementUrl).isEqualTo("SessionsList url")
}
}
@Test
fun `present - can deactivate account is false if the Matrix client say so`() = runTest {
val presenter = createPresenter(
createPresenter(
matrixClient = FakeMatrixClient(
canDeactivateAccountResult = { false }
)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
canDeactivateAccountResult = { false },
accountManagementUrlResult = { Result.success(null) },
),
).test {
val loadedState = awaitFirstItem()
assertThat(loadedState.canDeactivateAccount).isFalse()
}
@ -91,12 +107,13 @@ class PreferencesRootPresenterTest {
@Test
fun `present - developer settings is hidden by default in release builds`() = runTest {
val presenter = createPresenter(
createPresenter(
matrixClient = FakeMatrixClient(
canDeactivateAccountResult = { true },
accountManagementUrlResult = { Result.success(null) },
),
showDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.RELEASE))
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
val loadedState = awaitFirstItem()
assertThat(loadedState.showDeveloperSettings).isFalse()
}
@ -104,12 +121,13 @@ class PreferencesRootPresenterTest {
@Test
fun `present - developer settings can be enabled in release builds`() = runTest {
val presenter = createPresenter(
createPresenter(
matrixClient = FakeMatrixClient(
canDeactivateAccountResult = { true },
accountManagementUrlResult = { Result.success(null) },
),
showDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.RELEASE))
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
).test {
val loadedState = awaitFirstItem()
repeat(times = ShowDeveloperSettingsProvider.DEVELOPER_SETTINGS_COUNTER) {
assertThat(loadedState.showDeveloperSettings).isFalse()
@ -125,7 +143,7 @@ class PreferencesRootPresenterTest {
}
private fun createPresenter(
matrixClient: FakeMatrixClient = FakeMatrixClient(canDeactivateAccountResult = { true }),
matrixClient: FakeMatrixClient = FakeMatrixClient(),
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
showDeveloperSettingsProvider: ShowDeveloperSettingsProvider = ShowDeveloperSettingsProvider(aBuildMeta(BuildType.DEBUG)),
) = PreferencesRootPresenter(

View file

@ -17,10 +17,10 @@ core = "1.15.0"
# due to the DefaultMigrationStore not behaving as expected.
# Stick to 1.0.0 for now, and ensure that this scenario cannot be reproduced when upgrading the version.
datastore = "1.0.0"
constraintlayout = "2.2.0"
constraintlayout_compose = "1.1.0"
constraintlayout = "2.2.1"
constraintlayout_compose = "1.1.1"
lifecycle = "2.8.7"
activity = "1.10.0"
activity = "1.10.1"
media3 = "1.5.1"
camera = "1.4.1"
@ -88,7 +88,7 @@ androidx_corektx = { module = "androidx.core:core-ktx", version.ref = "core" }
androidx_annotationjvm = "androidx.annotation:annotation-jvm:1.9.1"
androidx_datastore_preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
androidx_datastore_datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
androidx_exifinterface = "androidx.exifinterface:exifinterface:1.3.7"
androidx_exifinterface = "androidx.exifinterface:exifinterface:1.4.0"
androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayout_compose" }
androidx_camera_lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
@ -163,7 +163,7 @@ coil = { module = "io.coil-kt:coil", version.ref = "coil" }
coil_compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
coil_gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" }
coil_test = { module = "io.coil-kt:coil-test", version.ref = "coil" }
compound = { module = "io.element.android:compound-android", version = "0.2.0" }
compound = { module = "io.element.android:compound-android", version = "25.2.26" }
datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" }
serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" }
kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.8"
@ -195,7 +195,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.3.0"
# Analytics
posthog = "com.posthog:posthog-android:3.11.3"
sentry = "io.sentry:sentry-android:8.2.0"
sentry = "io.sentry:sentry-android:8.3.0"
# main branch can be tested replacing the version with main-SNAPSHOT
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0"

View file

@ -93,7 +93,7 @@ internal fun MatrixBadgeAtomNegativePreview() = ElementPreview {
MatrixBadgeAtom.View(
MatrixBadgeAtom.MatrixBadgeData(
text = "Not trusted",
icon = CompoundIcons.Error(),
icon = CompoundIcons.ErrorSolid(),
type = MatrixBadgeAtom.Type.Negative,
)
)

View file

@ -118,7 +118,7 @@ private fun InformativeAnnouncement(
AnnouncementSurface(modifier = modifier) {
Row {
Icon(
imageVector = if (isError) CompoundIcons.Error() else CompoundIcons.Info(),
imageVector = if (isError) CompoundIcons.ErrorSolid() else CompoundIcons.Info(),
tint = if (isError) ElementTheme.colors.iconCriticalPrimary else ElementTheme.colors.iconPrimary,
contentDescription = null,
)

View file

@ -105,7 +105,7 @@ object BigIcon {
val icon = when (style) {
is Style.Default -> style.vectorIcon
Style.Alert,
Style.AlertSolid -> CompoundIcons.Error()
Style.AlertSolid -> CompoundIcons.ErrorSolid()
Style.Success,
Style.SuccessSolid -> CompoundIcons.CheckCircleSolid()
}

View file

@ -259,7 +259,7 @@ private fun SupportingTextLayout(validity: TextFieldValidity?, supportingText: S
when (validity) {
TextFieldValidity.Invalid -> {
Icon(
imageVector = CompoundIcons.Error(),
imageVector = CompoundIcons.ErrorSolid(),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = ElementTheme.colors.iconCriticalPrimary

View file

@ -72,11 +72,11 @@ class FakeMatrixClient(
private val syncService: FakeSyncService = FakeSyncService(),
private val encryptionService: FakeEncryptionService = FakeEncryptionService(),
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
private val accountManagementUrlString: Result<String?> = Result.success(null),
private val accountManagementUrlResult: (AccountManagementAction?) -> Result<String?> = { lambdaError() },
private val resolveRoomAliasResult: (RoomAlias) -> Result<Optional<ResolvedRoomAlias>> = {
Result.success(
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
)
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
)
},
private val getRoomPreviewResult: (RoomIdOrAlias, List<String>) -> Result<RoomPreview> = { _, _ -> Result.failure(AN_EXCEPTION) },
private val clearCacheLambda: () -> Unit = { lambdaError() },
@ -190,8 +190,8 @@ class FakeMatrixClient(
return Result.success(result)
}
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> {
return accountManagementUrlString
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> = simulateLongTask {
accountManagementUrlResult(action)
}
override suspend fun uploadMedia(

View file

@ -68,7 +68,7 @@ fun UnresolvedUserRow(
.padding(top = 3.dp)
) {
Icon(
imageVector = CompoundIcons.Error(),
imageVector = CompoundIcons.ErrorSolid(),
contentDescription = null,
modifier = Modifier
.size(18.dp)

View file

@ -18,4 +18,6 @@ package io.element.android.libraries.pushproviders.api
data class Distributor(
val value: String,
val name: String,
)
) {
val fullName = "$name ($value)"
}

View file

@ -98,7 +98,7 @@ private fun ColumnScope.TroubleshootTestView(
Icon(
contentDescription = null,
modifier = Modifier.size(24.dp),
imageVector = CompoundIcons.Error(),
imageVector = CompoundIcons.ErrorSolid(),
tint = ElementTheme.colors.textCriticalPrimary
)
}

View file

@ -9,6 +9,7 @@
<string name="a11y_pause">"Setter på pause"</string>
<string name="a11y_pin_field">"PIN-felt"</string>
<string name="a11y_play">"Spill av"</string>
<string name="a11y_poll">"Avstemning"</string>
<string name="a11y_poll_end">"Avsluttet avstemning"</string>
<string name="a11y_react_with">"Reager med %1$s"</string>
<string name="a11y_react_with_other_emojis">"Reager med andre emojier"</string>
@ -143,14 +144,17 @@
<string name="common_encryption_enabled">"Kryptering aktivert"</string>
<string name="common_enter_your_pin">"Skriv inn PIN-koden din"</string>
<string name="common_error">"Feil"</string>
<string name="common_everyone">"Alle"</string>
<string name="common_failed">"Mislyktes"</string>
<string name="common_favourite">"Favoritt"</string>
<string name="common_file">"Fil"</string>
<string name="common_file_saved_on_disk_android">"Fil lagret i Nedlastinger"</string>
<string name="common_forward_message">"Videresend melding"</string>
<string name="common_gif">"GIF"</string>
<string name="common_image">"Bilde"</string>
<string name="common_in_reply_to">"Som svar på %1$s"</string>
<string name="common_install_apk_android">"Installer APK"</string>
<string name="common_invite_unknown_profile">"Finner ikke denne Matrix-IDen, så invitasjonen blir kanskje ikke mottatt."</string>
<string name="common_leaving_room">"Forlater rommet"</string>
<string name="common_light">"Lys"</string>
<string name="common_link_copied_to_clipboard">"Lenke kopiert til utklippstavlen"</string>
@ -220,6 +224,7 @@
<string name="common_syncing">"Synkroniserer"</string>
<string name="common_system">"System"</string>
<string name="common_text">"Tekst"</string>
<string name="common_third_party_notices">"Varsler fra tredjeparter"</string>
<string name="common_thread">"Tråd"</string>
<string name="common_topic">"Emne"</string>
<string name="common_topic_placeholder">"Hva er dette rommet for?"</string>
@ -250,7 +255,11 @@
<string name="dialog_unsaved_changes_description_android">"Endringene dine er ikke lagret. Er du sikker på at du vil gå tilbake?"</string>
<string name="dialog_unsaved_changes_title">"Lagre endringer?"</string>
<string name="error_failed_creating_the_permalink">"Opprettelse av permalenken mislyktes"</string>
<string name="error_failed_loading_map">"%1$s kunne ikke laste inn kartet. Prøv igjen senere."</string>
<string name="error_failed_loading_messages">"Kunne ikke laste inn meldinger"</string>
<string name="error_failed_locating_user">"%1$s fikk ikke tilgang til lokasjonen din. Vennligst prøv igjen senere."</string>
<string name="error_missing_location_auth_android">"%1$s har ikke tilgang til lokasjonen din. Du kan aktivere tilgang i Innstillinger."</string>
<string name="error_missing_location_rationale_android">"%1$s har ikke tilgang til lokasjonen din. Aktiver tilgang nedenfor."</string>
<string name="error_some_messages_have_not_been_sent">"Noen meldinger er ikke sendt"</string>
<string name="error_unknown">"Beklager, det oppstod en feil"</string>
<string name="event_shield_reason_sent_in_clear">"Ikke kryptert."</string>
@ -258,13 +267,18 @@
<string name="invite_friends_text">"Hei, snakk med meg på %1$s: %2$s"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<string name="screen_media_picker_error_failed_selection">"Kunne ikke velge medium, prøv igjen."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Kunne ikke behandle media for opplasting, vennligst prøv igjen."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Opplasting av media mislyktes, vennligst prøv igjen."</string>
<string name="screen_room_error_failed_processing_media">"Kunne ikke behandle media for opplasting, vennligst prøv igjen."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Kunne ikke behandle medier for opplasting, vennligst prøv igjen."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Opplasting av medier mislyktes, vennligst prøv igjen."</string>
<string name="screen_room_error_failed_processing_media">"Kunne ikke behandle medier for opplasting, vennligst prøv igjen."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Kunne ikke hente brukerdetaljer"</string>
<string name="screen_share_location_title">"Del lokasjon"</string>
<string name="screen_share_my_location_action">"Del min lokasjon"</string>
<string name="screen_share_open_apple_maps">"Åpne i Apple Maps"</string>
<string name="screen_share_open_google_maps">"Åpne i Google Maps"</string>
<string name="screen_share_open_osm_maps">"Åpne i OpenStreetMap"</string>
<string name="screen_share_this_location_action">"Del denne lokasjonen"</string>
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Meldingen ble ikke sendt fordi %1$s ikke har verifisert alle enheter."</string>
<string name="screen_view_location_title">"Lokasjon"</string>
<string name="settings_version_number">"Versjon: %1$s (%2$s)"</string>
<string name="test_language_identifier">"en"</string>
</resources>

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:846bef7dc54b4d051bc0cd6ea90d66ba406a11d7dda36455dbdf3ee74aab2aae
size 8379
oid sha256:30efa4ef62ce38f3599c1a6f2a99c9a36e5704a70c99d83670b5f2169a8648f3
size 8378

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6baed152f58ed5335d559b7e6bc78867147ddd0b8df63e1881875caf0c10f470
size 10486
oid sha256:e3c97dbafc4e1e74be95014f9e4f4e10f9b6516d98e89a149791d61c2e1c5ddf
size 10484

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a9d7f9f3ba4857862530f0f278186dfbaa347d589abca7e2baf5142b2740ede4
size 7999
oid sha256:a1a5ca1ae30e3b1c4c2b242b3527a3c09b09acb415ad11333ad4b1f07182cbed
size 8005

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:813cedb8cb525406a5d475a8b431bc94888f5952009e28f71f983a03198227db
size 10028
oid sha256:eae95926adb295182d13dbbebcf1650ab1bf816a9d5edeb908cbd8683fec0c66
size 10031

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4944bbdf4621ecfef659746bb7f3fa16d2a107269cbb60d057bf91c9360a1894
size 82388
oid sha256:255709a0c6b3ffe337e5a3ce657e350539d9278a2a1c7994ac40752a0bffecff
size 82449

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c76c6454e90e4e23ad7ea92d930eeb47c7a68f81dcc7035b358203cb47639f13
size 75518
oid sha256:82812d497f41b7e34d00cf2fadbde22115200539fcb25660dd9f9c9378093f0e
size 75610

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:686ef0bf0a5813e09f1afd7824d5aa909c914bebe553d03955a869673e8f9e16
size 66270
oid sha256:2f07a472c171966ad4dd9e8401a634b9c55a303e1ca4ab08cb44c32e17b1a128
size 66278

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c8f9f67b6bd11de3983dfdd76c3a72ceaf8d920daebb5e4e749bb74ab5d95dfa
size 58396
oid sha256:f5a4097408c093e0919e54bdacfe29f3901fe2db7b0984b72c2003499a6dba11
size 58400

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:480dcf4628c371f8df8b6a26f73f0a5ed45e9ca89baf9f4ab52bd896ed1cc4d7
size 77593
oid sha256:5834fcf87293fd1c8887fceebba0b0e77be27af06843d5dea62a35e4becd2aca
size 77581

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96333cfda74bc44b7f9f95eab383ab26857b07d68ed30255ccb1ef3ae1558a4c
size 78110
oid sha256:ccae537ee569b5c4450b1e238aaaf4570818d050a228347eb6b09ea10997135c
size 78100

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d783fa26d1b4a891a000f638a3d6b7e3df08390b9f800e3e4d123e76cfc274fd
size 82488
oid sha256:75eff760370d8cbbd0d2795a049f06e5f9be75030ef064f3a0e6548750e0166e
size 82468

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3aabc27a2531ecd79a01de70cbb149ff4c77fb9d37f3cefd5727a083c3edf455
size 42712
oid sha256:82d60ec6eaddaa3a9cc4d9d23bf5fe2e6debbf0b441ecafa377e27770d26ecdb
size 42697

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:adb68fcd10b66e37f774560e59991bf6488043d4e1b66e6b1f19a648c90a6333
size 7572
oid sha256:d175ae95fed4ac24e9b506678c4ee1e235c3c8405915498f7f97db0764e5470c
size 7571

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72dbe69b1952d3c2e00509bb1a5e0493baa30c0ef4e444f652f3fd7f81a8eccd
oid sha256:0db80941c8c981d3f66bc6cd9364f0f8fd5b1e7033f81da46cdffe478f97c748
size 6528

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72dbe69b1952d3c2e00509bb1a5e0493baa30c0ef4e444f652f3fd7f81a8eccd
oid sha256:0db80941c8c981d3f66bc6cd9364f0f8fd5b1e7033f81da46cdffe478f97c748
size 6528

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ae3884c6bfded545fda8dfadfc94cb4c17bf2df9556c4d33a931c3509b8a885
size 38429
oid sha256:f49822ac3f9a328dcbc9747e49ab3b3af74de42f81f833640232199d85d8d961
size 38428

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:daba14ee51067d64e9098815b1d187a89b405b0f11b22f7fa888106cfb388fa9
oid sha256:0c2187b3eb917901c6d9fac36ae44c28c7c3946a055cbf156aab3063c8981e4e
size 52055

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:878d81472018964e1217c36708e31469a62486e1a913da26fc5a209731c4f9a1
size 11014
oid sha256:57ee28833b587ec86f93c685d716f0cce94b60e0752de3fc1a1a3d7ac05cbdb6
size 11013

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72dbe69b1952d3c2e00509bb1a5e0493baa30c0ef4e444f652f3fd7f81a8eccd
oid sha256:0db80941c8c981d3f66bc6cd9364f0f8fd5b1e7033f81da46cdffe478f97c748
size 6528

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e8638e7c07500bddc9c67480685d2548f0b50772977fee7565fb92ae72eaefcb
size 7504
oid sha256:d174f1f8e77c9cd02506b487e410a7bcbf4d571f048f739d4f10582c72e682d0
size 7501

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:000dbaa58af2f75293be0a3fed993447275582abaa33088b3f9c533ac4e0fec6
size 6298
oid sha256:000c36468af4793e4956a4a2a2be1867afefa13140219e2dd8b17b1f7670b6de
size 6299

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:000dbaa58af2f75293be0a3fed993447275582abaa33088b3f9c533ac4e0fec6
size 6298
oid sha256:000c36468af4793e4956a4a2a2be1867afefa13140219e2dd8b17b1f7670b6de
size 6299

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:375315a6a917f013079bfaa0d96bb3b90f72d485c3c4ed7b30c3f86dd9277793
size 39184
oid sha256:ed09d7879d10d22bc90bf0757bc649436f90428347cc8df2780c05e4c1c95fe5
size 39183

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:618164f424e1b2d334051d7d55dac014d77ea00ac8f15ae0f62fdd0fa77705fe
oid sha256:8cec0dc2a2b0870c7ee43a0329f9e8b67b42e7e4f26f349543e8c5d5841830fd
size 53161

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b81f55a04ecf3d4c3f266d5e0b67413be226933816195307e830d081a2bbc286
oid sha256:5694d2ee8d939aa3cc5107b7601ff251481fc8226981208b603507d407dcb12a
size 10637

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:000dbaa58af2f75293be0a3fed993447275582abaa33088b3f9c533ac4e0fec6
size 6298
oid sha256:000c36468af4793e4956a4a2a2be1867afefa13140219e2dd8b17b1f7670b6de
size 6299

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:72855bb6717291b00777e21711c0a5c8a7049e7b09cac7684dee9ae28e5be9f8
size 29925
oid sha256:58a0ee838378b313643ce3ebf3ea5c12565ff3da5e4fb1edb16a795f1c1a645e
size 29964

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2fa0cc7a8cfa1e1726d0ec75186cfd99fd67e9d5b168b0c6be669e355af2a1d6
size 43007
oid sha256:583665542f91d2a2d4788ff5475e063ac6168a077c75fb675fa8e4f2ae6e9aad
size 43055

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79c69f5ca1c04d4e6544decea4150eb5f43bdde58070d1dfe57d0a4c94022dd5
size 58850
oid sha256:22928ada21a391607fbcf349a5e086e98a2acd8bd6b48ed33cc118b8b0fd0747
size 58898

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d99e10bf3d0322ed25d883aa80376b0f1bd92d8174a6623e099272b4002fbfc4
size 56880
oid sha256:9333e27748a0699fdec91fcd805373b20776237d0a84c1d9b84b07d24cdc3394
size 56956

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e6f28ae60d74769a903228a58ed97f5753ec6e9a054b8e94d39d6c83ecb363d5
size 58260
oid sha256:51ddfe120825a6c3d1bd64d4e5711e6499e8c8b99d2e341352669b1c10d21458
size 58336

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2e987390f164dcacfafd455a3ccd3f4ae9af7d8e057d91772670d28c6f6e6310
size 56056
oid sha256:19aebb87ad9b9cc7808d63fe2c540428ebde6efef52e871320abd6afe4be0367
size 56105

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5b420340d9471294fce2458149f14c55df979060b248dba7cdd8bacfe71a39da
size 30808
oid sha256:67d05d7bdd0420627263d8bce97c871bcd44904a1652d6f475e9585c61883846
size 30875

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:05e71ef5a6fa821961c2a78885bab91eeeb783bac1fdaa495f32fd4173ecc615
size 43621
oid sha256:69c5790e896024624698b386b00803eb2d21627cf47024661dcf5cea81e0b6ac
size 43681

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cca307360497a2a298bb9968678c35afb44cdc68e2dd46a350868833bf770464
size 59968
oid sha256:5e06262926945b00d5625d85a662c110b1e6ce99689a89230e4c85fddf4009ba
size 60029

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9426c57dc59d8341087b3800a40847e63b8e6ee3c4e65cfe1712b3f5fa884618
size 58696
oid sha256:153dea4f37cf4ff00f61df87a71229704daa4df69d34fc6072ddfcc13ff58606
size 58777

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6527ca9d39895038bf0fec9cb9c1426f95db9f4b9460eff699c34bb59d11aaf8
size 60122
oid sha256:eedf66a56831cd3eb577abf89c20062caf109abb8c202d7bc925e7d2fa8c2a58
size 60193

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c5d4014d2701847fadd4e163c26e7ba220577c4b1e10ebd35c95b1c6aa81507b
size 57814
oid sha256:dd57076708a9a9100b0e59df7bae44513fe811ec4091cb9a7b9c5b35d3f2874b
size 57870

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3c89ad3af16b43b6798c30077651b7c9eefd0059d847f5ff643877a2d3d3403
size 17025
oid sha256:32e129c360deb12a615861c12ee2505c6e6250cbee681d4d60e58d3b0dcb492b
size 17022

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:30c89188415418f6015349fe9371792052f8c171272ce7ccb45352e79b15774f
size 20192
oid sha256:3169e88f2369e5f4d6cc8d5055af99e589c3a91bcd166acc246614a1206afb7b
size 20196

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a67b688327c3507e0072fad948b62c19acfb454aa05ac42832ee1a980c725492
size 20174
oid sha256:ff518448cacd57889acee5c330f251fa20ff7b9261d4538f89818acc217b1c6a
size 20186

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9be8c0f212d2ba8d1e4746038280114f50367f078e5ef63ae4e5faa730552630
size 15629
oid sha256:afd1edb1c5bd6e04ffcfe583697d24be3a18e778da9e4f991f90f3bc2da78e33
size 15637

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e04c7204d560370bcd2ed9bf775a892b65f22bd395f7079375e963cd46cce8ec
oid sha256:60a4b7d344da0151ed6b4153ec527d063233b7a6824264afd73c73af9d5a6586
size 18710

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9824bb57bbe04b48d5a2750a56a9cd36cd32bf4a4a2ebf9dd19c213e1cfb9d6
size 18565
oid sha256:5f592e046b694f20261ac2b154b50f766ecf3228dd69525eceae35fde9e7055c
size 18566

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f19636ec23d81de10c454b3a0f4c957b999b0fd706363854eb81b62d3072ebcd
size 25781
oid sha256:f7e9fe54b399dde0a7831ff37309c8a9d6acb5a369d54e9583892b4e2dc59c02
size 25779

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0838f9b076ed75cb55ea1119aa097401314bf175ead2c34ee84d7cefb65f38c6
size 19851
oid sha256:208e9d9f42a929c0bdb280c4442faf0211f43d3d350c48246b20ca3256a11690
size 19846

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcd340b12fb26fed072d336f68ce6fde749025b0043866be0de0ef507dab49c5
size 26671
oid sha256:f9988f9337ef201957b5eaac5277cb3d1243a2396c57bd3df725b692693fa482
size 26666

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e86339de00f7e6d44c1d36004c79a9c077d7d99d55ba01da8ea9e4d3b4567367
size 53085
oid sha256:bfde1df500000d69e1014077b973cfe7c4a5130419184d19d794bd7a975e67bb
size 53080

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8554336730b48bb509fc60fc6a7e8eb5cc21cc8e08bb2eee87a4e719b26c13f8
size 41765
oid sha256:0460a8404ecb789ae10223a4d948e7f93567f041342a2398b354aca39143276c
size 41781

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9f8d6270b9db5463649ce357463cec93945d924bebaeb7da14bfa2936088620
size 24735
oid sha256:e1f3d513a10202d05a3cbba270e1a5d63bbaceb74bf968d21aef01fb92c9e425
size 24730

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b96b798e72a139710e318a2eb5203078654a1b39126bc07eb46bc0cf5d65114
size 18783
oid sha256:fa26d53e0373f886180c38c7398597737dd808ef98e658a5f98e0fc74f150f36
size 18764

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6a1ce3733e36875756923872eb2f862c9248ad25af5189771ec29ccf3f8d15cd
size 25121
oid sha256:462c1055336d5cfe8342d87335a3ec89f57c5d1efb4e81da8b928a390acc1a59
size 25104

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:baa7c2ce997853e150eeb0193798af8d2ec1084b1791da579b2de6544e07d06f
size 52657
oid sha256:8deed7312d246c817c33b4c0034f139faa788e43dbfb5152fe613dc07320b2fd
size 52655

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3a52624a8bf7e12b374e769eabbe369fc2ec3ff7d9497b7dc093af4ca63df3e
size 40222
oid sha256:e08c4b737016219338da25ff1088b433c67af0ea7d74b451a99f496458a38b17
size 40239

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b80198aeffe7bfec1e5747edf031af3bb892797950a98bdbe4063841fcf69da6
size 269244
oid sha256:72a54bb0c6c72e992468d81df80d0d1d274bb8b94f6883df1d2891483a855c56
size 269259

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5188a7358c698d1a67ee483a5e9277cf1158dbeb98b94933ed71f779918cf1ab
size 345464
oid sha256:2ea10f84e31b3591596dbfb4b3930fb108ce5d7704bdda108a8dac9b78ccbca3
size 345504

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7f1d691d5fcf689cb4da9108f9a5d6608d6e18fa73c204c28202f6e71388be5
size 50333
oid sha256:634df4b9fab9797663e11293e5894de9854e918184d227e73e47be047bcb9623
size 50335

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d3e563a24b827e5f4f7519edc271b6e899852e6b9e33ef37763f018bb5f73f40
size 51279
oid sha256:a971216d0cfc58f65b3589479420c02f2a04010ac27d79ea2434075dcd6b4f7c
size 51283

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2c6c5eb2b3ecfabd9344898bcc581ebb81ac1620f8f749d8d5e76f19abbfe049
size 29395
oid sha256:fbdd42f8092b3bf8fc58ea012972a77444bff2200585fc4d9e0765453e98c251
size 29425

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fdf7ba134d16e9f6458d5bb88e43802fdc4c82fd7e0996c79c776add89c7702f
size 36363
oid sha256:e5bb0724cbb42ffe66a8c54e4301eccfac8e4f2f68621697c6859aa6a230405d
size 36376

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:86727f6cc8b7fcf6b73e71a861d316f32869fe76f8ce5c7a8e014dc6de1c1220
size 39469
oid sha256:8d3b3f549530f475fd984f839c7b28a7963ad7c8903b5f308d3925a60132991b
size 39468

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbd568c10eede6ac86ca33b69f1917e7e1f1aa11e0c793cadc5c04ee99751ca3
size 34936
oid sha256:e97676e6f90749d608c8289137c39668c122228f6660af9ed4b056f4f05d61ca
size 34946

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:73ededbedd5da3678425db2c85164fdc4839fcfb5dce8efa68a7c8e02aeaa75f
size 50130
oid sha256:90b00f22d1d2d1b22f6260c95ea3fe39851682d258df7012b89c68db2af01601
size 50133

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:731df5fbdcde3b3d7bf89777aefa1d0169cfc78a614ae2fce1f41a33d4d21a44
size 50993
oid sha256:18d2e25e32b0dc63b72a2c8c32213d2c7d9d499bf568b88addf24709e433ce34
size 50997

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6cfe8b7ff36288df2f4cc63f8d61d272b55a17e46c29f337fcbc6079c9c0abc
size 28768
oid sha256:c35203dccee8ae4c1f7257d54ce84394fcc1b433a1dbd3ca66f8989eb776edf1
size 28789

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3fb9749128fddfecf31764c27f941f789961c8540b0b8e51455d62e2f16d1a2
size 35455
oid sha256:66785e81ea5d103a8ea1aca54e537850d67becb84f3b4cdb463189b7b21afa68
size 35453

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe18077f8a3a64c9474376f7fac669321306837ae3c01437746d6060a290caf2
size 38425
oid sha256:913a9b7a9abe56116062436d405c97105d87d07ccbb012e65b5a6365974a6d66
size 38439

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f8cffbb53b029acdbdd0fcaf74dd5d4aa1f1099108137e1e6e4b29279f79678f
size 34264
oid sha256:d4a30364d8f94b77b7f518107c531f0fe2e1e9e16729777e8d84958e06b198af
size 34270

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a67bb9ba78ea1d8802ce503e7d6a16be2ff46415df0e435efd7a36f3ee711b0d
size 26453
oid sha256:4b588d1d5a02e4ccfd2e5d44d3c38723612e5fe4a051c5c582bc81bd250850df
size 26452

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe9aa023cb136da6bcb54fb4c371c16c9526d81bbda718ffa61a6a7e758fd64a
oid sha256:e533e9b4a2e10fed24d8379b2f2d96e5a2cc460d9059989f8ad29d535684343f
size 42170

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be2ae50ce1b0e23fec7bcbbe9ca0f0381af4d0f29f1e7498be837d9d53b75542
size 26003
oid sha256:fca69fe3a6bcaae65f686e3c26e2b6935de8e33c9080b0e9f3e58ce3448c0573
size 26004

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dc8f93a9c4ec2437ab05f1f1065d5518719942d8d55550b9911384442346c463
size 41086
oid sha256:084ae1a4a101781ef76bf1a3f68bfb1e8c80ca6ab1de773eaddedb92a12265a5
size 41087

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8f4a7102b45fc1acd7c8cba59282548ae36bf8a3e65acc12c4041990f0cc61c0
size 30165
oid sha256:5248793987bd2f6f98e571366e6fcc87085fdba5372a4a31314a52a13efcc78a
size 30160

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7b16d4f9226d4df9efa9b57a0f6573e9fd6b0301f2e4cde2794de2863cf31ac5
size 31373
oid sha256:2d835fa6c041ca10d362e7c0dd77fe784e2f3b903523b9a813e1cd47a04f53df
size 31365

Some files were not shown because too many files have changed in this diff Show more