Collapse long lists of message reactions (#806)

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
jonnyandrew
2023-07-07 13:19:11 +00:00
committed by GitHub
parent 54c7e8bb57
commit d8fcfc5844
72 changed files with 387 additions and 254 deletions
@@ -1,87 +0,0 @@
/*
* Copyright (c) 2022 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.features.messages.impl.timeline.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddReaction
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.theme.ElementTheme
@Composable
fun MessagesMoreReactionsButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
val buttonColor = ElementTheme.colors.bgSubtleSecondary
Surface(
modifier = modifier
.background(Color.Transparent)
// Outer border, same colour as background
.border(
BorderStroke(2.dp, MaterialTheme.colorScheme.background),
shape = RoundedCornerShape(corner = CornerSize(14.dp))
)
.padding(vertical = 2.dp, horizontal = 2.dp)
// Clip click indicator inside the outer border
.clip(RoundedCornerShape(corner = CornerSize(12.dp)))
.clickable(onClick = onClick)
.background(buttonColor, RoundedCornerShape(corner = CornerSize(12.dp)))
.padding(vertical = 4.dp, horizontal = 10.dp),
color = buttonColor
) {
Icon(
imageVector = Icons.Outlined.AddReaction,
contentDescription = "Add emoji",
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier
// Same size as the line height of reaction emoji text
.size(with(LocalDensity.current) { 20.sp.toDp() })
)
}
}
@Preview
@Composable
internal fun MessagesMoreReactionsButtonLightPreview() =
ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
internal fun MessagesMoreReactionsButtonDarkPreview() =
ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {
MessagesMoreReactionsButton(onClick = {})
}
@@ -22,40 +22,55 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddReaction
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Surface import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.theme.ElementTheme
@Composable @Composable
fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Modifier, onClick: () -> Unit) { fun MessagesReactionButton(
val buttonColor = if (reaction.isHighlighted) { onClick: () -> Unit,
content: MessagesReactionsButtonContent,
modifier: Modifier = Modifier,
) {
val buttonColor = if (content.isHighlighted) {
ElementTheme.colors.bgSubtlePrimary ElementTheme.colors.bgSubtlePrimary
} else { } else {
ElementTheme.colors.bgSubtleSecondary ElementTheme.colors.bgSubtleSecondary
} }
val borderColor = if (reaction.isHighlighted) {
val borderColor = if (content.isHighlighted) {
ElementTheme.colors.borderInteractivePrimary ElementTheme.colors.borderInteractivePrimary
} else { } else {
buttonColor buttonColor
} }
Surface( Surface(
modifier = modifier modifier = modifier
.background(Color.Transparent) .background(Color.Transparent)
@@ -74,11 +89,59 @@ fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Mo
.padding(vertical = 4.dp, horizontal = 10.dp), .padding(vertical = 4.dp, horizontal = 10.dp),
color = buttonColor color = buttonColor
) { ) {
Row( when (content) {
verticalAlignment = Alignment.CenterVertically is MessagesReactionsButtonContent.Icon -> IconContent(imageVector = content.imageVector)
) { is MessagesReactionsButtonContent.Text -> TextContent(text = content.text)
is MessagesReactionsButtonContent.Reaction -> ReactionContent(reaction = content.reaction)
}
}
}
sealed class MessagesReactionsButtonContent {
data class Text(val text: String) : MessagesReactionsButtonContent()
data class Icon(val imageVector: ImageVector) : MessagesReactionsButtonContent()
data class Reaction(val reaction: AggregatedReaction) : MessagesReactionsButtonContent()
val isHighlighted get() = this is Reaction && reaction.isHighlighted
}
private val reactionEmojiLineHeight = 20.sp
@Composable
private fun TextContent(
text: String,
modifier: Modifier = Modifier,
) = Text(
modifier = modifier
.height(reactionEmojiLineHeight.toDp()),
text = text,
style = ElementTextStyles.Regular.bodyMD
)
@Composable
private fun IconContent(
imageVector: ImageVector,
modifier: Modifier = Modifier
) = Icon(
imageVector = imageVector,
contentDescription = stringResource(id = R.string.screen_room_timeline_add_reaction),
tint = MaterialTheme.colorScheme.secondary,
modifier = modifier
.size(reactionEmojiLineHeight.toDp())
)
@Composable
fun ReactionContent(
reaction: AggregatedReaction,
modifier: Modifier = Modifier,
) = Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
Text( Text(
text = reaction.key, fontSize = 15.sp, lineHeight = 20.sp text = reaction.key,
fontSize = 15.sp, lineHeight = reactionEmojiLineHeight
) )
if (reaction.count > 1) { if (reaction.count > 1) {
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
@@ -88,21 +151,29 @@ fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Mo
fontSize = 14.sp fontSize = 14.sp
) )
} }
} }
@DayNightPreviews
@Composable
internal fun MessagesReactionButtonPreview(@PreviewParameter(AggregatedReactionProvider::class) reaction: AggregatedReaction) = ElementPreview {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Reaction(reaction),
onClick = {}
)
}
@DayNightPreviews
@Composable
internal fun MessagesReactionExtraButtonsPreview() = ElementPreview {
Row {
MessagesReactionButton(
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
onClick = {}
)
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text("12 more"),
onClick = {}
)
} }
} }
@Preview
@Composable
internal fun MessagesReactionButtonLightPreview(@PreviewParameter(AggregatedReactionProvider::class) reaction: AggregatedReaction) =
ElementPreviewLight { ContentToPreview(reaction) }
@Preview
@Composable
internal fun MessagesReactionButtonDarkPreview(@PreviewParameter(AggregatedReactionProvider::class) reaction: AggregatedReaction) =
ElementPreviewDark { ContentToPreview(reaction) }
@Composable
private fun ContentToPreview(reaction: AggregatedReaction) {
MessagesReactionButton(reaction, onClick = { })
}
@@ -249,7 +249,7 @@ private fun TimelineItemEventRowContent(
// Reactions // Reactions
if (event.reactionsState.reactions.isNotEmpty()) { if (event.reactionsState.reactions.isNotEmpty()) {
TimelineItemReactionsView( TimelineItemReactions(
reactionsState = event.reactionsState, reactionsState = event.reactionsState,
mainAxisAlignment = if (event.isMine) FlowMainAxisAlignment.End else FlowMainAxisAlignment.Start, mainAxisAlignment = if (event.isMine) FlowMainAxisAlignment.End else FlowMainAxisAlignment.Start,
onReactionClicked = onReactionClicked, onReactionClicked = onReactionClicked,
@@ -16,59 +16,184 @@
package io.element.android.features.messages.impl.timeline.components package io.element.android.features.messages.impl.timeline.components
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AddReaction
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.flowlayout.FlowMainAxisAlignment import com.google.accompanist.flowlayout.FlowMainAxisAlignment
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions
import io.element.android.features.messages.impl.timeline.model.aTimelineItemReactions import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
/**
* The maximum number of items that can be displayed before some items will be hidden
*
* TODO The threshold should be based on the number of rows, rather than items.
* Once items would spill onto a third row, they should be hidden.
* Note this could be particularly worthwhile to handle reactions that are
* longer than a single character (as annotation keys are free text).
*/
private const val COLLAPSE_ITEMS_THRESHOLD = 8
@Composable @Composable
fun TimelineItemReactionsView( fun TimelineItemReactions(
reactionsState: TimelineItemReactions, reactionsState: TimelineItemReactions,
mainAxisAlignment: FlowMainAxisAlignment, mainAxisAlignment: FlowMainAxisAlignment,
onReactionClicked: (emoji: String) -> Unit, onReactionClicked: (emoji: String) -> Unit,
onMoreReactionsClicked: () -> Unit, onMoreReactionsClicked: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var expanded: Boolean by rememberSaveable { mutableStateOf(false) }
val reactions by remember(reactionsState, expanded) {
derivedStateOf {
val numToDisplay = if (expanded) {
reactionsState.reactions.count()
} else {
COLLAPSE_ITEMS_THRESHOLD
}
reactionsState.reactions.take(numToDisplay).toPersistentList()
}
}
val expandableState by remember {
derivedStateOf {
if (expanded) {
ExpandableState.Expanded
} else {
val hiddenItems = reactionsState.reactions.count() - reactions.count()
if (hiddenItems > 0) {
ExpandableState.Collapsed(hidden = hiddenItems)
} else {
ExpandableState.None
}
}
}
}
TimelineItemReactionsView(
modifier = modifier,
reactions = reactions,
expandableState = expandableState,
mainAxisAlignment = mainAxisAlignment,
onReactionClick = onReactionClicked,
onMoreReactionsClick = onMoreReactionsClicked,
onExpandClick = { expanded = true },
onCollapseClick = { expanded = false }
)
}
private sealed class ExpandableState {
object None: ExpandableState()
data class Collapsed(val hidden: Int): ExpandableState()
object Expanded : ExpandableState()
}
@Composable
private fun TimelineItemReactionsView(
reactions: ImmutableList<AggregatedReaction>,
expandableState: ExpandableState,
mainAxisAlignment: FlowMainAxisAlignment,
onReactionClick: (emoji: String) -> Unit,
onMoreReactionsClick: () -> Unit,
onExpandClick: () -> Unit,
onCollapseClick: () -> Unit,
modifier: Modifier = Modifier
) =
FlowRow( FlowRow(
modifier = modifier, modifier = modifier,
mainAxisSpacing = 4.dp, mainAxisSpacing = 4.dp,
crossAxisSpacing = 4.dp, crossAxisSpacing = 4.dp,
mainAxisAlignment = mainAxisAlignment, mainAxisAlignment = mainAxisAlignment,
) { ) {
reactionsState.reactions.forEach { reaction -> reactions.forEach { reaction ->
MessagesReactionButton( MessagesReactionButton(
reaction = reaction, content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
onClick = { onReactionClicked(reaction.key) } onClick = { onReactionClick(reaction.key) }
) )
} }
MessagesMoreReactionsButton( when (expandableState) {
onClick = onMoreReactionsClicked ExpandableState.Expanded ->
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text(
text = stringResource(id = R.string.screen_room_timeline_less_reactions)
),
onClick = onCollapseClick,
)
is ExpandableState.Collapsed -> {
val hidden = expandableState.hidden
MessagesReactionButton(
content = MessagesReactionsButtonContent.Text(
text = pluralStringResource(id = R.plurals.screen_room_timeline_more_reactions, hidden, hidden)
),
onClick = onExpandClick,
) )
} }
ExpandableState.None -> {
// No expand or collapse action available
}
}
MessagesReactionButton(
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
onClick = onMoreReactionsClick
)
}
@DayNightPreviews
@Composable
fun TimelineItemReactionsViewPreview() = ElementPreview {
ContentToPreview(
reactions = aTimelineItemReactions(count = 1).reactions,
expandableState = ExpandableState.None,
)
} }
@Preview @DayNightPreviews
@Composable @Composable
internal fun TimelineItemReactionsViewLightPreview() = fun TimelineItemReactionsViewCollapsedPreview() = ElementPreview {
ElementPreviewLight { ContentToPreview() } ContentToPreview(
reactions = aTimelineItemReactions(count = 3).reactions,
expandableState = ExpandableState.Collapsed(hidden = 7),
)
}
@Preview @DayNightPreviews
@Composable @Composable
internal fun TimelineItemReactionsViewDarkPreview() = fun TimelineItemReactionsViewExpandedPreview() = ElementPreview {
ElementPreviewDark { ContentToPreview() } ContentToPreview(
reactions = aTimelineItemReactions(count = 10).reactions,
expandableState = ExpandableState.Expanded,
)
}
@Composable @Composable
private fun ContentToPreview() { private fun ContentToPreview(
reactions: ImmutableList<AggregatedReaction>,
expandableState: ExpandableState
) {
TimelineItemReactionsView( TimelineItemReactionsView(
reactionsState = aTimelineItemReactions(), reactions = reactions,
expandableState = expandableState,
mainAxisAlignment = FlowMainAxisAlignment.Center, mainAxisAlignment = FlowMainAxisAlignment.Center,
onReactionClicked = {}, onReactionClick = {},
onMoreReactionsClicked = {}, onMoreReactionsClick = {},
onExpandClick = {},
onCollapseClick = {}
) )
} }
@@ -4,6 +4,11 @@
<item quantity="one">"%1$d room change"</item> <item quantity="one">"%1$d room change"</item>
<item quantity="other">"%1$d room changes"</item> <item quantity="other">"%1$d room changes"</item>
</plurals> </plurals>
<string name="screen_room_timeline_less_reactions">"Show less"</string>
<plurals name="screen_room_timeline_more_reactions">
<item quantity="other">"%1$d more"</item>
</plurals>
<string name="screen_room_timeline_add_reaction">"Add emoji"</string>
<string name="screen_room_attachment_source_camera">"Camera"</string> <string name="screen_room_attachment_source_camera">"Camera"</string>
<string name="screen_room_attachment_source_camera_photo">"Take photo"</string> <string name="screen_room_attachment_source_camera_photo">"Take photo"</string>
<string name="screen_room_attachment_source_camera_video">"Record a video"</string> <string name="screen_room_attachment_source_camera_video">"Record a video"</string>
@@ -28,6 +28,13 @@ import androidx.compose.ui.unit.TextUnit
@Composable @Composable
fun Dp.toSp(): TextUnit = with(LocalDensity.current) { toSp() } fun Dp.toSp(): TextUnit = with(LocalDensity.current) { toSp() }
/**
* Convert Sp to Dp, regarding current density.
* Can be used for instance to use Sp unit for size.
*/
@Composable
fun TextUnit.toDp(): Dp = with(LocalDensity.current) { toDp() }
/** /**
* Convert Px value to Dp, regarding current density. * Convert Px value to Dp, regarding current density.
*/ */
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c8527bc63aceac59916ca5dfe9c73e9fd1309b957b26945e261849cb1b658253
size 6143
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:836fc6c1689c9be78cac4bcd0cfa4cb8fb5f8ed36ace9159889b2404feb8f52e
size 6095
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:048cafa3f3d2aa0ec9122c84a6a6fe39d16549021bbbe286c436900c81fb8873
size 6519
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5648d6a428f4b7fa20c9981c6e564555bc04ec6b0911028507e01f90a56a44cf
size 7414
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d76cd06cf3b36aeb3de567605f6ebc33508cb074049ad854ccec5cf5b382ff36
size 7263
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ab3023d14ccd579dc350bc019f94ea2ddc59ac55238d04f4a96c34a1036361df
size 8185
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:211784ef45a8d357c2a9e9d8b4a16ea10be71d14613ab1e32119ed8fa584831a
size 6365
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8e2085deec0b6096437c82204e8da30e143f3c669c9759dbf34c26d63bbf4442
size 7232
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c01fa8074194cf65443f2f321d3ce6b313b7ade8aa7f41155acbfd2a1728e210
size 7147
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:47ba3b562872e6078e48bd3168c3da54515ea7196fc5310e520a1876972bad16
size 8020
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b024b20585c98eddb6032795e2a434bf675f7254d1f3a649ccccbe4fb2351c8f
size 7844
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb3b2ccab39aa4cb7afde21d8a9b2c0d18f54d9f397e7020413281b3bfebaa2f
size 7634
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6ac13d38a9aaf4341ff8845141dcbc90033f7105750e0ea212bb7b4387dd72ad oid sha256:b8ca3e1c8ce3c9476cdc7db46aebc8b6970486ff01e05b82c162256373c8957f
size 141063 size 141055
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:651535d566f1bf250b0856d3fd6d3726aa2f6714d44217701e08c805d3771347 oid sha256:2bf89d0228a9771360eaad8a0bc514731153a351551df427cf7f7e44b886edfe
size 145406 size 145398
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7a1c0e5249236abdde5de337f2fe8c8f58bb2ad8bf31fe2f4214dbe35f327915 oid sha256:82b4fb72cd3517b4eedeba3172cc428ba67199df816c686d998027ed021c1bb0
size 129834 size 84164
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1276ef584823b28330ee3facc4cbebdaf9bc6cb02f59af2c023a740677940f02 oid sha256:7c95c7df9616dca071528f4185e63485e7e738e51e595d828f94b08205c6e33d
size 133065 size 87695
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:47760aad8040e34898f8d80d207a6fdb6255255f72bcc2f8cb99801bedebabd4 oid sha256:02f2eb6e37f1022aa13cbcdfc34e7955b99ee4c3d6b35668b548afc94d33ae31
size 119160 size 119137
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5896cb2b9847d2f67bc6d7c39a2499286c59945b9e2c4961e191e547739f41e7 oid sha256:d50e5ac4433e4a44091623f1813373884007506002cc0c657ca7231a6e309462
size 123936 size 123938
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6cc714b45529c63b455cd79fbec2057edf17eddc7352f01ed299115766894a33
size 13729
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1a633da75e5518aacf9b9be08afc27e0627cdebb86544f2de5b698103f1703c
size 13533
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:61eda889828d68c309da1ca9879e6b6436fdedb5abea86abfb227caea866efdb
size 15396
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a751a46c2aff2337d941442709910d442dd48496f8886e8dc510df17feea062c
size 27180
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71f688355e6a18472424c0958fe1c3baea7e53b4cc4d2d54806434174ff67e71
size 27002
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e99b245b027b2610947863591f7593bf273cf328ff64344b575423635e03ac24
size 15428
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:82550de45d39c52f34fca45f84b6e9eff53dfef6d2e9622dcaf49c707927d665
size 7873
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc39041ac035aa372640ca7b2e9af254564aa10ce7a7f4c55fbc4bca9f6dc6d9
size 7844
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0b5e8b131784aa74261ebeef065f027c8ed4ab6188b390bc9849d3d4bac747e2 oid sha256:6730052131679024056a86514c28639bd975277e46aefefa92b5b31a53a4037d
size 51648 size 51674
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8d26f0735eda30013ad31475347978d778e231e6b2ece7a3528a0b9a2f3743c5 oid sha256:a9843fe657398792239c1058a0814e639072ebd6673bb2ecf8ff3ae309add40c
size 63185 size 63126
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9eba210c27876f999977e75874b4ed2d89f591e16734133ae140f5e82e7fe879 oid sha256:43ee66a5472f660e9d0f1dfa3876406d5e05723b0c86f0adeb0e5f77bf0c1d27
size 49724 size 49740
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e88421b82ca846b94a0eba7a00d424dd077586ebdc28c5364cdb07e4f2d6e2ee oid sha256:b5dfdfdd9b5ffeee9d9af468c84144c85fcf16f145bfd9700825cf5bcb324cc2
size 66031 size 65969
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3e86e8491e170371cb229f2e58a1fda5efe5ac3dc3b87b4f928bfb74d9fd3ded oid sha256:cc84c10136a833f1f418de126535cccad09e6ea75f31ca7dd46f85e5eb1b272b
size 56368 size 56377
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:33e25642dd17c10bb39f576f33ebd673619b76c111fd4b5834cd51644cb57b8b oid sha256:14a299ad0ed6520f347d53206964d91691ce9e281001c93e3b5393d0a169d753
size 229779 size 229787
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7ae3b529f45c9a5c739193eee0dfd9c4ad73310deae8eb50e1b0f57205de84a6 oid sha256:9f2c3a472d0aad53f531852343b572e0ad980fbd658a7b84779bfceb635e8569
size 230761 size 230768
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:eece03c2cf471a428d6f383ab3ba2e763a333f10bedf6b4d7a86efd35ffb960d oid sha256:57cce1248b91028d53219b23d52c751a8ab9e85c475c11e60bb91f02b774c854
size 70996 size 70948
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:df108cf01c6ef1a061d7f84bdb283b9a7cea45ed3a6eea703c110a6981435733 oid sha256:131801a6f63c5f0738ec5303a631161ef651da6a714ce60eec9fc693d1fbccae
size 85260 size 85282
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b0efa0cbf46ffc038bd7ff522b0f81587438f564dd9a7ed7b5b995ea9b3fda31 oid sha256:dc88c24d4f5180ae3cfc1d936d901f3e31feaba8fabfcb8a67477f46ab10912e
size 174560 size 174514
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5ee5b3a256c3ebac9accb7a4361fd1e29c88e21d4c8b10be293bff368d3dd966 oid sha256:7b8110ca00a0889ac02c1ec820f7e1750d57f84ec42b033d662898e5ba06bd79
size 165233 size 165223
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:538df93c96ec8d52f370607cb460c0c507cbf1d716cd8150979bc555e7c4dd92 oid sha256:1075736c7eebc685290898a39d844cbd63984e031360a913904ace7ea8bd9a0e
size 53061 size 53065
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:09f899772bfd8fb4fcff729ba2b4236a842b6aba27a7c7efcbe15f60379ac067 oid sha256:192ba37d34e3634559d25fcaf5c214fd53784bb304a1396a3e7704b498eeae44
size 65090 size 65018
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a9cf9fd968fe92c8e5f4402182689d400a554e423905a8d69a065e8ecf7461ba oid sha256:30fbb6bca73b70b5466a95c1e99e35a9b722e768e192cc00edf16782881d0d69
size 53294 size 53302
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3565ffe08fd9472a2a3298e0d1c65b2066bbf318f7c3564d3ca8022a22815750 oid sha256:ae3cf36193474a4f2c057df5aa8d6caaca9cecf13abcd92998d8d6b46a054dc3
size 65710 size 65739
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:85c2a8b9910c6f751902a40702fe605b5b50ee22b9d61d258500c0c9dfed07dd oid sha256:ce73d9459b8d41a9a47de3eef6627110990e3d077d81e240e227c7ad70d8f90e
size 51179 size 51200
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e07e00d3940bbc0f0cee49c76f82a1ad638616ecbab98599fb94c871628a8557 oid sha256:2d437b4eb2aec598bdd2ef3c20ce186e18d61697fc6c4c53c8e6ed264dbdd377
size 68782 size 68813
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2858fb6aff4161239713634efe4040878a3e836173386c80675b962c9f87131f oid sha256:0317b3b8a387f168037a196e685d27fd89515048bf6f1cb28820bd4bcff5fb49
size 58413 size 58425
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9dff8dd2a36d8cb5521e7b7f4e395f87038dbd1c4886e92c94be122d18248f17 oid sha256:65f1933991e74965f8f0848c88e54305d84dea3820ba75722e831ee5b9b44e75
size 230285 size 230252
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:873e746f40b06d4cfe2af496dd4bf2d6ad761412ae3c3e72b6184282b2225346 oid sha256:df7c258ab50b5baeccf22434b343226d057b9127ce7a4a74489883e6aca98e94
size 231253 size 231221
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4d49e59b6ed1ccd2fbd22ed25ffd31619fda33b709a1af8546ceed2328fad6f0 oid sha256:c80f71a5db4355418c4ade1e46536ff44e0f97bb0177a658e5bec0da367c13e6
size 73767 size 73764
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:db4b63cae9b106c125e53296875d3032ea1cd2a455c39c39bb8bd3bd514ab2ac oid sha256:50ee9e9ca2bf1eb8d0a01210a919eb8370a8125ff6d63c9934861296d781148a
size 89827 size 89841
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a1800f8b515a557d93f25ef1c2dae4f11b29de95f4ee9af649bb40bad1014966 oid sha256:d077c1ca9d9f373b14e1cd657b8d112dd255b9956176cdc252155a53d433fa99
size 364477 size 364460
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:dc59dde6b5b1f0b894de2dc91516e2e9ae35d9b1d0d523b6a488b9366c57289d oid sha256:eeb039373b3898a42bbfd89c7bcb61b152cb45498f50994297a1f99ac13ae60b
size 323483 size 323473
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:eaa7b8ef747034fd29d383902436d491c4544e5a14f455fa29697120bbbcb204 oid sha256:773ce37927e091ed7eb458e2c1260e08c947bb2d737f5977b3c1443962938a14
size 54917 size 54940
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:88788496a3be3ef0a464377869ef1ca2e7abe4eb8b96c5d69b819afed0470683 oid sha256:af43a77aadb7fdf6e8fa2472836ecae4d1a764b2995a1dabff2f6c2ba9e35ccb
size 67671 size 67704
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:341ead0eb1a09351740738bd540bcf8c6c96862c3370b95b764617335e5b4f22 oid sha256:cd827d099fae0d46cfed9f9beaa8c8ee4231d79b86e3d9e5a0db435a7a89adab
size 52510 size 52537
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:96b7a3cd0f79994d4195e4abe1ee07381f98c6c5c51760226a995efc09617a03 oid sha256:da41884d88decec8e9fa742bd901916a74d6c5b321abc7f8bc6f0d03ec923409
size 54039 size 54062
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e8302d3b48c733500927ba7e5878f001bd7dced72ebd8386142931d557757028 oid sha256:497a831673a4837a548d3cd5a83f8c2d5ff379c93b8080994c5c18470fa730a7
size 52706 size 52731
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2c6dbd33edb880fea91d290a24260c17573d4d36140f12a472c28c77441c8684 oid sha256:beb5bf49b50d78bfa607190fee545d5664be56becf9cbb5ca930b0d3779c4c52
size 55697 size 55667
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:159b54d8c45229e37a3ca9748ab52a6ef448ad93aed209b4e770e94ff5196373 oid sha256:204246b9d5913925f9d3899a74c97d602aeb81b262797f4fbef1a3c20b4cbee1
size 50706 size 50665
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f1ffe365eedfe6b054a268fb3a6a10599df9152d5008359ea95ab7b90b13a33e oid sha256:d8c430b4caaafe56526e2436238675156948032d0877d72330f2cac03d2d2568
size 54389 size 54405
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2ddcc0519e33b2f6a93d873d2e6aee000ca5a38d2dbd2122869e0befa0c78c7d oid sha256:cec94dbc2f0515af4bf3c79f9376e37a2f2b6e7b1227af36a20963ac528b766a
size 55949 size 55964
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:96fdf93ee3e67f4f575928a842ce309c4675d361526b9b2714ef2c3fe7167ff4 oid sha256:21093f6d5209487fbc6218ad37a1daf62c946f38cefe8c1e82da8e9223335da7
size 54675 size 54696
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5694004acfac3f1c573dd7929897e01ea9b7e7927cc209e6f4b9bd5568c5ed96 oid sha256:7a4c2a1d91f1ba87019ee9ddc254184e6d241284ff15ebf3cca2ad2eeddc3a78
size 57714 size 57666
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d2ff9337e57cecce1404c44b7b9eb7d6fce65a22fb94326429bd25041e59a588 oid sha256:2f117563be9539efa9cc3790a3ca0ff8ff7718184a9a00eb2c540cfcb36f5d58
size 52250 size 52251