Commit Graph

143 Commits

Author SHA1 Message Date
nk
55a380d34b chore(app): bump 9 / 1.8 (фикс восстановления потока после смены сети) 2026-06-11 23:25:36 +03:00
nk
75d256eda5 fix(player): восстановление потока после смены/пропажи сети (машина, туннели)
Причина «падений через 10-15 мин в машине»: при смене сети (Wi-Fi↔LTE) или долгом
обрыве поток рвался, а переподключение СДАВАЛОСЬ навсегда после 10 попыток (~100с),
и не было реакции на возврат сети → радио не оживало, foreground-сервис отваливался,
процесс убивала система (это выглядело как «падение», хотя крэша не было).

- scheduleReconnect больше не сдаётся: переподключается, пока пользователь хочет
  играть (флаг intendedToPlay; пауза/стоп его снимают).
- Добавлен ConnectivityManager.registerDefaultNetworkCallback: при возврате/смене
  сети мгновенно re-prepare потока, не дожидаясь бэк-оффа.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 23:22:39 +03:00
nk
1771a5b975 chore(repo): игнор tempfiles/, фикс дизайн-файла и ассетов RuStore
- tempfiles/ в .gitignore (скрэтч: картинки, HTML-эксперименты, мокапы),
  кроме tempfiles/radiOLA.pen — дизайн-файл остаётся под версией
