Collapse long lists of message reactions (#806)
--------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
@@ -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.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
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.unit.dp
|
||||
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.AggregatedReactionProvider
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.ElementTextStyles
|
||||
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.Text
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
|
||||
@Composable
|
||||
fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
val buttonColor = if (reaction.isHighlighted) {
|
||||
fun MessagesReactionButton(
|
||||
onClick: () -> Unit,
|
||||
content: MessagesReactionsButtonContent,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val buttonColor = if (content.isHighlighted) {
|
||||
ElementTheme.colors.bgSubtlePrimary
|
||||
} else {
|
||||
ElementTheme.colors.bgSubtleSecondary
|
||||
}
|
||||
val borderColor = if (reaction.isHighlighted) {
|
||||
|
||||
val borderColor = if (content.isHighlighted) {
|
||||
ElementTheme.colors.borderInteractivePrimary
|
||||
} else {
|
||||
buttonColor
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.background(Color.Transparent)
|
||||
@@ -74,35 +89,91 @@ fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Mo
|
||||
.padding(vertical = 4.dp, horizontal = 10.dp),
|
||||
color = buttonColor
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = reaction.key, fontSize = 15.sp, lineHeight = 20.sp
|
||||
)
|
||||
if (reaction.count > 1) {
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = reaction.count.toString(),
|
||||
color = if (reaction.isHighlighted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
when (content) {
|
||||
is MessagesReactionsButtonContent.Icon -> IconContent(imageVector = content.imageVector)
|
||||
is MessagesReactionsButtonContent.Text -> TextContent(text = content.text)
|
||||
is MessagesReactionsButtonContent.Reaction -> ReactionContent(reaction = content.reaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun MessagesReactionButtonLightPreview(@PreviewParameter(AggregatedReactionProvider::class) reaction: AggregatedReaction) =
|
||||
ElementPreviewLight { ContentToPreview(reaction) }
|
||||
sealed class MessagesReactionsButtonContent {
|
||||
data class Text(val text: String) : MessagesReactionsButtonContent()
|
||||
data class Icon(val imageVector: ImageVector) : MessagesReactionsButtonContent()
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun MessagesReactionButtonDarkPreview(@PreviewParameter(AggregatedReactionProvider::class) reaction: AggregatedReaction) =
|
||||
ElementPreviewDark { ContentToPreview(reaction) }
|
||||
data class Reaction(val reaction: AggregatedReaction) : MessagesReactionsButtonContent()
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview(reaction: AggregatedReaction) {
|
||||
MessagesReactionButton(reaction, onClick = { })
|
||||
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 = reaction.key,
|
||||
fontSize = 15.sp, lineHeight = reactionEmojiLineHeight
|
||||
)
|
||||
if (reaction.count > 1) {
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = reaction.count.toString(),
|
||||
color = if (reaction.isHighlighted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
|
||||
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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ private fun TimelineItemEventRowContent(
|
||||
|
||||
// Reactions
|
||||
if (event.reactionsState.reactions.isNotEmpty()) {
|
||||
TimelineItemReactionsView(
|
||||
TimelineItemReactions(
|
||||
reactionsState = event.reactionsState,
|
||||
mainAxisAlignment = if (event.isMine) FlowMainAxisAlignment.End else FlowMainAxisAlignment.Start,
|
||||
onReactionClicked = onReactionClicked,
|
||||
|
||||
@@ -16,59 +16,184 @@
|
||||
|
||||
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.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.tooling.preview.Preview
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.flowlayout.FlowMainAxisAlignment
|
||||
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.aTimelineItemReactions
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
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 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
|
||||
fun TimelineItemReactionsView(
|
||||
fun TimelineItemReactions(
|
||||
reactionsState: TimelineItemReactions,
|
||||
mainAxisAlignment: FlowMainAxisAlignment,
|
||||
onReactionClicked: (emoji: String) -> Unit,
|
||||
onMoreReactionsClicked: () -> Unit,
|
||||
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(
|
||||
modifier = modifier,
|
||||
mainAxisSpacing = 4.dp,
|
||||
crossAxisSpacing = 4.dp,
|
||||
mainAxisAlignment = mainAxisAlignment,
|
||||
) {
|
||||
reactionsState.reactions.forEach { reaction ->
|
||||
reactions.forEach { reaction ->
|
||||
MessagesReactionButton(
|
||||
reaction = reaction,
|
||||
onClick = { onReactionClicked(reaction.key) }
|
||||
content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
|
||||
onClick = { onReactionClick(reaction.key) }
|
||||
)
|
||||
}
|
||||
MessagesMoreReactionsButton(
|
||||
onClick = onMoreReactionsClicked
|
||||
when (expandableState) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun TimelineItemReactionsViewLightPreview() =
|
||||
ElementPreviewLight { ContentToPreview() }
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun TimelineItemReactionsViewDarkPreview() =
|
||||
ElementPreviewDark { ContentToPreview() }
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview() {
|
||||
TimelineItemReactionsView(
|
||||
reactionsState = aTimelineItemReactions(),
|
||||
mainAxisAlignment = FlowMainAxisAlignment.Center,
|
||||
onReactionClicked = {},
|
||||
onMoreReactionsClicked = {},
|
||||
fun TimelineItemReactionsViewPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 1).reactions,
|
||||
expandableState = ExpandableState.None,
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun TimelineItemReactionsViewCollapsedPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 3).reactions,
|
||||
expandableState = ExpandableState.Collapsed(hidden = 7),
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun TimelineItemReactionsViewExpandedPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 10).reactions,
|
||||
expandableState = ExpandableState.Expanded,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview(
|
||||
reactions: ImmutableList<AggregatedReaction>,
|
||||
expandableState: ExpandableState
|
||||
) {
|
||||
TimelineItemReactionsView(
|
||||
reactions = reactions,
|
||||
expandableState = expandableState,
|
||||
mainAxisAlignment = FlowMainAxisAlignment.Center,
|
||||
onReactionClick = {},
|
||||
onMoreReactionsClick = {},
|
||||
onExpandClick = {},
|
||||
onCollapseClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<item quantity="one">"%1$d room change"</item>
|
||||
<item quantity="other">"%1$d room changes"</item>
|
||||
</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_photo">"Take photo"</string>
|
||||
<string name="screen_room_attachment_source_camera_video">"Record a video"</string>
|
||||
|
||||
@@ -28,6 +28,13 @@ import androidx.compose.ui.unit.TextUnit
|
||||
@Composable
|
||||
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.
|
||||
*/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user