Skip to content

Commit 82267a8

Browse files
committed
бля, я миню сломал, ужс, простите
1 parent c7f6527 commit 82267a8

2 files changed

Lines changed: 232 additions & 12 deletions

File tree

android/app/src/main/java/dev/c0redev/volter/ui/AppNavGraph.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Box
1616
import androidx.compose.foundation.BorderStroke
1717
import androidx.compose.foundation.layout.PaddingValues
1818
import androidx.compose.foundation.layout.Row
19+
import androidx.compose.foundation.layout.height
1920
import androidx.compose.foundation.layout.fillMaxWidth
2021
import androidx.compose.foundation.layout.fillMaxHeight
2122
import androidx.compose.foundation.layout.fillMaxSize
@@ -138,6 +139,7 @@ fun AppNavGraph(vm: ConnectionViewModel) {
138139
) {
139140
GlassBottomBar {
140141
NavigationBar(
142+
modifier = Modifier.height(74.dp),
141143
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.08f),
142144
tonalElevation = 0.dp,
143145
windowInsets = androidx.compose.foundation.layout.WindowInsets(0, 0, 0, 0),
@@ -307,10 +309,11 @@ private fun GlassBottomBar(content: @Composable () -> Unit) {
307309
tonalElevation = 6.dp,
308310
shadowElevation = 14.dp,
309311
) {
310-
Box(modifier = Modifier.fillMaxWidth()) {
312+
Box(modifier = Modifier.fillMaxWidth().height(74.dp)) {
311313
Box(
312314
modifier = Modifier
313-
.fillMaxSize()
315+
.fillMaxWidth()
316+
.height(74.dp)
314317
.clip(shape)
315318
.graphicsLayer {
316319
shader.setFloatUniform("resolution", size.width, size.height)

android/app/src/main/java/dev/c0redev/volter/ui/screens/HomeScreen.kt

Lines changed: 227 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
package dev.c0redev.volter.ui.screens
22

3+
import android.graphics.Paint
4+
import android.graphics.RuntimeShader
5+
import android.os.Build
36
import androidx.compose.animation.AnimatedVisibility
47
import androidx.compose.animation.core.animateFloatAsState
8+
import androidx.compose.animation.core.LinearEasing
9+
import androidx.compose.animation.core.RepeatMode
10+
import androidx.compose.animation.core.animateFloat
11+
import androidx.compose.animation.core.infiniteRepeatable
12+
import androidx.compose.animation.core.rememberInfiniteTransition
513
import androidx.compose.animation.core.tween
614
import androidx.compose.animation.expandVertically
715
import androidx.compose.animation.fadeIn
816
import androidx.compose.animation.fadeOut
917
import androidx.compose.animation.shrinkVertically
18+
import androidx.compose.foundation.background
1019
import androidx.compose.foundation.clickable
20+
import androidx.compose.foundation.horizontalScroll
21+
import androidx.compose.foundation.layout.Box
1122
import androidx.compose.foundation.layout.Arrangement
1223
import androidx.compose.foundation.layout.Column
1324
import androidx.compose.foundation.layout.PaddingValues
@@ -20,6 +31,7 @@ import androidx.compose.foundation.layout.padding
2031
import androidx.compose.foundation.layout.size
2132
import androidx.compose.foundation.lazy.LazyColumn
2233
import androidx.compose.foundation.shape.RoundedCornerShape
34+
import androidx.compose.foundation.rememberScrollState
2335
import androidx.compose.material.icons.Icons
2436
import androidx.compose.material.icons.automirrored.outlined.ReceiptLong
2537
import androidx.compose.material.icons.filled.CheckCircle
@@ -50,20 +62,53 @@ import androidx.compose.runtime.setValue
5062
import androidx.compose.ui.Alignment
5163
import androidx.compose.ui.Modifier
5264
import androidx.compose.ui.draw.scale
65+
import androidx.compose.ui.draw.blur
66+
import androidx.compose.ui.draw.clip
67+
import androidx.compose.ui.graphics.Brush
68+
import androidx.compose.ui.graphics.Color
69+
import androidx.compose.ui.graphics.StrokeCap
70+
import androidx.compose.ui.graphics.nativeCanvas
71+
import androidx.compose.ui.graphics.toArgb
5372
import androidx.compose.ui.graphics.vector.ImageVector
5473
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
74+
import androidx.compose.ui.platform.LocalDensity
5575
import androidx.compose.ui.platform.LocalHapticFeedback
5676
import androidx.compose.ui.res.stringArrayResource
5777
import androidx.compose.ui.res.stringResource
5878
import androidx.compose.ui.text.font.FontWeight
5979
import androidx.compose.ui.text.style.TextAlign
80+
import androidx.compose.ui.geometry.Offset
81+
import androidx.compose.ui.graphics.drawscope.Stroke
82+
import androidx.compose.ui.graphics.drawscope.rotate
83+
import androidx.compose.ui.unit.Dp
6084
import androidx.compose.ui.unit.dp
6185
import dev.c0redev.volter.BuildConfig
6286
import dev.c0redev.volter.R
6387
import dev.c0redev.volter.theme.VolterSpacing
6488
import dev.c0redev.volter.ui.ConnectionViewModel
6589
import dev.c0redev.volter.ui.components.SectionCard
6690

91+
private const val ringShaderSrc = """
92+
uniform float2 resolution;
93+
uniform float time;
94+
uniform float4 colorA;
95+
uniform float4 colorB;
96+
uniform float4 colorC;
97+
98+
half4 main(float2 fragCoord) {
99+
float2 uv = fragCoord / max(resolution, float2(1.0));
100+
float2 p = uv - 0.5;
101+
float r = length(p);
102+
float angle = atan(p.y, p.x);
103+
float wave = sin(angle * 6.0 + time * 2.4) * 0.5 + 0.5;
104+
float wave2 = cos(angle * 10.0 - time * 1.8) * 0.5 + 0.5;
105+
half3 c = mix(colorA.rgb, colorB.rgb, wave);
106+
c = mix(c, colorC.rgb, wave2 * 0.45);
107+
float glow = smoothstep(0.48, 0.12, abs(r - 0.40));
108+
return half4(c * (0.65 + glow * 0.85), glow);
109+
}
110+
"""
111+
67112
private data class GuideEntry(
68113
val title: String,
69114
val hint: String,
@@ -171,16 +216,7 @@ fun HomeScreen(
171216
verticalAlignment = Alignment.CenterVertically,
172217
horizontalArrangement = Arrangement.spacedBy(8.dp),
173218
) {
174-
Icon(
175-
imageVector = if (conn.connected) Icons.Default.CheckCircle else Icons.Default.Error,
176-
contentDescription = if (conn.connected) {
177-
stringResource(R.string.home_status_connected_cd)
178-
} else {
179-
stringResource(R.string.home_status_disconnected_cd)
180-
},
181-
tint = if (conn.connected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.onSurfaceVariant,
182-
modifier = Modifier.size(24.dp),
183-
)
219+
ConnectionGlowIndicator(connected = conn.connected)
184220
Text(
185221
text = stringResource(R.string.home_status_title),
186222
style = MaterialTheme.typography.titleLarge,
@@ -283,6 +319,21 @@ fun HomeScreen(
283319
modifier = Modifier.padding(top = 4.dp),
284320
)
285321
}
322+
323+
if (local.isNotEmpty()) {
324+
Spacer(modifier = Modifier.height(4.dp))
325+
Text(
326+
text = "Серверы подключения",
327+
style = MaterialTheme.typography.labelLarge,
328+
color = MaterialTheme.colorScheme.onSurfaceVariant,
329+
)
330+
ServerPillRow(
331+
servers = local,
332+
activeName = activeProfile,
333+
connectingName = connectingName,
334+
onSelect = { name, cfg -> vm.connect(name, cfg) },
335+
)
336+
}
286337
}
287338
}
288339
}
@@ -331,6 +382,172 @@ fun HomeScreen(
331382
}
332383
}
333384

385+
@Composable
386+
private fun ConnectionGlowIndicator(connected: Boolean, size: Dp = 26.dp) {
387+
val inf = rememberInfiniteTransition(label = "connGlow")
388+
val angle by inf.animateFloat(
389+
initialValue = 0f,
390+
targetValue = 360f,
391+
animationSpec = infiniteRepeatable(animation = tween(3600, easing = LinearEasing), repeatMode = RepeatMode.Restart),
392+
label = "connGlowAngle",
393+
)
394+
val base = if (connected) Color(0xFF8B5CF6) else MaterialTheme.colorScheme.onSurfaceVariant
395+
val glowAlpha = if (connected) 0.95f else 0.28f
396+
val ringWidth = with(LocalDensity.current) { 3.dp.toPx() }
397+
Box(
398+
modifier = Modifier.size(size),
399+
contentAlignment = Alignment.Center,
400+
) {
401+
if (connected) {
402+
Box(
403+
modifier = Modifier
404+
.size(size + 6.dp)
405+
.blur(8.dp)
406+
.background(Color(0xFF8B5CF6).copy(alpha = 0.35f), RoundedCornerShape(999.dp)),
407+
)
408+
}
409+
if (connected && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
410+
AgslRing(
411+
size = size,
412+
ringWidth = ringWidth,
413+
angle = angle,
414+
alpha = glowAlpha,
415+
)
416+
} else {
417+
androidx.compose.foundation.Canvas(modifier = Modifier.size(size)) {
418+
val sweep = Brush.sweepGradient(
419+
listOf(
420+
Color(0xFF8B5CF6),
421+
Color(0xFF6D28D9),
422+
Color(0xFFEC4899),
423+
Color(0xFF8B5CF6),
424+
),
425+
center = center,
426+
)
427+
rotate(angle) {
428+
drawArc(
429+
brush = if (connected) sweep else Brush.linearGradient(listOf(base, base)),
430+
startAngle = 0f,
431+
sweepAngle = 360f,
432+
useCenter = false,
433+
style = Stroke(width = ringWidth, cap = StrokeCap.Round),
434+
topLeft = Offset.Zero,
435+
size = this.size,
436+
alpha = glowAlpha,
437+
)
438+
}
439+
}
440+
}
441+
Box(
442+
modifier = Modifier
443+
.size(size - 10.dp)
444+
.clip(RoundedCornerShape(999.dp))
445+
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)),
446+
contentAlignment = Alignment.Center,
447+
) {
448+
Icon(
449+
imageVector = if (connected) Icons.Default.CheckCircle else Icons.Default.Error,
450+
contentDescription = null,
451+
tint = if (connected) Color(0xFF8B5CF6) else MaterialTheme.colorScheme.onSurfaceVariant,
452+
modifier = Modifier.size(14.dp),
453+
)
454+
}
455+
}
456+
}
457+
458+
@Composable
459+
private fun ServerPillRow(
460+
servers: List<dev.c0redev.volter.ui.ConfigItemState>,
461+
activeName: String?,
462+
connectingName: String?,
463+
onSelect: (String, dev.c0redev.volter.domain.model.Config) -> Unit,
464+
) {
465+
val scroll = rememberScrollState()
466+
Row(
467+
modifier = Modifier
468+
.fillMaxWidth()
469+
.clip(RoundedCornerShape(999.dp))
470+
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.54f))
471+
.horizontalScroll(scroll)
472+
.padding(horizontal = 8.dp, vertical = 6.dp),
473+
horizontalArrangement = Arrangement.spacedBy(8.dp),
474+
) {
475+
servers.forEach { item ->
476+
val selected = item.name == activeName || item.name == connectingName
477+
Box(
478+
modifier = Modifier
479+
.size(width = 122.dp, height = 36.dp)
480+
.clip(RoundedCornerShape(999.dp))
481+
.background(
482+
if (selected) MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f)
483+
else MaterialTheme.colorScheme.surface.copy(alpha = 0.6f),
484+
)
485+
.clickable { onSelect(item.name, item.config) }
486+
.padding(horizontal = 10.dp, vertical = 8.dp),
487+
contentAlignment = Alignment.Center,
488+
) {
489+
Text(
490+
text = item.name,
491+
style = MaterialTheme.typography.labelLarge,
492+
color = if (selected) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant,
493+
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
494+
)
495+
}
496+
}
497+
}
498+
}
499+
500+
@Composable
501+
private fun AgslRing(size: Dp, ringWidth: Float, angle: Float, alpha: Float) {
502+
val c1 = MaterialTheme.colorScheme.primary
503+
val c2 = MaterialTheme.colorScheme.tertiary
504+
val c3 = MaterialTheme.colorScheme.secondary
505+
val shader = remember {
506+
runCatching { RuntimeShader(ringShaderSrc) }.getOrNull()
507+
}
508+
val a1 = c1.toArgb()
509+
val a2 = c2.toArgb()
510+
val a3 = c3.toArgb()
511+
if (shader == null) {
512+
androidx.compose.foundation.Canvas(modifier = Modifier.size(size)) {
513+
val sweep = Brush.sweepGradient(listOf(c1, c2, c3, c1), center = center)
514+
rotate(angle) {
515+
drawArc(
516+
brush = sweep,
517+
startAngle = 0f,
518+
sweepAngle = 360f,
519+
useCenter = false,
520+
style = Stroke(width = ringWidth, cap = StrokeCap.Round),
521+
topLeft = Offset.Zero,
522+
size = this.size,
523+
alpha = alpha,
524+
)
525+
}
526+
}
527+
return
528+
}
529+
androidx.compose.foundation.Canvas(modifier = Modifier.size(size)) {
530+
shader.setFloatUniform("resolution", this.size.width, this.size.height)
531+
shader.setFloatUniform("time", angle / 57.2958f)
532+
shader.setFloatUniform("colorA", ((a1 shr 16) and 0xFF) / 255f, ((a1 shr 8) and 0xFF) / 255f, (a1 and 0xFF) / 255f, 1f)
533+
shader.setFloatUniform("colorB", ((a2 shr 16) and 0xFF) / 255f, ((a2 shr 8) and 0xFF) / 255f, (a2 and 0xFF) / 255f, 1f)
534+
shader.setFloatUniform("colorC", ((a3 shr 16) and 0xFF) / 255f, ((a3 shr 8) and 0xFF) / 255f, (a3 and 0xFF) / 255f, 1f)
535+
val p = Paint().apply {
536+
isAntiAlias = true
537+
style = Paint.Style.STROKE
538+
strokeWidth = ringWidth
539+
strokeCap = Paint.Cap.ROUND
540+
this.alpha = (alpha * 255f).toInt().coerceIn(0, 255)
541+
this.shader = shader
542+
}
543+
val native = drawContext.canvas.nativeCanvas
544+
native.save()
545+
native.rotate(angle, center.x, center.y)
546+
native.drawCircle(center.x, center.y, (this.size.minDimension * 0.5f) - (ringWidth * 0.5f), p)
547+
native.restore()
548+
}
549+
}
550+
334551
@Composable
335552
private fun StatusRow(label: String, value: String) {
336553
Row(

0 commit comments

Comments
 (0)