Merge pull request #3069 from element-hq/feature/bma/avatarCluster_x2

Avatar cluster for DM
This commit is contained in:
Benoit Marty 2024-06-24 10:53:50 +02:00 committed by GitHub
commit 010a90f9ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
98 changed files with 416 additions and 206 deletions

View file

@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
import io.element.android.libraries.matrix.api.room.powerlevels.canSendState
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
import io.element.android.libraries.matrix.ui.room.canCall
import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin
import io.element.android.services.analytics.api.AnalyticsService
@ -98,8 +99,9 @@ class RoomDetailsPresenter @Inject constructor(
val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC)
val canJoinCall by room.canCall(updateKey = syncUpdateTimestamp)
val dmMember by room.getDirectRoomMember(membersState)
val currentMember by room.getCurrentRoomMember(membersState)
val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember)
val roomType by getRoomType(dmMember)
val roomType by getRoomType(dmMember, currentMember)
val topicState = remember(canEditTopic, roomTopic, roomType) {
val topic = roomTopic
@ -165,10 +167,16 @@ class RoomDetailsPresenter @Inject constructor(
}
@Composable
private fun getRoomType(dmMember: RoomMember?): State<RoomDetailsType> = remember(dmMember) {
private fun getRoomType(
dmMember: RoomMember?,
currentMember: RoomMember?,
): State<RoomDetailsType> = remember(dmMember, currentMember) {
derivedStateOf {
if (dmMember != null) {
RoomDetailsType.Dm(dmMember)
if (dmMember != null && currentMember != null) {
RoomDetailsType.Dm(
me = currentMember,
otherMember = dmMember,
)
} else {
RoomDetailsType.Room
}

View file

@ -52,7 +52,10 @@ data class RoomDetailsState(
@Immutable
sealed interface RoomDetailsType {
data object Room : RoomDetailsType
data class Dm(val roomMember: RoomMember) : RoomDetailsType
data class Dm(
val me: RoomMember,
val otherMember: RoomMember,
) : RoomDetailsType
}
@Immutable

View file

@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.features.userprofile.shared.UserProfileState
import io.element.android.features.userprofile.shared.aUserProfileState
import io.element.android.libraries.matrix.api.core.RoomAlias
@ -141,6 +142,10 @@ fun aDmRoomDetailsState(
roomName: String = "Daniel",
) = aRoomDetailsState(
roomName = roomName,
roomType = RoomDetailsType.Dm(aDmRoomMember(isIgnored = isDmMemberIgnored)),
isPublic = false,
roomType = RoomDetailsType.Dm(
aRoomMember(),
aDmRoomMember(isIgnored = isDmMemberIgnored),
),
roomMemberDetailsState = aUserProfileState()
)

View file

@ -20,6 +20,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
@ -48,7 +49,6 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.leaveroom.api.LeaveRoomView
import io.element.android.features.roomdetails.impl.components.RoomBadge
import io.element.android.features.userprofile.shared.UserProfileHeaderSection
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
@ -56,6 +56,7 @@ import io.element.android.libraries.designsystem.components.ClickableLinkText
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.avatar.CompositeAvatar
import io.element.android.libraries.designsystem.components.avatar.DmAvatars
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.button.MainActionButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
@ -78,6 +79,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.api.user.MatrixUser
@ -128,39 +130,35 @@ fun RoomDetailsView(
roomId = state.roomId,
roomName = state.roomName,
roomAlias = state.roomAlias,
isEncrypted = state.isEncrypted,
isPublic = state.isPublic,
heroes = state.heroes,
openAvatarPreview = { avatarUrl ->
openAvatarPreview(state.roomName, avatarUrl)
},
)
MainActionsSection(
state = state,
onShareRoom = onShareRoom,
onInvitePeople = invitePeople,
onCall = onJoinCallClick,
)
}
is RoomDetailsType.Dm -> {
val member = state.roomType.roomMember
UserProfileHeaderSection(
avatarUrl = state.roomAvatarUrl ?: member.avatarUrl,
userId = member.userId,
userName = state.roomName,
openAvatarPreview = { avatarUrl ->
openAvatarPreview(member.getBestName(), avatarUrl)
DmHeaderSection(
me = state.roomType.me,
otherMember = state.roomType.otherMember,
roomName = state.roomName,
openAvatarPreview = { name, avatarUrl ->
openAvatarPreview(name, avatarUrl)
},
)
MainActionsSection(
state = state,
onShareRoom = onShareRoom,
onInvitePeople = invitePeople,
onCall = onJoinCallClick,
)
}
}
BadgeList(
isEncrypted = state.isEncrypted,
isPublic = state.isPublic,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
Spacer(Modifier.height(32.dp))
MainActionsSection(
state = state,
onShareRoom = onShareRoom,
onInvitePeople = invitePeople,
onCall = onJoinCallClick,
)
Spacer(Modifier.height(12.dp))
if (state.roomTopic !is RoomTopicState.Hidden) {
@ -326,8 +324,6 @@ private fun RoomHeaderSection(
roomId: RoomId,
roomName: String,
roomAlias: RoomAlias?,
isEncrypted: Boolean,
isPublic: Boolean,
heroes: ImmutableList<MatrixUser>,
openAvatarPreview: (url: String) -> Unit,
) {
@ -346,23 +342,56 @@ private fun RoomHeaderSection(
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
.testTag(TestTags.roomDetailAvatar)
)
Spacer(modifier = Modifier.height(24.dp))
TitleAndSubtitle(title = roomName, subtitle = roomAlias?.value)
}
}
@Composable
private fun DmHeaderSection(
me: RoomMember,
otherMember: RoomMember,
roomName: String,
openAvatarPreview: (name: String, url: String) -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
DmAvatars(
userAvatarData = me.getAvatarData(size = AvatarSize.DmCluster),
otherUserAvatarData = otherMember.getAvatarData(size = AvatarSize.DmCluster),
openAvatarPreview = { url -> openAvatarPreview(me.getBestName(), url) },
openOtherAvatarPreview = { url -> openAvatarPreview(roomName, url) },
)
TitleAndSubtitle(
title = roomName,
subtitle = otherMember.userId.value,
)
}
}
@Composable
private fun ColumnScope.TitleAndSubtitle(
title: String,
subtitle: String?,
) {
Spacer(modifier = Modifier.height(24.dp))
Text(
text = title,
style = ElementTheme.typography.fontHeadingLgBold,
textAlign = TextAlign.Center,
)
if (subtitle != null) {
Spacer(modifier = Modifier.height(6.dp))
Text(
text = roomName,
style = ElementTheme.typography.fontHeadingLgBold,
text = subtitle,
style = ElementTheme.typography.fontBodyLgRegular,
color = MaterialTheme.colorScheme.secondary,
textAlign = TextAlign.Center,
)
if (roomAlias != null) {
Spacer(modifier = Modifier.height(6.dp))
Text(
text = roomAlias.value,
style = ElementTheme.typography.fontBodyLgRegular,
color = MaterialTheme.colorScheme.secondary,
textAlign = TextAlign.Center,
)
}
BadgeList(isEncrypted = isEncrypted, isPublic = isPublic)
Spacer(Modifier.height(32.dp))
}
}
@ -370,11 +399,12 @@ private fun RoomHeaderSection(
private fun BadgeList(
isEncrypted: Boolean,
isPublic: Boolean,
modifier: Modifier = Modifier,
) {
if (isEncrypted || isPublic) {
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.padding(horizontal = 16.dp),
modifier = modifier
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
if (isEncrypted) {

View file

@ -169,8 +169,12 @@ class RoomDetailsPresenterTest {
val presenter = createRoomDetailsPresenter(room)
presenter.test {
val initialState = awaitItem()
assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Dm(otherRoomMember))
assertThat(initialState.roomType).isEqualTo(
RoomDetailsType.Dm(
me = myRoomMember,
otherMember = otherRoomMember,
)
)
cancelAndIgnoreRemainingEvents()
}
}

View file

@ -23,6 +23,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.roomdetails.impl.members.aRoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings
@ -177,7 +178,11 @@ class RoomDetailsViewTest {
fun `click on avatar test on DM`() {
val eventsRecorder = EventsRecorder<RoomDetailsEvent>(expectEvents = false)
val state = aRoomDetailsState(
roomType = RoomDetailsType.Dm(aDmRoomMember(avatarUrl = "an_avatar_url")),
roomType = RoomDetailsType.Dm(
aRoomMember(),
aDmRoomMember(avatarUrl = "an_avatar_url"),
),
roomName = "Daniel",
eventSink = eventsRecorder,
)
val callback = EnsureCalledOnceWithTwoParams("Daniel", "an_avatar_url")

View file

@ -17,14 +17,11 @@
package io.element.android.features.userprofile.shared
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -36,6 +33,8 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.testtags.TestTags
@ -55,15 +54,12 @@ fun UserProfileHeaderSection(
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(modifier = Modifier.size(70.dp)) {
Avatar(
avatarData = AvatarData(userId.value, userName, avatarUrl, AvatarSize.UserHeader),
modifier = Modifier
Avatar(
avatarData = AvatarData(userId.value, userName, avatarUrl, AvatarSize.UserHeader),
modifier = Modifier
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
.fillMaxSize()
.testTag(TestTags.memberDetailAvatar)
)
}
)
Spacer(modifier = Modifier.height(24.dp))
if (userName != null) {
Text(
@ -86,3 +82,14 @@ fun UserProfileHeaderSection(
Spacer(Modifier.height(40.dp))
}
}
@PreviewsDayNight
@Composable
internal fun UserProfileHeaderSectionPreview() = ElementPreview {
UserProfileHeaderSection(
avatarUrl = null,
userId = UserId("@alice:example.com"),
userName = "Alice",
openAvatarPreview = {},
)
}

View file

@ -36,6 +36,8 @@ enum class AvatarSize(val dp: Dp) {
SelectedUser(56.dp),
SelectedRoom(56.dp),
DmCluster(75.dp),
TimelineRoom(32.dp),
TimelineSender(32.dp),
TimelineReadReceipt(16.dp),

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.components.avatar
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.text.toPx
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
/** Ratio between the box size (120 on Figma) and the avatar size (75 on Figma). */
private const val SIZE_RATIO = 1.6f
/**
* https://www.figma.com/design/A2pAEvTEpJZBiOPUlcMnKi/Settings-%2B-Room-Details-(new)?node-id=1787-56333
*/
@Composable
fun DmAvatars(
userAvatarData: AvatarData,
otherUserAvatarData: AvatarData,
openAvatarPreview: (url: String) -> Unit,
openOtherAvatarPreview: (url: String) -> Unit,
modifier: Modifier = Modifier,
) {
val boxSize = userAvatarData.size.dp * SIZE_RATIO
val boxSizePx = boxSize.toPx()
val otherAvatarRadius = otherUserAvatarData.size.dp.toPx() / 2
Box(
modifier = modifier.size(boxSize),
) {
// Draw user avatar and cut top right corner
Avatar(
avatarData = userAvatarData,
modifier = Modifier
.align(Alignment.BottomStart)
.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}
.drawWithContent {
drawContent()
drawCircle(
color = Color.Black,
center = Offset(
x = boxSizePx - otherAvatarRadius,
y = size.height - (boxSizePx - otherAvatarRadius),
),
radius = otherAvatarRadius / 0.9f,
blendMode = BlendMode.Clear,
)
}
.clip(CircleShape)
.clickable(enabled = userAvatarData.url != null) {
userAvatarData.url?.let { openAvatarPreview(it) }
}
)
// Draw other user avatar
Avatar(
avatarData = otherUserAvatarData,
modifier = Modifier
.align(Alignment.TopEnd)
.clip(CircleShape)
.clickable(enabled = otherUserAvatarData.url != null) {
otherUserAvatarData.url?.let { openOtherAvatarPreview(it) }
}
.testTag(TestTags.memberDetailAvatar)
)
}
}
@Preview(group = PreviewGroup.Avatars)
@Composable
internal fun DmAvatarsPreview() = ElementThemedPreview {
val size = AvatarSize.DmCluster
DmAvatars(
userAvatarData = anAvatarData(
id = "Alice",
name = "Alice",
size = size,
),
otherUserAvatarData = anAvatarData(
id = "Bob",
name = "Bob",
size = size,
),
openAvatarPreview = {},
openOtherAvatarPreview = {},
)
}

View file

@ -63,3 +63,14 @@ fun MatrixRoom.getDirectRoomMember(roomMembersState: MatrixRoomMembersState): St
}
}
}
@Composable
fun MatrixRoom.getCurrentRoomMember(roomMembersState: MatrixRoomMembersState): State<RoomMember?> {
val roomMembers = roomMembersState.roomMembers()
return remember(roomMembersState) {
derivedStateOf {
roomMembers
?.find { it.userId == sessionId }
}
}
}

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74b7eafa18ce0487d8a6bc1bec69de692355e51885ab2f50f633e8c0a1602e98
size 40826
oid sha256:cd1fee6b0af1818d9b0fb6809620fa8118b970f439ae75c504ffd4e172aa60d3
size 41670

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74b7eafa18ce0487d8a6bc1bec69de692355e51885ab2f50f633e8c0a1602e98
size 40826
oid sha256:cd1fee6b0af1818d9b0fb6809620fa8118b970f439ae75c504ffd4e172aa60d3
size 41670

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21a24d8102042a68337b99c60df2ac1f408b37abe47550f9e4f160ca7938d266
size 41782
oid sha256:5649302bb7468c7ec2214b509259df584574e52662eaca1782ad732c6c9ac659
size 42296

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21a24d8102042a68337b99c60df2ac1f408b37abe47550f9e4f160ca7938d266
size 41782
oid sha256:5649302bb7468c7ec2214b509259df584574e52662eaca1782ad732c6c9ac659
size 42296

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:732ca333bbb7e529807fa5cab67746e136d0ef66b7f7f7072c4f000698cb459e
size 20876

View file

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

View file

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

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2df1ef825be0992971cbade0c7e8b9c0f51da948fb468b77780603fda60ab764
size 21442

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4a99ca956353e88e7358c372b0ddda72f3b1b9e558b55b4455122af109b6be5a
size 18957

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:071f13f5bbcf43b4e8a76c98749657daf4586a87d495fdcf605bf526496478eb
size 21608

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:194bdc3cc468226d24d7803bb4b13bceff736b673d74e79513a09ace0b1ecea5
size 33334

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56abdea2af9b91a54a1573fcbfaa328ebca94628c5a9da7ecb39dded0e3288aa
size 20279

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a4adebc4076d7ad97900d19fc2432524a6c62486fa22c917a94c91166881c29
size 21141

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:41501177b8bbf7285b48fce8db920d9de5374d0bb586279025f05a6ede316377
size 18108

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3724877c12301966d5457287fc32694617624c7fbc23c3b3989fbccc80dbcf0d
size 21228

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56abdea2af9b91a54a1573fcbfaa328ebca94628c5a9da7ecb39dded0e3288aa
size 20279

View file

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

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5179ae27b40a0611e8ac715e501b01e715ceb8f227d7b432652186f974f3f512
size 18970

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54c198bbebdad2dc4110ca04d3a83b70d75557fb0872157c1724f339054d1234
size 22020

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:65e816869277c12044991fb26427aac17d65072f1e0d7218b53583363a81024f
size 19249

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:611ac8c529d73b14a73a73aeb7cec1eb1a50085d536bd700567462256df30974
size 22026

View file

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

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06310ae4063a59045b5da45b811b9cb29bd483beb86cce8262494177d3925e0f
size 14583
oid sha256:bd5e2eac32ac182e297879363f4ca5d9f93c82ab200e9c3ed15fc42710bbba18
size 16769

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:db4f833beff2bb588fccc1294e76703ff64d427bbf141cf730538a856992d609
size 13808
oid sha256:8f6739cf3b393f3ca39cb7f266baf64199b90e4369ed658beb0641e093e572c7
size 14963

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be37247d044b78541c1da7d4e28a3906e89a369f978a486ae1c1aa70b583658f
size 16321
oid sha256:dd5e60140ca1468932b81d2c708ce98066e1f4fcc0cd3a315986379ce1e66670
size 21426

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85db59158254fa043e53feeddce69cfe89a308a385dde3dcd9a6ff6b94aef7c2
size 15105
oid sha256:06310ae4063a59045b5da45b811b9cb29bd483beb86cce8262494177d3925e0f
size 14583

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d22ae88ce675b24306df9d598e78600ccb31e203b30f5cfc9e94432bedc518bd
size 14324
oid sha256:db4f833beff2bb588fccc1294e76703ff64d427bbf141cf730538a856992d609
size 13808

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3672bb10f51f674dda4d06eced8f5a6e977bd10902d4de7bf9cc4e915d83bb7
size 16836
oid sha256:be37247d044b78541c1da7d4e28a3906e89a369f978a486ae1c1aa70b583658f
size 16321

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0d524d6f2a6f0cb2b06e8a359c66819dc7e8eb03ad0c17612d35f002f2189f9
size 15484
oid sha256:85db59158254fa043e53feeddce69cfe89a308a385dde3dcd9a6ff6b94aef7c2
size 15105

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ebe050aae72443fdfb7a8cb11e7b5d89a0614907155c3bbebda656c8d44b30a1
size 15141
oid sha256:d22ae88ce675b24306df9d598e78600ccb31e203b30f5cfc9e94432bedc518bd
size 14324

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e56fff6052b5c1b3b61a2aa73cad2f03881d86d465dbc59465d63c4aeac3ef4
size 16371
oid sha256:e3672bb10f51f674dda4d06eced8f5a6e977bd10902d4de7bf9cc4e915d83bb7
size 16836

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:77d4c098189f99f7091738791218ae663d168d30a442804b8923243c8727598b
size 16145
oid sha256:f0d524d6f2a6f0cb2b06e8a359c66819dc7e8eb03ad0c17612d35f002f2189f9
size 15484

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74da4791ade57bbf0b0393143c3f40e9c8aaec36f438ecc85a872ad0f0e03da3
size 15388
oid sha256:ebe050aae72443fdfb7a8cb11e7b5d89a0614907155c3bbebda656c8d44b30a1
size 15141

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:96518c010196d80e8696ffcaea032468bb4d1edfbe6b1187286da488600363bf
size 17891
oid sha256:7e56fff6052b5c1b3b61a2aa73cad2f03881d86d465dbc59465d63c4aeac3ef4
size 16371

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ee072668c6161aa3e2195c2f963bb502112bf6ade57a35be44b013b30aa3d0a9
size 19308
oid sha256:77d4c098189f99f7091738791218ae663d168d30a442804b8923243c8727598b
size 16145

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06d03aa5e848872228a58b399685bc78dfdbad783cb28bfed609dcdc371a168f
size 18520
oid sha256:74da4791ade57bbf0b0393143c3f40e9c8aaec36f438ecc85a872ad0f0e03da3
size 15388

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9dd0232c7847d5c0bb1047cfc6be5d4d43d2cc2e9347aff70a793103b511ad98
size 21073
oid sha256:96518c010196d80e8696ffcaea032468bb4d1edfbe6b1187286da488600363bf
size 17891

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f96f83d866d29d3eb713adf9fb8e216414a1eb924a63d0458c77522ba0de0499
size 16709
oid sha256:ee072668c6161aa3e2195c2f963bb502112bf6ade57a35be44b013b30aa3d0a9
size 19308

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93e866bf58f381020d9516662332a6c986bfd61acc9954723b27adb52c152d68
size 15473
oid sha256:06d03aa5e848872228a58b399685bc78dfdbad783cb28bfed609dcdc371a168f
size 18520

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f27e368c8c68032dc4eac63012fad459785c42c4063aca99eb6be9d8abe99508
size 19751
oid sha256:9dd0232c7847d5c0bb1047cfc6be5d4d43d2cc2e9347aff70a793103b511ad98
size 21073

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1e531de46bbc667647a29dc40e8751bad96a089d6765fcddfc69c47147602563
size 12918
oid sha256:f96f83d866d29d3eb713adf9fb8e216414a1eb924a63d0458c77522ba0de0499
size 16709

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bc57d1ccd6657417469d0d76de066f3a555a5296a83b1cb7168cd64680f97bcd
size 12584
oid sha256:93e866bf58f381020d9516662332a6c986bfd61acc9954723b27adb52c152d68
size 15473

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0960f771d8158f52ad925c04c3d9770f4babec0d9ce017958ccd813b4c0012bd
size 13796
oid sha256:f27e368c8c68032dc4eac63012fad459785c42c4063aca99eb6be9d8abe99508
size 19751

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe2a0b40572b28fb5934d999ce4fecdf76f7efb61caf390669054c084af594ad
size 18778
oid sha256:1e531de46bbc667647a29dc40e8751bad96a089d6765fcddfc69c47147602563
size 12918

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:57358f0990a41b7d22a42b5f06662a8549de79a50126491fa9518bc3ce33bf9a
size 17099
oid sha256:bc57d1ccd6657417469d0d76de066f3a555a5296a83b1cb7168cd64680f97bcd
size 12584

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a5aab04563cc94c4a7636402def1be7d738dd182f733de390d1ad028e99e332
size 22831
oid sha256:0960f771d8158f52ad925c04c3d9770f4babec0d9ce017958ccd813b4c0012bd
size 13796

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:16b2c55f72419639690b09ef46c85c1d284d1da79439adacb38775ae6352c3fb
size 21125
oid sha256:fe2a0b40572b28fb5934d999ce4fecdf76f7efb61caf390669054c084af594ad
size 18778

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a86f83a07283d0c98c64d3a041a6d0b285991f64a34ad372f07d2e04e4d0eff6
size 19455
oid sha256:57358f0990a41b7d22a42b5f06662a8549de79a50126491fa9518bc3ce33bf9a
size 17099

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6202d80bb0a938bdfca6afef92fc71a00863d2e2c119f62fc61ede3df47cf7b
size 24925
oid sha256:5a5aab04563cc94c4a7636402def1be7d738dd182f733de390d1ad028e99e332
size 22831

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7eb42896831203a1cd8f1ac85b96d33d1048d626f5ee4778733a28d48aeebea4
size 16744
oid sha256:16b2c55f72419639690b09ef46c85c1d284d1da79439adacb38775ae6352c3fb
size 21125

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93f23803bd0b5633615fb7c1cb40c76a7a1a755f43fb755a08cb390dfd956d75
size 15967
oid sha256:a86f83a07283d0c98c64d3a041a6d0b285991f64a34ad372f07d2e04e4d0eff6
size 19455

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:67a8223f107fa05f1c5d66efc85f68325b4bf835e371ad44553bd6d6edd4a201
size 18491
oid sha256:c6202d80bb0a938bdfca6afef92fc71a00863d2e2c119f62fc61ede3df47cf7b
size 24925

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f600185bb7a9fe6f4e425d8bf43b2743e6169f3a13c34117059aaa863c1b1de0
size 21593
oid sha256:7eb42896831203a1cd8f1ac85b96d33d1048d626f5ee4778733a28d48aeebea4
size 16744

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2559b3b0e5242e9867aa5c821551a17dbab723e1018476289cb91ed9b9f9ffc4
size 20736
oid sha256:93f23803bd0b5633615fb7c1cb40c76a7a1a755f43fb755a08cb390dfd956d75
size 15967

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:be8c830118af26d3d39a80d5dfa3a750e6e3830a30b066ca91b7ed305a2ee482
size 23624
oid sha256:67a8223f107fa05f1c5d66efc85f68325b4bf835e371ad44553bd6d6edd4a201
size 18491

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e70ce111262f22f14ada0993a32d23e1532c6d7a24f71c38d6aba4af6610054
size 17350
oid sha256:f600185bb7a9fe6f4e425d8bf43b2743e6169f3a13c34117059aaa863c1b1de0
size 21593

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:88fc583b69d153e9027a2df3f98b45a607dcf32838af033b82b3b94e3e03dcc2
size 16486
oid sha256:2559b3b0e5242e9867aa5c821551a17dbab723e1018476289cb91ed9b9f9ffc4
size 20736

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:672f829a7227921b7179e91c1ee3ef58ff2b7dab7f131b687c4fe0b9862d2a2c
size 19436
oid sha256:be8c830118af26d3d39a80d5dfa3a750e6e3830a30b066ca91b7ed305a2ee482
size 23624

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6be06e6a243feb8096a2f121e5cadbd5d82351807b537c938e0e7cbfdf52739
size 21115
oid sha256:3e70ce111262f22f14ada0993a32d23e1532c6d7a24f71c38d6aba4af6610054
size 17350

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:94607803d470f7f5772cbf421b3e56a845556773b3545cae5b42314a1804a02a
size 18858
oid sha256:88fc583b69d153e9027a2df3f98b45a607dcf32838af033b82b3b94e3e03dcc2
size 16486

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f3a7f0a4944f35fd726d5090eab08140b57332e6f7d8eb475fc6bf9ef37bcdf
size 26033
oid sha256:672f829a7227921b7179e91c1ee3ef58ff2b7dab7f131b687c4fe0b9862d2a2c
size 19436

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61d634e424c3858902d03a50a128ecbba4c62766ebba97cd04f6eb147f41f71d
size 15042
oid sha256:f6be06e6a243feb8096a2f121e5cadbd5d82351807b537c938e0e7cbfdf52739
size 21115

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4fea2725865c6277387d8f9d4f1bc980548b671f2946c89dbb9f4cad34be80d7
size 14267
oid sha256:94607803d470f7f5772cbf421b3e56a845556773b3545cae5b42314a1804a02a
size 18858

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e88e6991bca9f0c99c28b6126b10135d0ba8de1faa7bc6174e6f66bc11b2ce03
size 16794
oid sha256:2f3a7f0a4944f35fd726d5090eab08140b57332e6f7d8eb475fc6bf9ef37bcdf
size 26033

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61d634e424c3858902d03a50a128ecbba4c62766ebba97cd04f6eb147f41f71d
size 15042

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4fea2725865c6277387d8f9d4f1bc980548b671f2946c89dbb9f4cad34be80d7
size 14267

View file

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

View file

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