Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,47 @@ jobs:
- name: Юнит-тесты
run: ./gradlew testDebugUnitTest

# Заметки к релизу пишутся тем же PR, которым вносится изменение. Галочка в
# шаблоне PR это уже просит — и её ставили, не заполняя раздел: 0.2.0 и 0.2.1
# вышли с заметками, собранными из git-лога. Проверяет машина.
changelog:
name: Заметки к релизу
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v4

- name: Раздел «Не выпущено» заполнен
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
run: |
set -euo pipefail

# Ровно те же два условия, при которых release-on-merge.yml не
# выпускает релиз: нечего выпускать — нечего и описывать.
if echo "$LABELS" | grep -qF 'без-релиза'; then
echo "Метка «без-релиза» — заметки не нужны"
exit 0
fi

FILES="$(gh pr view "$PR" --json files --jq '.files[].path')"
if [ -z "$(printf '%s\n' "$FILES" | grep -vE '(^docs/|\.md$)' || true)" ]; then
echo "PR тронул только документацию — заметки не нужны"
exit 0
fi

# Логика «что считается записью» одна на проверку и на выпуск —
# она живёт в скрипте, здесь только повод его дёрнуть.
if ! python3 scripts/bump-version.py --check-unreleased; then
echo "::error file=CHANGELOG.md::раздел «Не выпущено» пуст — допишите заметку к релизу"
exit 1
fi

backend:
runs-on: ubuntu-latest
steps:
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,27 @@ jobs:
"dist/neiro-$VERSION.apk" "dist/mapping-$VERSION.txt.gz" dist/SHA256SUMS.txt
fi

# Телефоны узнают о релизе в ту же минуту: neiro-push рассылает
# data-push app_update, приложение проверяет GitHub и показывает
# уведомление. Без этого шага новость ждала бы суточной проверки.
#
# continue-on-error: недоступный Pi — не повод отмечать релиз упавшим.
# APK уже опубликован, и телефоны найдут его сами, просто позже.
- name: Сказать телефонам о релизе
continue-on-error: true
env:
BASE_URL: ${{ secrets.NEIRO_PUSH_API_BASE_URL }}
RELEASE_NOTIFY_KEY: ${{ secrets.NEIRO_PUSH_RELEASE_NOTIFY_KEY }}
run: |
if [ -z "$BASE_URL" ] || [ -z "$RELEASE_NOTIFY_KEY" ]; then
echo "::notice::секретов для neiro-push нет, пуш о релизе пропускаю"
exit 0
fi
curl -fsS --max-time 60 -X POST "${BASE_URL%/}/v1/release/notify" \
-H "Authorization: Bearer $RELEASE_NOTIFY_KEY" \
-H "Content-Type: application/json" \
-d "{\"version_name\":\"$VERSION\"}"

# Ключи не должны пережить сборку даже на одноразовой машине.
- name: Убрать секреты
if: always()
Expand Down
58 changes: 55 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,61 @@

## [Не выпущено]

Пока пусто. Сюда дописывается изменение, заметное пользователю, — тем же PR,
которым оно вносится, а не по памяти в день выпуска. Заголовки внутри:
«Добавлено», «Изменено», «Исправлено», «Убрано».
### Добавлено

- **Уведомление о новой версии приходит сразу** — раньше приложение узнавало о
вышедшем обновлении в течение суток: проверяло раз в день и при открытии.
Теперь сообщение о новой версии приходит в течение минуты после выпуска,
даже если приложение закрыто. Отключается там же, где и было, — «О
программе» → «Проверять автоматически».

## [0.2.1] — 2026-08-20

### Исправлено

- **Стоимость занятия за текущий месяц** — цена бралась из последнего закрытого
расчёта, а он закрывается в конце месяца. После повышения ставки приложение
считало заработок по старой цене до конца месяца. Теперь ставка берётся из
начисления за день, то есть видна сразу.
- **Склонения в подписях и в дате календаря** — «2 занятия», «5 занятий»,
«21 августа» вместо форм, которые не сходились с числом.

### Изменено

- **Кнопка «Сегодня» вернулась к прежнему виду** — после перевода на стеклянный
вид она стала заметно больше и перетягивала внимание с календаря.
- **Стеклянный вид включён по умолчанию** — на чистой установке приложение
сразу выглядит так, как задумано. Выключенный вручную тумблер остаётся
выключенным.

## [0.2.0] — 2026-08-20

### Добавлено

- **Стеклянный вид** — диалоги стали полупрозрачными, календарь за ними
размывается. Тумблер в «Настройки → Внешний вид → Стеклянный вид»; выключите
его, и всё рисуется как раньше, непрозрачным.
- **Свайп уведомлений в обе стороны** — уведомление в ленте убирается движением
и влево, и вправо, а не только в одну сторону.

### Изменено

