diff --git a/CHANGELOG.md b/CHANGELOG.md index 473a95a5c6..608414806b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ __All the `tgbotapi.extensions.*` packages have been removed__ * Constructor of `UnknownInlineKeyboardButton` is not internal and can be created with any `json` ([#563](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/563)) * All the interfaces from `dev.inmo.tgbotapi.types.files.abstracts` have been replaced to `dev.inmo.tgbotapi.types.files` and converted to sealed ([#550](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/550)) * `PassportFile` has been replaced to `dev.inmo.tgbotapi.types.files` +* `WebApps`: + * Created 🎉 ## 0.38.13 diff --git a/settings.gradle b/settings.gradle index bd0430c7c5..6cf2139f0b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,4 +17,5 @@ include ":tgbotapi.utils" include ":tgbotapi.behaviour_builder" include ":tgbotapi.behaviour_builder.fsm" include ":tgbotapi" +include ":tgbotapi.webapps" include ":docs" diff --git a/tgbotapi.webapps/build.gradle b/tgbotapi.webapps/build.gradle new file mode 100644 index 0000000000..257fcf8f02 --- /dev/null +++ b/tgbotapi.webapps/build.gradle @@ -0,0 +1,49 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" +} + +project.version = "$library_version" +project.group = "$library_group" +project.description = "Web App bindings for the Telegram Web Apps API" + +apply from: "../publish.gradle" + +repositories { + mavenLocal() + mavenCentral() +} + +kotlin { + js(IR) { + browser() + nodejs() + } + + sourceSets { + commonMain { + dependencies { + implementation kotlin('stdlib') + api project(":tgbotapi.core") + } + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ColorScheme.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ColorScheme.kt new file mode 100644 index 0000000000..2b30dec7da --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ColorScheme.kt @@ -0,0 +1,6 @@ +package dev.inmo.tgbotapi.webapps + +enum class ColorScheme { + LIGHT, + DARK +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventHandler.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventHandler.kt new file mode 100644 index 0000000000..f56acf10d2 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventHandler.kt @@ -0,0 +1,4 @@ +package dev.inmo.tgbotapi.webapps + +typealias EventHandler = WebApp.() -> Unit +typealias ViewportChangedEventHandler = WebApp.(Boolean) -> Unit diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventType.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventType.kt new file mode 100644 index 0000000000..1771966db3 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventType.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.webapps + +sealed class EventType(val typeName: String) { + object ThemeChanged : EventType("themeChanged") + object ViewportChanged : EventType("viewportChanged") + object MainButtonClicked : EventType("mainButtonClicked") +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/MainButton.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/MainButton.kt new file mode 100644 index 0000000000..3fabb5a2e7 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/MainButton.kt @@ -0,0 +1,55 @@ +package dev.inmo.tgbotapi.webapps + +import kotlin.js.Json +import kotlin.js.json + +external class MainButton { + val text: String + fun setText(text: String): MainButton + + var color: String + var textColor: String + + val isVisible: Boolean + fun show(): MainButton + fun hide(): MainButton + + val isActive: Boolean + fun enable(): MainButton + fun disable(): MainButton + + val isProgressVisible: Boolean + fun showProgress(leaveActive: Boolean = definedExternally): MainButton + fun hideProgress(): MainButton + + internal fun onClick(eventHandler: () -> Unit): MainButton + + internal fun setParams(params: Json): MainButton +} + +data class MainButtonParams( + val text: String? = null, + val color: String? = null, + val textColor: String? = null, + val isActive: Boolean? = null, + val isVisible: Boolean? = null +) + +fun MainButton.onClick(eventHandler: EventHandler) = onClick { + val that = js("this").unsafeCast() + that.eventHandler() +} + +fun MainButton.setParams(params: MainButtonParams) = setParams( + json( + *listOfNotNull( + params.text ?.let { "text" to params.text }, + params.color ?.let { "color" to params.color }, + params.textColor ?.let { "text_color" to params.textColor }, + params.isActive ?.let { "is_active" to params.isActive }, + params.isVisible ?.let { "is_visible" to params.isVisible }, + ).toTypedArray() + ) +) + + diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/Telegram.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/Telegram.kt new file mode 100644 index 0000000000..3666fc481b --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/Telegram.kt @@ -0,0 +1,10 @@ +package dev.inmo.tgbotapi.webapps + +import org.w3c.dom.Window + +external interface Telegram { + val WebApp: WebApp +} + +val Window.Telegram + get() = asDynamic().Telegram.unsafeCast() diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ThemeParams.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ThemeParams.kt new file mode 100644 index 0000000000..a8db758c55 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ThemeParams.kt @@ -0,0 +1,16 @@ +package dev.inmo.tgbotapi.webapps + +external interface ThemeParams { + @JsName("bg_color") + val backgroundColor: String? + @JsName("text_color") + val textColor: String? + @JsName("hint_color") + val hintColor: String? + @JsName("link_color") + val linkColor: String? + @JsName("button_color") + val buttonColor: String? + @JsName("button_text_color") + val buttonTextColor: String? +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebApp.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebApp.kt new file mode 100644 index 0000000000..8a21b744a7 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebApp.kt @@ -0,0 +1,75 @@ +package dev.inmo.tgbotapi.webapps + +external class WebApp { + val initData: String + val initDataUnsafe: WebAppInitData + + @JsName("colorScheme") + val colorSchemeRaw: String + val themeParams: ThemeParams + + val isExpanded: Boolean + val viewportHeight: Float + val viewportStableHeight: Float + + @JsName("MainButton") + val mainButton: MainButton + + internal fun onEvent(type: String, callback: () -> Unit) + @JsName("onEvent") + internal fun onEventWithBoolean(type: String, callback: (Boolean) -> Unit) + + fun offEvent(type: String, callback: () -> Unit) + @JsName("offEvent") + fun offEventWithBoolean(type: String, callback: (Boolean) -> Unit) + + fun sendData(data: String) + + fun ready() + fun expand() + fun close() +} + +val WebApp.colorScheme: ColorScheme + get() = when (colorSchemeRaw) { + "light" -> ColorScheme.LIGHT + "dark" -> ColorScheme.DARK + else -> ColorScheme.LIGHT + } + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onEvent(type: EventType, eventHandler: EventHandler) = { + eventHandler(js("this").unsafeCast()) +}.also { + onEvent( + type.typeName, + callback = it + ) +} + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onEvent(type: EventType.ViewportChanged, eventHandler: ViewportChangedEventHandler) = { it: Boolean -> + eventHandler(js("this").unsafeCast(), it) +}.also { + onEventWithBoolean( + type.typeName, + callback = it + ) +} + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onThemeChanged(eventHandler: EventHandler) = onEvent(EventType.ThemeChanged, eventHandler) +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onMainButtonClicked(eventHandler: EventHandler) = onEvent(EventType.MainButtonClicked, eventHandler) +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onViewportChanged(eventHandler: ViewportChangedEventHandler) = onEvent(EventType.ViewportChanged, eventHandler) diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppInitData.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppInitData.kt new file mode 100644 index 0000000000..a00181ab91 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppInitData.kt @@ -0,0 +1,19 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.tgbotapi.types.MilliSeconds +import dev.inmo.tgbotapi.types.WebAppQueryId + +external interface WebAppInitData { + val queryId: WebAppQueryId? + + val user: WebAppUser? + val receiver: WebAppUser? + + @JsName("start_param") + val startParam: String? + + @JsName("auth_date") + val authDate: MilliSeconds + + val hash: String +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppUser.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppUser.kt new file mode 100644 index 0000000000..a192b422ff --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppUser.kt @@ -0,0 +1,37 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.micro_utils.language_codes.IetfLanguageCode +import dev.inmo.tgbotapi.types.* + +external interface WebAppUser { + val id: Identifier + @JsName(isBotField) + val isBot: Boolean? + @JsName(firstNameField) + val firstName: String + @JsName(lastNameField) + val lastName: String? + @JsName(usernameField) + val username: String? + @JsName(languageCodeField) + val languageCode: String? + @JsName(photoUrlField) + val photoUrl: String? +} + +fun WebAppUser.asUser() = if (isBot == true) { + CommonBot( + UserId(id), + username ?.let(::Username) ?: error("Username is absent for bot, but must exists"), + firstName, + lastName ?: "" + ) +} else { + CommonUser( + UserId(id), + firstName, + lastName ?: "", + username ?.let(::Username), + languageCode ?.let(::IetfLanguageCode) + ) +}