# Конфигурация Android SDK

Инициализируйте SDK один раз при старте приложения. [Установка и минимальное приложение](./overview.md); точные сигнатуры собраны в [справочнике API](./api_reference.md).

## Инициализация

Основной метод настройки SDK. Вызывается один раз при старте приложения.

| Параметр | Тип | Описание |
| :--- | :--- | :--- |
| `context` | `Context` | Контекст приложения. |
| `apiKey` | `String` | Ваш API key. |
| `section` | `String` | Идентификатор секции проекта. |
| `gravityEventCallback` | `(TrackingEvent) -> Unit` | Обязательный callback для действий SDK. |
| `productViewBuilder` | `ProductViewBuilder?` | Шаблон карточки товара, который приложение передает SDK. Нужен для карточек в дизайне приложения; без него SDK использует демонстрационную карточку. |
| `productFilter` | `((Slot) -> Boolean)?` | Фильтр товаров на клиенте. |
| `uiSettings` | `UISettings?` | UI-настройки встроенных форматов SDK. |
| `logLevel` | `LogLevel` | Уровень логирования внутреннего SDK-логгера. По умолчанию `LogLevel.NONE`. |

Если `GravitySDK.instance` вызывается до `initialize(...)`, SDK выбросит исключение `GravitySDK has not been initialized`.

## Логирование SDK

Android SDK поддерживает параметр `logLevel` в `initialize(...)`. Он управляет только внутренними логами SDK и не влияет на `gravityEventCallback`, отправку событий или логи приложения.

Логи пишутся через Android `Log` с тегом `GravitySDK`.

- `LogLevel.DEBUG` включает HTTP debug-логи SDK, включая request/response, и сообщения об ошибках.
- `LogLevel.INFO` включает информационные сообщения, например о блокировке показа кампаний, и ошибки.
- `LogLevel.ERROR` оставляет только ошибки SDK.
- `LogLevel.NONE` отключает локальный вывод SDK. Отправка внутренних ошибок в `sdk-sentry.gravityfield.ai/error` выполняется отдельно и не отключается уровнем логирования.

По умолчанию используется `LogLevel.NONE`. При стандартной production-инициализации SDK не пишет в системный лог HTTP-запросы, ответы и API key. Полные request/response логи появляются только при явном `LogLevel.DEBUG`, поэтому этот режим стоит включать только на время отладки.

## Глобальные настройки

Метод задает глобальные настройки, которые применяются ко всем последующим вызовам `trackView(...)`, `triggerEvent(...)` и `getContentBySelector(...)`.

Пример:

```kotlin
GravitySDK.instance.setOptions(
    options = Options(
        isReturnUserInfo = true,
        isReturnAnalyticsMetadata = true,
    ),
    contentSettings = ContentSettings(
        skusOnly = false,
        fields = listOf("name", "price", "imageUrl"),
    ),
    proxyUrl = "https://my-proxy.example.com/v2",
)
```

### Что настраивается через `Options`

| Поле | Значение по умолчанию | Назначение |
| :--- | :--- | :--- |
| `isReturnCounter` | `false` | Возвращать счетчики в ответе. |
| `isReturnUserInfo` | `false` | Возвращать информацию о пользователе. |
| `isReturnAnalyticsMetadata` | `false` | Возвращать дополнительные аналитические метаданные. |
| `isImplicitPageview` | `false` | Использовать неявный pageview-режим. |
| `isImplicitImpression` | `true` | Разрешить неявные impression для соответствующих сценариев. |

### Что настраивается через `ContentSettings`

| Поле | Значение по умолчанию | Назначение |
| :--- | :--- | :--- |
| `skusOnly` | `false` | Возвращать только SKU вместо расширенных товарных данных. |
| `fields` | `null` | Ограничить список товарных полей в ответе. |

Практически это означает следующее:

- если `skusOnly = true`, SDK запрашивает только SKU; приложение само догружает товарные данные;
- если `fields` задан, SDK запрашивает только указанные товарные поля;
- если `setOptions(...)` не вызывается, используются стандартные значения `Options()` и `ContentSettings()`.

`setOptions(...)` заменяет настройки целиком; `null` сбрасывает соответствующий объект к значениям по умолчанию. SDK также передаёт `isBuildEngagementUrl = true`; через публичный конструктор `Options` этот флаг не меняется.

`proxyUrl` задаёт полный базовый URL API, например `https://my-proxy.example.com/v2`, без завершающего `/`. SDK добавляет `/visit`, `/event` или `/choose`. Tracking URL из ответа и адрес отправки ошибок этим параметром не заменяются. Без `proxyUrl` используется `https://evs-01.gravityfield.ai/v2`.

