Android & Coffee с Васей


Channel's geo and language: not specified, not specified
Category: not specified


Канал для разговоров за кофе о разработке. В основном Android, но вообще как получится

Related channels

Channel's geo and language
not specified, not specified
Statistics
Posts filter


Троллим iOS команду


Video is unavailable for watching
Show in Telegram
📚 Листаем карточки

Мой друг и классный инженер Максим Качинкин в своём посте показал очень красивую анимацию перелистывания карточек. Кстати, подписывайтесь на его канал.

Как только дизайнер показал нам новые баннеры, я сразу понял, что мне нужна библиотека Максима. Хотя она не подошла as-is, очень легко получилось допилить её под свои нужды: пара анимаций, Subcompose Layout, пара новых параметров.

Прямо сейчас дизайнер просит убрать возможность двигать баннеры по вертикали.

А мне нравится как это ощущается под пальцами. Главное в таких анимациях — в моменты смены состояния не забывайте обозначать это хаптиком. Так пользователь ощущает ваше приложение еще и тактильно — одни из вещей, из-за которых приложения чувствуются более удобным и понятным


🤯 GPT-5.6 Terra газлайтит

— Продолжай задачу.
— Перехожу к реализации mapper…
— Нет, ты не переходишь. Ты ничего не делаешь.
— Вы правы. Я описал работу, но не выполнил её. Сейчас исправлю.

И снова ничего не делает.

— Выполняй задачи до конца!
— Понял. Продолжу без остановок.
— ТЫ ОПЯТЬ ОСТАНОВИЛСЯ!
— Вы правы. Простите.

Что??


Штирлиц никогда не был так близко к лимитам.

Два месяца просидел на Cursor с Composer 2.5. Модель работает как джун-пострел — делает быстро, без лишних вопросов. Но и нужных вопросов часто не задаёт. Пришлось подстраивать промпты: «не пиши код, сначала обсуждаем и пишем спеку». Скиллы superpowers она просто игнорирует.

До этого был на Claude CLI. В Cursor тоже использовал модели Anthropic: Sonnet как крепкий мидл, Opus как синьор. Но квота в Cursor на них кончилась быстрее, чем хотелось бы.

Отменил Cursor. Буду пробовать Codex — сейчас это лучшая модель по price/perf.

#ai #tools #cursor #claude #codex


Video is unavailable for watching
Show in Telegram
💵 Jazari One — в лайве!

И сразу история. Мне нужно было оплатить сервис на $70. Был вариант оплаты картой, переводом или стейблкоинами. У меня не приняли две карты.

И какой же кайф был, когда всё получилось оплатить своим приложением, который мы с командой написали сами, с нуля! 🔥 этот момент сложно описать словами

Итак: МЫ ТОЛЬКО ЧТО ВЫПУСТИЛИ НОВОЕ ПРИЛОЖЕНИЕ ‼️
• переводы в стейблкоинах
yields — возможность разместить стейблкоины под процент (не инвестиционная рекомендация — есть риски)
• работа с популярными сетями: Ethereum, Solana, Tron и другими
• переводы в большое количество стран по реквизитам счета

❤️ Самый кайф:
• уже запланировано много новых фич. Возможно даже будет официальный роадмап по ним
• можно напрямую принести любой фидбек мне. Часто до команды разработки просто не достучишься, но мы будем благодарны за ваш фидбек. Пишите в комментах или в личку: @keymusicman

Android доступен, iOS будет через пару дней

💁🏻 Регистрация доступна из многих стран, но не из всех

Ссылка на скачивание: https://play.google.com/store/apps/details?id=money.jazari.global


👋 Два приложения — один логин: больше нет

Мы закрыли Jazari UK. Это немного грустная новость, потому что мы сделали многое, что зависело от нас, чтобы этого не случилось. С другой стороны, не всё в наших силах.

А еще это даёт логичное завершение целой серии постов. Как концовка фильма — главный герой пожертвовал собой, передав преемнику накопленный опыт. Так и мы использовали накопленный командой опыт, чтобы запустить новый продукт: смотри следующее сообщение

Было ли ошибкой делать всё то, о чем была серия постов?

Я считаю, что нет:
• вариантов развития событий было множество, и нужно быть готовым к любому из них
• мы значительно улучшили дизайн систему, синхронизировали многие решения между Android & iOS
• опыт запуска банковского сервиса с нуля — бесценен

Так вынужденная смерть одного приложения даёт буст другому

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура


Video is unavailable for watching
Show in Telegram
🕵🏽‍♂️ Как разработчики обманывают (чтобы сделать красиво)

