Skip to content

Commit d1ad439

Browse files
committed
Усилить проверку курсов и выпуск 1.8.1
1 parent 9d78a97 commit d1ad439

39 files changed

Lines changed: 891 additions & 186 deletions

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: gradle
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
day: monday
8+
open-pull-requests-limit: 5
9+
groups:
10+
android-dependencies:
11+
patterns:
12+
- "androidx.*"
13+
14+
- package-ecosystem: github-actions
15+
directory: /
16+
schedule:
17+
interval: weekly
18+
day: monday
19+
open-pull-requests-limit: 5

.github/workflows/android.yml

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name: Проверка Android
33
on:
44
push:
55
branches: [main]
6+
tags: ["v*"]
67
pull_request:
78

89
permissions:
@@ -16,19 +17,92 @@ jobs:
1617

1718
steps:
1819
- name: Получить исходный код
19-
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
20+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21+
with:
22+
fetch-depth: 0
2023

2124
- name: Настроить JDK 17
22-
uses: actions/setup-java@d7793b545071e98d581d3bf084a51c3213318a07 # v4
25+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
2326
with:
2427
distribution: temurin
2528
java-version: "17"
2629

2730
- name: Проверить Gradle Wrapper
28-
uses: gradle/actions/wrapper-validation@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
31+
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
2932

3033
- name: Настроить Gradle
31-
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
34+
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
35+
36+
- name: Проверить пробелы в изменениях
37+
env:
38+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
39+
run: |
40+
if [[ -n "$BASE_SHA" ]]; then
41+
git diff --check "$BASE_SHA"...HEAD
42+
else
43+
git diff --check HEAD^ HEAD
44+
fi
3245
3346
- name: Проверить проект
34-
run: ./gradlew --no-daemon test lint assembleDebug
47+
run: ./gradlew --no-daemon test lint assembleDebug assembleRelease
48+
49+
- name: Сохранить неподписанную релизную сборку
50+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
51+
with:
52+
name: CleanRate-unsigned-${{ github.sha }}
53+
path: |
54+
app/build/outputs/apk/release/app-release-unsigned.apk
55+
app/build/outputs/mapping/release/mapping.txt
56+
if-no-files-found: error
57+
retention-days: 14
58+
59+
provenance:
60+
name: Происхождение сборки тега
61+
if: startsWith(github.ref, 'refs/tags/v')
62+
needs: [test, android-runtime]
63+
runs-on: ubuntu-latest
64+
permissions:
65+
contents: read
66+
id-token: write
67+
attestations: write
68+
69+
steps:
70+
- name: Получить неподписанную сборку
71+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
72+
with:
73+
name: CleanRate-unsigned-${{ github.sha }}
74+
path: release
75+
76+
- name: Подтвердить происхождение APK
77+
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
78+
with:
79+
subject-path: release/app-release-unsigned.apk
80+
81+
android-runtime:
82+
name: Проверка на Android 15
83+
runs-on: ubuntu-latest
84+
timeout-minutes: 30
85+
permissions:
86+
contents: read
87+
88+
steps:
89+
- name: Получить исходный код
90+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
91+
92+
- name: Настроить JDK 17
93+
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
94+
with:
95+
distribution: temurin
96+
java-version: "17"
97+
98+
- name: Настроить Gradle
99+
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
100+
101+
- name: Запустить инструментальные тесты
102+
uses: ReactiveCircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
103+
with:
104+
api-level: 35
105+
arch: x86_64
106+
target: default
107+
disable-animations: true
108+
script: ./gradlew --no-daemon connectedDebugAndroidTest

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
Все заметные изменения проекта перечисляются в этом файле.
44