- **Новый вид календаря** — чёрный фон и новые плитки дня; шапка и нижняя
плашка дня стали прозрачными и больше не перекрывают список занятий.
- **Уведомления о занятиях по умолчанию** — на чистой установке напоминание
перед занятием и сводки на сегодня и на завтра выключены, а напоминание об
архиве включено. Уже настроенного вами это не касается.
- **Списки в диалогах не обрезаются** — длинный список прокручивается целиком,
а его края растворяются, показывая, что дальше есть ещё.

### Исправлено

- **Счётчик уведомлений в шапке** — двузначное число обрезалось кружком.
- **Строки «Налог за месяц» и «Стоимость одного занятия»** — они появлялись в
диалоге «Финансы» сами, даже если их не включали: стоило один раз тронуть
любой тумблер в настройках прибыли, и старое значение уезжало в память
навсегда. Обе строки служебные, для проверки расчётов, — теперь они
выключены. Включённые заново после обновления остаются включёнными.

## [0.1.7] — 2026-08-15

Expand Down
20 changes: 20 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@
- `Refactor CalendarHeader for improved usability`
- любое сообщение длиннее одной короткой фразы

## Тронул код — дописал CHANGELOG

Любая правка, которую видно пользователю, идёт вместе с записью в разделе
«Не выпущено» файла `CHANGELOG.md` — **тем же изменением**, а не потом.

Записи пишутся для того, кто пользуется приложением: «что изменилось у меня на
экране», а не «какой класс переехал». Заголовки внутри раздела — «Добавлено»,
«Изменено», «Исправлено», «Убрано».

Проверяется машиной: `python3 scripts/bump-version.py --check-unreleased`, и
тем же на каждом PR с кодом (job `changelog` в `ci.yml`). Пустой раздел роняет
CI.

Почему: слияние в main выпускает релиз сразу, а `bump-version.py` молча
пропускает пустой раздел — версия уходит со списком коммитов вместо описания,
и дописать его уже некуда. Так вышли 0.2.0 и 0.2.1.

Выпускать нечего (правка тестов, чистый рефакторинг, работа только с
документацией) — метка «без-релиза» на PR, тогда ни релиза, ни проверки.

## Без Gradle-сборки