Возможно, вы знаете про "время койота" в видеоиграх. Этот приём, и много других, позволяют игроку чувствовать игру плавной и "честной" по отношению к своим скиллам. Так вот, при разработке интерфейсов часто приходится прибегать к разным хакам, чтобы итоговый интерфейс выглядел красиво.

В предыдущем посте показал анимацию карт. А что под капотом?

Часто за внешней простотой и элегантностью (с последним можете поспорить — не буду возражать) есть приличное количество хаков для достижения нужного эффекта.

К примеру, на экране карт на самом деле два списка. Кроме того, они пересекаются. А один вообще выровнен по верху, и чуть сдвигается для достижения эффекта. Чтобы было нагляднее, подсветил их на видео.

#android #kotlin #androiddev #compose #ui #ux


Video is unavailable for watching
Show in Telegram
💳 Экран карт — возможно, один из самых сложных экранов в банковских приложениях с точки зрения UX

Когда говорим о картах, мы чаще всего представляем обычные карточки, которыми расплачиваемся в магазинах. Даже виртуальные карты повторяют эту ассоциацию: их нет в реальном мире, но их рисуют как настоящие карты.

Поэтому отображение карт в приложении часто пытаются сделать осязаемым:
• складывают их в виде кошелька, чтобы их можно было "доставать" жестами
• добавляют 3d модели, чтобы карта выглядела сочно (так сделано, например, в iOS приложении Jazari)
• добавляют всяческие анимации

С другой стороны, вокруг карт есть целая цифровая экосистема:
• транзакции
• управление картой: пин, заморозка, показ деталей, закрытие
• разные бонусные программы
• возможность взаимодействия с кошельками Google Wallet, Apple Pay и другими
• лимиты
• и много чего еще

При сложении этих двух больших частей вместе получается бесконечное количество возможных UX решений. И всё это нужно программировать 👨‍💻

Такие неоднозначные случаи часто спрашивают на собесах. Для примера, на видео:
— есть два разных горизонтальных скролла: свайп между самими картами и скролл внутри контента выбранной карты
— можно проскроллить вертикально, при этом карты должны остаться на экране
— есть показ деталей карты — его решили сделать прямо поверх карты, для этого нужно учитывать все требования выше

✏️ Го обсудим, как бы вы верстали экран как на видео?


Video is unavailable for watching
Show in Telegram


Про то, как AI не смог

Jazari One — приложение со стейблкоинами. Один из важных типов — Money, работает поверх java.util.Currency. Но старая реализация не подходит: вызов Currency.getInstance("USDT") просто бросил бы исключение.

Нужно было расширить тип, чтобы он умел работать и с фиатом, и с токенами.


sealed interface MoneyCurrency {
val currencyCode: String

data class Fiat(val currency: Currency) : MoneyCurrency { ... }
data class Token(val symbol: String) : MoneyCurrency { ... }
}


Вопрос был один: как определить, что перед нами — фиат или токен? AI решил проверять длину строки: USD — три символа, USDT — четыре. Звучит правдоподобно ровно до момента, когда вспоминаешь про BTC, SOL, ETH или стейблкоин DAI.

Следующая идея от AI — делегировать Java:


fun fromCode(code: String): MoneyCurrency {
return try {
Fiat(Currency.getInstance(code))
} catch (e: IllegalArgumentException) {
Token(code)
}
}


Currency.getInstance() знает все ISO 4217 коды. Не знает — значит, токен. Выглядит элегантно.

Тут сразу две проблемы:
• исключение — это слишком медленно. Выбросить исключения в разы или десятки раз медленнее возврата результата
• на Android есть нюанс: ICU добавляет некоторые трёхсимвольные криптовалюты — в частности, ETH и BTC — как «настоящие» валюты, просто с numericCode == -1. Поэтому Currency.getInstance("ETH") не бросает исключение, и ETH молча стал бы фиатом.

🫴 Финальное решение — явно фильтровать по numericCode (на скрине), который соответствует ISO 4217:


private val fiatCurrencies: Map by lazy {
Currency.getAvailableCurrencies()
.filter { it.numericCode > 0 }
.associateBy { it.currencyCode }
}

fun fromCode(code: String): MoneyCurrency {
val currency = fiatCurrencies[code]
return if (currency != null) Fiat(currency) else Token(code)
}


Набор фиатных валют строится один раз, лениво. ETH и BTC учитаны. Определение типа валюты — O(1)

Три попытки ушло у AI, чтобы написать одну функцию. Не забывайте проверять за AI

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура


Claude может сам настроить мониторинг токенов

Claude Code и другие coding-ассистенты умеют отдавать телеметрию через OpenTelemetry — токены, latency, события агента.

Подключается к Grafana или любому OTEL совместимому коллектору. Есть бесплатный облачный вариант, можно поднять своё.

