nfcGuard

Блокирует отвлекающие приложения, пока вы не коснётесь физической NFC-метки. Оставьте метку где-нибудь подальше, и открыть Instagram будет стоить прогулки.

Платформа
Android 8.0+
Технологии
Kotlin, Jetpack Compose
Тесты
333 модульных теста
Пользователи
1 250+, все органические

Программные блокировщики не работают по одной причине: выключатель находится на том же экране, что и то, чего вы избегаете. nfcGuard переносит выключатель в физический мир. Заблокированное приложение остаётся заблокированным, пока вы не коснётесь конкретной NFC-метки. Если метка лежит на кухне, каждый порыв полистать ленту стоит похода на кухню.

Экран блокировки
Экран блокировки
Режимы
Режимы
Расписания
Расписания
Временная разблокировка
Временная разблокировка
Потеря метки
Потеря метки
Главный экран
Главный экран

Как это работает

Режим — это набор приложений, которые заблокированы или разрешены. Включите его вручную или привяжите расписание, чтобы он включался сам по рабочим дням. Пока режим активен, эти приложения не откроются. Касание привязанной NFC-метки снимает блокировку.

Каждая метка сама решает, сколько свободы даёт. Одна снимает режим полностью, другая ограничена пятью минутами, после чего режим возвращается сам. Щедрую метку держите дома, строгую в офисе. Режим без привязанных меток принимает любой NFC-объект, например транспортную карту или наушники.

Потеряли метку? Процедура восстановления на главном экране предлагает задание на внимание с таймером, а затем позволяет выбрать, какие метки потеряны. Режимы выключаются, эти метки удаляются, остальная настройка сохраняется.

Распространение

nfcGuard пользуются больше 1 250 человек, и каждый нашёл его сам: без рекламы и платного продвижения.

Архитектура

Проект разделён на два Gradle-модуля, чтобы правила можно было тестировать без телефона.

  • :domain — чистый Kotlin: состояние приложения и четыре объекта логики (NfcUnlockLogic, ScheduleTransitions, ModeActivationLogic, BlockDecider). Он не может импортировать Android, это проверяет компилятор, и тесты идут на обычной JVM.
  • :app содержит Compose UI, сервисы, ресиверы и виджет. Слои идут в одном направлении: домен, данные, побочные эффекты, сервис и UI, у каждой задачи один владелец.
  • AppStateRepository владеет сохранённым состоянием. Каждое изменение проходит через один update { } под мьютексом, и только один файл знает ключ хранилища.
  • StateSyncer владеет побочными эффектами. После каждой записи он перезапускает сервис блокировки, переставляет будильники и обновляет виджеты, так что ни одному экрану не нужно об этом помнить.
  • BlockerService на каждом такте проверяет активное приложение и применяет решение: оверлеем или принудительным закрытием, если включены специальные возможности, которых требуют Pixel и Samsung.

Режимы, расписания и метки экспортируются в JSON или YAML и импортируются обратно с полной заменой или слиянием по id.

Чему я научился

Android жёстко борется с долгоживущими приложениями, и каждый производитель делает это по-своему. Большая часть работы ушла на то, чтобы оставаться в живых: автозапуск на Xiaomi и Oppo, исключения из оптимизации батареи, путь через специальные возможности для Pixel и страница разрешений, которая перепроверяет каждое при возвращении. Логика в модуле без Android и сделала эти постоянные изменения безопасными.