Собственная интеграция

Вызывайте headless API PaspoID напрямую из Compose, Activity или Fragment.

Чтобы использовать свой UI, вызывайте SDK напрямую. Создайте PaspoID из host-Activity и вызовите authenticate из корутины. Метод не бросает исключений; любой исход, включая отмену и отсутствие приложения Paspo ID, возвращается как PaspoAuthResult.

Действуют два ограничения:

  • Одновременно может выполняться только один authenticate; новый вызов сбрасывает предыдущий.
  • PaspoID держит ссылку на Activity и должен оставаться в UI-слое. Не храните его в ViewModel или синглтоне; передавайте во ViewModel только результат.

Compose

@Composable
fun LoginScreen() {
    val activity = LocalActivity.current as? ComponentActivity ?: return
    val paspoId = remember(activity) { PaspoID(activity) }
    val scope = rememberCoroutineScope()

    Button(
        onClick = {
            scope.launch {
                onPaspoResult(paspoId.authenticate(PaspoScope.PHONES, backend.requestSsoNonce()))
            }
        },
    ) {
        Text("Войти через Paspo")
    }
}

remember(activity) хранит единственный PaspoID между рекомпозициями; rememberCoroutineScope отменяет вызов, если пользователь уходит с экрана.

Activity

class LoginActivity : AppCompatActivity() {

    private val paspoId = PaspoID(this)

    private fun signInWithPaspo() = lifecycleScope.launch {
        onPaspoResult(paspoId.authenticate(PaspoScope.PHONES, backend.requestSsoNonce()))
    }
}

Fragment

class LoginFragment : Fragment(R.layout.fragment_login) {

    // lazy: на момент создания фрагмента Activity ещё не присоединена
    private val paspoId by lazy { PaspoID(requireActivity() as ComponentActivity) }

    private fun signInWithPaspo() = viewLifecycleOwner.lifecycleScope.launch {
        onPaspoResult(paspoId.authenticate(PaspoScope.PHONES, backend.requestSsoNonce()))
    }
}

Запускайте на viewLifecycleOwner.lifecycleScope, а не на скоупе самого фрагмента, чтобы колбэк не отработал на уничтоженной view.

Пересоздание Activity

Если Activity пересоздаётся, пока открыт экран Paspo ID (поворот, смерть процесса), результат теряется. Обработайте это как отмену и дайте пользователю повторить; проще всего зафиксировать ориентацию экрана входа.

Вход, инициированный из Paspo ID

Пользователь может начать вход и изнутри приложения Paspo ID. В этом случае Paspo ID запускает ваше приложение с action paspo.id.ssoprovider.action.AUTHENTICATE (константа PaspoID.ACTION_SIGN_IN). Поддержка этой точки входа опциональна.

Объявите intent-filter на Activity входа:

<activity
    android:name=".LoginActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="paspo.id.ssoprovider.action.AUTHENTICATE" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

и запускайте поток при получении action:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (intent.action == PaspoID.ACTION_SIGN_IN) {
        signInWithPaspo()
    }
}

Intent не несёт дополнительных данных; это лишь сигнал о намерении пользователя войти.

Проверка доступности

Чтобы адаптировать UI до любого взаимодействия:

if (paspoId.checkInstallation()) {
    // Paspo ID установлен
}

Вызов опционален: authenticate выполняет проверку сам и, если Paspo ID не установлен, открывает Play Store и возвращает NotInstalled.

paspoId.cleanup() очищает эфемерные ключи. SDK вызывает его автоматически при завершении и при ошибках; вручную он нужен только если вы сами прерываете поток, например когда пользователь уходит с экрана входа.