Android‑приложение для экосистемы Amulet. Приложение выступает центром управления физическим устройством Amulet (BLE/NFC, OTA), а также предоставляет практики для ментального здоровья, социальные функции («объятия»), библиотеку и редактор паттернов.
- Скриншоты
- Ключевые возможности (по коду)
- Как устроено приложение
- Конфигурация
- Технологический стек и архитектура
- Требования
- Быстрый старт
- Команды Gradle
- Правила модульных зависимостей
- Troubleshooting
- Contributing
- Модульная структура
Главный экран быстрый старт и устройства |
Практики подбор и эмоциональное состояние |
Сессия таймер и этапы практики |
Объятия пара и быстрые действия |
Паттерны пресеты и категории |
Редактор рисование таймлайна |
Полная галерея — в screenshots/.
- Практики и расписание: поиск/детали/сессия/календарь/редактор практик (
:feature:practices). - Курсы: экран деталей курса и прогресс (часть
:feature:practices). - Паттерны: список/выбор/редактор/предпросмотр, отдельный полноэкранный редактор таймлайна (
:feature:patterns). - Устройства: список/детали/паринг (вложенный граф)/OTA (экран обновления) (
:feature:devices). - Объятия: главный экран, история, настройки, эмоции и редактор эмоции, паринг по коду, детали объятия; есть диплинки
amulet://hugs…(:feature:hugs). - Настройки: главный экран + профиль/приватность; переходы в устройства и hugs settings (
:feature:settings). - Гостевой режим: отдельное состояние
AuthState.GuestвSessionViewModel.
app/src/main/java/com/example/amulet_android_app/AmuletApp.kt—Application. Инициализирует телеметрию, OneSignal, синхронизацию push‑токена, а также запускаетDataInitializer.initializeIfNeeded()иAutoConnectLastDeviceUseCase().app/src/main/java/com/example/amulet_android_app/MainActivity.kt— Compose‑entrypoint. Дополнительно восстанавливаетAmuletForegroundService, если есть активная сессия практики.
UI‑корень app/src/main/kotlin/com/example/amulet_android_app/presentation/AmuletApp.kt переключается по состоянию SessionViewModel:
Loading→ SplashLoggedOut→ стартуетAuthGraphLoggedIn/Guest→ стартуетDashboardGraphи показываетMainScaffold
Источник истины по сессии — UserSessionProvider.sessionContext из :shared.
Главный NavHost находится в app/src/main/kotlin/com/example/amulet_android_app/navigation/AppNavHost.kt и включает графы:
dashboardGraphdevicesGraphpatternsGraphpracticesGraphhugsGraphsettingsGraphauthGraph
Примеры диплинков, определённых в feature‑навигации:
amulet://practices/session/{practiceId}amulet://hugsamulet://hugs/{hugId}amulet://hugs/pair?code=…&inviterName=…
- DI контейнер уровня Android: Hilt.
- Для
:shared(KMP) используется Koin.
В app/src/main/java/com/example/amulet_android_app/di/KoinBridgeModule.kt поднимается Koin и в него пробрасываются Android‑реализации репозиториев (:data:*) и провайдеры сессии.
Далее в app/feature коде usecase’ы из :shared получаются через koin.get() и выдаются наружу через @Provides.
MainScaffold (app/.../navigation/MainScaffold.kt) управляет bottom bar через LocalScaffoldState.
Bottom bar показывается только на route’ах, начинающихся с:
dashboardpracticeshugspatternssettings
core/foreground/.../AmuletForegroundService.kt — foreground‑сервис, который держит работу «с амулетом» в фоне.
Сервис предоставляет binder‑интерфейс AmuletControl:
- запуск/остановка сессии практики
- предпросмотр паттерна на устройстве (через
GetPatternByIdUseCase+PreviewPatternOnDeviceUseCase)
app/build.gradle.kts читает local.properties и прокидывает значения в BuildConfig:
SUPABASE_URLSUPABASE_REST_URL(по умолчаниюhttps://api.amulet.app/v2)SUPABASE_ANON_KEYTURNSTILE_SITE_KEYONESIGNAL_APP_ID
Дальше эти значения используются в рантайме:
SupabaseEnvironment(supabaseUrl, restUrl, anonKey)NetworkModuleстроитbaseUrlизSupabaseEnvironment.restUrlи добавляет заголовокapikey: <anonKey>AuthInterceptorдобавляетAuthorization: <idToken>(значение берётся изIdTokenProvider)CaptchaInterceptorдобавляетX-Captcha-Token(одноразовый токен изTurnstileTokenStore)OneSignalManager.initialize(BuildConfig.ONESIGNAL_APP_ID, BuildConfig.DEBUG)
- Сессия пользователя приложения хранится в proto‑DataStore
user_session.pb(:core:auth).UserSessionManagerImplпреобразует данные вUserSessionContextи поддерживает гостевой режим. - Сессия Supabase хранится в Preferences DataStore
supabase_session(:core:supabase). ИспользуетсяSupabaseAuthSessionManagerиSupabaseSessionStorage.
OneSignalManagerобрабатывает foreground‑уведомления и клики; по клику открывает диплинкamulet://hugsилиamulet://hugs/{hugId}.PushTokenSyncManagerподписывается наplayerId()и вызываетSyncPushTokenUseCaseиз:shared.
WorkManagerOutboxScheduler ставит уникальную работу outbox_sync (ExistingWorkPolicy.KEEP) через OutboxWorker.
Work request создаётся с constraint NetworkType.CONNECTED; есть режим expedited (fallback RUN_AS_NON_EXPEDITED_WORK_REQUEST).
Проверка требуемых permissions инкапсулирована в BlePermissionsHelper (:core:ble):
- Android 12+ (
S):BLUETOOTH_SCAN,BLUETOOTH_CONNECT - Android 11 и ниже:
BLUETOOTH,BLUETOOTH_ADMIN,ACCESS_FINE_LOCATION
- Язык: Kotlin (Kotlin 2.2.x)
- Gradle: Gradle Wrapper 8.13, кастомные плагины в
build-logic/ - Android:
compileSdk=36,minSdk=26(см.build-logic/src/main/kotlin/amulet/android/common/AndroidConvention.kt) - UI: Jetpack Compose + Navigation Compose
- UI-архитектура: feature‑модули + графы Navigation Compose; корневой роутинг в
AppNavHost - Асинхронность: Coroutines + Flow
- DI: Hilt + bridge к Koin (
KoinBridgeModuleподнимает Koin и пробрасывает репозитории/UseCase) - Сеть: Retrofit + OkHttp + kotlinx.serialization
- Локальное хранение: Room (
AmuletDatabase, версия 15) + DataStore (сессии пользователя и Supabase) - Offline-first синхронизация: Outbox + WorkManager (
:core:sync) - BLE:
AmuletBleManager+ Flow Control (FlowControlManager) + OTA (:core:ble)
Версии зависимостей задаются через Gradle Version Catalog: gradle/libs.versions.toml.
- Android Studio (актуальная версия)
- JDK 21 (используется Gradle toolchain), target bytecode: JVM 17
Приложение читает параметры из local.properties и прокидывает их в BuildConfig (см. app/build.gradle.kts).
Рекомендуемый путь:
- Скопировать
local.example.propertiesвlocal.properties. - Заполнить значения.
Пример (ключи добавляй без кавычек):
# Android SDK
sdk.dir=/path/to/Android/sdk
# Supabase
SUPABASE_URL=https://<your-project>.supabase.co
SUPABASE_REST_URL=https://api.amulet.app/v2
SUPABASE_ANON_KEY=<your_anon_key>
# Turnstile
TURNSTILE_SITE_KEY=<your_site_key>
# OneSignal
ONESIGNAL_APP_ID=<your_app_id>Важно:
local.propertiesи секреты не должны попадать в git.- Не добавляй значения ключей в README/issue/PR.
- Открой проект в Android Studio.
- Дождись завершения Gradle Sync.
- Запусти конфигурацию
appна эмуляторе/устройстве.
Точки входа:
app/src/main/java/com/example/amulet_android_app/AmuletApp.kt—Application(инициализация телеметрии, OneSignal, стартовая инициализация данных, автоподключение к последнему устройству).app/src/main/java/com/example/amulet_android_app/MainActivity.kt— Compose entrypoint.
Все команды запускаются из корня репозитория.
./gradlew tasksСборка:
./gradlew :app:assembleDebugЮнит‑тесты:
./gradlew testПроверка кода (Detekt):
./gradlew detektАрхитектурные тесты (ArchUnit):
./gradlew :architecture-test:testВ корне подключён Gradle‑плагин amulet.dependency.rules (см. build-logic/.../DependencyRulesPlugin.kt), который валидирует зависимости между модулями:
:feature:*не может зависеть от:feature:*:feature:*не может зависеть от:data:*напрямую:data:*не может зависеть от:feature:*и:app:sharedдолжен оставаться platform‑agnostic и не может зависеть от:core:*,:feature:*,:app
- Сборка падает из-за JDK: проект использует Gradle toolchain 21, но target bytecode — JVM 17. Проверь, что Android Studio видит JDK 21.
- Не работают запросы к API: убедись, что заполнены
SUPABASE_REST_URLиSUPABASE_ANON_KEY(в сеть они уходят какbaseUrlи заголовокapikey). - Captcha/Turnstile не применяется: заголовок
X-Captcha-Tokenдобавляется только когда вTurnstileTokenStoreесть токен (послеconsumeToken()он одноразово исчезает). - Не приходят push: если
ONESIGNAL_APP_IDпустой,OneSignalManagerпропускает инициализацию. - BLE не работает: проверь разрешения (см. раздел про BLE) и что устройство на Android 12+ выдало
BLUETOOTH_SCAN/CONNECT.
- Новые feature‑модули должны использовать convention plugin
amulet.android.featureи не зависеть от:data:*/других:feature:*(это проверяетсяamulet.dependency.rules). - Новые core/data‑модули:
amulet.android.core/amulet.android.data. - Где смотреть правила:
- конвенции Android/Compose/Toolchain:
build-logic/src/main/kotlin/amulet/android/common/AndroidConvention.kt - правила зависимостей:
build-logic/src/main/kotlin/amulet/dependency/DependencyRulesPlugin.kt
- конвенции Android/Compose/Toolchain:
Проект многомодульный (см. settings.gradle.kts). Крупные группы:
:app— Android‑приложение: Compose UI, навигация, DI‑композиция, wiring окружения.:shared— KMP‑модуль с доменными моделями/контрактами/UseCase’ами.
:core:auth:core:ble:core:config:core:crypto:core:database:core:design:core:foreground:core:network:core:notifications:core:supabase:core:sync:core:telemetry:core:turnstile
:data:auth:data:courses:data:devices:data:hugs:data:patterns:data:practices:data:privacy:data:rules:data:telemetry:data:user
:feature:auth:feature:control-center:feature:dashboard:feature:devices:feature:hugs:feature:onboarding:feature:pairing:feature:patterns:feature:practices:feature:profile:feature:sessions:feature:settings
detekt-rules/(:detekt-rules) — кастомные правила Detekt.architecture-test/(:architecture-test) — архитектурные тесты (ArchUnit).build-logic/— Gradle convention plugins (amulet.android.application,amulet.android.library,amulet.android.feature,amulet.android.core,amulet.android.data,amulet.kotlin.multiplatform.shared,amulet.dependency.rules).





