Навигация по циклу статей:
Часть 1. Пишем веб-приложение кликер на Kotlin
Часть 2. Пишем кликер для Telegram на Kotlin – текущая статья
Часть 2 с половиной. Аутентификация пользователя с rest-framework. TMA на KMP – в разработке
Часть 3. Добавляем оплату через Telegram Mini Apps на Kotlin – в разработке
Раскрытые темы в цикле
-
Web приложение на Kotlin – часть 1
-
Интеграция приложения с Telegram Mini Apps – часть 2
-
Работа с элементами интерфейса TMA приложения. Тема,
MainButton
,BackButton
– часть 2 -
Поделиться ссылкой на приложение через Telegram. Передача данных через ссылку – часть 2
-
Аутентификации через TMA приложение – часть 2 и 2.5
-
Telegram Payments API– часть 3
Запускаем приложение в Telegram
Telegram Mini Apps (далее — TMA), если просто, то это обычные веб-приложения, которые имеют некоторый ограниченный доступ к API, определяемый Telegram. Из чего следует возможность использовать любой стек, применяемый в разработке веб-приложений. В этой статье мы запустим разработанное в предыдущей статье веб-приложение в Telegram
Для добавления веб-приложения в Telegram достаточно создать бота через BotFather по документации. Сам токен пока не понадобится, сейчас нужно настроить бота на открытие нашего приложения. В настройках бота (в чате с BotFather) переходим в меню Bot Settings
>> Menu Button
>> Customize menu button
. Теперь нас просят ввести ссылку, по которой располагается наш бот.
localhost
не подойдёт, поскольку, скорее всего, у вас он не настроен на приём запросов по https. Поэтому идём по самому простому пути, используемngrok
. Для настройки зарегистрируйтесь на сайте ngrok, установите его и пройдите первоначальную настройку. Сам запуск, последний этап, прост:
ngrok http http://localhost:8080 --host-header="localhost:8080"
После этого получаем хост, работающий с https схемой
Далее отправляем его в BotFather и задаём надпись на кнопке. Готово, теперь у нашего свежесозданного бота есть кнопка. Нажимаем и открывается наше веб-приложение.
Навигация с использованием TMA (MainButton, BackButton)
Добавим второй экран, не будем подключать никакие библиотеки для навигации, используем enum, state и when.
// Don't use in real code enum class Screen { CLICKER, FRIENDS_LIST, } // Don't use in real code var currentScreen by mutableStateOf(Screen.CLICKER) @Composable fun App() { when (currentScreen) { Screen.CLICKER -> ClickerScreen( onFriendsList = { currentScreen = Screen.FRIENDS_LIST } ) Screen.FRIENDS_LIST -> FriendsListScreen( onBack = { currentScreen = Screen.CLICKER }, ) } }
Интерфейс, который ранее располагался в App()
выносим как отдельный экран с callback на переход на другой экран
val score = mutableStateOf(0L) @Composable fun ClickerScreen( onFriendsList: () -> Unit, ) { Div( attrs = { classes(ClickerStyles.MainContainer) } ) { Div( attrs = { classes(ClickerStyles.ClickerBlock) } ) { H2 { Text("Score: ${score.value}") } Img( src = Res.images.click_item.fileUrl, attrs = { classes(ClickerStyles.MainImage) onClick { score.value += 1 } } ) } } }
Вторым экраном станет самая важная функция таких кликеров – список друзей. Мы будем приглашать их в нашу игру через ссылку.
@Composable fun FriendsListScreen( onBack: () -> Unit, ) { Div( attrs = { classes(FriendsListStyle.Container) } ) { H4 { Text("У тебя пока нет друзей...") } } }
Отлично! Теперь у нас есть экраны, но по ним нельзя переходить, воспользуемся стандартными средствами, которые предоставляет Telegram: это MainButton
и BackButton
.
Первым делом нужно подключить сам TMA API в наше веб приложение, воспользуемся сторонней, уже готовой обёрткой над JS библиотекой TMA
jsMain.dependencies { //… implementation("dev.inmo:tgbotapi.webapps:15.0.0") }
Однако этого недостаточно, эта зависимость является лишь врапером для API телеграмм, в наш проект нужно подключить js версию TMA API. Добавим её как script
в index.html
документе.
<head> ... <script src="https://telegram.org/js/telegram-web-app.js"></script> </head>
Теперь нам доступен глобальный объект webApp
, у которого уже есть поля backButton
и mainButton
.
Однако проявим смекалку и адаптируем их под использование из composable функции – сделаем из них компоненты.
@Composable fun WebAppMainButton( text: String, onClick: () -> Unit, visible: Boolean = true, active: Boolean? = undefined, loading: Boolean = false, loadingLeaveActive: Boolean = false, hideOnDispose: Boolean = true, // use if next screen has MainButton too color: String? = undefined, textColor: String? = undefined, ) { DisposableEffect(visible) { if (visible) { webApp.mainButton.show() } else { webApp.mainButton.hide() } onDispose { if (hideOnDispose) webApp.mainButton.hide() } } DisposableEffect( keys = arrayOf( onClick, text, color, textColor, visible, active, loading, loadingLeaveActive ), ) { webApp.mainButton.onClick(onClick) webApp.mainButton.setParams( MainButtonParams( text = text, color = color, textColor = textColor, isActive = active, isVisible = visible, ) ) if (loading) webApp.mainButton.showProgress(leaveActive = loadingLeaveActive) else webApp.mainButton.hideProgress() onDispose { webApp.mainButton.offClick(onClick) } } } @Composable fun WebAppBackButton( onClick: () -> Unit, ) { DisposableEffect(Unit) { webApp.backButton.onClick(onClick) webApp.backButton.show() onDispose { webApp.backButton.hide() webApp.backButton.offClick(onClick) } } }
Теперь будет проще работать с TMA API. Добавим главную кнопку перехода в список друзей на экран кликера, а на экран списка друзей кнопку назад.
@Composable fun FriendsListScreen( onBack: () -> Unit, ) { WebAppBackButton(onClick = onBack) // ... } @Composable fun ClickerScreen( onFriendsList: () -> Unit, ) { WebAppMainButton( text = "Список друзей", hideOnDispose = false, onClick = onFriendsList ) // ... }
Тема как у Telegram
Цвета этого приложения сильно выделяются, но TMA API предоставляет свои цвета для применения их в своём CSS. Добавим же эти стили к себе через CSS in Kotlin. Создадим AppStyles, где будут применяться эти значения как значения css variables
object TMAVariables { val BackgroundColorValue = Color("#ffffff") val BackgroundColor by variable<CSSColorValue>() val SecondaryBackgroundColorValue = Color("#ffffff") val SecondaryBackgroundColor by variable<CSSColorValue>() val TextColorValue = Color("#000000") val TextColor by variable<CSSColorValue>() val HintColorValue = Color.gray val HintColor by variable<CSSColorValue>() // И т. д., полный код на github }
Для простого применения ThemeParams
, предоставляемого библиотекой создаём функцию расширения для StyleScope
. Поскольку поля могут быть null, нужно определить дефолтные значения для каждой variable.
fun StyleScope.applyThemeVariables(theme: ThemeParams) { BackgroundColor(theme.backgroundColor?.toCSSColorValue() ?: BackgroundColorValue) SecondaryBackgroundColor(theme.secondaryBackgroundColor?.toCSSColorValue() ?: SecondaryBackgroundColorValue) TextColor(theme.textColor?.toCSSColorValue() ?: TextColorValue) HintColor(theme.hintColor?.toCSSColorValue() ?: HintColorValue) // И т. д., полный код на github }
Создадим новую, главную таблицу стилей, где применим созданные CSS variables для всего приложения. Дополнительно можно указать параметры для различных тегов.
class AppStyles(val themeParams: ThemeParams) : StyleSheet() { init { "body" style { applyThemeVariables(themeParams) backgroundColor(TMAVariables.BackgroundColor.value()) color(TMAVariables.TextColor.value()) } "a" style { color(TMAVariables.LinkColor.value()) } "button" style { color(TMAVariables.ButtonTextColor.value()) backgroundColor(TMAVariables.ButtonColor.value()) } } }
Далее нужно применить этот стиль в renderComposable
, передав параметры темы webApp.themeParams
renderComposable(rootElementId = "app") { Style(AppStyles(webApp.themeParams)) // ... }
Запускаем и видим, что цвета теперь сочетаются с Telegram
Авторизация пользователя и сохранение данных
Краткая справка: каждый TMA запускается с query params, которые можно использовать для получения id и краткой информации о пользователе, а также аутентифицировать. Они хранятся в полях webApp.initData
и webApp.initDataUnsafe
. Причина разделения – это то, что initData – это сырые данные, которые можно отправить на сервер для валидации, initDataUnsafe
– это type-safe объект, где хранятся эти данные уже в преобразованном виде, но на самом клиенте никак нельзя безопасно проверить данные на правильность, но можно использовать их для отображения какой-либо информации о пользователе, например, username или id.
Для авторизации будем отправлять к каждому запросу заголовок с webApp.initData
. TapClient
создаётся в commonMain и заранее зададим запросы, которые нам понадобятся в будущем.
object TapClient { private val client: HttpClient = HttpClient(TapClientEngine) { // … authConfig() } suspend fun sendCurrentScore(score: Long) { client.post("/score") { setBody(SendCurrentScoreRequest(score)) contentType(ContentType.Application.Json) } } suspend fun fetchCurrentScore(): Long { return client.get("/score").body<GetCurrentScoreResponse>().score } suspend fun fetchFriendsList(): List<FriendInfo> { return client.get("/friends").body<GetFriendsListResponse>().friends } }
Где authConfig()
определён как expect
и реализуется в исходниках jsMain
actual fun HttpClientConfig<HttpClientEngineConfig>.authConfig() { defaultRequest { header("tma-data", webApp.initData) } }
Теперь каждый запрос будет проверяться на то, что он запущен из Telegram с верными дынными. Подробнее в другой статье, про реализацию серверной части (статья в разработке).
Прямые ссылки, позвать друга
Direct link – возможность открывать TMA приложение по ссылке, в том числе с предопределёнными параметрами.
Для подключения прямых ссылок возвращаемся к BotFather и пишем команду /newapp
, выбираем нашего бота и далее
-
задаём название нашего TMA приложению
-
Короткое описание
-
Картинку приложению 640×360.
-
Добавляем GIF
-
Ссылка, которая будет открывать (такая же что, мы задавали при подключении кнопки в приложении)
-
appname, по которому будет располагаться приложение
Теперь мы можем открывать приложение по ссылке и передавать параметр как query параметр с ключём startapp. Параметры, переданные в прямой ссылке появятся в webApp.initDataUnsafe.startParam
. Так мы и будем добавлять друзей.
Сначала добавим кнопку на отправку ссылке на экран с друзьями. Это лишь пример использования TMA API и лучше реализовать генерацию ссылки на стороне сервера. Вызов buildInviteLink()
создаёт ссылку, перейдя по которой открывается наше TMA приложение, а buildShareLink()
использует возможности Telegram для отправки сообщения, содержащего ссылку на переход.
// Use build config instead private const val AppLink = "https://t.me/botusername/appname" private const val ShareMessage = "%D0%9F%D0%BE%D0%BF%D1%80%D0%BE%D0%B1%D1%83%D0%B9+%D1%8D%D1%82%D0%BE%D1%82+%D0%BD%D0%BE%D0%B2%D1%8B%D0%B9+%D0%BA%D0%BB%D0%B8%D0%BA%D0%B5%D1%80%21" private fun buildInviteLink(telegramId: Long): String { return "$AppLink?startapp=ref_$telegramId" } private fun buildShareLink(telegramId: Long): String { return "https://t.me/share/url?url=${buildInviteLink(telegramId)}&text=${ShareMessage}" }
Осталось добавить MainButton
, нажатие на которую генерирует ссылку на отправку сообщения, и открывает её через webApp.openTelegramLink()
. Telegram обработает такую ссылку открытием экрана “переслать сообщение“
@Composable fun FriendsListScreen( onBack: () -> Unit, ) { WebAppMainButton( text = "Пригласить друзей", onClick = { val id = webApp.initDataUnsafe.user?.id ?: return@WebAppMainButton webApp.openTelegramLink(buildShareLink(id.long)) } ) //... }
Теперь по этой кнопке можно отправить сообщение со ссылкой-приглашением любому пользователю. Обработку приглашений отдельно делать на стороне клиента (только для информирования о том, что его пригласили) не нужно, поскольку сервер и так получает все initData от клиента с каждым запросом.
Итоги
В данной статье мы научились использовать TMA API в нашем приложении на Kotlin. Самое главное, что нужно для приложения – это стили, аутентификация и какое-никакое управление интерфейсом Telegram нам предоставляет, дальше можно делать с такими приложениями, что захочет заказчик. Исходный код можно посмотреть в ветке clicker
нашего проекта с шаблоном на GitHub
Мы не только делимся своим опытом в статьях, но и используем его во благо бизнеса. Возможно, именно вашего! Обращайтесь к нам для разработки мобильных приложений под ключ. Работаем на подряде, субподряде, предоставляем аутстафф.
ссылка на оригинал статьи https://habr.com/ru/articles/831696/
Добавить комментарий