5+
## 1.8.1 — 2026-08-02
6+
7+
- данные с устаревшей или неправдоподобно будущей датой больше не считаются актуальными;
8+
- сетевые ответы ограничены по размеру, разрешены только HTTPS-переходы, XML ЦБ разбирается с защитой от внешних сущностей;
9+
- точки графика размещаются по реальным календарным датам, а пропуски данных явно обозначаются;
10+
- кэш истории ограничен, загрузка имеет общий тайм-аут, параллельное обновление источников ускорено;
11+
- график получил действия для TalkBack;
12+
- проект переведён на Android API 36, Gradle 8.14.5 и Android Gradle Plugin 8.11.1;
13+
- CI проверяет релизную сборку и запуск на Android 15, сохраняет неподписанный APK и подтверждает происхождение сборок тегов;
14+
- добавлены Dependabot, инструкция проверяемого выпуска и сведения о происхождении звука.
15+
516
## 1.8.0 — 2026-08-02
617

718
- мировой курс переведён на открытый источник `exchange-api` с лицензией CC0 1.0;

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
1. Создайте ответвление репозитория и отдельную ветку.
66
2. Не добавляйте ключи подписи, пароли, APK, AAB, `local.properties` и локальные пути.
7-
3. Выполните `./gradlew test lint assembleDebug`.
7+
3. Выполните `./gradlew test lint assembleDebug assembleRelease`.
88
4. Заголовок и описание запроса на слияние, а также комментарии об изменениях пишите на русском языке.
99
5. Опишите изменение и результат проверки в запросе на слияние.
1010

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Конвертер валют для Android
1+
# Конвертер валют для Android
22

33
Персональный Android-конвертер валют без рекламы, регистрации и аналитики.
44

@@ -23,7 +23,7 @@
2323
## Установка APK
2424

2525
1. Откройте раздел **Releases** справа на странице репозитория.
26-
2. Выберите последний выпуск и скачайте файл `CleanRate-1.8.0.apk`.
26+
2. Выберите последний выпуск и скачайте файл `CleanRate-1.8.1.apk`.
2727
3. Если Android спросит разрешение, временно разрешите установку из браузера или файлового менеджера.
2828
4. Откройте APK и подтвердите установку.
2929
5. После установки верните запрет на установку неизвестных приложений, если он больше не нужен.
@@ -41,6 +41,9 @@
4141
- введённые суммы и собственные курсы банка остаются на устройстве;
4242
- ключ подписи не хранится в репозитории.
4343

44+
Официальный сертификат подписи имеет SHA-256-отпечаток
45+
`1798311602fa64b2f43832bcd16252d6ae338654653f9b149f4b33261b83ded0`.
46+
4447
Полный текст: [политика конфиденциальности](PRIVACY.md). Инструкции по сообщениям об уязвимостях: [SECURITY.md](SECURITY.md).
4548

4649
## Источники курсов
@@ -52,15 +55,15 @@
5255

5356
## Сборка
5457

55-
Нужны JDK 17 и Android SDK 35.
58+
Нужны JDK 17 и Android SDK 36.
5659

5760
```shell
58-
./gradlew test lint assembleDebug
61+
./gradlew test lint assembleDebug assembleRelease
5962
```
6063

6164
Отладочный APK появится в `app/build/outputs/apk/debug/app-debug.apk`.
6265

63-
Релизные APK подписываются владельцем проекта локально. Ключ подписи и его пароль нельзя добавлять в Git, задачи CI или публичные архивы.
66+
Релизные APK подписываются владельцем проекта локально. Ключ подписи и его пароль нельзя добавлять в Git, задачи CI или публичные архивы. Полная проверяемая процедура описана в [инструкции по выпуску](RELEASING.md).
6467

6568
## Участие в разработке
6669

RELEASE_NOTES_1.8.1.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Чистый курс 1.8.1
2+
3+
Технический выпуск, который делает обновление курсов и публикацию APK проверяемыми.
4+
5+
## Что изменилось
6+
7+
- приложение отличает свежую дату курса от времени последней проверки и предупреждает об устаревших данных;
8+
- ответы сервисов ограничены по размеру, перенаправления остаются только на HTTPS, XML Банка России защищён от внешних сущностей;
9+
- график сохраняет календарное расстояние между датами и сообщает о пропущенных точках;
10+
- выбор дат графика доступен через действия TalkBack;
11+
- кэш истории ограничен 24 наборами, а одна загрузка — 60 секундами;
12+
- целевая версия Android обновлена до API 36;
13+
- CI собирает debug и release, запускает инструментальные тесты на Android 15, а для тега создаёт аттестацию происхождения неподписанного APK.
14+
15+
Данные, настройки и последний экран сохраняются как раньше. Обновление устанавливается поверх 1.8.0 без удаления пользовательских данных.