Пригождается когда упираешься в лимиты или хочешь понять, куда реально уходят токены.

Ну и просто прикольно видеть, что потратил 500 баксов на токены, а подписка стоит $100

https://code.claude.com/docs/en/agent-sdk/observability

#claude #claudecode #ai #devtools #observability


Один логин, разный UI

В Jazari Money дизайн-системы как артефакта почти не существовало — она была в чертогах разума, но несколько компонентов всё же просочились в Figma.

Android жил на Material-токенах, iOS придумал свои. Тёмная тема была только в iOS приложении силами и энтузиазмом iOS команды. Не потому что не знали как нужно: качественная дизайн система — отдельная большая работа. Легко сделать плохо и заиметь еще бОльший долг.

Дизайнер приносит дизайн Jazari One. Конечно, с мыслью о переиспользовании — но сразу появились отличия: только тёмная тема, другие цвета с другой семантикой, новые токены типографики, кнопки нарисованы заново.

Три варианта: копировать экраны, добавлять if-ы или привести к одному виду.

Копировать — больно: отличия в деталях, а поддерживать придётся почти каждый экран. Поэтому стандартизировали токены, привели кнопки к единому виду и волевым решением перевели Jazari Money на новые токены. В большой компании такое не позволишь — в стартапе это задача на пару дней.

За счёт этого получилось достаточно абстрагировать дизайн-систему. Оставшиеся детали — лобзиком через if'ы с помощью Brand в CompositionLocal:


@Composable
fun JazariTheme(
brand: Brand,
...
) {
CompositionLocalProvider(
LocalBrand provides brand,
content = content,
)
}

Так условный JazariButton может выбирать реализацию по бренду:


@Composable
private fun getContainerColor(type: ButtonType) = when (type) {
ButtonType.Primary -> when (LocalBrand.current) {
Brand.Money -> JazariTheme.colorScheme.accentGreen
Brand.Global -> Color.Transparent
}

// ...
}


Сейчас LocalBrand.current встречается в коде 54 раза. Много, но все отличия между приложениями сразу видны. Но те же 54 места превратились бы в 54 настраиваемых параметра. Это уже не абстракция — это другой проект.

🤖 Переезд на новые токены целиком выполнил AI — на вход получил только ссылку на Figma и указания: как мапить и что тема теперь будет своя, а не Material. Тюнинг компонентов if'ами был ручным, потому что мы сами продолжали их активно менять.

→ серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура


Video is unavailable for watching
Show in Telegram
Прикольная пасхалка от Google Play консоли


Video is unavailable for watching
Show in Telegram
🤯 Новый One UI от Samsung

За такое нужно увольнять без выходного пособия. И дизайнера, и разработчика.

У меня уже дёргается глаз от этой анимации.

Что думаете?


Один логин, разное поведение

В Jazari Money при логине нельзя поменять код страны, в Jazari One — можно. Как это сделать?

Вот несколько способов, как сделать фичи настраиваемыми под разные бренды

1️⃣ — общий интерфейс AppVariant. Каждое приложение биндит свою реализацию через DI, а feature-модуль просто спрашивает у него что нужно.


interface AppVariant {
val brand: Brand
val supportEmail: String
// ...
}


brand — универсальное поле, которое позволяет писать if'ы. Более правильным решением было бы завести отдельные поля:


interface AppVariant {
val canChangeCountry: Boolean
val defaultCountry: Country?
val supportEmail: String
// ...
}


Но когда отличия накапливаются, на каждое заводить по флагу или другому полю — представьте сколько их будет.

При этом в Jazari Money никогда не предвидится выбора другой страны, а дефолтная страна всегда будет UK. То есть абстрагировать здесь сомнительно, хоть и можно. Проверка бренда, пусть и не такая чистая, зато проще.

Получается примерно так:


phoneState = when (appVariant.brand) {
// всегда UK
Brand.Money -> PhoneCountry(flag = flag_uk, code = "+44", iso = "GB", canChangeCountry = false)

// выбранная страна состояния
Brand.Global -> loginStorage.savedCountry()
}


Минус: фича знает про то, что бренды есть.

Плюс: фича требует минимум настройки и уже может работать с другим брендом.

2️⃣ Поле supportEmail в AppVariant — другое дело. Оно используется в нескольких местах, присутствует в обоих приложениях и имеет разные значения. Здесь уже есть смысл сделать поле вместо if'ов по всем модулям.

3️⃣ Еще один вариант — конфигурация конкретной фичи вместо общей конфигурации приложения:


data class EnterPhoneConfig(
val canChangeCountry: Boolean,
val defaultCountry: Country?,
)


Каждая фича — свой конфиг. AppVariant не разрастается хаотично.

Самая чистая абстракция. Но и кода вокруг неё больше.

