feat: Revamp UnlockScreen layout and enhance user experience with improved avatar display and dropdown account selection
This commit is contained in:
@@ -345,74 +345,31 @@ fun UnlockScreen(
|
||||
Modifier.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.imePadding()
|
||||
.padding(horizontal = 24.dp)
|
||||
.padding(horizontal = 32.dp)
|
||||
.statusBarsPadding(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
|
||||
// Rosetta Logo
|
||||
// 🔥 Большой аватар пользователя - главный элемент (как в desktop)
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(300)) + scaleIn(tween(300, easing = FastOutSlowInEasing))
|
||||
enter = fadeIn(tween(400)) + scaleIn(tween(400, easing = FastOutSlowInEasing))
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rosetta_icon),
|
||||
contentDescription = "Rosetta",
|
||||
modifier = Modifier.size(100.dp).clip(CircleShape)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.clip(RoundedCornerShape(28.dp))
|
||||
.background(
|
||||
if (selectedAccount != null) {
|
||||
val colors = getAvatarColor(selectedAccount!!.publicKey, isDarkTheme)
|
||||
colors.backgroundColor
|
||||
} else {
|
||||
cardBackground
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Title
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(400, delayMillis = 200))
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Welcome Back",
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
AnimatedVisibility(visible = visible, enter = fadeIn(tween(600, delayMillis = 300))) {
|
||||
Text(
|
||||
text = "Select your account and enter password",
|
||||
fontSize = 16.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// Account Selector Card
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(400, delayMillis = 350))
|
||||
) {
|
||||
Column {
|
||||
// Account selector dropdown
|
||||
Card(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable(enabled = accounts.size > 1) {
|
||||
isDropdownExpanded = !isDropdownExpanded
|
||||
},
|
||||
colors = CardDefaults.cardColors(containerColor = cardBackground),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Avatar
|
||||
if (selectedAccount != null) {
|
||||
val database = RosettaDatabase.getDatabase(context)
|
||||
val avatarRepository = remember(selectedAccount!!.publicKey) {
|
||||
@@ -421,157 +378,98 @@ fun UnlockScreen(
|
||||
AvatarImage(
|
||||
publicKey = selectedAccount!!.publicKey,
|
||||
avatarRepository = avatarRepository,
|
||||
size = 48.dp,
|
||||
size = 120.dp,
|
||||
isDarkTheme = isDarkTheme
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = "?",
|
||||
fontSize = 48.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// Account info
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
// 🔥 Имя пользователя с иконкой переключения (как в desktop)
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(tween(400, delayMillis = 150))
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.then(
|
||||
if (accounts.size > 1)
|
||||
Modifier.clickable { isDropdownExpanded = !isDropdownExpanded }
|
||||
else Modifier
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = selectedAccount?.name ?: "Select Account",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (selectedAccount != null) {
|
||||
val displayText = selectedAccount!!.encryptedAccount.username
|
||||
?: selectedAccount!!.publicKey.take(20) + "..."
|
||||
Text(
|
||||
text = displayText,
|
||||
fontSize = 13.sp,
|
||||
color = secondaryTextColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown arrow with rotation (only show if multiple accounts)
|
||||
// Иконка переключения аккаунта (только если > 1 аккаунта)
|
||||
if (accounts.size > 1) {
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Icon(
|
||||
imageVector = TablerIcons.ChevronDown,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor.copy(alpha = 0.6f),
|
||||
modifier =
|
||||
Modifier.size(24.dp).graphicsLayer {
|
||||
rotationZ = 180f * dropdownProgress
|
||||
}
|
||||
contentDescription = "Switch account",
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdown list with animation
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Подзаголовок
|
||||
AnimatedVisibility(visible = visible, enter = fadeIn(tween(400, delayMillis = 200))) {
|
||||
Text(
|
||||
text = "Enter password to unlock",
|
||||
fontSize = 15.sp,
|
||||
color = secondaryTextColor,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(40.dp))
|
||||
|
||||
// 🔥 Dropdown для выбора аккаунта (показывается только при клике на иконку)
|
||||
AnimatedVisibility(
|
||||
visible = isDropdownExpanded && accounts.size > 1,
|
||||
enter =
|
||||
fadeIn(tween(150)) +
|
||||
expandVertically(
|
||||
expandFrom = Alignment.Top,
|
||||
animationSpec =
|
||||
tween(200, easing = FastOutSlowInEasing)
|
||||
),
|
||||
exit =
|
||||
fadeOut(tween(100)) +
|
||||
shrinkVertically(
|
||||
shrinkTowards = Alignment.Top,
|
||||
animationSpec = tween(150)
|
||||
)
|
||||
enter = fadeIn(tween(200)) + expandVertically(expandFrom = Alignment.Top),
|
||||
exit = fadeOut(tween(150)) + shrinkVertically(shrinkTowards = Alignment.Top)
|
||||
) {
|
||||
Card(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.heightIn(
|
||||
max =
|
||||
if (accounts.size > 5) 350.dp
|
||||
else ((accounts.size * 64 + 70).dp)
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 20.dp)
|
||||
.heightIn(max = if (accounts.size > 5) 300.dp else ((accounts.size * 64 + 16).dp)),
|
||||
colors = CardDefaults.cardColors(containerColor = cardBackground),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
Column {
|
||||
// Search field - only show if more than 3 accounts
|
||||
if (accounts.size > 3) {
|
||||
OutlinedTextField(
|
||||
value = searchQuery,
|
||||
onValueChange = { searchQuery = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
"Search accounts...",
|
||||
color =
|
||||
secondaryTextColor.copy(
|
||||
alpha = 0.6f
|
||||
)
|
||||
)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
TablerIcons.Search,
|
||||
contentDescription = null,
|
||||
tint = secondaryTextColor.copy(alpha = 0.6f)
|
||||
)
|
||||
},
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor =
|
||||
Color.Transparent,
|
||||
focusedContainerColor =
|
||||
Color.Transparent,
|
||||
unfocusedContainerColor =
|
||||
Color.Transparent,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor,
|
||||
cursorColor = PrimaryBlue
|
||||
),
|
||||
singleLine = true,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = 12.dp,
|
||||
vertical = 8.dp
|
||||
)
|
||||
.focusRequester(searchFocusRequester),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
|
||||
Divider(
|
||||
color =
|
||||
if (isDarkTheme) Color(0xFF3A3A3A)
|
||||
else Color(0xFFE0E0E0),
|
||||
thickness = 0.5.dp
|
||||
)
|
||||
}
|
||||
|
||||
// Account list
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(
|
||||
vertical =
|
||||
if (accounts.size <= 3) 8.dp
|
||||
else 0.dp
|
||||
)
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
|
||||
) {
|
||||
items(filteredAccounts, key = { it.publicKey }) { account ->
|
||||
val isSelected =
|
||||
account.publicKey == selectedAccount?.publicKey
|
||||
val itemScale by
|
||||
animateFloatAsState(
|
||||
targetValue = if (isSelected) 1f else 0.98f,
|
||||
label = "itemScale"
|
||||
)
|
||||
items(accounts, key = { it.publicKey }) { account ->
|
||||
val isSelected = account.publicKey == selectedAccount?.publicKey
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.scale(itemScale)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clickable {
|
||||
selectedAccount = account
|
||||
@@ -580,19 +478,12 @@ fun UnlockScreen(
|
||||
error = null
|
||||
}
|
||||
.background(
|
||||
if (isSelected)
|
||||
PrimaryBlue.copy(
|
||||
alpha = 0.1f
|
||||
)
|
||||
if (isSelected) PrimaryBlue.copy(alpha = 0.1f)
|
||||
else Color.Transparent
|
||||
)
|
||||
.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 12.dp
|
||||
),
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Avatar
|
||||
val database = RosettaDatabase.getDatabase(context)
|
||||
val avatarRepository = remember(account.publicKey) {
|
||||
AvatarRepository(context, database.avatarDao(), account.publicKey)
|
||||
@@ -610,9 +501,7 @@ fun UnlockScreen(
|
||||
Text(
|
||||
text = account.name,
|
||||
fontSize = 15.sp,
|
||||
fontWeight =
|
||||
if (isSelected) FontWeight.SemiBold
|
||||
else FontWeight.Normal,
|
||||
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
@@ -638,32 +527,14 @@ fun UnlockScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredAccounts.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = "No accounts found",
|
||||
color = secondaryTextColor,
|
||||
fontSize = 14.sp,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().padding(24.dp),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// Password Field
|
||||
// 🔥 Минималистичное поле пароля
|
||||
AnimatedVisibility(
|
||||
visible = visible && !isDropdownExpanded,
|
||||
enter = fadeIn(tween(400, delayMillis = 400))
|
||||
enter = fadeIn(tween(400, delayMillis = 300))
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
@@ -671,8 +542,12 @@ fun UnlockScreen(
|
||||
password = it
|
||||
error = null
|
||||
},
|
||||
label = { Text("Password") },
|
||||
placeholder = { Text("Enter your password") },
|
||||
placeholder = {
|
||||
Text(
|
||||
"Password",
|
||||
color = secondaryTextColor.copy(alpha = 0.6f)
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
if (passwordVisible) VisualTransformation.None
|
||||
@@ -683,27 +558,26 @@ fun UnlockScreen(
|
||||
imageVector =
|
||||
if (passwordVisible) TablerIcons.EyeOff
|
||||
else TablerIcons.Eye,
|
||||
contentDescription = if (passwordVisible) "Hide" else "Show"
|
||||
contentDescription = if (passwordVisible) "Hide" else "Show",
|
||||
tint = secondaryTextColor
|
||||
)
|
||||
}
|
||||
},
|
||||
isError = error != null,
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = PrimaryBlue,
|
||||
unfocusedBorderColor =
|
||||
if (isDarkTheme) Color(0xFF4A4A4A)
|
||||
else Color(0xFFD0D0D0),
|
||||
focusedLabelColor = PrimaryBlue,
|
||||
cursorColor = PrimaryBlue,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedContainerColor = cardBackground,
|
||||
unfocusedContainerColor = cardBackground,
|
||||
focusedTextColor = textColor,
|
||||
unfocusedTextColor = textColor,
|
||||
errorBorderColor = Color(0xFFE53935)
|
||||
cursorColor = PrimaryBlue,
|
||||
errorBorderColor = Color(0xFFE53935),
|
||||
errorContainerColor = cardBackground
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
keyboardOptions =
|
||||
KeyboardOptions(
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = ImeAction.Done
|
||||
)
|
||||
@@ -722,13 +596,11 @@ fun UnlockScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Unlock Button
|
||||
// 🔥 Градиентная кнопка Unlock
|
||||
AnimatedVisibility(
|
||||
visible = visible && !isDropdownExpanded,
|
||||
enter = fadeIn(tween(400, delayMillis = 500))
|
||||
enter = fadeIn(tween(400, delayMillis = 400))
|
||||
) {
|
||||
Column {
|
||||
// Check if biometric unlock is available for selected account
|
||||
val canUseBiometric = remember(selectedAccount, isBiometricEnabled, biometricAvailable) {
|
||||
selectedAccount != null &&
|
||||
isBiometricEnabled &&
|
||||
@@ -742,11 +614,8 @@ fun UnlockScreen(
|
||||
|
||||
val showBiometricButton = canUseBiometric && hasSavedPassword
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Password Unlock Button
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
// Основная кнопка Unlock
|
||||
Button(
|
||||
onClick = {
|
||||
if (selectedAccount == null) {
|
||||
@@ -773,20 +642,19 @@ fun UnlockScreen(
|
||||
},
|
||||
enabled = selectedAccount != null && password.isNotEmpty() && !isUnlocking,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(56.dp),
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
.fillMaxWidth()
|
||||
.height(54.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue,
|
||||
contentColor = Color.White,
|
||||
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
||||
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
||||
disabledContainerColor = PrimaryBlue.copy(alpha = 0.4f),
|
||||
disabledContentColor = Color.White.copy(alpha = 0.6f)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
shape = RoundedCornerShape(14.dp)
|
||||
) {
|
||||
if (isUnlocking) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
modifier = Modifier.size(22.dp),
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
@@ -796,62 +664,91 @@ fun UnlockScreen(
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(text = "Unlock", fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
}
|
||||
|
||||
// Biometric Unlock Button
|
||||
// Кнопка биометрии (если доступна)
|
||||
if (showBiometricButton) {
|
||||
Button(
|
||||
onClick = {
|
||||
tryBiometricUnlock()
|
||||
},
|
||||
enabled = !isUnlocking,
|
||||
modifier = Modifier
|
||||
.width(56.dp)
|
||||
.height(56.dp),
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue,
|
||||
contentColor = Color.White,
|
||||
disabledContainerColor = PrimaryBlue.copy(alpha = 0.5f),
|
||||
disabledContentColor = Color.White.copy(alpha = 0.5f)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
contentPadding = PaddingValues(0.dp)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
TextButton(
|
||||
onClick = { tryBiometricUnlock() },
|
||||
enabled = !isUnlocking
|
||||
) {
|
||||
Icon(
|
||||
imageVector = TablerIcons.Fingerprint,
|
||||
contentDescription = "Unlock with biometric",
|
||||
modifier = Modifier.size(28.dp)
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "Use biometric",
|
||||
color = PrimaryBlue,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// Create New Account button
|
||||
// 🔥 Footer - Create/Import account
|
||||
AnimatedVisibility(
|
||||
visible = visible && !isDropdownExpanded,
|
||||
enter = fadeIn(tween(400, delayMillis = 600))
|
||||
enter = fadeIn(tween(400, delayMillis = 500))
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
) {
|
||||
// Разделитель
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(0.5.dp)
|
||||
.background(secondaryTextColor.copy(alpha = 0.3f))
|
||||
)
|
||||
Text(
|
||||
text = " or ",
|
||||
color = secondaryTextColor,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(0.5.dp)
|
||||
.background(secondaryTextColor.copy(alpha = 0.3f))
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
TextButton(onClick = onSwitchAccount) {
|
||||
Icon(
|
||||
imageVector = TablerIcons.UserPlus,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = "Create New Account", color = PrimaryBlue, fontSize = 15.sp)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
Text(
|
||||
text = "Create or import account",
|
||||
color = PrimaryBlue,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user