docs: полный аудит приложения 2026-07-07 (6 направлений, с file:line)
This commit is contained in:
@@ -0,0 +1,252 @@
|
|||||||
|
# Полный аудит f7cloud_mobile — 2026-07-07
|
||||||
|
|
||||||
|
> Автор: `b-dev-mobile` (ячейка B). Метод: 6 параллельных агентов-исследователей по
|
||||||
|
> направлениям (архитектура, безопасность, сеть, производительность/UX, качество кода,
|
||||||
|
> сборка+vendored-форк). Все находки — с `file:line`.
|
||||||
|
>
|
||||||
|
> **Объём:** ~38 500 строк собственного Kotlin в 178 файлах (app/core/feature) +
|
||||||
|
> ~92 600 строк vendored-форка talk-android (~704 файла). Тестов — **0**. Статанализа — нет. CI — нет.
|
||||||
|
|
||||||
|
## Общий вердикт
|
||||||
|
|
||||||
|
Приложение функционально зрелое (8 работающих модулей, дисциплинированные ViewModel+StateFlow,
|
||||||
|
чистый `.gitignore` без секретов, ноль TODO-долга), но построено как «долгий прототип»: релизной
|
||||||
|
сборки физически не существует, безопасность на уровне dev-стенда, инфраструктура качества
|
||||||
|
отсутствует, в календарной логике — баги порчи данных.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴 Критическое (до любой раздачи пользователям)
|
||||||
|
|
||||||
|
### C1. Release-сборки не существует
|
||||||
|
- Нигде нет `signingConfig`/`storeFile` (grep по всем `*.gradle` пуст) → `assembleRelease` даёт
|
||||||
|
неподписанный APK (`INSTALL_PARSE_FAILED_NO_CERTIFICATES`).
|
||||||
|
- `app/build.gradle:28-33` — `minifyEnabled true` + `shrinkResources true`, но
|
||||||
|
`app/proguard-rules.pro` — **1 строка (только комментарий)**; `vendor/.../proguard-rules.pro` —
|
||||||
|
всё закомментировано; у `:vendor:talk-app` нет `consumerProguardFiles`; R8 full mode включён
|
||||||
|
по умолчанию (AGP 8). Точки отказа при R8: LoganSquare `$$JsonObjectMapper` (vendor build.gradle:265),
|
||||||
|
WebRTC JNI `org.webrtc.**` (:333), Retrofit/gson-модели, EventBus `@Subscribe`, Parceler,
|
||||||
|
kotlinx.serialization. Вероятен отказ ещё на этапе сборки (missing classes: conscrypt, RxJava `sun.misc`).
|
||||||
|
- Проект живёт на debug-APK 130 МБ.
|
||||||
|
|
||||||
|
### C2. TLS выключается галочкой навсегда (MITM)
|
||||||
|
- `core/network/.../UnsafeSsl.kt:12-23` — trust-all `X509TrustManager` (пустые checkServerTrusted/
|
||||||
|
checkClientTrusted) + `hostnameVerifier { _, _ -> true }`.
|
||||||
|
- Применяется ко всем клиентам: `NetworkFactory.kt:18`, `LoginFlowClient.kt:82,168,216` (передача
|
||||||
|
логина/пароля при QR-логине). В WebView продублировано: `OfficeWebViewClient.kt:91-92`
|
||||||
|
(`onReceivedSslError → proceed()`).
|
||||||
|
- Включается чекбоксом «Доверять сертификату (dev)» `AppScaffold.kt:880-888`, сохраняется в prefs
|
||||||
|
(`AuthStore.kt:13`) навсегда, молча применяется ко всем последующим сессиям, без предупреждения.
|
||||||
|
- Fix: убрать из release (`BuildConfig.DEBUG`), заменить на certificate pinning, диалог-предупреждение.
|
||||||
|
|
||||||
|
### C3. Пароль аккаунта открытым текстом + бэкапится
|
||||||
|
- `core/auth/.../AuthStore.kt:6,12` — `app_password` в обычном SharedPreferences (`f7_auth`,
|
||||||
|
MODE_PRIVATE). EncryptedSharedPreferences/MasterKey/Keystore в проекте нет вообще (grep пуст).
|
||||||
|
- `app/src/main/AndroidManifest.xml:25` — `allowBackup="true"` без `dataExtractionRules` →
|
||||||
|
`f7_auth` (пароль) и `f7_app_lock` (pin_hash) уходят в облачный/adb-бэкап.
|
||||||
|
- Пароль передаётся через Intent extras: `MailScreen.kt:128-131` → MailComposeActivity (утечка в recents/dumpsys).
|
||||||
|
- Logout (`AppScaffold.kt:175-180`) — только локальная очистка; серверная ревокация
|
||||||
|
`DELETE /ocs/v2.php/core/apppassword` НЕ вызывается — токен живёт на сервере.
|
||||||
|
|
||||||
|
### C4. ANR-риск на входящем звонке
|
||||||
|
- `feature/talk-native/.../TalkVendorUserSync.kt:23` — `runBlocking(Dispatchers.IO)` с сетью/БД.
|
||||||
|
- Вызывается из `F7MobileApp.onCreate` (`F7MobileApp.kt:41` — блокирует холодный старт) и из
|
||||||
|
`TalkNativeCallLauncher.launchRoomCall` → `MainActivity.onCreate:105,120` (клик «принять звонок» на main).
|
||||||
|
- `F7MobileApp : F7cloudTalkApplication` (F7MobileApp.kt:27) — `super.onCreate()` инициализирует
|
||||||
|
весь vendor-стек (Dagger, Room+SQLCipher, WorkManager) на холодном старте.
|
||||||
|
|
||||||
|
### C5. Тихая порча календарных данных (ICS)
|
||||||
|
- **Два расходящихся парсера дат**: `CalendarIcs.parseIcsInstant` (`CalendarIcs.kt:176-193`) парсит
|
||||||
|
`...T100000Z` как ЛОКАЛЬНОЕ время; `CalDavClient.parseIcsInstant` (`CalDavClient.kt:584-602`) — как
|
||||||
|
UTC. `TZID` игнорируют оба (regex `CalendarIcs.kt:47` отрезает параметры). → события со сдвигом зоны,
|
||||||
|
по-разному в календаре и задачах.
|
||||||
|
- **ATTENDEE-парсер сломан**: `CalendarIcs.parseLines` (`:195-206`) кладёт `ATTENDEE;CN=...;PARTSTAT=...`
|
||||||
|
под один ключ `"ATTENDEE"` с отрезанными параметрами, а `parseAttendeeLine` (`:208-216`) ищет
|
||||||
|
`CN=`/`PARTSTAT=` в ключе, где их уже нет → участники схлопываются в одного, статусы дефолтные.
|
||||||
|
При редактировании события это PUT'ится на сервер (порча данных).
|
||||||
|
- **unescape неверного порядка**: `CalendarIcs.kt:268-269` — `\n` заменяется до `\\`.
|
||||||
|
- `CalDavClient.parseVTodo` (`:925-951`) работает без line-unfolding (длинная свёрнутая SUMMARY обрезается).
|
||||||
|
- vCard: паттерн `N[^:]*:` (`CardDavClient.kt:32-41`) заматчит и `NOTE:`/`NICKNAME:`.
|
||||||
|
|
||||||
|
### C6. GPL-3.0 нарушается уже сейчас
|
||||||
|
- У корневого репо **нет LICENSE/COPYING** (в корне только CHANGELOG/INTEGRATION/NOTICE/PROJECT-STATUS/
|
||||||
|
README). Vendored GPL-код линкуется в один APK → всё приложение — производная работа под GPL-3.0-or-later.
|
||||||
|
- Копирайты Nextcloud **заменены** на «F7cloud contributors»: `vendor/.../AndroidManifest.xml:4`
|
||||||
|
(`SPDX-FileCopyrightText: 2017-2024 F7cloud...`). GPLv3 §4 требует сохранять оригинальные уведомления.
|
||||||
|
- Раздача даже debug-APK — уже distribution; архив исходников к сборкам не публикуется.
|
||||||
|
- Проприетарный Firebase слинкован в GPL-APK (`vendor build.gradle:337`, `core/push`). У ячейки есть
|
||||||
|
свой `f7push` — основной flavor можно сделать без FCM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟠 Архитектура
|
||||||
|
|
||||||
|
- **God-composable**: `app/.../ui/AppScaffold.kt:112-671` — 920 строк, ~35 `mutableStateOf`/34
|
||||||
|
`rememberSaveable` в одной функции: роутер табов + логин (`:675-760`) + push-роутинг (`:358-383`) +
|
||||||
|
FCM-регистрация (`:299-309`) + permissions + QR. Рекомпозиция корня на любое изменение.
|
||||||
|
- Связь app→feature — самодельные «nonce-каунтеры»: `filesUploadRequest++`, `mailPushRequest++`
|
||||||
|
(`AppScaffold.kt:159-168`) — теряют события при гонках. Лучше SharedFlow.
|
||||||
|
- Слой пробивается в UI: `AppSheets.kt:109-150` (NotificationsSheet дёргает repo из composable),
|
||||||
|
`MailScreen.kt:83-89` (composable сам собирает OkHttpClient).
|
||||||
|
- **DI нет** (Hilt/Koin отсутствуют, Dagger только в vendor): ~25 `object`-синглтонов с сетью
|
||||||
|
(`CalDavClient.kt:80` — object на 1050 строк), inline-фабрики VM в composable (`MailScreen.kt:75-81`).
|
||||||
|
- Утечка контекста: `app/.../OfficeWebViewPool.kt:12-40` держит WebView с Activity-контекстом до 15 мин.
|
||||||
|
- **navigation-compose:2.8.9 подключён, но НЕ используется** (нет NavHost): навигация — ручной
|
||||||
|
`when(activeTab)` (`AppScaffold.kt:535-624`) + boolean-флаги + свой BackHandler (`:428-447`).
|
||||||
|
Deep links снаружи не работают (нет intent-filter со схемой; `f7cloud://talk` в `MainActivity.kt:79`
|
||||||
|
недостижим из браузера). `SavedStateHandle` — 0 вхождений: process death теряет внутриэкранное состояние.
|
||||||
|
- **Мёртвый код**: `feature/widgets` (538 строк, 4 файла) НЕ в `settings.gradle` — не компилируется;
|
||||||
|
`core/data`, `core/ui` — пустые заготовки в `settings.gradle:23,28`.
|
||||||
|
- Топ god-файлов: `MailComponents.kt` 1898, `TalkComponents.kt` 1591, `FilesComponents.kt` 1457,
|
||||||
|
`CalendarComponents.kt` 1362, `TasksComponents.kt` 1066, `CalDavClient.kt` 1050, `MailViewModel.kt` 1012,
|
||||||
|
`MailRepository.kt` 991, `AppScaffold.kt` 920.
|
||||||
|
|
||||||
|
## 🟠 Сетевой слой
|
||||||
|
|
||||||
|
- **Новый `OkHttpClient` на каждый запрос**: `NetworkFactory.kt:14-20` — фабрика строит новый
|
||||||
|
`.build()`; вызывается в начале каждого метода репозитория (`MailRepository.kt:635-641`,
|
||||||
|
`TalkRepository.kt:331-332`, `FilesRepository.kt:20,62,79...`, ещё 31 call-site). Нулевое
|
||||||
|
переиспользование соединений (TLS-handshake на каждый запрос), утечка ExecutorService.
|
||||||
|
`SupportRepository.kt:340` — вообще без callTimeout.
|
||||||
|
- HTTP-кэша (`.cache()`) нет; ETag/If-Modified-Since не используется (только If-Match при записи CalDAV).
|
||||||
|
- **Обработка 401 скопирована ~60 раз** (`if (code == 401) throw UnauthorizedException()` в каждом
|
||||||
|
методе) вместо interceptor'а. OCS-конверт распарсен независимо 4 раза (`TalkRepository.getOcsResponse:367`,
|
||||||
|
`WidgetsRepository.getOcsData:124`, `NotificationsRepository:32`, `AppMenuRepository:20`) при живом
|
||||||
|
`OcsJson.kt`. Заголовок `OCS-APIRequest` вручную в 37 местах. DAV-хелперы (`localTag/readText/toDavPath/
|
||||||
|
resolveHref`) скопированы трижды (`DavClient.kt:242`, `CalDavClient.kt:867/1024`, `CardDavClient.kt:288`).
|
||||||
|
- **Парсинг 100% ручной** org.json (`optString` — 206 вызовов, молчаливые дефолты) + ICS/vCard регэксами.
|
||||||
|
- `F7PushRegistrar.kt:53-77` — сырой `HttpURLConnection`, дублирует Basic-auth и **игнорирует
|
||||||
|
trustAllCerts** (push сломан на self-signed).
|
||||||
|
- **Инкрементальной синхронизации DAV нет**: ни sync-token/sync-collection, ни CTag. Контакты качают
|
||||||
|
**все vCard с base64-фото** и потом `.take(500)` (`CardDavClient.kt:142-157`). Календарь делает
|
||||||
|
`listCalendars` PROPFIND перед каждой операцией (`CalendarRepository.kt:188-204`); задачи —
|
||||||
|
`listTaskLists` на каждый toggle (`TasksRepository.kt:138-145`).
|
||||||
|
- `WidgetsRepository.kt:65` — `withTimeoutOrNull(2000)` поверх блокирующего `execute()` (отмена не
|
||||||
|
прерывает сокет). Talk long-poll (`TalkRepository.kt:26-41`) — readTimeout == серверному таймауту (гонка).
|
||||||
|
- Main-safety на соглашении: репозитории `suspend`, но без `withContext(Dispatchers.IO)` внутри
|
||||||
|
(`MailRepository`, `TalkRepository.kt:58`); `TasksRepository`/`CalendarRepository`/`AppMenuRepository` —
|
||||||
|
вообще не suspend. Спасает только дисциплина вызывающих (`viewModelScope.launch(Dispatchers.IO)`).
|
||||||
|
|
||||||
|
## 🟠 Производительность и UX
|
||||||
|
|
||||||
|
- **APK ~130 МБ**: `settings.gradle:35-36` включает весь vendor как библиотеку (`feature/talk-native`
|
||||||
|
`api project(':vendor:talk-app')`). WebRTC `android-talk-webrtc:132.6834.0` (vendor build.gradle:333) —
|
||||||
|
`libjingle_peerconnection_so.so` на **4 ABI** (~30-45 МБ × ABI ≈ 60-70 МБ); sqlcipher (:281),
|
||||||
|
conscrypt (:238) — тоже × 4 ABI; `emoji2-bundled` (~10 МБ); 48 локалей vendor. Ни `splits`, ни
|
||||||
|
`abiFilters`, ни `bundle{}`, ни `resConfigs` нигде нет.
|
||||||
|
→ `abiFilters 'arm64-v8a','armeabi-v7a'` + `resConfigs 'ru','en'` + рабочий R8 = 130 → **30-45 МБ**.
|
||||||
|
- Compose: ключи чата Talk включают `$index` (`TalkScreen.kt:597-604`) — новое сообщение пересоздаёт
|
||||||
|
весь список; `formatEnvelopeDate` (`MailComponents.kt:644,1480-1507`) создаёт 2×Calendar +
|
||||||
|
SimpleDateFormat на каждую строку/рекомпозицию; `F7RelativeTime.kt:22` — `DateTimeFormatter.ofPattern`
|
||||||
|
на каждый вызов. `derivedStateOf` — 3 шт. на проект.
|
||||||
|
- Иконки UI грузятся **с сервера как SVG по сети** (`FilesComponents.kt:265,310,325,356`,
|
||||||
|
`MailComponents.kt:650` — звезда в каждой строке письма, ~15+ мест) → офлайн списки лысеют.
|
||||||
|
Coil diskCache = 2% cacheDir (`F7MobileApp.kt:79-94`), `crossfade(false)`.
|
||||||
|
- Офлайн-кэш: только контакты (Room, с TTL 10 мин — образец), файлы (огрызок: имя+флаг для корня,
|
||||||
|
`FilesRepository.kt:40-53`), почта (свой JSON-файловый кэш `MailCacheRepository.kt` — третий
|
||||||
|
самописный механизм). Календарь/задачи/Deck/поддержка/виджеты офлайн — пустой экран.
|
||||||
|
- `runBlocking` на старте (см. C4). SplashScreen API не используется (белый экран на Android 8-11).
|
||||||
|
- **Тёмной темы нет**: `F7Theme.kt:29-36` всегда `F7LightScheme`, нет `isSystemInDarkTheme`/`values-night`;
|
||||||
|
+ 77 инлайновых `Color(0xFF...)` мимо `F7Colors` (`FilesComponents.kt:305`, `SupportComponents.kt:64-66`...).
|
||||||
|
- **Локализация невозможна**: `stringResource` — 6 раз, в `strings.xml` 15 строк; 73+ русских строк
|
||||||
|
захардкожены в `Text("...")`.
|
||||||
|
- Pull-to-refresh только в Файлах (`FilesScreen.kt:219`). Ошибки местами Toast без retry
|
||||||
|
(`MailScreen.kt:348,405`). Skeleton/shimmer нет (51 CircularProgressIndicator).
|
||||||
|
`contentDescription=null` в 66 местах (в т.ч. кликабельные иконки). WindowSizeClass/sw600dp — нет.
|
||||||
|
|
||||||
|
## 🟠 Качество кода и процессы
|
||||||
|
|
||||||
|
- **0 тестов** (нет `src/test`/`src/androidTest` ни в одном модуле). Мешают: 40+ `object` с логикой,
|
||||||
|
репозитории без интерфейсов, `LocalDate.now()`/`ZoneId.systemDefault()` внутри логики
|
||||||
|
(`TasksGrouping.kt:23`, `F7RelativeTime.kt:15`, `CalendarIcs.kt:121,184`) — Clock не инжектируется.
|
||||||
|
Готовые кандидаты на чистый JUnit: `CalendarIcs`, `TasksGrouping`, `LoginFlowClient`.
|
||||||
|
- Обработка ошибок: 223 `runCatching` (~65 глотают через getOrNull/getOrDefault), ~121 `error()` как
|
||||||
|
flow-control (нетипизированный IllegalStateException — «нет сети» и «битый JSON» неразличимы),
|
||||||
|
молчаливые catch (`CardDavClient.kt:437`, `DavClient.kt:237,252`, `F7PushRegistrar.kt:87-89` — пустое
|
||||||
|
тело). Логирования почти нет (35 Log.*/println на проект).
|
||||||
|
- `!!` — 70 шт. (UI-гонки: `MailSettingsScreens.kt:557-558` `draft!!`, `TalkComponents.kt:876`).
|
||||||
|
QR-логин ломается на пароле с `&` (`LoginFlowClient.kt:196+`, лимит `size > 3`). Курсор пагинации
|
||||||
|
почты обрезает Long→Int (`MailRepository.kt:54-62`). `loadThread` → `getOrDefault(emptyList())`
|
||||||
|
(сеть-ошибка = «переписки нет», `MailRepository.kt:88-93`).
|
||||||
|
- Дубли: относительное время — 4 реализации (`F7RelativeTime.kt`, `TalkFormatting.kt:94`,
|
||||||
|
`MailComponents.kt:1480`, `SupportComponents.kt:68`); открытие файла с тостом дословно скопировано
|
||||||
|
(`SupportFileIO.kt:56-66` == `LocalFileOpener.kt:14-25`); `serverUrl.trimEnd('/')` — 40+ мест.
|
||||||
|
- Магия: 77 `Color(0x...)` вне designsystem; ~1087 `.dp` без spacing-токенов; 52 захардкоженных
|
||||||
|
`ocs/v2.php|remote.php` пути (нет `ApiPaths`); таймауты литералами (`F7PushRegistrar.kt:54-55`
|
||||||
|
`15000`, `TalkAttachmentUploader.kt:49-51` 5/5/6 мин).
|
||||||
|
- **lint/detekt/ktlint не настроены** (нет `lint{}`, baseline, `.editorconfig`).
|
||||||
|
- **Version catalog отсутствует** (`gradle/libs.versions.toml` нет): coroutines 1.8.1/1.9.0/1.10.1/1.10.2,
|
||||||
|
Coil 2.6.0/2.7.0 (при Coil 3.x актуальном), два Compose BOM (свой 2025.02 vs vendor 2026.01),
|
||||||
|
Firebase тремя декларациями (bom 33.7.0 / messaging 25.0.1 / 24.0.1), zxing 3.3.0 (2017 г.),
|
||||||
|
guava целиком (`app/build.gradle:101`). `org.json:json:20240303` дублирует платформенный (бесполезен).
|
||||||
|
- `gradle.properties`: `parallel=false` (тормоз 20 модулей), `enableJetifier=true` (legacy),
|
||||||
|
`nonTransitiveRClass=false`; тестовые `NC_TEST_SERVER_*` — мусор от vendor.
|
||||||
|
- Git: `.gitignore` полный (build/, local.properties, *.apk, keystores, google-services.json закрыты —
|
||||||
|
секретов в репо НЕТ). LFS не используется; `design/` — 1.66 МБ (28 PNG); vendor закоммичен целиком
|
||||||
|
(1484 файла, pack 6.95 МБ) без своей git-истории.
|
||||||
|
|
||||||
|
## Vendored-форк talk-android (v23.0.0)
|
||||||
|
|
||||||
|
- Подключён только модуль app форка (`settings.gradle:37-38`), режим library (`f7cloudAsLibrary=true`,
|
||||||
|
flavor `f7`). `feature:talk-native` — тонкая обёртка (8 файлов, 364 строки), реально используется
|
||||||
|
<10% vendor (звонковый стек: CallActivity, CallForegroundService, F7cloudTalkApplication, F7TalkUserSync).
|
||||||
|
Чат — свой Compose-модуль `feature:talk` (OCS напрямую). Но компилируется и в APK попадает весь vendor.
|
||||||
|
- Ребрендинг пакетов полный: `com.nextcloud.talk` → `ru.f7cloud.talk` (609/704 файла). Остатки
|
||||||
|
`com.nextcloud` (34 файла) легитимны (внешняя библиотека nextcloud-android-common:ui, mimeType, Transifex).
|
||||||
|
- **Git-истории форка НЕТ** (один коммит «Initial import», у vendor нет своего `.git`) → обновление на
|
||||||
|
новый апстрим = повторный ручной rename 700 файлов. Рекомендация: отдельный git-репо с ветками
|
||||||
|
`upstream` (чистый v23) / `f7` (rename-скрипт + патчи коммитами).
|
||||||
|
- `build.f7cloud.gradle:6` — `DEBUG=true` захардкожен и для release (debug-логи vendor в проде).
|
||||||
|
- `README.F7CLOUD.md:20` — «not yet compiled» устарел (модуль давно в графе).
|
||||||
|
- Cleartext: `vendor/.../network_security_config.xml:9` — `cleartextTrafficPermitted="true"` + доверие
|
||||||
|
user-CA; наследуется в app. Fix: переопределить свой networkSecurityConfig.
|
||||||
|
- Совместимость v23-клиент ↔ сервер spreed 22 (Talk API v4): vendored-клиент договаривается по
|
||||||
|
capabilities (`ApiUtils.kt:100-131` conversation-v4→v1; `CapabilitiesUtil.kt:336` SERVER_VERSION_MIN=17) —
|
||||||
|
безопасно, фичи 23 без поддержки просто не включатся. НО собственный `feature:talk` хардкодит `/api/v4`
|
||||||
|
без capability-гейтов (`TalkRepository.kt:17,37,46...`) — для spreed 22 ок, регресс-тест звонков на
|
||||||
|
реальном 22 обязателен.
|
||||||
|
|
||||||
|
## GPL-3.0 — что сделать (см. C6)
|
||||||
|
|
||||||
|
1. `LICENSE` (GPL-3.0-or-later) в корень; свой код остаётся вашим (копирайт ваш), лицензируется под GPL.
|
||||||
|
2. Вернуть копирайты Nextcloud в изменённых заголовках vendor (свои — добавлять рядом).
|
||||||
|
3. Публиковать архив исходников с каждым распространяемым APK (шаг CI, git-тег в Gitea).
|
||||||
|
4. Firebase+GPL: основной flavor без FCM на базе `f7push`/UnifiedPush, FCM — опциональный flavor.
|
||||||
|
5. Бренд/дизайн GPL НЕ затрагивает (это товарный знак) — тема и UI сохраняются полностью.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## План действий (приоритизированный)
|
||||||
|
|
||||||
|
### Этап 0 — санитария релиза (дни) = п.1 плана mail/035
|
||||||
|
1. Keystore + `signingConfig` из env/CI secrets; proguard-правила (webrtc/LoganSquare/EventBus/
|
||||||
|
`-dontwarn`) + `consumerProguardFiles` у vendor; до готовности — `minifyEnabled false` как страховка.
|
||||||
|
2. `abiFilters 'arm64-v8a','armeabi-v7a'` + `resConfigs 'ru','en'` → минус ~половина APK.
|
||||||
|
3. Безопасность-минимум: trust-all → только debug; `allowBackup="false"`; `AuthStore` →
|
||||||
|
EncryptedSharedPreferences с миграцией; ревокация app password при logout; `FLAG_SECURE` на логин/PIN.
|
||||||
|
4. GPL: LICENSE в корень, вернуть копирайты Nextcloud, публикация исходников с APK.
|
||||||
|
5. `gradle.properties`: `parallel=true`, `nonTransitiveRClass=true`, убрать jetifier; удалить мёртвые
|
||||||
|
`core/data`, `core/ui`, решить судьбу `feature/widgets`.
|
||||||
|
|
||||||
|
### Этап 1 — фундамент (2-3 недели)
|
||||||
|
6. CI (Gitea Actions, инфра B): lint+тесты → assembleDebug на push → по тегу подписанный release +
|
||||||
|
исходники; versionCode из CI.
|
||||||
|
7. Единый `OkHttpClient` (пул, `.cache(20MB)`, interceptor 401, OCS-заголовки); `withContext(IO)`
|
||||||
|
внутрь репозиториев; убрать `runBlocking` из старта и пути звонка.
|
||||||
|
8. Первые 10 unit-тестов (ICS-парсер первым — зафиксировать баги C5, потом чинить); detekt+ktlint
|
||||||
|
с baseline; version catalog.
|
||||||
|
9. Починить баги данных: единый ICS-парсер с TZID/ATTENDEE/unescape; QR-пароль с `&`; курсор почты.
|
||||||
|
|
||||||
|
### Этап 2 — систематика (1-2 месяца)
|
||||||
|
10. DI (Hilt / ручной AppContainer), интерфейсы репозиториев; разбор AppScaffold на MainViewModel +
|
||||||
|
LoginScreen; navigation-compose по-настоящему или удалить.
|
||||||
|
11. kotlinx.serialization вместо org.json; sync-token/CTag для DAV; кэш календаря/задач; локальные иконки.
|
||||||
|
12. UX: pull-to-refresh везде, единый F7ErrorState+retry, SplashScreen, строки→strings.xml→тёмная тема.
|
||||||
|
13. Vendor: отдельный git-репо (upstream/f7 ветки); flavor без Firebase на базе f7push.
|
||||||
|
|
||||||
|
## Что хорошо (сохранять)
|
||||||
|
|
||||||
|
ViewModel-слой единообразен (viewModelScope.launch(IO), без блокировок main кроме talk-bootstrap);
|
||||||
|
просмотр HTML-писем безопасен (`MailMessageBodyView.kt:69` — JS off, domStorage off, фильтр запросов);
|
||||||
|
кэш контактов с TTL — образец; секретов в git нет; ключи LazyColumn почти везде на месте; ребрендинг
|
||||||
|
vendor чистый; собственная пагинация почты работает; биометрия корректна (`AppLockGate.kt:67-73`).
|
||||||
Reference in New Issue
Block a user