У текущей реализации Android `/choose` есть [особенности сериализации](./custom_ui.md#формат-selector-в-android-104), в том числе для `ContentSettings`.

## Кастомизация UI

### Настройка шрифта

Встроенные UI-компоненты SDK можно стилизовать через `UISettings`.

```kotlin
val uiSettings = UISettings(fontResId = R.font.my_custom_font)

GravitySDK.initialize(
    context = this,
    apiKey = "YOUR_API_KEY",
    section = "YOUR_SECTION_ID",
    gravityEventCallback = ::handleGravityEvent,
    uiSettings = uiSettings,
)
```

После этого встроенные текстовые элементы SDK будут использовать указанный шрифт.

## Подключение исходного модуля

Если нужно собирать SDK из исходников, скопируйте `gravity_sdk`, подключите `include(":gravity_sdk")` и `implementation(project(":gravity_sdk"))`. Дополнительно перенесите version catalog `gravity_sdk/gradle/libs.versions.toml` как `gravitySdk` в `settings.gradle.kts` и задайте `SDK_VERSION=1.0.4` в `gradle.properties`:

```kotlin
dependencyResolutionManagement {
    // Используйте блок repositories вашего проекта.
    versionCatalogs {
        create("gravitySdk") {
            from(files("gravity_sdk/gradle/libs.versions.toml"))
        }
    }
}
```

Версии Android Gradle Plugin, Kotlin и Compose compiler согласуйте с основным проектом. Простого копирования модуля без его catalog и `SDK_VERSION` недостаточно.

## Обработка обратных вызовов (Callbacks)

Подпишитесь на события SDK через `gravityEventCallback` в `initialize(...)`. SDK доставляет tracking callbacks через главный поток; навигацию выполняйте в контексте актуального экрана.

### Справочник по событиям `TrackingEvent`

| Событие | Описание | Требует обработки приложением |
| :--- | :--- | :--- |
| `ContentLoadEvent` | Контент загружен. | Нет |
| `ContentImpressionEvent` | Контент показан. | Нет |
| `ContentVisibleImpressionEvent` | Контент стал видимым. | Нет |
| `ContentCloseEvent` | Контент закрыт. | Нет |
| `CopyEvent` | Пользователь скопировал значение. | Нет |
| `CancelEvent` | Пользователь отменил действие. | Нет |
| `FollowUrlEvent` | Пользователь нажал на URL. | Да |
| `FollowDeeplinkEvent` | Пользователь нажал на deeplink. | Да |
| `RequestPushEvent` | Пользователь инициировал сценарий push-permission. | Опционально |
| `ProductImpressionEvent` | Карточка товара стала видимой. | Нет |

Пример обработки:

```kotlin
private fun handleGravityEvent(event: TrackingEvent) {
    when (event) {
        is FollowUrlEvent -> {
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(event.url))
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            startActivity(intent)
        }
        is FollowDeeplinkEvent -> {
            // Выполните навигацию по deeplink
        }
        is RequestPushEvent -> {
            // При необходимости обработайте событие в аналитике
        }
        else -> Unit
    }
}
```

## Передача статуса Push-уведомлений

Если таргетинг кампаний зависит от статуса push-разрешения, передавайте его в SDK.

### `setNotificationPermissionStatus()`

Допустимые значения:

- `NotificationPermissionStatus.GRANTED`
- `NotificationPermissionStatus.DENIED`
- `NotificationPermissionStatus.UNKNOWN`

Пример:

```kotlin
import androidx.core.app.NotificationManagerCompat

val areNotificationsEnabled =
    NotificationManagerCompat.from(context).areNotificationsEnabled()

val status = if (areNotificationsEnabled) {
    NotificationPermissionStatus.GRANTED
} else {
    NotificationPermissionStatus.DENIED
}

GravitySDK.instance.setNotificationPermissionStatus(status)
```

SDK добавляет это значение в `device.permission` последующих запросов. Для базовой интеграции этот шаг не обязателен, но он необходим, если push-статус участвует в таргетинге.

## Блокировка показа in-app кампаний

Используйте presentation lock, когда приложение показывает собственную модалку, bottom sheet, onboarding или другой приоритетный слой интерфейса, который не должен перекрываться кампанией Gravity.

```kotlin
private fun showCheckoutBottomSheet() {
    GravitySDK.instance.lockPresentation()

    CheckoutBottomSheet(
        onDismiss = {
            GravitySDK.instance.unlockPresentation()
        }
    ).show(supportFragmentManager, "checkout")
}
```

Пока блокировка активна, SDK может загрузить контент, но не будет показывать новую in-app кампанию поверх текущего интерфейса. Уже открытый Gravity-контент этим вызовом не закрывается.

Если приложению нужно отслеживать состояние блокировки, подпишитесь на изменения:

```kotlin
GravitySDK.instance.setPresentationLockListener { locked ->
    // Обновите состояние приложения, если это нужно вашему UI
}
```

Это один Boolean-флаг, счётчика вложенных блокировок нет. Повторный lock не требует дополнительного unlock. Пропущенная кампания не ставится в очередь и не появится автоматически после разблокировки. Ручная загрузка и inline-компоненты не блокируются. В Android 1.0.4 переход `OPEN_STEP` вызывает показ напрямую и также не проверяет presentation lock.

Listener вызывается синхронно на потоке вызывающего кода при lock/unlock; при подписке текущее состояние не отправляется. Для снятия подписки передайте `null`. При `dispose()` listener очищается.
