Merge branch 'develop' into feature/fga/fix_room_list_scroll_position

This commit is contained in:
ganfra 2023-07-18 23:07:10 +02:00
commit d4ee9fd9d3
56 changed files with 97 additions and 143 deletions

View file

@ -139,7 +139,9 @@ class LoggedInFlowNode @AssistedInject constructor(
} }
}, },
onResume = { onResume = {
syncService.startSync() lifecycleScope.launch {
syncService.startSync()
}
}, },
onPause = { onPause = {
syncService.stopSync() syncService.stopSync()

View file

@ -139,7 +139,7 @@ class InviteListPresenter @Inject constructor(
private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<Async<RoomId>>) = launch { private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<Async<RoomId>>) = launch {
suspend { suspend {
client.getRoom(roomId)?.use { client.getRoom(roomId)?.use {
it.acceptInvitation().getOrThrow() it.join().getOrThrow()
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId) notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId)
analyticsService.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite)) analyticsService.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
} }
@ -150,7 +150,7 @@ class InviteListPresenter @Inject constructor(
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<Async<Unit>>) = launch { private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<Async<Unit>>) = launch {
suspend { suspend {
client.getRoom(roomId)?.use { client.getRoom(roomId)?.use {
it.rejectInvitation().getOrThrow() it.leave().getOrThrow()
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId) notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId)
} }
Unit Unit

View file

@ -211,7 +211,6 @@ class InviteListPresenterTests {
skipItems(2) skipItems(2)
Truth.assertThat(room.isInviteRejected).isTrue()
Truth.assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1) Truth.assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1)
} }
} }
@ -225,7 +224,7 @@ class InviteListPresenterTests {
val room = FakeMatrixRoom() val room = FakeMatrixRoom()
val presenter = createPresenter(client) val presenter = createPresenter(client)
val ex = Throwable("Ruh roh!") val ex = Throwable("Ruh roh!")
room.givenRejectInviteResult(Result.failure(ex)) room.givenLeaveRoomError(ex)
client.givenGetRoomResult(A_ROOM_ID, room) client.givenGetRoomResult(A_ROOM_ID, room)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
@ -242,7 +241,6 @@ class InviteListPresenterTests {
val newState = awaitItem() val newState = awaitItem()
Truth.assertThat(room.isInviteRejected).isTrue()
Truth.assertThat(newState.declinedAction).isEqualTo(Async.Failure<Unit>(ex)) Truth.assertThat(newState.declinedAction).isEqualTo(Async.Failure<Unit>(ex))
} }
} }
@ -256,7 +254,7 @@ class InviteListPresenterTests {
val room = FakeMatrixRoom() val room = FakeMatrixRoom()
val presenter = createPresenter(client) val presenter = createPresenter(client)
val ex = Throwable("Ruh roh!") val ex = Throwable("Ruh roh!")
room.givenRejectInviteResult(Result.failure(ex)) room.givenLeaveRoomError(ex)
client.givenGetRoomResult(A_ROOM_ID, room) client.givenGetRoomResult(A_ROOM_ID, room)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
@ -298,7 +296,6 @@ class InviteListPresenterTests {
val newState = awaitItem() val newState = awaitItem()
Truth.assertThat(room.isInviteAccepted).isTrue()
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Success(A_ROOM_ID)) Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Success(A_ROOM_ID))
Truth.assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1) Truth.assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1)
} }
@ -313,7 +310,7 @@ class InviteListPresenterTests {
val room = FakeMatrixRoom() val room = FakeMatrixRoom()
val presenter = createPresenter(client) val presenter = createPresenter(client)
val ex = Throwable("Ruh roh!") val ex = Throwable("Ruh roh!")
room.givenAcceptInviteResult(Result.failure(ex)) room.givenJoinRoomResult(Result.failure(ex))
client.givenGetRoomResult(A_ROOM_ID, room) client.givenGetRoomResult(A_ROOM_ID, room)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
@ -322,10 +319,7 @@ class InviteListPresenterTests {
val originalState = awaitItem() val originalState = awaitItem()
originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0])) originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0]))
val newState = awaitItem() Truth.assertThat(awaitItem().acceptedAction).isEqualTo(Async.Failure<RoomId>(ex))
Truth.assertThat(room.isInviteAccepted).isTrue()
Truth.assertThat(newState.acceptedAction).isEqualTo(Async.Failure<RoomId>(ex))
} }
} }
@ -338,7 +332,7 @@ class InviteListPresenterTests {
val room = FakeMatrixRoom() val room = FakeMatrixRoom()
val presenter = createPresenter(client) val presenter = createPresenter(client)
val ex = Throwable("Ruh roh!") val ex = Throwable("Ruh roh!")
room.givenAcceptInviteResult(Result.failure(ex)) room.givenJoinRoomResult(Result.failure(ex))
client.givenGetRoomResult(A_ROOM_ID, room) client.givenGetRoomResult(A_ROOM_ID, room)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {

View file

@ -51,8 +51,8 @@ import io.element.android.libraries.theme.ElementTheme
private val BUBBLE_RADIUS = 12.dp private val BUBBLE_RADIUS = 12.dp
private val BUBBLE_INCOMING_OFFSET = 16.dp private val BUBBLE_INCOMING_OFFSET = 16.dp
// Design says: The maximum width of a bubble is still 3/4 of the screen width // Design says: The maximum width of a bubble is still 3/4 of the screen width. But try with 85% now.
private const val BUBBLE_WIDTH_RATIO = 0.75f private const val BUBBLE_WIDTH_RATIO = 0.85f
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable

View file

@ -145,7 +145,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" }
timber = "com.jakewharton.timber:timber:5.0.1" timber = "com.jakewharton.timber:timber:5.0.1"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.31" matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.34"
sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" }

View file

@ -95,9 +95,7 @@ interface MatrixRoom : Closeable {
suspend fun leave(): Result<Unit> suspend fun leave(): Result<Unit>
suspend fun acceptInvitation(): Result<Unit> suspend fun join(): Result<Unit>
suspend fun rejectInvitation(): Result<Unit>
suspend fun inviteUserById(id: UserId): Result<Unit> suspend fun inviteUserById(id: UserId): Result<Unit>

View file

@ -22,7 +22,7 @@ interface SyncService {
/** /**
* Tries to start the sync. If already syncing it has no effect. * Tries to start the sync. If already syncing it has no effect.
*/ */
fun startSync(): Result<Unit> suspend fun startSync(): Result<Unit>
/** /**
* Tries to stop the sync. If service is not syncing it has no effect. * Tries to stop the sync. If service is not syncing it has no effect.

View file

@ -73,10 +73,12 @@ import java.io.File
import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters
import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset
import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility
import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class RustMatrixClient constructor( class RustMatrixClient constructor(
private val client: Client, private val client: Client,
private val syncService: ClientSyncService,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
appCoroutineScope: CoroutineScope, appCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
@ -86,14 +88,11 @@ class RustMatrixClient constructor(
) : MatrixClient { ) : MatrixClient {
override val sessionId: UserId = UserId(client.userId()) override val sessionId: UserId = UserId(client.userId())
private val app = client.app().use { builder -> private val roomListService = syncService.roomListService()
builder.finish()
}
private val roomListService = app.roomListService()
private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val sessionDispatcher = dispatchers.io.limitedParallelism(64)
private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}") private val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-${sessionId}")
private val verificationService = RustSessionVerificationService() private val verificationService = RustSessionVerificationService()
private val syncService = RustSyncService(app, roomListService.stateFlow(), sessionCoroutineScope) private val rustSyncService = RustSyncService(syncService, roomListService.stateFlow(), sessionCoroutineScope)
private val pushersService = RustPushersService( private val pushersService = RustPushersService(
client = client, client = client,
dispatchers = dispatchers, dispatchers = dispatchers,
@ -131,7 +130,7 @@ class RustMatrixClient constructor(
init { init {
client.setDelegate(clientDelegate) client.setDelegate(clientDelegate)
syncService.syncState rustSyncService.syncState
.onEach { syncState -> .onEach { syncState ->
if (syncState == SyncState.Syncing) { if (syncState == SyncState.Syncing) {
onSlidingSyncUpdate() onSlidingSyncUpdate()
@ -247,7 +246,7 @@ class RustMatrixClient constructor(
} }
} }
override fun syncService(): SyncService = syncService override fun syncService(): SyncService = rustSyncService
override fun sessionVerificationService(): SessionVerificationService = verificationService override fun sessionVerificationService(): SessionVerificationService = verificationService
@ -259,7 +258,7 @@ class RustMatrixClient constructor(
sessionCoroutineScope.cancel() sessionCoroutineScope.cancel()
client.setDelegate(null) client.setDelegate(null)
verificationService.destroy() verificationService.destroy()
app.destroy() syncService.destroy()
roomListService.destroy() roomListService.destroy()
notificationClient.destroy() notificationClient.destroy()
client.destroy() client.destroy()

View file

@ -181,9 +181,11 @@ class RustMatrixAuthenticationService @Inject constructor(
*/ */
} }
private fun createMatrixClient(client: Client): MatrixClient { private suspend fun createMatrixClient(client: Client): MatrixClient {
val syncService = client.syncService().finish()
return RustMatrixClient( return RustMatrixClient(
client = client, client = client,
syncService = syncService,
sessionStore = sessionStore, sessionStore = sessionStore,
appCoroutineScope = appCoroutineScope, appCoroutineScope = appCoroutineScope,
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,

View file

@ -43,7 +43,6 @@ import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline
import io.element.android.libraries.matrix.impl.timeline.backPaginationStatusFlow import io.element.android.libraries.matrix.impl.timeline.backPaginationStatusFlow
import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow
import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -82,6 +81,7 @@ class RustMatrixRoom(
// Create a dispatcher for all room methods... // Create a dispatcher for all room methods...
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32) private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
//...except getMember methods as it could quickly fill the roomDispatcher... //...except getMember methods as it could quickly fill the roomDispatcher...
private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8) private val roomMembersDispatcher = coroutineDispatchers.io.limitedParallelism(8)
@ -257,15 +257,9 @@ class RustMatrixRoom(
} }
} }
override suspend fun acceptInvitation(): Result<Unit> = withContext(roomDispatcher) { override suspend fun join(): Result<Unit> = withContext(roomDispatcher) {
runCatching { runCatching {
innerRoom.acceptInvitation() innerRoom.join()
}
}
override suspend fun rejectInvitation(): Result<Unit> = withContext(roomDispatcher) {
runCatching {
innerRoom.rejectInvitation()
} }
} }

View file

@ -17,8 +17,8 @@
package io.element.android.libraries.matrix.impl.sync package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.sync.SyncState
import org.matrix.rustcomponents.sdk.AppState
import org.matrix.rustcomponents.sdk.RoomListServiceState import org.matrix.rustcomponents.sdk.RoomListServiceState
import org.matrix.rustcomponents.sdk.SyncServiceState
internal fun RoomListServiceState.toSyncState(): SyncState { internal fun RoomListServiceState.toSyncState(): SyncState {
return when (this) { return when (this) {
@ -30,10 +30,11 @@ internal fun RoomListServiceState.toSyncState(): SyncState {
} }
} }
internal fun AppState.toSyncState(): SyncState { internal fun SyncServiceState.toSyncState(): SyncState {
return when (this) { return when (this) {
AppState.RUNNING -> SyncState.Syncing SyncServiceState.IDLE -> SyncState.Idle
AppState.TERMINATED -> SyncState.Terminated SyncServiceState.RUNNING -> SyncState.Syncing
AppState.ERROR -> SyncState.InError SyncServiceState.TERMINATED -> SyncState.Terminated
SyncServiceState.ERROR -> SyncState.InError
} }
} }

View file

@ -26,24 +26,24 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import org.matrix.rustcomponents.sdk.App
import org.matrix.rustcomponents.sdk.RoomListServiceState import org.matrix.rustcomponents.sdk.RoomListServiceState
import org.matrix.rustcomponents.sdk.SyncServiceInterface
import timber.log.Timber import timber.log.Timber
class RustSyncService( class RustSyncService(
private val app: App, private val innerSyncService: SyncServiceInterface,
roomListStateFlow: Flow<RoomListServiceState>, roomListStateFlow: Flow<RoomListServiceState>,
sessionCoroutineScope: CoroutineScope sessionCoroutineScope: CoroutineScope
) : SyncService { ) : SyncService {
override fun startSync() = runCatching { override suspend fun startSync() = runCatching {
Timber.v("Start sync") Timber.v("Start sync")
app.start() innerSyncService.start()
} }
override fun stopSync() = runCatching { override fun stopSync() = runCatching {
Timber.v("Stop sync") Timber.v("Stop sync")
app.pause() innerSyncService.pause()
} }
override val syncState: StateFlow<SyncState> = override val syncState: StateFlow<SyncState> =

View file

@ -21,14 +21,14 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.buffer
import org.matrix.rustcomponents.sdk.App import org.matrix.rustcomponents.sdk.SyncService
import org.matrix.rustcomponents.sdk.AppState import org.matrix.rustcomponents.sdk.SyncServiceState
import org.matrix.rustcomponents.sdk.AppStateObserver import org.matrix.rustcomponents.sdk.SyncServiceStateObserver
fun App.stateFlow(): Flow<AppState> = fun SyncService.stateFlow(): Flow<SyncServiceState> =
mxCallbackFlow { mxCallbackFlow {
val listener = object : AppStateObserver { val listener = object : SyncServiceStateObserver {
override fun onUpdate(state: AppState) { override fun onUpdate(state: SyncServiceState) {
trySendBlocking(state) trySendBlocking(state)
} }
} }

View file

@ -63,8 +63,7 @@ class FakeMatrixRoom(
private var userDisplayNameResult = Result.success<String?>(null) private var userDisplayNameResult = Result.success<String?>(null)
private var userAvatarUrlResult = Result.success<String?>(null) private var userAvatarUrlResult = Result.success<String?>(null)
private var updateMembersResult: Result<Unit> = Result.success(Unit) private var updateMembersResult: Result<Unit> = Result.success(Unit)
private var acceptInviteResult = Result.success(Unit) private var joinRoomResult = Result.success(Unit)
private var rejectInviteResult = Result.success(Unit)
private var inviteUserResult = Result.success(Unit) private var inviteUserResult = Result.success(Unit)
private var canInviteResult = Result.success(true) private var canInviteResult = Result.success(true)
private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>() private val canSendStateResults = mutableMapOf<StateEventType, Result<Boolean>>()
@ -101,11 +100,6 @@ class FakeMatrixRoom(
var sendLocationCount: Int = 0 var sendLocationCount: Int = 0
private set private set
var isInviteAccepted: Boolean = false
private set
var isInviteRejected: Boolean = false
private set
var invitedUserId: UserId? = null var invitedUserId: UserId? = null
private set private set
@ -196,16 +190,11 @@ class FakeMatrixRoom(
return Result.success(Unit) return Result.success(Unit)
} }
override suspend fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit) override suspend fun leave(): Result<Unit> =
leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
override suspend fun acceptInvitation(): Result<Unit> { override suspend fun join(): Result<Unit> {
isInviteAccepted = true return joinRoomResult
return acceptInviteResult
}
override suspend fun rejectInvitation(): Result<Unit> {
isInviteRejected = true
return rejectInviteResult
} }
override suspend fun inviteUserById(id: UserId): Result<Unit> = simulateLongTask { override suspend fun inviteUserById(id: UserId): Result<Unit> = simulateLongTask {
@ -316,12 +305,8 @@ class FakeMatrixRoom(
userAvatarUrlResult = avatarUrl userAvatarUrlResult = avatarUrl
} }
fun givenAcceptInviteResult(result: Result<Unit>) { fun givenJoinRoomResult(result: Result<Unit>) {
acceptInviteResult = result joinRoomResult = result
}
fun givenRejectInviteResult(result: Result<Unit>) {
rejectInviteResult = result
} }
fun givenInviteUserResult(result: Result<Unit>) { fun givenInviteUserResult(result: Result<Unit>) {

View file

@ -29,7 +29,7 @@ class FakeSyncService : SyncService {
syncStateFlow.value = SyncState.InError syncStateFlow.value = SyncState.InError
} }
override fun startSync(): Result<Unit> { override suspend fun startSync(): Result<Unit> {
syncStateFlow.value = SyncState.Syncing syncStateFlow.value = SyncState.Syncing
return Result.success(Unit) return Result.success(Unit)
} }

View file

@ -41,6 +41,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
@ -108,7 +109,9 @@ class RoomListScreen(
DisposableEffect(Unit) { DisposableEffect(Unit) {
Timber.w("Start sync!") Timber.w("Start sync!")
matrixClient.syncService().startSync() runBlocking {
matrixClient.syncService().startSync()
}
onDispose { onDispose {
Timber.w("Stop sync!") Timber.w("Stop sync!")
matrixClient.syncService().stopSync() matrixClient.syncService().stopSync()

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:341bd6a72040547c9f8e30574dcf4cf416bca3f2581c8ecd460d4291d3eb1974 oid sha256:38c6d3f4a47ed89c3deb4150024b49c0a533a859f21a1592a82309b8c7316ea4
size 138376 size 152242

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4e86c185d1b669c3c15614054f877fbf2219a2e700d36a765664ac132d148fe7 oid sha256:f82840398e396e038ad68a5fe855394a0088c413e7aade339998b8ed63fb1c09
size 142142 size 157273

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2531a3845cd5136fd543a90ad05d497eda31a9bd8662a1e0ea7d503d6cbe76a9 oid sha256:527dcf9001131276820a7853ae40a91e906ce9398609e439328991beb75bdcbc
size 62525 size 62184

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:aab12d61c532d72ce9b2cae609c4161976500032b2b432b961cda5652595e9fc oid sha256:116d47547b64ac8f5403acfb79b1e1f0cdb6f8de6dafb3a5ca59351a6fa8cc4e
size 64624 size 64207

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ec8841fe56779bd507a130cb527a045ea2cad4da922f7828f5795dda4d10445d oid sha256:ab3ce102aa2a3be65e52d26f261c5860f022bd0755c8069ff86c7e75bfe6952c
size 68965 size 68744

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ef0bb4d881ed1113c8c0ad320cf3c8f220624514afae63ce82364e3b2c98c907 oid sha256:08479dcbaaa215af9df4d8bf2d257b0eb3a33da55dcb449ea5b6441972765636
size 70923 size 70616

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9c8fbdf767801956f2211c6b3425dd48e0c891ceda7c32d8e932cc4e68bba188 oid sha256:37fadb92faac0c5f85e1e198170d4e10b2b7d54bb3d97e3efd3a0c02e4e6f609
size 64393 size 63795

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:38d8b6b08174f0735c3e1ad0aa48ea4eed5d3c59d2ef58f1eb6016af454e51fa oid sha256:90d0cb45a49afc59df03cd4caa6bab215560cbd13c259d25a7ec7990e551df91
size 66925 size 66397

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:c48bd869248350abe61e639a9201a68905364aa9ac94c5205daa27e31500614e oid sha256:86b912e31d126699fa2e7858d78e703676ed220f159994b2e8ea885c07054a43
size 71261 size 70816

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:984a26ad164ef50fd356184662b9e25c8d9e1d7a277e47fdb6222877a52edc26 oid sha256:e830ca620428646fc06ff1682354445bf4afc9566c55c7ec56fa87aca4cb4167
size 74019 size 73556

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:10ca9141ab5b383d5fdc73ffff878663706d56cf96bfc3f4dfb47a5c102e460b oid sha256:7c5ef7519aef3badeda3983b2ec5474f31404bc06b4b46b9829848dc7884fe1d
size 84109 size 81749

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ccd377cc8c709cc6dc963224ce1937b3d72a39281327ed917791441817df979b oid sha256:c9e9b769422e4714e87ace97058e5a8f064e3b927e3a7c6042494de8381d6b09
size 87831 size 85856

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:63fb78f8b81e7a621223b6fb9d133cd66edde2dd837e8213d0195d253abb5d93 oid sha256:4970830b042e7b6588c03bf632ae3ade5b39de7339e27618ee341cbd9861c0e9
size 119401 size 129351

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3c04acca24e2da6eb9b04e63d9ec4db7f9620e85a6ceb65190a8ab96dd6fee51 oid sha256:4aba48e03e8424e5ff131c22fd5a606280745dd318f5822e2167f3b39794ff41
size 124793 size 134569

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:14ecdfd226a25743e3fac8850318216d6ec193755fa7559fd5523236b7835bc6 oid sha256:dc695bfc589e8e5a852eeb9b2828ce968d17519bb5be957cf2734a7d6d9cc356
size 89754 size 89482

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d08de923f29ec6c3c2bf735ca0d015ca1097c95484119d1d39bb8c3f93f31100 oid sha256:9efb4310c2eed085f8f72be66d725c628e07373cd18c262c18be510d7b042f69
size 363776 size 393373

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3e8832da336aea6c7ebb3621668569ff16238042fb9650afcd938594a59fd3ae oid sha256:19982d091ffcb15a422e75461adb3d039775645eaf74b6bfd4a3ee617dd4555f
size 322771 size 347932

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f729f89dce978bb9e061826389e0db1af0e18835e4cd1ab6cbfaa31f4fd3152f oid sha256:167f8368e0b94aa05e5d1cc30055e3b0c2c910752d719092ef2d34aae09dc832
size 84942 size 84649

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:03a4b62920af61098e850cfca6a6f43acd7b310094fb70e1c3e4b7e29465e244 oid sha256:6ee754e789926bfd12212a21b6ff882ffca337d6903125ba5e1d7cd0c1c218ec
size 173851 size 189364

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ebcbb40acf19fb489f6a8068a22af0b79c304be46863e77fa12b3b333065a1b2 oid sha256:29f9ba8c19287b037f67aec5af4469174eb878b7ed5baf222f66e159faee6e16
size 164622 size 178410