RELEASING.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Проверяемый выпуск Android-приложения
2+
3+
Эта процедура связывает исходный коммит, проверку CI, неподписанную сборку, локальную подпись и опубликованный APK. Секретный ключ остаётся только у владельца.
4+
5+
## Один раз для репозитория
6+
7+
1. Включить **Settings → General → Releases → Release immutability**. Правило действует только на будущие выпуски.
8+
2. Включить **Settings → Security → Advanced Security → Private vulnerability reporting**, Dependabot alerts и Dependabot security updates.
9+
3. Защитить `main`: изменения через pull request и обязательная успешная проверка **Тесты, анализ и сборка**.
10+
4. Настроить подпись тегов Git и проверить, что GitHub показывает метку **Verified**.
11+
12+
## Проверка исходного кода
13+
14+
1. Убедиться, что версия в `app/build.gradle.kts`, `CHANGELOG.md` и заметках выпуска совпадает.
15+
2. Выполнить на чистом рабочем дереве:
16+
17+
```shell
18+
./gradlew --no-daemon test lint assembleDebug assembleRelease
19+
git diff --check
20+
```
21+
22+
3. Слить прошедший ревью pull request в `main`.
23+
4. Создать подписанный тег на точном коммите выпуска и отправить его:
24+
25+
```shell
26+
git tag -s v1.8.1 -m "Чистый курс 1.8.1"
27+
git push origin v1.8.1
28+
```
29+
30+
## Получение и подпись APK
31+
32+
1. Дождаться успешной задачи GitHub Actions для тега.
33+
2. Скачать артефакт `CleanRate-unsigned-COMMIT_SHA` из этой задачи.
34+
3. Проверить его происхождение:
35+
36+
```shell
37+
gh attestation verify app-release-unsigned.apk --repo Etogerman/clean-rate-android
38+
```
39+
40+
4. Подписать APK локально штатным `apksigner`, не копируя ключ в репозиторий или CI:
41+
42+
```shell
43+
apksigner sign --ks /БЕЗОПАСНЫЙ/ПУТЬ/release.jks --out CleanRate-1.8.1.apk app-release-unsigned.apk
44+
apksigner verify --verbose --print-certs CleanRate-1.8.1.apk
45+
```
46+
47+
Ожидаемый SHA-256-отпечаток сертификата:
48+
49+
```text
50+
1798311602fa64b2f43832bcd16252d6ae338654653f9b149f4b33261b83ded0
51+
```
52+
53+
5. Создать контрольную сумму:
54+
55+
```shell
56+
shasum -a 256 CleanRate-1.8.1.apk > CleanRate-1.8.1-SHA256SUMS.txt
57+
```
58+
59+
## Публикация
60+
61+
1. Создать черновик GitHub Release на уже проверенном теге `v1.8.1`.
62+
2. Приложить `CleanRate-1.8.1.apk`, файл контрольной суммы и текст `RELEASE_NOTES_1.8.1.md`.
63+
3. Ещё раз скачать файлы из черновика, сверить SHA-256, имя пакета, `versionCode`, `versionName` и сертификат.
64+
4. Опубликовать выпуск. После публикации не заменять файлы; исправления выпускать новой версией.
65+
66+
Подписанный APK закономерно отличается по SHA-256 от неподписанного артефакта CI: блок подписи добавляется после сборки. Проверка сертификата подтверждает владельца, аттестация — происхождение исходной неподписанной сборки, а неизменяемый Release защищает опубликованные файлы от последующей замены.

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
Не публикуйте ключи, пароли, персональные данные или подробности реально эксплуатируемой уязвимости в открытом Issue.
1010

