feat(splash+icon): фон иконки-градиент под тему + темо-зависимый сплэш
- Подложка adaptive-иконки: градиент под акцент темы + радиальное свечение + мягкая тень от логотипа (ic_bg_<тема>, было плоским цветом). Иконку-лого не трогал. - Сплэш под выбранную тему: системный сплэш Android 12+ нельзя перекрасить под выбор пользователя (alias-тема на ColorOS игнорится), поэтому системный = просто тёмный (splash_transparent), а красивый сплэш рисуем сами на Compose (SplashOverlay): 3D-лого + акцентное свечение + тень + анимация, цвет берём из текущей темы. - Тему на старте читаем синхронно из SharedPreferences (мгновенно, без блокировки кадра). - Ускорен холодный старт до первого кадра 1.48с→1.11с: сплэш рисуется на первом дешёвом кадре, тяжёлый контент (ViewModels/плеер) композится под ним; старт PlayerService уведён с критического пути. Остаток — оверхед debug-сборки.
@@ -42,42 +42,50 @@
|
||||
Переключается в рантайме (LauncherIconManager) при смене темы. -->
|
||||
<activity-alias android:name=".MainAliasForest" android:enabled="true" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_forest" android:roundIcon="@mipmap/ic_launcher_forest_round">
|
||||
android:icon="@mipmap/ic_launcher_forest" android:roundIcon="@mipmap/ic_launcher_forest_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Forest">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasOcean" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_ocean" android:roundIcon="@mipmap/ic_launcher_ocean_round">
|
||||
android:icon="@mipmap/ic_launcher_ocean" android:roundIcon="@mipmap/ic_launcher_ocean_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Ocean">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasSunset" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_sunset" android:roundIcon="@mipmap/ic_launcher_sunset_round">
|
||||
android:icon="@mipmap/ic_launcher_sunset" android:roundIcon="@mipmap/ic_launcher_sunset_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Sunset">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasAmethyst" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_amethyst" android:roundIcon="@mipmap/ic_launcher_amethyst_round">
|
||||
android:icon="@mipmap/ic_launcher_amethyst" android:roundIcon="@mipmap/ic_launcher_amethyst_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Amethyst">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasNeon" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_neon" android:roundIcon="@mipmap/ic_launcher_neon_round">
|
||||
android:icon="@mipmap/ic_launcher_neon" android:roundIcon="@mipmap/ic_launcher_neon_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Neon">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasAmber" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_amber" android:roundIcon="@mipmap/ic_launcher_amber_round">
|
||||
android:icon="@mipmap/ic_launcher_amber" android:roundIcon="@mipmap/ic_launcher_amber_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Amber">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasIce" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_ice" android:roundIcon="@mipmap/ic_launcher_ice_round">
|
||||
android:icon="@mipmap/ic_launcher_ice" android:roundIcon="@mipmap/ic_launcher_ice_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Ice">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias android:name=".MainAliasRose" android:enabled="false" android:exported="true"
|
||||
android:targetActivity=".MainActivity" android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher_rose" android:roundIcon="@mipmap/ic_launcher_rose_round">
|
||||
android:icon="@mipmap/ic_launcher_rose" android:roundIcon="@mipmap/ic_launcher_rose_round"
|
||||
android:theme="@style/Theme.Radiola.Splash.Rose">
|
||||
<intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
|
||||
@@ -61,20 +61,39 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
startService(Intent(this, PlayerService::class.java))
|
||||
lifecycleScope.launch {
|
||||
tokenDataStore.preload()
|
||||
// Старт плеер-сервиса уводим с критического пути запуска — ускоряет
|
||||
// появление первого кадра (сплэша).
|
||||
startService(Intent(this@MainActivity, PlayerService::class.java))
|
||||
}
|
||||
ensureBackgroundPlaybackAllowed()
|
||||
enableEdgeToEdge()
|
||||
// Тему берём из быстрого SharedPreferences (его пишет LauncherIconManager при
|
||||
// смене темы) — синхронно и МГНОВЕННО, без блокировки первого кадра. Так сплэш
|
||||
// и приложение сразу нужного цвета, и тёмный системный сплэш не висит лишнее.
|
||||
val initialPaletteId = getSharedPreferences("radiola_prefs", MODE_PRIVATE)
|
||||
.getString("icon_alias", "forest") ?: "forest"
|
||||
setContent {
|
||||
// Выбранная цветовая тема (мгновенно перекрашивает всё приложение).
|
||||
val paletteId by settingsRepository.getThemePalette().collectAsState(initial = "forest")
|
||||
val paletteId by settingsRepository.getThemePalette().collectAsState(initial = initialPaletteId)
|
||||
// Иконка лаунчера следует теме (срабатывает на старте и при смене темы).
|
||||
LaunchedEffect(paletteId) {
|
||||
launcherIconManager.applyIfNeeded(com.radiola.ui.theme.ThemePalette.fromId(paletteId))
|
||||
}
|
||||
RadiolaTheme(palette = com.radiola.ui.theme.ThemePalette.fromId(paletteId)) {
|
||||
// Сплэш рисуем на ПЕРВОМ (дешёвом) кадре; тяжёлый контент (ViewModels,
|
||||
// плеер) композим следующим кадром ПОД сплэшем — так логотип появляется
|
||||
// почти сразу, без долгого тёмного ожидания холодного старта.
|
||||
var showSplash by remember { mutableStateOf(true) }
|
||||
var contentReady by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) { contentReady = true }
|
||||
LaunchedEffect(Unit) {
|
||||
kotlinx.coroutines.delay(1600)
|
||||
showSplash = false
|
||||
}
|
||||
|
||||
if (contentReady) {
|
||||
val navController = rememberNavController()
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
var showPlayer by remember { mutableStateOf(false) }
|
||||
@@ -298,6 +317,20 @@ class MainActivity : ComponentActivity() {
|
||||
onDismiss = { if (!update.forceUpdate) pendingUpdate = null }
|
||||
)
|
||||
}
|
||||
|
||||
} // конец if (contentReady): тяжёлый контент композится под сплэшем
|
||||
|
||||
// Тематический экран загрузки поверх всего (рисуем сами — системный
|
||||
// сплэш Android 12+ нельзя перекрасить под выбранную тему).
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = showSplash,
|
||||
enter = androidx.compose.animation.EnterTransition.None,
|
||||
exit = androidx.compose.animation.fadeOut(androidx.compose.animation.core.tween(450))
|
||||
) {
|
||||
com.radiola.ui.components.SplashOverlay(
|
||||
com.radiola.ui.theme.ThemePalette.fromId(paletteId)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
96
app/src/main/java/com/radiola/ui/components/SplashOverlay.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
package com.radiola.ui.components
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.radiola.ui.theme.RadiolaWordmark
|
||||
import com.radiola.ui.theme.ThemePalette
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Свой экран загрузки: тематический 3D-логотип с акцентным свечением под цвет темы,
|
||||
* мягкой тенью и плавным появлением. Рисуем сами (а не системный сплэш), потому что
|
||||
* Android 12+ не даёт менять иконку системного сплэша под выбранную пользователем тему.
|
||||
*/
|
||||
@Composable
|
||||
fun SplashOverlay(palette: ThemePalette, modifier: Modifier = Modifier) {
|
||||
val colors = palette.colors
|
||||
// Появление снапное: лого видно почти сразу (короткий fade), затем мягкий «вдох».
|
||||
val scale = remember { Animatable(0.92f) }
|
||||
val fade = remember { Animatable(0f) }
|
||||
LaunchedEffect(Unit) {
|
||||
launch { fade.animateTo(1f, tween(200)) }
|
||||
launch { scale.animateTo(1f, tween(420, easing = FastOutSlowInEasing)) }
|
||||
}
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize().background(colors.bgBase),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// Акцентное свечение под цвет темы
|
||||
Box(
|
||||
Modifier
|
||||
.size(380.dp)
|
||||
.graphicsLayer { alpha = fade.value }
|
||||
.background(
|
||||
Brush.radialGradient(
|
||||
listOf(
|
||||
colors.accent.copy(alpha = 0.32f),
|
||||
colors.accent.copy(alpha = 0.10f),
|
||||
Color.Transparent
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
// Мягкая тень логотипа
|
||||
Image(
|
||||
painter = painterResource(palette.logoRes),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(Color.Black.copy(alpha = 0.5f)),
|
||||
modifier = Modifier
|
||||
.size(176.dp)
|
||||
.scale(scale.value)
|
||||
.offset(y = 14.dp)
|
||||
.blur(18.dp)
|
||||
.graphicsLayer { alpha = fade.value }
|
||||
)
|
||||
// Логотип
|
||||
Image(
|
||||
painter = painterResource(palette.logoRes),
|
||||
contentDescription = "radiOLA",
|
||||
modifier = Modifier
|
||||
.size(176.dp)
|
||||
.scale(scale.value)
|
||||
.graphicsLayer { alpha = fade.value }
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Box(Modifier.graphicsLayer { alpha = fade.value }) {
|
||||
RadiolaWordmark(fontSize = 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable-nodpi/ic_bg_amber.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_amethyst.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_forest.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_ice.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_neon.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_ocean.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_rose.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
app/src/main/res/drawable-nodpi/ic_bg_sunset.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_amber.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_amethyst.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_forest.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_ice.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_neon.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_ocean.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_rose.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
app/src/main/res/drawable-nodpi/splash_logo_sunset.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
8
app/src/main/res/drawable/splash_transparent.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Прозрачная «иконка» системного сплэша: на холодном старте показываем только
|
||||
тёмный фон (без зелёной R), а тематический логотип со свечением рисуем сами
|
||||
поверх на Compose (SplashOverlay), когда уже знаем выбранную тему. -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<size android:width="1dp" android:height="1dp" />
|
||||
</shape>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_forest" />
|
||||
<background android:drawable="@drawable/ic_bg_forest" />
|
||||
<foreground android:drawable="@drawable/ic_fg_forest" />
|
||||
<monochrome android:drawable="@drawable/ic_fg_forest" />
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_amber"/>
|
||||
<background android:drawable="@drawable/ic_bg_amber"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_amber"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_amber"/>
|
||||
<background android:drawable="@drawable/ic_bg_amber"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_amber"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_amethyst"/>
|
||||
<background android:drawable="@drawable/ic_bg_amethyst"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_amethyst"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_amethyst"/>
|
||||
<background android:drawable="@drawable/ic_bg_amethyst"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_amethyst"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_forest"/>
|
||||
<background android:drawable="@drawable/ic_bg_forest"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_forest"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_forest"/>
|
||||
<background android:drawable="@drawable/ic_bg_forest"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_forest"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_ice"/>
|
||||
<background android:drawable="@drawable/ic_bg_ice"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_ice"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_ice"/>
|
||||
<background android:drawable="@drawable/ic_bg_ice"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_ice"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_neon"/>
|
||||
<background android:drawable="@drawable/ic_bg_neon"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_neon"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_neon"/>
|
||||
<background android:drawable="@drawable/ic_bg_neon"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_neon"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_ocean"/>
|
||||
<background android:drawable="@drawable/ic_bg_ocean"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_ocean"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_ocean"/>
|
||||
<background android:drawable="@drawable/ic_bg_ocean"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_ocean"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_rose"/>
|
||||
<background android:drawable="@drawable/ic_bg_rose"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_rose"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_rose"/>
|
||||
<background android:drawable="@drawable/ic_bg_rose"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_rose"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_forest" />
|
||||
<background android:drawable="@drawable/ic_bg_forest" />
|
||||
<foreground android:drawable="@drawable/ic_fg_forest" />
|
||||
<monochrome android:drawable="@drawable/ic_fg_forest" />
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_sunset"/>
|
||||
<background android:drawable="@drawable/ic_bg_sunset"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_sunset"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_bg_sunset"/>
|
||||
<background android:drawable="@drawable/ic_bg_sunset"/>
|
||||
<foreground android:drawable="@drawable/ic_fg_sunset"/>
|
||||
</adaptive-icon>
|
||||
|
||||
@@ -11,7 +11,53 @@
|
||||
тёмный фон + наша иконка, затем переход в основную тему. -->
|
||||
<style name="Theme.Radiola.Splash" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/brand_bg</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo</item>
|
||||
<!-- Прозрачная иконка: системный сплэш = только тёмный фон (без зелёной R).
|
||||
Тематический логотип со свечением рисуем сами (SplashOverlay) — система
|
||||
на Android 12+ не даёт менять иконку сплэша под выбранную тему. -->
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_transparent</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
|
||||
<!-- Сплэш под каждую тему (задаётся через android:theme нужного activity-alias).
|
||||
Активен alias текущей темы → холодный старт показывает её цвет и лого. -->
|
||||
<style name="Theme.Radiola.Splash.Forest" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_forest</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_forest</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Ocean" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_ocean</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_ocean</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Sunset" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_sunset</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_sunset</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Amethyst" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_amethyst</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_amethyst</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Neon" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_neon</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_neon</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Amber" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_amber</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_amber</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Ice" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_ice</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_ice</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
<style name="Theme.Radiola.Splash.Rose" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@color/ic_bg_rose</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_logo_rose</item>
|
||||
<item name="postSplashScreenTheme">@style/Theme.Radiola</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
73
design/logos/gen_bg.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Фоны adaptive-иконки: градиент под акцент темы + радиальное свечение + тень от логотипа."""
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
import numpy as np, os
|
||||
|
||||
TH = r"C:\radiOLA\design\logos\gen\themed"
|
||||
DRW = r"C:\radiOLA\app\src\main\res\drawable-nodpi"
|
||||
PREV = os.environ["LOCALAPPDATA"] + r"\Temp\radiola_bg_preview.png"
|
||||
S = 432
|
||||
THEMES = [
|
||||
("forest", (0xA8,0xE0,0x5F), (0x0C,0x14,0x10)),
|
||||
("ocean", (0x4F,0xD6,0xE0), (0x0A,0x0F,0x1A)),
|
||||
("sunset", (0xFF,0x8A,0x5B), (0x1A,0x0F,0x0C)),
|
||||
("amethyst",(0xB3,0x88,0xFF), (0x12,0x0E,0x1A)),
|
||||
("neon", (0xFF,0x4D,0x9D), (0x0D,0x0A,0x12)),
|
||||
("amber", (0xFF,0xC2,0x47), (0x14,0x11,0x0A)),
|
||||
("ice", (0x7F,0xB3,0xFF), (0x0C,0x10,0x14)),
|
||||
("rose", (0xFF,0x7E,0xA8), (0x16,0x0E,0x12)),
|
||||
]
|
||||
|
||||
def lerp(a,b,t): return tuple(a[i]+(b[i]-a[i])*t for i in range(3))
|
||||
|
||||
yy,xx = np.mgrid[0:S,0:S]
|
||||
cx,cy=S*0.5,S*0.46
|
||||
rad = np.sqrt((xx-cx)**2+(yy-cy)**2)/(S*0.62) # 0 в центре → ~1 к краю
|
||||
diag = (xx+yy)/(2*S) # 0 верх-лево → 1 низ-право
|
||||
|
||||
def make_bg(accent, bg):
|
||||
acc=np.array(accent,float); base=np.array(bg,float)
|
||||
# 1) база: лёгкий диагональный градиент (верх чуть светлее с оттенком акцента, низ темнее)
|
||||
top = lerp(bg, accent, 0.10); top=np.array([min(255,c*1.15) for c in top])
|
||||
bot = np.array([c*0.7 for c in bg])
|
||||
g = diag[...,None]
|
||||
img = top*(1-g) + bot*g
|
||||
# 2) радиальное акцентное свечение в центре
|
||||
glow = np.clip(1-rad,0,1)**1.7
|
||||
img = img + (acc-img)*(glow[...,None]*0.30)
|
||||
# 3) виньетка по углам
|
||||
vig = np.clip(rad-0.6,0,1)
|
||||
img = img*(1-vig[...,None]*0.35)
|
||||
return Image.fromarray(np.clip(img,0,255).astype("uint8"),"RGB").convert("RGBA")
|
||||
|
||||
def r_shadow(theme):
|
||||
"""Мягкая тень от логотипа (силуэт R), запечённая в фон."""
|
||||
logo=Image.open(os.path.join(TH,f"logo_{theme}.png")).convert("RGBA")
|
||||
logo=logo.crop(logo.getbbox())
|
||||
target=196; sc=target/max(logo.size)
|
||||
logo=logo.resize((int(logo.width*sc),int(logo.height*sc)))
|
||||
sh=Image.new("RGBA",(S,S),(0,0,0,0))
|
||||
# силуэт из альфы
|
||||
sil=Image.new("RGBA",(S,S),(0,0,0,0))
|
||||
a=logo.split()[3]
|
||||
blk=Image.new("RGBA",logo.size,(0,0,0,150))
|
||||
sil.paste(blk,((S-logo.width)//2+7,(S-logo.height)//2+14),a) # сдвиг вниз-вправо
|
||||
sil=sil.filter(ImageFilter.GaussianBlur(16))
|
||||
return sil
|
||||
|
||||
cols=4; rows=2; pad=16
|
||||
sheet=Image.new("RGB",(cols*S//2+(cols+1)*pad, rows*S//2+(rows+1)*pad),(28,28,32))
|
||||
for i,(name,acc,bg) in enumerate(THEMES):
|
||||
base=make_bg(acc,bg)
|
||||
base.alpha_composite(r_shadow(name))
|
||||
base.convert("RGB").save(os.path.join(DRW,f"ic_bg_{name}.png"))
|
||||
# превью: фон + foreground + круглая маска
|
||||
fg=Image.open(os.path.join(DRW,f"ic_fg_{name}.png")).convert("RGBA")
|
||||
full=base.copy(); full.alpha_composite(fg)
|
||||
mask=Image.new("L",(S,S),0); ImageDraw.Draw(mask).ellipse([0,0,S,S],fill=255)
|
||||
out=Image.new("RGB",(S,S),(28,28,32)); out.paste(full.convert("RGB"),(0,0),mask)
|
||||
out=out.resize((S//2,S//2))
|
||||
r=i//cols; c=i%cols; x=pad+c*(S//2+pad); y=pad+r*(S//2+pad)
|
||||
sheet.paste(out,(x,y))
|
||||
sheet.save(PREV)
|
||||
print("bg generated + preview", PREV)
|
||||