QR экран: обои вместо градиентов, circular reveal анимация при смене обоев и темы, кнопка sun/moon переключает тему приложения, cooldown 600ms. Убрана кнопка Start New Call с экрана звонков.
This commit is contained in:
@@ -1863,7 +1863,8 @@ fun MainScreen(
|
|||||||
onScanQr = {
|
onScanQr = {
|
||||||
navStack = navStack.filterNot { it is Screen.MyQr }
|
navStack = navStack.filterNot { it is Screen.MyQr }
|
||||||
pushScreen(Screen.QrScanner)
|
pushScreen(Screen.QrScanner)
|
||||||
}
|
},
|
||||||
|
onToggleTheme = onToggleTheme
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,11 +109,6 @@ fun CallsHistoryScreen(
|
|||||||
contentPadding = PaddingValues(bottom = 16.dp)
|
contentPadding = PaddingValues(bottom = 16.dp)
|
||||||
) {
|
) {
|
||||||
item(key = "start_new_call") {
|
item(key = "start_new_call") {
|
||||||
StartNewCallRow(
|
|
||||||
isDarkTheme = isDarkTheme,
|
|
||||||
onClick = onStartNewCall
|
|
||||||
)
|
|
||||||
Divider(color = dividerColor, thickness = 0.5.dp)
|
|
||||||
Text(
|
Text(
|
||||||
text = "You can add up to 200 participants to a call.",
|
text = "You can add up to 200 participants to a call.",
|
||||||
color = secondaryTextColor,
|
color = secondaryTextColor,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.rosetta.messenger.ui.qr
|
package com.rosetta.messenger.ui.qr
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.CubicBezierEasing
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -10,55 +13,69 @@ import androidx.compose.runtime.*
|
|||||||
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.Brush
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import androidx.compose.ui.graphics.BlendMode
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.CompositingStrategy
|
||||||
|
import androidx.compose.ui.graphics.ImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.graphics.drawscope.clipPath
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.positionInRoot
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
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 androidx.core.view.drawToBitmap
|
||||||
|
import com.rosetta.messenger.R
|
||||||
import com.rosetta.messenger.repository.AvatarRepository
|
import com.rosetta.messenger.repository.AvatarRepository
|
||||||
import com.rosetta.messenger.ui.components.AvatarImage
|
import com.rosetta.messenger.ui.components.AvatarImage
|
||||||
|
import com.rosetta.messenger.ui.settings.ThemeWallpapers
|
||||||
import com.rosetta.messenger.utils.QrCodeGenerator
|
import com.rosetta.messenger.utils.QrCodeGenerator
|
||||||
import compose.icons.TablerIcons
|
import compose.icons.TablerIcons
|
||||||
import compose.icons.tablericons.*
|
import compose.icons.tablericons.*
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.hypot
|
||||||
|
|
||||||
// Theme presets — gradient background + QR foreground color
|
// QR theme = wallpaper + QR color
|
||||||
private data class QrTheme(
|
private data class QrTheme(
|
||||||
val gradientColors: List<Color>,
|
val wallpaperId: String,
|
||||||
val qrColor: Int,
|
val qrColor: Int,
|
||||||
val name: String
|
val tintColor: Color,
|
||||||
|
val isDark: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
private val qrThemes = listOf(
|
private val qrThemes = listOf(
|
||||||
QrTheme(
|
QrTheme("dark_01", 0xFF5AA5FF.toInt(), Color(0xFF5AA5FF), true),
|
||||||
listOf(Color(0xFF667EEA), Color(0xFF764BA2)),
|
QrTheme("dark_02", 0xFF9575CD.toInt(), Color(0xFF9575CD), true),
|
||||||
0xFF4A3F9F.toInt(),
|
QrTheme("dark_03", 0xFF4DB6AC.toInt(), Color(0xFF4DB6AC), true),
|
||||||
"Purple"
|
QrTheme("light_01", 0xFF228BE6.toInt(), Color(0xFF228BE6), false),
|
||||||
),
|
QrTheme("light_02", 0xFF43A047.toInt(), Color(0xFF43A047), false),
|
||||||
QrTheme(
|
QrTheme("light_03", 0xFFE64980.toInt(), Color(0xFFE64980), false),
|
||||||
listOf(Color(0xFF43E97B), Color(0xFF38F9D7)),
|
|
||||||
0xFF2D8F5E.toInt(),
|
|
||||||
"Green"
|
|
||||||
),
|
|
||||||
QrTheme(
|
|
||||||
listOf(Color(0xFFF78CA0), Color(0xFFF9748F), Color(0xFFFD868C)),
|
|
||||||
0xFFBF3A5A.toInt(),
|
|
||||||
"Pink"
|
|
||||||
),
|
|
||||||
QrTheme(
|
|
||||||
listOf(Color(0xFF4FACFE), Color(0xFF00F2FE)),
|
|
||||||
0xFF2171B5.toInt(),
|
|
||||||
"Blue"
|
|
||||||
),
|
|
||||||
QrTheme(
|
|
||||||
listOf(Color(0xFFFFA751), Color(0xFFFFE259)),
|
|
||||||
0xFFC67B1C.toInt(),
|
|
||||||
"Orange"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun maxRevealRadius(center: Offset, bounds: IntSize): Float {
|
||||||
|
if (bounds.width <= 0 || bounds.height <= 0) return 0f
|
||||||
|
val w = bounds.width.toFloat()
|
||||||
|
val h = bounds.height.toFloat()
|
||||||
|
return maxOf(
|
||||||
|
hypot(center.x, center.y),
|
||||||
|
hypot(w - center.x, center.y),
|
||||||
|
hypot(center.x, h - center.y),
|
||||||
|
hypot(w - center.x, h - center.y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MyQrCodeScreen(
|
fun MyQrCodeScreen(
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
@@ -67,12 +84,31 @@ fun MyQrCodeScreen(
|
|||||||
username: String,
|
username: String,
|
||||||
avatarRepository: AvatarRepository? = null,
|
avatarRepository: AvatarRepository? = null,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onScanQr: () -> Unit = {}
|
onScanQr: () -> Unit = {},
|
||||||
|
onToggleTheme: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var selectedThemeIndex by remember { mutableIntStateOf(0) }
|
val view = LocalView.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var selectedThemeIndex by remember { mutableIntStateOf(if (isDarkTheme) 0 else 3) }
|
||||||
|
|
||||||
|
// Auto-switch to matching theme group when app theme changes
|
||||||
|
LaunchedEffect(isDarkTheme) {
|
||||||
|
val currentTheme = qrThemes.getOrNull(selectedThemeIndex)
|
||||||
|
if (currentTheme != null && currentTheme.isDark != isDarkTheme) {
|
||||||
|
// Map to same position in the other group
|
||||||
|
val posInGroup = if (currentTheme.isDark) selectedThemeIndex else selectedThemeIndex - 3
|
||||||
|
selectedThemeIndex = if (isDarkTheme) posInGroup.coerceIn(0, 2) else (posInGroup + 3).coerceIn(3, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val theme = qrThemes[selectedThemeIndex]
|
val theme = qrThemes[selectedThemeIndex]
|
||||||
|
|
||||||
|
val wallpaperResId = remember(theme.wallpaperId) {
|
||||||
|
ThemeWallpapers.drawableResOrNull(theme.wallpaperId)
|
||||||
|
}
|
||||||
|
|
||||||
val qrBitmap = remember(publicKey, selectedThemeIndex) {
|
val qrBitmap = remember(publicKey, selectedThemeIndex) {
|
||||||
QrCodeGenerator.generateQrBitmap(
|
QrCodeGenerator.generateQrBitmap(
|
||||||
content = QrCodeGenerator.profilePayload(publicKey),
|
content = QrCodeGenerator.profilePayload(publicKey),
|
||||||
@@ -88,27 +124,90 @@ fun MyQrCodeScreen(
|
|||||||
else QrCodeGenerator.profileShareUrlByKey(publicKey)
|
else QrCodeGenerator.profileShareUrlByKey(publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Circular reveal state
|
||||||
|
val revealRadius = remember { Animatable(0f) }
|
||||||
|
var revealActive by remember { mutableStateOf(false) }
|
||||||
|
var revealSnapshot by remember { mutableStateOf<ImageBitmap?>(null) }
|
||||||
|
var revealCenter by remember { mutableStateOf(Offset.Zero) }
|
||||||
|
var rootSize by remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
var lastRevealTime by remember { mutableLongStateOf(0L) }
|
||||||
|
val revealCooldownMs = 600L
|
||||||
|
|
||||||
|
fun startReveal(newIndex: Int, center: Offset) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (revealActive || newIndex == selectedThemeIndex || now - lastRevealTime < revealCooldownMs) return
|
||||||
|
lastRevealTime = now
|
||||||
|
if (rootSize.width <= 0 || rootSize.height <= 0) {
|
||||||
|
selectedThemeIndex = newIndex
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val snapshot = runCatching { view.drawToBitmap() }.getOrNull()
|
||||||
|
if (snapshot == null) {
|
||||||
|
selectedThemeIndex = newIndex
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxR = maxRevealRadius(center, rootSize)
|
||||||
|
if (maxR <= 0f) {
|
||||||
|
selectedThemeIndex = newIndex
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
revealActive = true
|
||||||
|
revealCenter = center
|
||||||
|
revealSnapshot = snapshot.asImageBitmap()
|
||||||
|
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
revealRadius.snapTo(0f)
|
||||||
|
selectedThemeIndex = newIndex
|
||||||
|
kotlinx.coroutines.delay(16) // wait one frame for recomposition
|
||||||
|
revealRadius.animateTo(
|
||||||
|
targetValue = maxR,
|
||||||
|
animationSpec = tween(
|
||||||
|
durationMillis = 400,
|
||||||
|
easing = CubicBezierEasing(0.45f, 0.05f, 0.55f, 0.95f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
revealSnapshot = null
|
||||||
|
revealActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Brush.verticalGradient(theme.gradientColors))
|
.onSizeChanged { rootSize = it }
|
||||||
) {
|
) {
|
||||||
|
// Wallpaper background
|
||||||
|
if (wallpaperResId != null) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = wallpaperResId),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(modifier = Modifier.fillMaxSize().background(if (theme.isDark) Color(0xFF1A1A1A) else Color(0xFFF0F0F5)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main content
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.statusBarsPadding().height(40.dp))
|
Spacer(modifier = Modifier.statusBarsPadding().height(40.dp))
|
||||||
|
|
||||||
// QR Card with avatar overlapping
|
// QR Card with overlapping avatar
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(horizontal = 32.dp),
|
modifier = Modifier.padding(horizontal = 32.dp),
|
||||||
contentAlignment = Alignment.TopCenter
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
// White card
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(top = 40.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 40.dp),
|
|
||||||
shape = RoundedCornerShape(24.dp),
|
shape = RoundedCornerShape(24.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
|
||||||
@@ -119,7 +218,6 @@ fun MyQrCodeScreen(
|
|||||||
.padding(top = 52.dp, bottom = 24.dp, start = 24.dp, end = 24.dp),
|
.padding(top = 52.dp, bottom = 24.dp, start = 24.dp, end = 24.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
// QR code
|
|
||||||
if (qrBitmap != null) {
|
if (qrBitmap != null) {
|
||||||
Image(
|
Image(
|
||||||
bitmap = qrBitmap.asImageBitmap(),
|
bitmap = qrBitmap.asImageBitmap(),
|
||||||
@@ -130,7 +228,6 @@ fun MyQrCodeScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// Username
|
|
||||||
Text(
|
Text(
|
||||||
text = if (username.isNotBlank()) "@${username.uppercase()}"
|
text = if (username.isNotBlank()) "@${username.uppercase()}"
|
||||||
else displayName.uppercase(),
|
else displayName.uppercase(),
|
||||||
@@ -142,7 +239,7 @@ fun MyQrCodeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avatar overlapping the card top
|
// Avatar overlapping
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(80.dp)
|
.size(80.dp)
|
||||||
@@ -163,12 +260,11 @@ fun MyQrCodeScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// Bottom sheet area
|
// Bottom sheet
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
|
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
|
||||||
color = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White,
|
color = if (isDarkTheme) Color(0xFF1C1C1E) else Color.White,
|
||||||
tonalElevation = 0.dp,
|
|
||||||
shadowElevation = 16.dp
|
shadowElevation = 16.dp
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -178,71 +274,120 @@ fun MyQrCodeScreen(
|
|||||||
.padding(top = 16.dp, bottom = 16.dp),
|
.padding(top = 16.dp, bottom = 16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
// Handle bar
|
// Handle
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(36.dp)
|
.width(36.dp).height(4.dp)
|
||||||
.height(4.dp)
|
|
||||||
.clip(RoundedCornerShape(2.dp))
|
.clip(RoundedCornerShape(2.dp))
|
||||||
.background(Color.Gray.copy(alpha = 0.3f))
|
.background(Color.Gray.copy(alpha = 0.3f))
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
// Header: Close + title
|
// Header: X + "QR Code" + theme toggle
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(TablerIcons.X, contentDescription = "Close",
|
||||||
|
tint = if (isDarkTheme) Color.White else Color.Black)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Text("QR Code", fontSize = 17.sp, fontWeight = FontWeight.SemiBold,
|
||||||
|
color = if (isDarkTheme) Color.White else Color.Black)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
var themeButtonPos by remember { mutableStateOf(Offset.Zero) }
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
// Snapshot → toggle theme → circular reveal
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
if (!revealActive && rootSize.width > 0 && now - lastRevealTime >= revealCooldownMs) {
|
||||||
|
lastRevealTime = now
|
||||||
|
val snapshot = runCatching { view.drawToBitmap() }.getOrNull()
|
||||||
|
if (snapshot != null) {
|
||||||
|
val maxR = maxRevealRadius(themeButtonPos, rootSize)
|
||||||
|
revealActive = true
|
||||||
|
revealCenter = themeButtonPos
|
||||||
|
revealSnapshot = snapshot.asImageBitmap()
|
||||||
|
// Switch to matching wallpaper in new theme
|
||||||
|
val posInGroup = if (isDarkTheme) selectedThemeIndex else selectedThemeIndex - 3
|
||||||
|
val newIndex = if (isDarkTheme) (posInGroup + 3).coerceIn(3, 5) else posInGroup.coerceIn(0, 2)
|
||||||
|
selectedThemeIndex = newIndex
|
||||||
|
onToggleTheme()
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
revealRadius.snapTo(0f)
|
||||||
|
kotlinx.coroutines.delay(16)
|
||||||
|
revealRadius.animateTo(
|
||||||
|
targetValue = maxR,
|
||||||
|
animationSpec = tween(400, easing = CubicBezierEasing(0.45f, 0.05f, 0.55f, 0.95f))
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
revealSnapshot = null
|
||||||
|
revealActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onToggleTheme()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onToggleTheme()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.onGloballyPositioned { coords ->
|
||||||
|
val pos = coords.positionInRoot()
|
||||||
|
val sz = coords.size
|
||||||
|
themeButtonPos = Offset(pos.x + sz.width / 2f, pos.y + sz.height / 2f)
|
||||||
|
}
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
TablerIcons.X,
|
imageVector = if (isDarkTheme) TablerIcons.Sun else TablerIcons.MoonStars,
|
||||||
contentDescription = "Close",
|
contentDescription = "Toggle theme",
|
||||||
tint = if (isDarkTheme) Color.White else Color.Black
|
tint = if (isDarkTheme) Color.White else Color.Black
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
Text(
|
|
||||||
"QR Code",
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = if (isDarkTheme) Color.White else Color.Black
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
Spacer(modifier = Modifier.width(48.dp))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
// Theme selector
|
// Wallpaper selector — show current theme's wallpapers
|
||||||
|
val currentThemes = qrThemes.filter { it.isDark == isDarkTheme }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
) {
|
) {
|
||||||
qrThemes.forEachIndexed { index, t ->
|
currentThemes.forEach { t ->
|
||||||
|
val index = qrThemes.indexOf(t)
|
||||||
val isSelected = index == selectedThemeIndex
|
val isSelected = index == selectedThemeIndex
|
||||||
|
val wpRes = ThemeWallpapers.drawableResOrNull(t.wallpaperId)
|
||||||
|
var itemPosition by remember { mutableStateOf(Offset.Zero) }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f).aspectRatio(0.85f)
|
||||||
.aspectRatio(0.85f)
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
.border(
|
.border(
|
||||||
width = if (isSelected) 2.dp else 0.dp,
|
width = if (isSelected) 2.dp else 0.dp,
|
||||||
color = if (isSelected) Color(0xFF3390EC) else Color.Transparent,
|
color = if (isSelected) Color(0xFF3390EC) else Color.Transparent,
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
)
|
)
|
||||||
.background(Brush.verticalGradient(t.gradientColors))
|
.onGloballyPositioned { coords ->
|
||||||
.clickable { selectedThemeIndex = index },
|
val pos = coords.positionInRoot()
|
||||||
|
val sz = coords.size
|
||||||
|
itemPosition = Offset(pos.x + sz.width / 2f, pos.y + sz.height / 2f)
|
||||||
|
}
|
||||||
|
.clickable { if (index != selectedThemeIndex) startReveal(index, itemPosition) },
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Icon(
|
if (wpRes != null) {
|
||||||
TablerIcons.Scan,
|
Image(painter = painterResource(id = wpRes), contentDescription = null,
|
||||||
contentDescription = t.name,
|
modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
|
||||||
tint = Color.White.copy(alpha = 0.8f),
|
}
|
||||||
modifier = Modifier.size(24.dp)
|
Icon(TablerIcons.Scan, contentDescription = null,
|
||||||
)
|
tint = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Black.copy(alpha = 0.35f),
|
||||||
|
modifier = Modifier.size(22.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,10 +403,7 @@ fun MyQrCodeScreen(
|
|||||||
}
|
}
|
||||||
context.startActivity(Intent.createChooser(intent, "Share Profile"))
|
context.startActivity(Intent.createChooser(intent, "Share Profile"))
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp).height(50.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.height(50.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF3390EC)),
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF3390EC)),
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) {
|
) {
|
||||||
@@ -270,24 +412,45 @@ fun MyQrCodeScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
// Scan QR button
|
// Scan QR
|
||||||
TextButton(onClick = onScanQr) {
|
TextButton(onClick = onScanQr) {
|
||||||
Icon(
|
Icon(TablerIcons.Scan, contentDescription = null,
|
||||||
TablerIcons.Scan,
|
tint = Color(0xFF3390EC), modifier = Modifier.size(20.dp))
|
||||||
contentDescription = null,
|
|
||||||
tint = Color(0xFF3390EC),
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(
|
Text("Scan QR Code", color = Color(0xFF3390EC),
|
||||||
"Scan QR Code",
|
fontSize = 15.sp, fontWeight = FontWeight.Medium)
|
||||||
color = Color(0xFF3390EC),
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Circular reveal overlay
|
||||||
|
if (revealActive) {
|
||||||
|
val snapshot = revealSnapshot
|
||||||
|
if (snapshot != null) {
|
||||||
|
Canvas(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
|
) {
|
||||||
|
val dstSize = IntSize(size.width.toInt(), size.height.toInt())
|
||||||
|
// Draw old screenshot
|
||||||
|
drawImage(
|
||||||
|
image = snapshot,
|
||||||
|
srcOffset = IntOffset.Zero,
|
||||||
|
srcSize = IntSize(snapshot.width, snapshot.height),
|
||||||
|
dstOffset = IntOffset.Zero,
|
||||||
|
dstSize = dstSize
|
||||||
|
)
|
||||||
|
// Cut expanding circle to reveal new theme underneath
|
||||||
|
drawCircle(
|
||||||
|
color = Color.Transparent,
|
||||||
|
radius = revealRadius.value,
|
||||||
|
center = revealCenter,
|
||||||
|
blendMode = BlendMode.Clear
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user