1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-20 01:39:09 -05:00

Merge branch 'release/2.4.1'

This commit is contained in:
Mikołaj Pich 2024-02-17 13:10:34 +01:00
commit 90a5b9e20f
16 changed files with 83 additions and 28 deletions

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 146 versionCode 147
versionName "2.4.0" versionName "2.4.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -195,7 +195,7 @@ ext {
} }
dependencies { dependencies {
implementation 'io.github.wulkanowy:sdk:2.4.0' implementation 'io.github.wulkanowy:sdk:2.4.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
@ -252,7 +252,7 @@ dependencies {
implementation 'com.fredporciuncula:flow-preferences:1.9.1' implementation 'com.fredporciuncula:flow-preferences:1.9.1'
implementation 'org.apache.commons:commons-text:1.11.0' implementation 'org.apache.commons:commons-text:1.11.0'
playImplementation platform('com.google.firebase:firebase-bom:32.7.1') playImplementation platform('com.google.firebase:firebase-bom:32.7.2')
playImplementation 'com.google.firebase:firebase-analytics' playImplementation 'com.google.firebase:firebase-analytics'
playImplementation 'com.google.firebase:firebase-messaging' playImplementation 'com.google.firebase:firebase-messaging'
playImplementation 'com.google.firebase:firebase-crashlytics:' playImplementation 'com.google.firebase:firebase-crashlytics:'

View File

@ -94,6 +94,7 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() } loginFormUsername.doOnTextChanged { _, _, _, _ -> presenter.onUsernameTextChanged() }
loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() } loginFormPass.doOnTextChanged { _, _, _, _ -> presenter.onPassTextChanged() }
loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() } loginFormHost.setOnItemClickListener { _, _, _, _ -> presenter.onHostSelected() }
loginFormDomainSuffix.doOnTextChanged { _, _, _, _ -> presenter.onDomainSuffixChanged() }
loginFormSignIn.setOnClickListener { presenter.onSignInClick() } loginFormSignIn.setOnClickListener { presenter.onSignInClick() }
loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() } loginFormAdvancedButton.setOnClickListener { presenter.onAdvancedLoginClick() }
loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() } loginFormPrivacyLink.setOnClickListener { presenter.onPrivacyLinkClick() }
@ -188,6 +189,12 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
} }
} }
override fun setDomainSuffixInvalid() {
with(binding.loginFormDomainSuffixLayout) {
error = getString(R.string.login_invalid_domain_suffix)
}
}
override fun clearUsernameError() { override fun clearUsernameError() {
binding.loginFormUsernameLayout.error = null binding.loginFormUsernameLayout.error = null
binding.loginFormErrorBox.isVisible = false binding.loginFormErrorBox.isVisible = false
@ -206,6 +213,10 @@ class LoginFormFragment : BaseFragment<FragmentLoginFormBinding>(R.layout.fragme
binding.loginFormErrorBox.isVisible = false binding.loginFormErrorBox.isVisible = false
} }
override fun clearDomainSuffixError() {
binding.loginFormDomainSuffixLayout.error = null
}
override fun showSoftKeyboard() { override fun showSoftKeyboard() {
activity?.showSoftInput() activity?.showSoftInput()
} }

View File

@ -101,6 +101,12 @@ class LoginFormPresenter @Inject constructor(
} }
} }
fun onDomainSuffixChanged() {
view?.apply {
clearDomainSuffixError()
}
}
fun updateCustomDomainSuffixVisibility() { fun updateCustomDomainSuffixVisibility() {
view?.run { view?.run {
showDomainSuffixInput("customSuffix" in formHostValue) showDomainSuffixInput("customSuffix" in formHostValue)
@ -159,7 +165,7 @@ class LoginFormPresenter @Inject constructor(
fun onSignInClick() { fun onSignInClick() {
val loginData = getLoginData() val loginData = getLoginData()
if (!validateCredentials(loginData.login, loginData.password, loginData.baseUrl)) return if (!validateCredentials(loginData)) return
resourceFlow { resourceFlow {
studentRepository.getUserSubjectsFromScrapper( studentRepository.getUserSubjectsFromScrapper(
@ -229,24 +235,29 @@ class LoginFormPresenter @Inject constructor(
view?.onRecoverClick() view?.onRecoverClick()
} }
private fun validateCredentials(login: String, password: String, host: String): Boolean { private fun validateCredentials(loginData: LoginData): Boolean {
var isCorrect = true var isCorrect = true
if (login.isEmpty()) { if (loginData.login.isEmpty()) {
view?.setErrorUsernameRequired() view?.setErrorUsernameRequired()
isCorrect = false isCorrect = false
} else { } else {
if ("@" in login && "login" in host) { if ("@" in loginData.login && "login" in loginData.baseUrl) {
view?.setErrorLoginRequired() view?.setErrorLoginRequired()
isCorrect = false isCorrect = false
} }
if ("@" !in login && "email" in host) { if ("@" !in loginData.login && "email" in loginData.baseUrl) {
view?.setErrorEmailRequired() view?.setErrorEmailRequired()
isCorrect = false isCorrect = false
} }
if ("@" in login && "||" !in login && "login" !in host && "email" !in host) {
val emailHost = login.substringAfter("@") val isEmailLogin = "@" in loginData.login
val emailDomain = URL(host).host val isEmailWithLogin = "||" !in loginData.login
val isLoginNotRequired = "login" !in loginData.baseUrl
val isEmailNotRequired = "email" !in loginData.baseUrl
if (isEmailLogin && isEmailWithLogin && isLoginNotRequired && isEmailNotRequired) {
val emailHost = loginData.login.substringAfter("@")
val emailDomain = URL(loginData.baseUrl).host
if (!emailHost.equals(emailDomain, true)) { if (!emailHost.equals(emailDomain, true)) {
view?.setErrorEmailInvalid(domain = emailDomain) view?.setErrorEmailInvalid(domain = emailDomain)
isCorrect = false isCorrect = false
@ -254,16 +265,21 @@ class LoginFormPresenter @Inject constructor(
} }
} }
if (password.isEmpty()) { if (loginData.password.isEmpty()) {
view?.setErrorPassRequired(focus = isCorrect) view?.setErrorPassRequired(focus = isCorrect)
isCorrect = false isCorrect = false
} }
if (password.length < 6 && password.isNotEmpty()) { if (loginData.password.length < 6 && loginData.password.isNotEmpty()) {
view?.setErrorPassInvalid(focus = isCorrect) view?.setErrorPassInvalid(focus = isCorrect)
isCorrect = false isCorrect = false
} }
if (loginData.domainSuffix !in listOf("", "rc", "kurs")) {
view?.setDomainSuffixInvalid()
isCorrect = false
}
return isCorrect return isCorrect
} }
} }

View File

@ -46,12 +46,16 @@ interface LoginFormView : BaseView {
fun setErrorEmailInvalid(domain: String) fun setErrorEmailInvalid(domain: String)
fun setDomainSuffixInvalid()
fun clearUsernameError() fun clearUsernameError()
fun clearPassError() fun clearPassError()
fun clearHostError() fun clearHostError()
fun clearDomainSuffixError()
fun showSoftKeyboard() fun showSoftKeyboard()
fun hideSoftKeyboard() fun hideSoftKeyboard()

View File

@ -96,10 +96,7 @@ class LoginSymbolPresenter @Inject constructor(
?.takeIf { it.symbol == loginData.userEnteredSymbol } ?.takeIf { it.symbol == loginData.userEnteredSymbol }
if (enteredSymbolDetails?.error is InvalidSymbolException) { if (enteredSymbolDetails?.error is InvalidSymbolException) {
view?.run { showInvalidSymbolError()
setErrorSymbolInvalid()
showContact(true)
}
} else { } else {
Timber.i("Login with symbol result: Success") Timber.i("Login with symbol result: Success")
view?.navigateToStudentSelect(loginData, requireNotNull(user.data)) view?.navigateToStudentSelect(loginData, requireNotNull(user.data))
@ -128,6 +125,9 @@ class LoginSymbolPresenter @Inject constructor(
loginErrorHandler.dispatch(user.error) loginErrorHandler.dispatch(user.error)
lastError = user.error lastError = user.error
view?.showContact(true) view?.showContact(true)
if (user.error is InvalidSymbolException) {
showInvalidSymbolError()
}
} }
} }
}.onResourceNotLoading { }.onResourceNotLoading {
@ -145,6 +145,13 @@ class LoginSymbolPresenter @Inject constructor(
return normalizedSymbol in definitelyInvalidSymbols return normalizedSymbol in definitelyInvalidSymbols
} }
private fun showInvalidSymbolError() {
view?.run {
setErrorSymbolInvalid()
showContact(true)
}
}
fun onFaqClick() { fun onFaqClick() {
view?.openFaqPage() view?.openFaqPage()
} }

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import android.util.AndroidRuntimeException
import java.net.CookiePolicy import java.net.CookiePolicy
import java.net.CookieStore import java.net.CookieStore
import java.net.HttpCookie import java.net.HttpCookie
@ -9,7 +10,18 @@ import java.net.CookieManager as JavaCookieManager
class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) { class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL) {
private val webkitCookieManager: WebkitCookieManager = WebkitCookieManager.getInstance() private val webkitCookieManager: WebkitCookieManager? = getWebkitCookieManager()
/**
* @see [https://stackoverflow.com/a/70354583/6695449]
*/
private fun getWebkitCookieManager(): WebkitCookieManager? {
return try {
WebkitCookieManager.getInstance()
} catch (e: AndroidRuntimeException) {
null
}
}
override fun put(uri: URI?, responseHeaders: Map<String?, List<String?>>?) { override fun put(uri: URI?, responseHeaders: Map<String?, List<String?>>?) {
if (uri == null || responseHeaders == null) return if (uri == null || responseHeaders == null) return
@ -23,7 +35,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
// process each of the headers // process each of the headers
for (headerValue in responseHeaders[headerKey].orEmpty()) { for (headerValue in responseHeaders[headerKey].orEmpty()) {
webkitCookieManager.setCookie(url, headerValue) webkitCookieManager?.setCookie(url, headerValue)
} }
} }
} }
@ -34,7 +46,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
): Map<String, List<String>> { ): Map<String, List<String>> {
require(!(uri == null || requestHeaders == null)) { "Argument is null" } require(!(uri == null || requestHeaders == null)) { "Argument is null" }
val res = mutableMapOf<String, List<String>>() val res = mutableMapOf<String, List<String>>()
val cookie = webkitCookieManager.getCookie(uri.toString()) val cookie = webkitCookieManager?.getCookie(uri.toString())
if (cookie != null) res["Cookie"] = listOf(cookie) if (cookie != null) res["Cookie"] = listOf(cookie)
return res return res
} }
@ -50,7 +62,7 @@ class WebkitCookieManagerProxy : JavaCookieManager(null, CookiePolicy.ACCEPT_ALL
cookies.remove(uri, cookie) cookies.remove(uri, cookie)
override fun removeAll(): Boolean { override fun removeAll(): Boolean {
webkitCookieManager.removeAllCookies(null) webkitCookieManager?.removeAllCookies(null) ?: return false
return true return true
} }
} }

View File

@ -1,8 +1,5 @@
Wersja 2.4.0 Wersja 2.4.1
— naprawiliśmy logowanie do aplikacji na odmianie standardowej - drobne poprawki stabilności aplikacji i odświeżania danych
— naprawiliśmy wyświetlanie lekcji na kolejny dzień w kafelku na ekranie Start
— dodaliśmy oceny opisowe
— dodaliśmy kolorowe opisy we frekwencji we wpisach innych niż obecność
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -261,6 +261,7 @@
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:hint="@string/login_domain_suffix_hint" android:hint="@string/login_domain_suffix_hint"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout" app:layout_constraintTop_toBottomOf="@+id/loginFormHostLayout"

View File

@ -56,6 +56,7 @@
<string name="login_invalid_email">Neplatný e-mail</string> <string name="login_invalid_email">Neplatný e-mail</string>
<string name="login_invalid_login">Místo e-mailu použijte přiřazené přihlašovací údaje</string> <string name="login_invalid_login">Místo e-mailu použijte přiřazené přihlašovací údaje</string>
<string name="login_invalid_custom_email">Použijte přiřazené přihlašovací nebo e-mail v @%1$s</string> <string name="login_invalid_custom_email">Použijte přiřazené přihlašovací nebo e-mail v @%1$s</string>
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu</string> <string name="login_invalid_symbol">Neplatný symbol. Pokud jej nemůžete najít, kontaktujte školu</string>
<string name="login_invalid_symbol_definitely">Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu</string> <string name="login_invalid_symbol_definitely">Nevymýšlejte si! Pokud symbol nemůžete najít, kontaktujte školu</string>
<string name="login_incorrect_symbol">Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+</string> <string name="login_incorrect_symbol">Žák nebyl nalezen. Zkontrolujte správnost symbolu a vybrané varianty deníku UONET+</string>

View File

@ -56,6 +56,7 @@
<string name="login_invalid_email">Ungültige email</string> <string name="login_invalid_email">Ungültige email</string>
<string name="login_invalid_login">Den zugewiesenen Login anstelle von email verwenden</string> <string name="login_invalid_login">Den zugewiesenen Login anstelle von email verwenden</string>
<string name="login_invalid_custom_email">Benutze den zugewiesenen Login oder E-Mail in @%1$s</string> <string name="login_invalid_custom_email">Benutze den zugewiesenen Login oder E-Mail in @%1$s</string>
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string> <string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string>
<string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string> <string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string>
<string name="login_incorrect_symbol">Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers</string> <string name="login_incorrect_symbol">Schüler nicht gefunden. Überprüfen Sie das Symbol und die gewählte Variation des UONET+ Registers</string>

View File

@ -56,6 +56,7 @@
<string name="login_invalid_email">Nieprawidłowy adres e-mail</string> <string name="login_invalid_email">Nieprawidłowy adres e-mail</string>
<string name="login_invalid_login">Użyj loginu zamiast adresu e-mail</string> <string name="login_invalid_login">Użyj loginu zamiast adresu e-mail</string>
<string name="login_invalid_custom_email">Użyj loginu lub adresu e-mail w @%1$s</string> <string name="login_invalid_custom_email">Użyj loginu lub adresu e-mail w @%1$s</string>
<string name="login_invalid_domain_suffix">Nieprawidłowy sufiks domeny</string>
<string name="login_invalid_symbol">Nieprawidłowy symbol. Jeśli nie możesz go znaleźć, skontaktuj się ze szkołą</string> <string name="login_invalid_symbol">Nieprawidłowy symbol. Jeśli nie możesz go znaleźć, skontaktuj się ze szkołą</string>
<string name="login_invalid_symbol_definitely">Nie zmyślaj! Jeśli nie możesz znaleźć symbolu, skontaktuj się ze szkołą</string> <string name="login_invalid_symbol_definitely">Nie zmyślaj! Jeśli nie możesz znaleźć symbolu, skontaktuj się ze szkołą</string>
<string name="login_incorrect_symbol">Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+</string> <string name="login_incorrect_symbol">Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+</string>

View File

@ -56,6 +56,7 @@
<string name="login_invalid_email">Неверный e-mail</string> <string name="login_invalid_email">Неверный e-mail</string>
<string name="login_invalid_login">Используйте назначенный логин вместо e-mail</string> <string name="login_invalid_login">Используйте назначенный логин вместо e-mail</string>
<string name="login_invalid_custom_email">Используйте назначенный логин или email в @%1$s</string> <string name="login_invalid_custom_email">Используйте назначенный логин или email в @%1$s</string>
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string> <string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string>
<string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string> <string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string>
<string name="login_incorrect_symbol">Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+</string> <string name="login_incorrect_symbol">Ученик не найден. Проверьте symbol и выбранный тип дненика UONET+</string>

View File

@ -56,6 +56,7 @@
<string name="login_invalid_email">Neplatný e-mail</string> <string name="login_invalid_email">Neplatný e-mail</string>
<string name="login_invalid_login">Namiesto e-mailu použite priradené prihlasovacie údaje</string> <string name="login_invalid_login">Namiesto e-mailu použite priradené prihlasovacie údaje</string>
<string name="login_invalid_custom_email">Použite priradené prihlasovacie alebo e-mail v @%1$s</string> <string name="login_invalid_custom_email">Použite priradené prihlasovacie alebo e-mail v @%1$s</string>
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu</string> <string name="login_invalid_symbol">Neplatný symbol. Pokiaľ ho nemôžete nájsť, kontaktujte školu</string>
<string name="login_invalid_symbol_definitely">Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu</string> <string name="login_invalid_symbol_definitely">Nevymýšľajte si! Pokiaľ symbol nemôžete nájsť, kontaktujte školu</string>
<string name="login_incorrect_symbol">Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+</string> <string name="login_incorrect_symbol">Žiak nebol nájdený. Skontrolujte správnosť symbolu a vybrané varianty denníka UONET+</string>

View File

@ -56,6 +56,7 @@
<string name="login_invalid_email">Недійсна адреса e-mail</string> <string name="login_invalid_email">Недійсна адреса e-mail</string>
<string name="login_invalid_login">Використовуйте призначений логін замість адреси e-mail</string> <string name="login_invalid_login">Використовуйте призначений логін замість адреси e-mail</string>
<string name="login_invalid_custom_email">Використовуйте призначений логін або адресу e-mail в @%1$s</string> <string name="login_invalid_custom_email">Використовуйте призначений логін або адресу e-mail в @%1$s</string>
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою</string> <string name="login_invalid_symbol">Некоректний символ. Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою</string>
<string name="login_invalid_symbol_definitely">Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою</string> <string name="login_invalid_symbol_definitely">Не вигадуйте! Якщо ви не можете знайти його, будь ласка, зв\'яжіться зі школою</string>
<string name="login_incorrect_symbol">Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+</string> <string name="login_incorrect_symbol">Студента не знайдено. Перевірте symbol та обраний тип щоденника UONET+</string>

View File

@ -61,6 +61,7 @@
<string name="login_invalid_email">Invalid email</string> <string name="login_invalid_email">Invalid email</string>
<string name="login_invalid_login">Use the assigned login instead of email</string> <string name="login_invalid_login">Use the assigned login instead of email</string>
<string name="login_invalid_custom_email">Use the assigned login or email in @%1$s</string> <string name="login_invalid_custom_email">Use the assigned login or email in @%1$s</string>
<string name="login_invalid_domain_suffix">Invalid domain suffix</string>
<string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string> <string name="login_invalid_symbol">Invalid symbol. If you cannot find it, please contact the school</string>
<string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string> <string name="login_invalid_symbol_definitely">Don\'t make this up! If you cannot find it, please contact the school</string>
<string name="login_incorrect_symbol">Student not found. Validate the symbol and the chosen variation of the UONET+ register</string> <string name="login_incorrect_symbol">Student not found. Validate the symbol and the chosen variation of the UONET+ register</string>

View File

@ -16,7 +16,7 @@ buildscript {
classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16" classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$kotlin_version-1.0.16"
classpath 'com.android.tools.build:gradle:8.2.2' classpath 'com.android.tools.build:gradle:8.2.2'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
classpath 'com.google.gms:google-services:4.4.0' classpath 'com.google.gms:google-services:4.4.1'
classpath 'com.huawei.agconnect:agcp:1.9.1.303' classpath 'com.huawei.agconnect:agcp:1.9.1.303'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
classpath "com.github.triplet.gradle:play-publisher:3.8.4" classpath "com.github.triplet.gradle:play-publisher:3.8.4"