Никогда не запускать `./gradlew` или любые Gradle-команды (`assembleDebug`, `build`, `test` и т.п.).
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@
публикует релиз с описанием изменений. Разряд версии задаётся меткой PR —
[как это устроено](docs/updater/RELEASE.md#как-выпустить-версию).

Приложение раз в сутки спрашивает GitHub, не вышло ли что-то новее. Нашло —
показывает уведомление; по согласию скачивает APK, сверяет SHA256 **и** подпись
со своей, и ставит поверх. Настройки, календарь и архив остаются на месте.
О вышедшей версии приложение узнаёт сразу: сервер `neiro-push` рассылает push
в ту же минуту, когда релиз опубликован. Push не дошёл — приложение и само
спрашивает GitHub раз в сутки и при открытии. Нашло — показывает уведомление;
по согласию скачивает APK, сверяет SHA256 **и** подпись со своей, и ставит
поверх. Настройки, календарь и архив остаются на месте.
На Android 12+ обновление приложения самого себя проходит без системного
диалога — согласия, которое пользователь уже дал, достаточно.

Expand Down Expand Up @@ -142,6 +144,7 @@ FCM-токены устройств. Сервер — Raspberry Pi владел
| Архив | [docs/archive/](docs/archive/README.md) | Закрытые аудиты, старая переработка и оба погашенных поколения push-сервиса — история, не руководство |
| Скриншоты | [docs/screenshots.md](docs/screenshots.md) | Что снимать, требования README и RuStore, скрипт разбора кадров |
| Боковая панель | [docs/profile-drawer.md](docs/profile-drawer.md) | Drawer профиля, жесты, файлы в коде |
| Оформление | [docs/appearance.md](docs/appearance.md) | Тема, палитры, семантические цвета и стеклянный вид: что где живёт и почему |
| Аудит | [docs/audit/METHODIKA.md](docs/audit/METHODIKA.md) | Как проводить аудит: границы, чек-листы по областям, формат пакета, история прошлых аудитов |
| Последний аудит | [docs/archive/audit-14.08.26/](docs/archive/audit-14.08.26/REPORT.md) | Пакет от 14.08.2026: 18 находок, все закрыты; отчёт о прогоне, архитектура на дату |
| Что осталось доделать | [docs/next/](docs/next/README.md) | Доводка после перехода на `neiro-push`: шесть этапов с планом работ |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,30 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.runBlocking
import ru.greemlab.neiro.data.network.YClientsRepository
import ru.greemlab.neiro.update.UpdateCheckCoordinator

/**
* FCM: сервер шлёт события занятий (`session_events`) прямо в payload — правим
* календарь и показываем уведомление без похода в YClients. Если пуш не влез в
* лимит, сервер шлёт нудж (`sync_events`) — забираем через WorkManager, сеть в
* FCM-сервисе не место (app.md §6.3).
*
* Третий вид — `app_update`: сервер получил от GitHub Actions новость о новом
* релизе и разослал её на телефоны, чтобы обновление не ждало суточной
* проверки.
*/
class NeiroFirebaseMessagingService : FirebaseMessagingService() {

override fun onMessageReceived(message: RemoteMessage) {
when (message.data["action"]) {
"session_events" -> handleSessionEvents(message)
"sync_events" -> PushEventsSyncCoordinator.enqueue(applicationContext)
// «Вышел релиз» — сеть отсюда не трогаем по тем же причинам, что и
// при нудже: проверку ставит WorkManager (app.md §6.3).
"app_update" -> UpdateCheckCoordinator.onUpdatePush(
applicationContext,
message.data["version_name"],
)
}
}

Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/ru/greemlab/neiro/theme/GlassStyle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ import androidx.compose.ui.window.DialogWindowProvider
/**
* Стеклянный вид поверхностей — тумблер в «Настройки → Внешний вид».
*
* Выключен по умолчанию: при `false` всё рисуется ровно как раньше, обычным
* [Surface] с непрозрачным цветом схемы. Настройка живёт в
* `AppearancePreferences`, сюда её кладёт `NeiroApp`.
* Включён по умолчанию (`AppearancePreferences.DEFAULT_GLASS`) — приложение с
* ним и задумано. При `false` всё рисуется ровно как раньше, обычным
* [Surface] с непрозрачным цветом схемы; настройку сюда кладёт `NeiroApp`.
*
* Значение по умолчанию самого `CompositionLocal` — `false`: превью и тесты,
* которые про стекло ничего не знают, должны получать простую поверхность, а
* не лезть за настройкой в `Context`.
*/
val LocalGlassEnabled = staticCompositionLocalOf { false }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ object NeiroSurfaceAlpha {
const val PANEL = 0.41f

/** Карточка внутри панели: плашка дня. */
const val CARD = 0.26f
const val CARD = 0f

/** Плитка внутри панели или карточки: счётчики, суммы. */
const val TILE = 0.40f
Expand Down
12 changes: 8 additions & 4 deletions app/src/main/java/ru/greemlab/neiro/theme/ThemeMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import ru.greemlab.neiro.data.THEME_SYSTEM
/**
* Что выбрано в настройках: светлая, тёмная или «как в системе».
*
* Хранится строкой — значение [storageId] уезжает в DataStore, экспорт архива
* и синхронизацию, поэтому переименовывать его нельзя: старый бэкап должен
* читаться новой версией приложения. Внутри кода со строками работать не надо,
* есть [fromStorageId].
* Хранится строкой — значение [storageId] лежит в DataStore приложения
* (`CalendarDataStore`, ключ `app_theme`), поэтому переименовывать его нельзя:
* записанное старой версией должна прочитать новая. Внутри кода со строками
* работать не надо, есть [fromStorageId].
*
* Устройство тема не покидает: в файл экспорта архива она не входит, в
* синхронизацию с YClients тоже, а облачный бэкап Android выключен целиком
* (`backup_rules.xml`).
*
* Это «какой яркости» тема, а не «какого цвета» — цвет живёт отдельно, в
* [NeiroPalette], и одна палитра всегда умеет обе яркости.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import kotlinx.coroutines.flow.asStateFlow
/**
* Оформление приложения поверх выбранной темы (светлая/тёмная/системная).
*
* Живёт в отдельных prefs, а не в DataStore рядом с темой: тема уезжает в
* синхронизацию и экспорт, а вид поверхностей — местная настройка телефона.
* Живёт в отдельных prefs, а не в DataStore рядом с темой: тот держит данные
* календаря, и класть туда тумблер оформления значит будить перерисовку всего
* снимка состояния ради одного `Boolean`.
*
* Стеклянный вид включён по умолчанию: приложение с ним и задумано, и первый
* запуск должен показать его сразу. Дефолт срабатывает только пока ключа в
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/ru/greemlab/neiro/update/ReleaseVersion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ data class ReleaseVersion(
)
}

/**
* Версия без `v` — в таком виде она приходит в пуше `app_update` и
* лежит в `version.properties`. Разбор общий с тегом: две разные
* реализации однажды разошлись бы на «0.1.100».
*/
fun parseName(name: String): ReleaseVersion? =
parseTag("v" + name.trim().removePrefix("v"))

/** null — тег не нашей схемы; такой релиз молча игнорируем. */
fun parseTag(tag: String): ReleaseVersion? {
val m = TAG_REGEX.matchEntire(tag.trim()) ?: return null
Expand Down
Loading
Loading