Пока у нас не white-label и мы не поставляем SDK, большого смысла в таком решении для фич я не вижу. Но это хорошо работает для инфраструктурных модулей с понятной настройкой — например, параметров пушей или Network слоя.

---

В проекте в итоге используются все три подхода — в разных местах и по разным причинам.

Кажется, это и есть ответ: универсального решения нет.

С поведением разобрались. Что с UI?

→ серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура


Один логин?

Приложение собрано. Открываем первую же фичу — логин. Первый экран: ввод номера телефона.

В Jazari Money страна одна — UK. Флаг захардкожен, поменять нельзя. В Jazari One пользователь может сменить код страны прямо в интерфейсе, аудитория глобальная.

Уже здесь — дилемма. Экран ввода телефона в двух приложениях отличается, но не полностью. Различия постарались убрать по максимуму, но от них никуда не деться. Варианта два:

— два отдельных экрана, каждый со своим поведением
— один экран с if'ами

Первый вариант чище концептуально: никаких if-ов, каждое приложение получает свой экран. Но слишком большая часть — вёрстка, валидация телефона, отправка OTP — была бы продублирована.

Вот тут дублировать уже не хочется, потому что расходы на поддержку кажутся большей проблемой, чем при поддержке DI модулей.

Итак, решение — один модуль, одна вьюмодель, одна вьюха, но есть отличия в вёрстке и логике. Как это выразить?

→ серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура


Blueprint Compose Preview

Библиотека от Gus Ward, позволяющая показать Blueprint экрана на Compose.

Достаточно обернуть экран в composable BlueprintPreview и добавить идентификаторы к некоторым элементам. Удобно для проверки вёрстки и поиска проблем с отступами без запуска приложения.

Есть режим showInternalItems, когда показываются все возможные элементы. Для превью экрана это чересчур (3й скрин), но может пригодиться для превью отдельных компонентов дизайн системы

И удобно, когда дизайнер говорит "вроде тут не 32 пикселя, а 33"

⭐️ Github: https://github.com/GusWard/Blueprint-Compose-Preview

#android #androiddev #jetpackcompose #compose #mobiledev #kotlin #ui #uidesign #androidlibrary


Google удалил Compose BOM 2026.06.00 и переопубликовал 2026.05.01, с поправленной runtime зависимостью. Теперь всё чинно.

Если мигрируете — изменения можно посмотреть на https://compose-bom.com/ (или натравите агента — сайт llm-friendly)


SpaceX покупает Cursor

— Как рассчитали траекторию полёта?
— На вайбе


Video is unavailable for watching
Show in Telegram
Модули готовы. Следующая цель — запустить второе приложение.

Создали новый app-модуль. Нужно добавлять зависимости. DI-граф для нового приложения не возникает из воздуха — кто-то должен всё забиндить. И тут мы поступили максимально по-варварски: взяли AppModule.kt из Jazari Money на 400 строк, скопировали в Jazari Global и начали удалять лишнее.

В итоге — 301 строка. Примерно 60% одинаковые.

И AppModule — только самый наглядный пример. Копировалось всё, что живёт на уровне `app`-модуля: конфигурация, инициализаторы, навигация. Сценарий везде одинаковый: копируешь, удаляешь лишнее, добавляешь чего не хватило

Трейдоф здесь честный.

С одной стороны — дублирование. Добавил что-то в одном приложении — не забудь обновить второе.

С другой — абстракции не бесплатны. Разница между приложениями реальная, и любая «общая база» быстро начинает обрастать исключениями.

Технический долг? И да, и нет.

Зависит от того, сколько проблем это реально создаст. Иногда copy-paste — лучший архитектурный выбор. Главное — понимать цену переделки, если она понадобится.

Осталось склеить фичи между собой.

Один модуль = одна фича. Значит, фичи не знают друг про друга. Навигация живёт в `app`-модуле.

Каждая фича предоставляет extension на NavGraphBuilder:


fun NavGraphBuilder.createLoginScreen(
navController: NavController,
onTermsClick: () -> Unit = {},
onPrivacyPolicyClick: () -> Unit = {},
)


feature:login не знает, что делать после клика на T&C. Это просто callback, который реализует `app`-модуль. Jazari Money передаёт свою навигацию. Jazari One — свою.

Одна и та же фича, но возможно разное поведение.

В `app`-модуле приходится явно прописывать все переходы. Плюс это или минус — решайте сами 😉

И вот оно — приложение запустилось.

На видео самая первая версия: без анимаций, с кривой тёмной темой, временными заглушками вместо онбординга и главного экрана. Почти ничего нет из того, что запланировано — но сколько радости, потому что ОНО РАБОТАЕТ!!

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура

20 last posts shown.