mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
feat: Add more animations to recording start
This commit is contained in:
parent
b6346ef0e3
commit
096cf56436
@ -1,42 +0,0 @@
|
||||
package app.myzel394.alibi.ui.components.RecorderScreen.atoms
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RecordingProgress(
|
||||
recordingTime: Long,
|
||||
progress: Float,
|
||||
) {
|
||||
// Only show animation when the recording has just started
|
||||
val recordingJustStarted = recordingTime <= 1L
|
||||
var progressVisible by remember { mutableStateOf(!recordingJustStarted) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
progressVisible = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = progressVisible,
|
||||
enter = expandHorizontally(
|
||||
tween(1000)
|
||||
)
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress,
|
||||
modifier = Modifier
|
||||
.width(300.dp)
|
||||
)
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package app.myzel394.alibi.ui.components.RecorderScreen.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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.unit.dp
|
||||
import app.myzel394.alibi.ui.components.atoms.Pulsating
|
||||
import app.myzel394.alibi.ui.utils.formatDuration
|
||||
|
||||
@Composable
|
||||
fun RecordingTime(
|
||||
time: Long,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Pulsating {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.Red)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = formatDuration(time * 1000),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,30 +1,103 @@
|
||||
package app.myzel394.alibi.ui.components.RecorderScreen.molecules
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.EaseOutElastic
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.DeleteButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.PauseResumeButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.SaveButton
|
||||
import app.myzel394.alibi.ui.utils.RandomStack
|
||||
import app.myzel394.alibi.ui.utils.rememberInitialRecordingAnimation
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun RecordingControl(
|
||||
isPaused: Boolean,
|
||||
recordingTime: Long,
|
||||
onDelete: () -> Unit,
|
||||
onPauseResume: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
) {
|
||||
val animateIn = rememberInitialRecordingAnimation(recordingTime)
|
||||
|
||||
var deleteButtonAlphaIsIn by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val deleteButtonAlpha by animateFloatAsState(
|
||||
if (deleteButtonAlphaIsIn) 1f else 0f,
|
||||
label = "deleteButtonAlpha",
|
||||
animationSpec = tween(durationMillis = 500)
|
||||
)
|
||||
|
||||
var pauseButtonAlphaIsIn by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val pauseButtonAlpha by animateFloatAsState(
|
||||
if (pauseButtonAlphaIsIn) 1f else 0f,
|
||||
label = "pauseButtonAlpha",
|
||||
animationSpec = tween(durationMillis = 500)
|
||||
)
|
||||
|
||||
var saveButtonAlphaIsIn by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val saveButtonAlpha by animateFloatAsState(
|
||||
if (saveButtonAlphaIsIn) 1f else 0f,
|
||||
label = "saveButtonAlpha",
|
||||
animationSpec = tween(durationMillis = 500)
|
||||
)
|
||||
|
||||
LaunchedEffect(animateIn) {
|
||||
if (animateIn) {
|
||||
val stack = RandomStack.of(arrayOf(1, 2, 3).asIterable())
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
val next = stack.popRandom()
|
||||
when (next) {
|
||||
1 -> {
|
||||
deleteButtonAlphaIsIn = true
|
||||
}
|
||||
|
||||
2 -> {
|
||||
pauseButtonAlphaIsIn = true
|
||||
}
|
||||
|
||||
3 -> {
|
||||
saveButtonAlphaIsIn = true
|
||||
}
|
||||
}
|
||||
|
||||
delay(250)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
.weight(1f)
|
||||
.alpha(deleteButtonAlpha),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
DeleteButton(onDelete = onDelete)
|
||||
@ -32,6 +105,8 @@ fun RecordingControl(
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.alpha(pauseButtonAlpha),
|
||||
) {
|
||||
PauseResumeButton(
|
||||
isPaused = isPaused,
|
||||
@ -42,7 +117,8 @@ fun RecordingControl(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
.weight(1f)
|
||||
.alpha(saveButtonAlpha),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
SaveButton(onSave = onSave)
|
||||
|
@ -1,21 +1,34 @@
|
||||
package app.myzel394.alibi.ui.components.RecorderScreen.molecules
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
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.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.myzel394.alibi.R
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RecordingProgress
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RecordingTime
|
||||
import app.myzel394.alibi.ui.components.atoms.Pulsating
|
||||
import app.myzel394.alibi.ui.utils.formatDuration
|
||||
import app.myzel394.alibi.ui.utils.isSameDay
|
||||
import app.myzel394.alibi.ui.utils.rememberInitialRecordingAnimation
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
@ -28,51 +41,87 @@ fun RecordingStatus(
|
||||
recordingStart: LocalDateTime,
|
||||
maxDuration: Long,
|
||||
) {
|
||||
val animateIn = rememberInitialRecordingAnimation(recordingTime)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
RecordingTime(recordingTime)
|
||||
RecordingProgress(
|
||||
recordingTime = recordingTime,
|
||||
progress = progress,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.ui_recorder_info_saveNowTime,
|
||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).format(
|
||||
LocalDateTime.now().minusSeconds(
|
||||
min(
|
||||
maxDuration / 1000,
|
||||
recordingTime
|
||||
)
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Pulsating {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.Red)
|
||||
)
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = formatDuration(recordingTime * 1000),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = recordingStart.let {
|
||||
if (isSameDay(it, LocalDateTime.now())) {
|
||||
stringResource(
|
||||
R.string.ui_recorder_info_startTime_short,
|
||||
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
.format(it)
|
||||
)
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.ui_recorder_info_startTime_full,
|
||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)
|
||||
.format(it)
|
||||
)
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = animateIn,
|
||||
enter = expandHorizontally(
|
||||
tween(1000)
|
||||
)
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress,
|
||||
modifier = Modifier
|
||||
.width(300.dp)
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = animateIn, enter = fadeIn()) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.ui_recorder_info_saveNowTime,
|
||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)
|
||||
.format(
|
||||
LocalDateTime.now().minusSeconds(
|
||||
min(
|
||||
maxDuration / 1000,
|
||||
recordingTime
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = animateIn, enter = fadeIn()) {
|
||||
Text(
|
||||
text = recordingStart.let {
|
||||
if (isSameDay(it, LocalDateTime.now())) {
|
||||
stringResource(
|
||||
R.string.ui_recorder_info_startTime_short,
|
||||
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
.format(it)
|
||||
)
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.ui_recorder_info_startTime_full,
|
||||
DateTimeFormatter.ofLocalizedDateTime(
|
||||
FormatStyle.MEDIUM,
|
||||
FormatStyle.SHORT
|
||||
)
|
||||
.format(it)
|
||||
)
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,10 @@
|
||||
package app.myzel394.alibi.ui.components.RecorderScreen.organisms
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.width
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -24,13 +15,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.DeleteButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.PauseResumeButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RealtimeAudioVisualizer
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RecordingProgress
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RecordingTime
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.SaveButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.MicrophoneStatus
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingControl
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingStatus
|
||||
@ -80,6 +65,7 @@ fun AudioRecordingStatus(
|
||||
|
||||
RecordingControl(
|
||||
isPaused = audioRecorder.isPaused,
|
||||
recordingTime = audioRecorder.recordingTime,
|
||||
onDelete = {
|
||||
scope.launch {
|
||||
runCatching {
|
||||
|
@ -1,16 +1,10 @@
|
||||
package app.myzel394.alibi.ui.components.RecorderScreen.organisms
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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
|
||||
@ -19,29 +13,17 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CameraAlt
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.util.TypedValueCompat
|
||||
import app.myzel394.alibi.R
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.DeleteButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.PauseResumeButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RecordingProgress
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.RecordingTime
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.SaveButton
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.atoms.TorchStatus
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingControl
|
||||
import app.myzel394.alibi.ui.components.RecorderScreen.molecules.RecordingStatus
|
||||
@ -152,6 +134,7 @@ fun VideoRecordingStatus(
|
||||
|
||||
RecordingControl(
|
||||
isPaused = videoRecorder.isPaused,
|
||||
recordingTime = videoRecorder.recordingTime,
|
||||
onDelete = {
|
||||
scope.launch {
|
||||
runCatching {
|
||||
|
22
app/src/main/java/app/myzel394/alibi/ui/utils/animations.kt
Normal file
22
app/src/main/java/app/myzel394/alibi/ui/utils/animations.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package app.myzel394.alibi.ui.utils
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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
|
||||
|
||||
@Composable
|
||||
fun rememberInitialRecordingAnimation(recordingTime: Long): Boolean {
|
||||
// Only show animation when the recording has just started
|
||||
val recordingJustStarted = recordingTime <= 1L
|
||||
var progressVisible by rememberSaveable { mutableStateOf(!recordingJustStarted) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
progressVisible = true
|
||||
}
|
||||
|
||||
return progressVisible
|
||||
}
|
40
app/src/main/java/app/myzel394/alibi/ui/utils/stack.kt
Normal file
40
app/src/main/java/app/myzel394/alibi/ui/utils/stack.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package app.myzel394.alibi.ui.utils
|
||||
|
||||
// A stack that allows you to randomly pop items from it
|
||||
class RandomStack<T> {
|
||||
private val stack = mutableListOf<T>()
|
||||
|
||||
fun push(item: T) {
|
||||
stack.add(item)
|
||||
}
|
||||
|
||||
fun pop(): T {
|
||||
val index = stack.size - 1
|
||||
val item = stack[index]
|
||||
|
||||
stack.removeAt(index)
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
fun popRandom(): T {
|
||||
val index = (0..<stack.size).random()
|
||||
val item = stack[index]
|
||||
|
||||
stack.removeAt(index)
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
fun isEmpty() = stack.isEmpty()
|
||||
|
||||
companion object {
|
||||
fun <T> of(items: Iterable<T>): RandomStack<T> {
|
||||
val stack = RandomStack<T>()
|
||||
|
||||
items.forEach(stack::push)
|
||||
|
||||
return stack
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user