11-
После публикации репозитория используйте **Security → Advisories → New draft security advisory**. Если функция недоступна, создайте краткий Issue без технических подробностей и попросите владельца проекта открыть приватный канал.
11+
Используйте **Security → Advisories → Report a vulnerability**. Эта форма передаёт отчёт владельцу приватно. Не создавайте публичный Issue с техническими подробностями уязвимости.
1212

1313
## Проверка APK
1414

15-
Скачивайте APK только из раздела Releases этого репозитория и сверяйте SHA-256 с файлом `SHA256SUMS.txt`, приложенным к тому же выпуску.
15+
Скачивайте APK только из раздела Releases этого репозитория и сверяйте SHA-256 с файлом `SHA256SUMS.txt`, приложенным к тому же выпуску. Дополнительно проверьте сертификат командой `apksigner verify --print-certs CleanRate-VERSION.apk`: SHA-256 должен совпасть с отпечатком из README.

THIRD_PARTY.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,13 @@
1515
## AndroidX и Jetpack Compose
1616

1717
Интерфейс и жизненный цикл приложения построены на официальных библиотеках AndroidX с лицензией Apache License 2.0.
18+
19+
## Kotlin и kotlinx.coroutines
20+
21+
Исходный код компилируется Kotlin, а фоновые операции используют kotlinx.coroutines. Оба проекта JetBrains распространяются по лицензии Apache License 2.0.
22+
23+
## Звук клавиши
24+
25+
`key_click.wav` — короткий синтетический звук «мягкий пузырёк», подготовленный специально для этого проекта локальным звуковым синтезом и не заимствованный из сторонней библиотеки звуков. Он распространяется вместе с исходным кодом по лицензии проекта Apache License 2.0.
26+
27+
Краткая копия этих сведений включается внутрь APK в `assets/legal/NOTICE.txt`.

app/build.gradle.kts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ plugins {
66

77
android {
88
namespace = "ru.abrikosov.cleanrate"
9-
compileSdk = 35
9+
compileSdk = 36
1010

1111
defaultConfig {
1212
applicationId = "ru.abrikosov.cleanrate"
1313
minSdk = 26
14-
targetSdk = 35
15-
versionCode = 12
16-
versionName = "1.8.0"
14+
targetSdk = 36
15+
versionCode = 13
16+
versionName = "1.8.1"
1717

1818
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1919
vectorDrawables.useSupportLibrary = true
@@ -54,12 +54,17 @@ dependencies {
5454

5555
implementation(composeBom)
5656
androidTestImplementation(composeBom)
57+
androidTestImplementation("androidx.test:core-ktx:1.7.0")
58+
androidTestImplementation("androidx.test.ext:junit:1.3.0")
59+
androidTestImplementation("androidx.test:runner:1.7.0")
5760

58-
implementation("androidx.core:core-ktx:1.15.0")
59-
implementation("androidx.activity:activity-compose:1.10.0")
60-
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
61-
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
62-
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
61+
// Следующие core 1.19 и lifecycle 2.11 требуют API 37 и AGP 9.1.
62+
// Для выпуска с targetSdk 36 используются последние совместимые стабильные версии.
63+
implementation("androidx.core:core-ktx:1.18.0")
64+
implementation("androidx.activity:activity-compose:1.13.0")
65+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
66+
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0")
67+
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
6368
implementation("androidx.compose.foundation:foundation")
6469
implementation("androidx.compose.ui:ui")
6570
implementation("androidx.compose.ui:ui-tooling-preview")
@@ -70,5 +75,5 @@ dependencies {
7075
debugImplementation("androidx.compose.ui:ui-test-manifest")
7176

7277
testImplementation("junit:junit:4.13.2")
73-
testImplementation("org.json:json:20240303")
78+
testImplementation("org.json:json:20260719")
7479
}

0 commit comments

Comments
 (0)