- коммит изменений radiOLA.pen
- docs/rustore-listing.md и design/logos/*.html — ассеты карточки RuStore

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:28:48 +03:00
nk
91777fc459 feat(background): подсказка ColorOS «Разрешить работу в фоне» + bump 8/1.7
На Oppo/OnePlus/Realme стандартного исключения из оптимизации батареи мало —
система схлопывает приложение в фоне. Один раз показываем пояснение и открываем
настройки приложения, чтобы юзер включил «Разрешить работу в фоновом режиме».
2026-06-11 14:46:06 +03:00
nk
7a00f53b20 fix(now-playing): мгновенный рефреш эфира при возврате из фона + восстановление сессии
Опрос now-playing был привязан к play()/жизни ViewModel — при заморозке ColorOS
в фоне или пересоздании ViewModel трек «застывал». Теперь: startNowPlaying()
с мгновенным refresh, восстановление привязки к играющей станции из
PlayerController.currentStationId, и onAppForeground() на ON_RESUME.
2026-06-10 18:05:32 +03:00
nk
3c7ae1eb4c fix(deeplink): объявить видимость пакета re.sova.five (Android 11+ queries) 2026-06-08 14:52:05 +03:00
nk
d31e5d1119 chore(app): bump версии до 7 / 1.6 (RuStore-релиз) 2026-06-08 14:45:36 +03:00
nk
ab09d92b0d feat(deeplink): кнопка поиска в SOVA (re.sova.five, только sideload) 2026-06-08 14:41:42 +03:00
nk
c75ff8cb9a build(app): релизная подпись из keystore.properties (Задача 6) 2026-06-08 14:26:02 +03:00
nk
9e729512e9 build: игнорировать keystore.properties и *.jks (релизные секреты) 2026-06-08 14:06:26 +03:00
nk
92a7c614c1 feat(deeplink): прямое открытие в пакете стороннего сервиса (packageName) 2026-06-08 14:05:59 +03:00
nk
cbd6451ee0 refactor(settings): убрать осиротевший тумблер записи; тестер под SHOW_DEV_TOOLS 2026-06-08 14:05:11 +03:00
nk
6a21a84b86 build(app): REQUEST_INSTALL_PACKAGES только в sideload 2026-06-08 14:03:21 +03:00
nk
8dc0d46c40 feat(app): апдейтер только в sideload; API/BASE_URL на https 2026-06-08 14:03:21 +03:00
nk
34bd6ab02e build(app): product flavors store/sideload + BuildConfig флаги 2026-06-08 14:03:21 +03:00
nk
86b39f9fea chore: bump backend submodule (privacy) 2026-06-08 13:58:13 +03:00
nk
dbc99bcb10 docs: план реализации подготовки к RuStore 2026-06-08 13:45:21 +03:00
nk
6159cc13cc docs(rustore-spec): добавить кнопку SOVA (дип-линк, только sideload) 2026-06-08 13:38:23 +03:00
nk
56d96382fa docs(rustore-spec): убрать тумблер записи (мёртвый) + тестер станций под флаг store 2026-06-08 13:33:05 +03:00
nk
4391f3ec33 docs: дизайн-спек подготовки к публикации в RuStore 2026-06-08 09:47:10 +03:00
nk
bb40d26621 chore: bump backend submodule (compose: конфиг авто-обновления) 2026-06-07 19:27:36 +03:00
nk
9828bdf8d1 chore(app): bump версии до 6 / 1.5 (релиз: распознавание Shazam + история) 2026-06-07 19:22:06 +03:00
nk
bdeb57c2ad fix(app): кнопка распознавания видна и при пустом исполнителе трека
track == null почти не выполнялось: «безымянные» станции шлют ICY-строку без
разделителя → parseIcyTitle делает трек с пустым artist. Показываем кнопку, когда
нет РЕАЛЬНОГО трека (track null ИЛИ пустой artist/song ИЛИ song == имя станции).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:59:46 +03:00
nk
38ddc96fab feat(app): сообщение «слишком много запросов» (429) при распознавании
Bump backend submodule (глобальный лимит распознаваний).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:47:38 +03:00
nk
c0ee47b699 fix(app): таймаут 30с для запроса распознавания Shazam
Бэкенд-распознавание асинхронное (чанк аудио + поллинг ~до 18с) — точечно
поднимаем readTimeout до 30с только для пути shazam/recognize (базовый 10с мал).
Bump backend submodule (двухстадийный флоу shazam-api.com).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:45:08 +03:00
nk
69682268f3 feat(app): кнопка «Распознать трек» (Shazam) + история распознанных
- кнопка распознавания в плеере: видна только на музыкальных станциях без
  метаданных эфира (track == null), показывает спиннер и результат через Toast
- распознанный трек отображается в плеере и пишется в ОТДЕЛЬНУЮ историю
  распознанных (не дублируется в историю эфирных треков — гейт по ключу)
- экран Истории: переключатель «Треки эфира | Распознанные», два списка
- Room: таблица recognized_track (миграция 7→8), DAO/репозиторий
- ShazamRepository → POST /shazam/recognize/{stationId}, маппинг 503/400 в текст
- MusicGenres.isMusicStation — клиентский гейт (синхронизирован с бэкендом)
- bump backend submodule (модуль shazam)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:38:17 +03:00
nk
251809df33 fix(filters): уменьшение чипа только у кнопки (offset=0 в покое = полный)
Баг: offset первого чипа в покое = 0 (считается от начала контента, не от вьюпорта),
а формула ждала offset≥235px → «Все» и соседние были невидимы/крошечные через пол-экрана.
Фикс: recedeFactor = info.offset / shrinkPx + 1 — полный размер в покое и правее (offset≥0),
уменьшение/затухание ТОЛЬКО когда чип уезжает влево под кнопку (offset<0, зона ~32dp).
Отступ-зазор уменьшен 96→60dp (Радио) / 100→64dp (Чарты) — «Все» вплотную к кнопке.
2026-06-07 17:55:11 +03:00
nk
8b1c65fa43 feat(filters): чип уменьшается и тает у кнопки (эффект отдаления в глубину)
Вместо простого затухания края — per-chip трансформация по позиции: чип масштабируется
0.42..1 + alpha по recedeFactor (offset элемента в LazyRow: правее 92dp — норма, к 46dp —
исчез). transformOrigin центр-лево → будто уходит в глубину под кнопку-категорию.
Отступ слева увеличен (взлётка под кнопкой 96/100dp). Радио и Чарты.
2026-06-07 17:43:10 +03:00
nk
87dca7a6df feat(filters): чипы растворяются под кнопкой-категорией (fade left edge)
Кнопка-категории теперь ПОВЕРХ чипов (Box-оверлей), чипы идут во всю ширину с
отступом слева под кнопку. У левого края — затухание прозрачности (Modifier.
fadingStartEdge: graphicsLayer Offscreen + horizontalGradient BlendMode.DstIn), так
чипы при прокрутке влево красиво уплывают под кнопку и растворяются, а не обрезаются.
FilterChips/GenreSelector получили параметр contentPadding. Экраны Радио и Чарты.
2026-06-07 17:33:35 +03:00
nk
78282e97ca feat(filters): быстрый выбор категории + очистка поиска
- Кнопка-«категории» (круглая, акцентная рамка, иконка SlidersHorizontal) СЛЕВА от
  чипа «Все» — на экранах Радио и Чарты. Открывает шторку со списком всех категорий
  (Радио — жанры, Чарты — стили) + поиск, чтобы не листать чипы. CategoryPicker —
  переиспользуемый компонент с поиском и отметкой выбранного.
- SearchBar: анимированная кнопка очистки (X, scale+fade появление, haptic) при
  непустом запросе.
2026-06-07 17:25:12 +03:00
nk
645c2f14db fix(nav): анимация иконки заметнее — медленнее и с отскоком (spring 0.36/240) 2026-06-07 17:08:55 +03:00
nk
a5d9a06c3f feat(nav+fav): порядок меню, анимация иконки при выборе, индикатор на избранном
- Порядок нижнего меню: Радио · Избранное · История · Чарты · Запись · Настройки.
- Иконка вкладки при выборе делает упругий scale-«поп» (spring MediumBouncy) —
  в нижнем баре и боковом рейле.
- На экране «Избранное» играющая станция теперь подсвечивается так же, как на
  главной: вращающееся свечение под обложкой + индикатор-эквалайзер в углу
  (FavoritesViewModel отдаёт playingStationId/isPlaying из PlayerController,
  FavoritesScreen передаёт isCurrent/isPlaying в StationCard).
2026-06-07 17:06:28 +03:00
nk
d63c1d4187 feat(splash+icon): фон иконки-градиент под тему + темо-зависимый сплэш
- Подложка adaptive-иконки: градиент под акцент темы + радиальное свечение + мягкая
  тень от логотипа (ic_bg_<тема>, было плоским цветом). Иконку-лого не трогал.
- Сплэш под выбранную тему: системный сплэш Android 12+ нельзя перекрасить под выбор
  пользователя (alias-тема на ColorOS игнорится), поэтому системный = просто тёмный
  (splash_transparent), а красивый сплэш рисуем сами на Compose (SplashOverlay):
  3D-лого + акцентное свечение + тень + анимация, цвет берём из текущей темы.
- Тему на старте читаем синхронно из SharedPreferences (мгновенно, без блокировки кадра).
- Ускорен холодный старт до первого кадра 1.48с→1.11с: сплэш рисуется на первом
  дешёвом кадре, тяжёлый контент (ViewModels/плеер) композится под ним; старт
  PlayerService уведён с критического пути. Остаток — оверхед debug-сборки.
2026-06-07 16:57:01 +03:00
nk
01729e0a52 fix(brand): отступы лого в иконке + новый логотип на сплэше
- ic_fg_<тема> уменьшены (196/432) — у логотипа появились поля от краёв подложки.
- splash: windowSplashScreenAnimatedIcon → @drawable/splash_logo (новая монограмма,
  forest-цвет, т.к. сплэш показывается до загрузки темы); было @mipmap/ic_launcher.
- дефолтная ic_launcher/round переведена на новый forest-логотип (недавние/настройки).
2026-06-07 16:22:47 +03:00
nk
2fcc065a18 feat(brand): новый 3D-логотип (монограмма R) + лого/иконка под цветовую тему
Логотип: монограмма-R пользователя отрендерена в матовый 3D через routerai
(gpt-5.4-image), один мастер перекрашен под 8 тем (recolor по яркости, форма
идентична).
- Внутри приложения: AppMark показывает перекрашенный 3D-логотип текущей палитры
  (LocalThemePalette + ThemePalette.logoRes, drawable logo_<тема>).
- Иконка лаунчера следует теме: 8 adaptive-иконок (ic_fg_<тема> + ic_bg_<тема>) и
  8 activity-alias в манифесте; LauncherIconManager включает alias выбранной темы,
  гасит остальные (ровно один активен, guard против лишних миганий). Переключение —
  в MainActivity по LaunchedEffect(paletteId). На ColorOS иконка может обновляться
  с задержкой — особенность системы.
Скрипты генерации в design/logos (ключ routerai — вне репо, ~/.routerai_key).
2026-06-07 16:17:39 +03:00
nk
07f56acf27 fix(player): фон не глохнет — запрос уведомлений + исключение из оптимизации батареи
На OnePlus/ColorOS радио глохло в фоне даже с wake mode. Причины: POST_NOTIFICATIONS
не выдан (медиа-уведомление не показывалось → foreground-сервис хрупкий) и
приложение не в вайтлисте Doze. MainActivity на старте запрашивает POST_NOTIFICATIONS
(13+), затем системный диалог REQUEST_IGNORE_BATTERY_OPTIMIZATIONS (один раз).
v1.4 / versionCode 5 (clean-сборка).
2026-06-07 14:42:00 +03:00
nk
69f48d235e fix(release): v1.3 (versionCode 4) clean-сборка — лечит зацикливание обновления
Причина петли: при bump 2→3 инкрементальная сборка НЕ пересобрала BuildConfig.dex —
в v3 APK manifest versionCode=3, но BuildConfig.VERSION_CODE остался 2. Приложение
сравнивало manifest(3) > BuildConfig(2) → всегда предлагало обновиться, ставило
тот же v3, снова предлагало. Фикс: ./gradlew clean перед сборкой релиза —
BuildConfig теперь совпадает с versionCode. Проверено на телефоне: v4, диалога нет.
2026-06-07 14:29:26 +03:00
nk
44807c9dba chore(release): v1.2 (versionCode 3) — фоновое воспроизведение, эквалайзер, темы 2026-06-07 12:37:01 +03:00
nk
6eb614a729 fix(player): не глохнуть в фоне (wake mode) + авто-переподключение потока
Симптом: по Bluetooth в машине с выключенным экраном радио через время замолкало.
Причины и фиксы:
- setWakeMode(C.WAKE_MODE_NETWORK) + право WAKE_LOCK — ExoPlayer держит partial
  wakelock + wifilock во время игры. Без этого система усыпляла CPU/Wi-Fi при
  выключенном экране → буфер пустел → поток глох (главная причина).
- onPlayerError → scheduleReconnect(): при обрыве сети (туннели, край соты) поток
  пере-готавливается с нарастающей задержкой (2с→15с, до 10 попыток), а не
  замолкает навсегда. Счётчик сбрасывается при успешном старте; переподключение
  отменяется при ручной паузе/стопе/смене станции.
2026-06-07 12:35:07 +03:00
nk
e736c2393f feat(eq): настоящий эквалайзер + улучшайзеры звука (audiofx)
Раньше пресет эквалайзера в настройках был косметикой (лежал в DataStore, к звуку
не подключён). Теперь — реальные системные эффекты на фикс. аудиосессии плеера:
- AudioEffectsController: графический Equalizer (полосы устройства), BassBoost,
  Virtualizer (объём), LoudnessEnhancer (громкость тихих, до +12 дБ). Привязка к
  generateAudioSessionId() в PlayerController, переживает смену станций. Применение
  в реальном времени, сохранение в DataStore (commit на отпускании слайдера).
- Отдельный экран EqualizerScreen (Настройки → ЗВУК → Эквалайзер): тумблер,
  системные пресеты + «Свой», слайдеры полос (±дБ), bass/virtualizer/loudness.
- Эффекты best-effort: при отсутствии поддержки блок недоступен (null), UI скрывает.
- Убран фейковый чип-пресет Flat/Rock/Pop/Jazz/Bass.
2026-06-06 21:14:38 +03:00
nk
0c01eaab2d feat(update): авто-обновление APK (как в nkVPN)
UpdateManager: на старте дёргает /app-version, при version_code > BuildConfig.
VERSION_CODE показывает UpdateDialog. Скачивает APK во внутр. Download, сверяет
SHA-256 (защита от подмены по HTTP/битой загрузки), ставит через системный
установщик (FileProvider). force_update делает диалог необкрываемым. versionCode
1→2, versionName 1.0→1.1. Добавлено право REQUEST_INSTALL_PACKAGES, путь в file_paths.
2026-06-06 20:21:55 +03:00
nk
ed926e0a9d feat(theme): выбор цветовой темы (8 палитр) в настройках
8 тёмных палитр (Лес/Океан/Закат/Аметист/Неон/Янтарь/Лёд/Роза) в Palettes.kt.
RadiolaColors теперь несёт все токены + градиент; RadiolaTheme(palette) строит из
неё и RadiolaColors, и Material ColorScheme — всё приложение берёт цвета через
RadiolaTheme.colors, поэтому смена палитры перекрашивает мгновенно. Бренд-марка
(AppMark/Wordmark) тоже следует теме. Выбор в DataStore (theme_palette, дефолт
forest), читается в MainActivity и подаётся в тему. Секция «ТЕМА ОФОРМЛЕНИЯ» в
настройках — горизонтальный ряд свотчей с превью (фон+акцент+градиент).
2026-06-06 19:11:33 +03:00
nk
d9acc0efb4 fix(sleep): звук сна вплывает в конце таймера, а не в первые 90 секунд
Было: при таймере со звуком радио кроссфейдилось в шум в НАЧАЛЕ (CROSSFADE_MS=90с),
и для 15-мин таймера уже через ~1.5 мин играл полный белый шум всё оставшееся время.

Стало: радио играет почти весь таймер; в последние SOUND_OUTRO_MS (3 мин, но не
больше половины таймера) включается звук сна — радио кроссфейдится в шум, шум держится,
в самом конце затухает в тишину. Генератор шума стартует лениво (только в аутро, не
молотит весь таймер). Засыпаешь под радио, а не под резкий шум сразу.
2026-06-06 18:21:04 +03:00
nk
84c2b33473 perf(android): ленивый плеер записей, O(n) merge каталога, @Immutable, лог только в debug
- RecordingPlaybackController: ExoPlayer создаётся лениво (на первый play) и
  освобождается в stop() — раньше второй плеер висел в памяти всю сессию у каждого.
  Поллер позиции 500мс крутится только во время игры (был вечный 2 Гц main-loop).
- StationRepositoryImpl.refreshStations: merge каталога O(n) через apiById/apiByName
  индексы вместо .find на каждую станцию (было O(n²) ~700×700 на холодном старте).
  Убраны verbose Log.d-трейсы (оставлен Log.e/.w на ошибки).
- Track/Station/StreamQuality помечены @Immutable — read-only модели, иначе
  списки tags/qualities делали класс нестабильным → лишние рекомпозиции списков.
- HttpLoggingInterceptor только при BuildConfig.DEBUG (включён buildConfig feature):
  в релизе нет оверхеда на каждый запрос и утечки URL в logcat.
2026-06-06 17:13:48 +03:00
nk
f423344d13 perf(android): батарея и плавность — gate FFT, изоляция рекомпозиции, поллинг на паузе
- AudioSpectrumAnalyzer: FFT считается ТОЛЬКО когда открыт плеер (флаг active);
  раньше ~86 FFT/с молотили всегда при проигрывании (даже экран выкл) — главный
  пожиратель батареи. Включается из VisualizerHost через DisposableEffect.
- Спектр (45/с) собирается в leaf VisualizerHost, а не на верху PlayerBottomSheet —
  весь плеер больше не рекомпозится 45 раз/сек.
- now-playing поллинг (5с) останавливается на паузе (isPlaying.collectLatest) —
  раньше на паузе зря дёргали сеть каждые 5с.
- PlayerService.onDestroy отменяет serviceScope (singleton-плеер НЕ релизим).
- refreshStations (парс ~700 станций + сеть + Room) уведён на Dispatchers.IO с
  главного потока (jank/ANR на старте).
- Coil ImageLoader: память 25% + диск 100МБ (обложки не перекачиваются каждую сессию).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:33:00 +03:00
nk
861b0e2b8f feat: будильник с радиостанцией + выбор битрейта по умолчанию
Будильник (Settings → Будильник): несколько будильников, время, станция, дни недели,
fade-in пробуждения. AlarmManager.setAlarmClock (вне doze) + фолбэк, BootReceiver
перепланирует после перезагрузки, AlarmReceiver→PlayerService (foreground) →
PlayerController.startAlarmPlayback (нарастание громкости). Room: AlarmEntity/Dao, БД v7.
Выбор битрейта по умолчанию в Settings (Авто/Эконом/Стандарт/Высокое) → preferredBitrate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:25:42 +03:00
nk
4411d53a6c feat(player): звуки для сна (белый/розовый/коричневый шум) + Smart Sleep Fade
SleepSoundPlayer — процедурная генерация цветного шума через AudioTrack (розовый —
фильтр Келлета, коричневый — random walk). В таймере сна выбор звука: радио плавно
перетекает в выбранный шум (кроссфейд ≤90с), шум играет, к концу затухает — как в
спеке («Smart Sleep Fade»). В шторке таймера — чипы выбора звука.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:08:32 +03:00
nk
bda2c5b30f feat(player): таймер сна с плавным затуханием (fade-out)
P0-фича из спеки. PlayerController: startSleepTimer/cancelSleepTimer — в последние
20с экспоненциальный fade-out громкости (frac^2), затем пауза + возврат громкости.
В плеере — пилюля «Таймер сна» (иконка Moon): при активном показывает остаток
M:SS акцентом. Шторка с интервалами 15/30/45/60/90/120 мин + «Выключить».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 10:08:54 +03:00
nk
29cbe8997f feat(stations): убран чип «Новый год» (межсезонный тег Record)
Christmas Chill и др. остаются в разделе Radio Record (genre=Radio Record),
просто скрыт сам тег-чип через hiddenTags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 08:36:41 +03:00
nk
5da077b698 fix(stations): Новое Радио BY — маунт Wake Up (wakeupshow→wakeup, был 404)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 21:21:52 +03:00