diff --git a/app/build.gradle b/app/build.gradle
index a3e3b940..ab4e8392 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,6 +27,11 @@ android {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
+ resValue 'string', 'asset_statements', """
+ [{
+ "include": "${getProperty('oneTapHost')}/.well-known/assetlinks.json"
+ }]
+ """
}
buildTypes {
@@ -34,11 +39,13 @@ android {
signingConfig signingConfigs.debug
minifyEnabled true
buildConfigField "String", "BASE_URL", "\"https://cataas.com/api/\""
+ buildConfigField "String", "ONE_TAP_URL", "\"${getProperty('oneTapHost')}\""
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
buildConfigField "String", "BASE_URL", "\"https://cataas.com/api/\""
+ buildConfigField "String", "ONE_TAP_URL", "\"${getProperty('oneTapHost')}\""
}
}
compileOptions.coreLibraryDesugaringEnabled = true
@@ -72,7 +79,8 @@ ktlint {
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.8")
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutine_version"
implementation "androidx.navigation:navigation-runtime-ktx:2.5.1"
implementation "androidx.navigation:navigation-compose:2.5.1"
@@ -161,4 +169,5 @@ dependencies {
implementation "androidx.health:health-connect-client:1.0.0-alpha03"
implementation "com.google.android.gms:play-services-wallet:19.1.0"
implementation "com.google.android.gms:play-services-pay:16.0.3"
+ implementation 'com.google.android.gms:play-services-auth:20.3.0'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fbf77697..0bb32e5b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -24,6 +24,11 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/full_backup_content"
tools:targetApi="32">
+
+
+
+
+ @PUT("${BuildConfig.ONE_TAP_URL}/auth/oneTapUpdateUser")
+ suspend fun updateUser(@Body user: OneTapUser): Response
+
+ @DELETE("${BuildConfig.ONE_TAP_URL}/auth/oneTapDeleteUser")
+ suspend fun deleteUser(@Query("userId") userId: String)
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/CreateUserScreen.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/CreateUserScreen.kt
new file mode 100644
index 00000000..b3e994d2
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/CreateUserScreen.kt
@@ -0,0 +1,81 @@
+package com.skyyo.samples.features.oneTap
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.material.TextField
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.flowWithLifecycle
+import com.skyyo.samples.extensions.toast
+import kotlinx.coroutines.flow.receiveAsFlow
+
+@Composable
+fun CreateUserScreen(viewModel: CreateUserViewModel = hiltViewModel()) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val events = remember {
+ viewModel.events.receiveAsFlow().flowWithLifecycle(lifecycleOwner.lifecycle)
+ }
+
+ LaunchedEffect(Unit) {
+ events.collect { event ->
+ when (event) {
+ is UserEvent.ShowToast -> context.toast(event.message)
+ }
+ }
+ }
+
+ val user by viewModel.user.collectAsState()
+ Column(
+ modifier = Modifier.systemBarsPadding(),
+ verticalArrangement = remember { Arrangement.spacedBy(20.dp) }
+ ) {
+ LabeledTextField(label = "User name: ", text = user.name)
+ LabeledTextField(label = "User surname: ", text = user.surname)
+ LabeledTextField(
+ label = "User phone: ",
+ text = user.phone,
+ onTextChanged = viewModel::setPhone
+ )
+ Button(onClick = viewModel::applyUpdate) {
+ Text(text = "apply")
+ }
+ }
+}
+
+@Composable
+private fun LabeledTextField(label: String, text: String, onTextChanged: (String) -> Unit = {}) {
+ val labelStyle = remember {
+ TextStyle(
+ fontSize = 12.sp,
+ color = Color.DarkGray,
+ fontWeight = FontWeight.W300
+ )
+ }
+ val textStyle = remember {
+ TextStyle(
+ fontSize = 16.sp,
+ color = Color.Black,
+ fontWeight = FontWeight.W600
+ )
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(text = label, style = labelStyle)
+ TextField(
+ modifier = Modifier.padding(start = 10.dp),
+ value = text,
+ textStyle = textStyle,
+ onValueChange = onTextChanged
+ )
+ }
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/CreateUserViewModel.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/CreateUserViewModel.kt
new file mode 100644
index 00000000..28c7f8c4
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/CreateUserViewModel.kt
@@ -0,0 +1,56 @@
+package com.skyyo.samples.features.oneTap
+
+import androidx.core.os.bundleOf
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.skyyo.samples.application.CODE_200
+import com.skyyo.samples.application.Destination
+import com.skyyo.samples.application.models.OneTapUser
+import com.skyyo.samples.application.network.calls.OneTapCalls
+import com.skyyo.samples.extensions.navigateWithObject
+import com.skyyo.samples.extensions.tryOrNull
+import com.skyyo.samples.utils.NavigationDispatcher
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+const val CREATE_USER_KEY = "user"
+
+@HiltViewModel
+class CreateUserViewModel @Inject constructor(
+ private val handle: SavedStateHandle,
+ private val oneTapCalls: OneTapCalls,
+ private val navigationDispatcher: NavigationDispatcher
+) : ViewModel() {
+ val events = Channel(Channel.UNLIMITED)
+ val user = handle.getStateFlow(CREATE_USER_KEY, OneTapUser.empty)
+
+ fun setPhone(phone: String) {
+ handle[CREATE_USER_KEY] = user.value.copy(phone = phone)
+ }
+
+ fun applyUpdate() = viewModelScope.launch(Dispatchers.IO) {
+ val response = tryOrNull { oneTapCalls.updateUser(user.value) }
+ when {
+ response?.code() == CODE_200 -> {
+ navigationDispatcher.emit {
+ it.popBackStack()
+ it.navigateWithObject(
+ route = Destination.OneTapAuthorised.route,
+ arguments = bundleOf(USER_KEY to user.value)
+ )
+ }
+ }
+ else -> {
+ val message = when (response) {
+ null -> "No internet"
+ else -> "Update failed with code: ${response.code()}"
+ }
+ events.send(UserEvent.ShowToast(message))
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapEvent.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapEvent.kt
new file mode 100644
index 00000000..03da0d66
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapEvent.kt
@@ -0,0 +1,5 @@
+package com.skyyo.samples.features.oneTap
+
+sealed class OneTapEvent {
+ class ShowToast(val message: String) : OneTapEvent()
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapScreen.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapScreen.kt
new file mode 100644
index 00000000..b9042984
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapScreen.kt
@@ -0,0 +1,65 @@
+package com.skyyo.samples.features.oneTap
+
+import android.app.Activity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.IntentSenderRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.flowWithLifecycle
+import com.google.android.gms.auth.api.identity.Identity
+import com.skyyo.samples.extensions.toast
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.tasks.await
+
+@Composable
+fun OneTapScreen(viewModel: OneTapViewModel = hiltViewModel()) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val events = remember {
+ viewModel.events.receiveAsFlow().flowWithLifecycle(lifecycleOwner.lifecycle)
+ }
+
+ LaunchedEffect(Unit) {
+ events.collect { event ->
+ when (event) {
+ is OneTapEvent.ShowToast -> context.toast(event.message)
+ }
+ }
+ }
+
+ val oneTapClient = remember { Identity.getSignInClient(context) }
+ val oneTapLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.StartIntentSenderForResult()
+ ) {
+ when (it.resultCode) {
+ Activity.RESULT_OK -> viewModel.oneTapAccepted(oneTapClient, it.data)
+ Activity.RESULT_CANCELED -> viewModel.oneTapRejected()
+ }
+ }
+ val isOneTapUiRejected by viewModel.isOneTapUiRejected.collectAsState()
+
+ LaunchedEffect(isOneTapUiRejected) {
+ if (!isOneTapUiRejected) {
+ try {
+ val signInResult = oneTapClient.beginSignIn(viewModel.oneTapRequest).await()
+ oneTapLauncher.launch(
+ IntentSenderRequest.Builder(signInResult.pendingIntent.intentSender).build()
+ )
+ } catch (e: Exception) {
+ // No Google Accounts found. Just continue presenting the signed-out UI.
+ }
+ }
+ }
+
+ Column(Modifier.fillMaxSize().systemBarsPadding()) {
+ Text(text = "Unauthorised content")
+ }
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapViewModel.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapViewModel.kt
new file mode 100644
index 00000000..4a2f640d
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/OneTapViewModel.kt
@@ -0,0 +1,101 @@
+package com.skyyo.samples.features.oneTap
+
+import android.content.Intent
+import androidx.core.os.bundleOf
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.android.gms.auth.api.identity.BeginSignInRequest
+import com.google.android.gms.auth.api.identity.SignInClient
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.CommonStatusCodes
+import com.skyyo.samples.application.CODE_200
+import com.skyyo.samples.application.Destination
+import com.skyyo.samples.application.models.OneTapAuthoriseUserRequest
+import com.skyyo.samples.application.network.calls.OneTapCalls
+import com.skyyo.samples.extensions.navigateWithObject
+import com.skyyo.samples.extensions.tryOrNull
+import com.skyyo.samples.utils.NavigationDispatcher
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+// sample web app with one tap support can be found here - https://easy-foul-ketchup.glitch.me
+private const val WEB_CLIENT_ID = "991166295182-b0s899mj3ev0rbqmuugs77390jgidm5g.apps.googleusercontent.com"
+private const val IS_ONE_TAP_UI_REJECTED_KEY = "isOneTapUiRejected"
+
+@HiltViewModel
+class OneTapViewModel @Inject constructor(
+ private val handle: SavedStateHandle,
+ private val navigationDispatcher: NavigationDispatcher,
+ private val oneTapCalls: OneTapCalls
+) : ViewModel() {
+ val events = Channel(Channel.UNLIMITED)
+ val isOneTapUiRejected = handle.getStateFlow(IS_ONE_TAP_UI_REJECTED_KEY, false)
+
+ val oneTapRequest = BeginSignInRequest.builder()
+ .setGoogleIdTokenRequestOptions(
+ BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
+ .setSupported(true)
+ .setServerClientId(WEB_CLIENT_ID)
+ .setFilterByAuthorizedAccounts(true) // Only show accounts previously used to sign in.
+ .build()
+ )
+ .setAutoSelectEnabled(true) // Automatically sign in when exactly one credential is retrieved.
+ .build()
+
+ fun oneTapAccepted(client: SignInClient, data: Intent?) = viewModelScope.launch(Dispatchers.IO) {
+ try {
+ val credential = client.getSignInCredentialFromIntent(data)
+ val idToken = credential.googleIdToken!!
+ val response = tryOrNull { oneTapCalls.authorise(OneTapAuthoriseUserRequest(idToken)) }
+ when {
+ response?.code() == CODE_200 -> {
+ val user = response.body()!!
+ if (user.isCompleted) {
+ navigationDispatcher.emit {
+ it.popBackStack()
+ it.navigateWithObject(
+ route = Destination.OneTapAuthorised.route,
+ arguments = bundleOf(USER_KEY to user)
+ )
+ }
+ } else {
+ navigationDispatcher.emit {
+ it.popBackStack()
+ it.navigateWithObject(
+ route = Destination.OneTapSignUpFinish.route,
+ arguments = bundleOf(CREATE_USER_KEY to user)
+ )
+ }
+ }
+ }
+ else -> {
+ val message = when (response) {
+ null -> "No internet"
+ else -> "Update failed with code: ${response.code()}"
+ }
+ events.send(OneTapEvent.ShowToast(message))
+ }
+ }
+ } catch (e: ApiException) {
+ when (e.statusCode) {
+ CommonStatusCodes.CANCELED -> {
+ handle[IS_ONE_TAP_UI_REJECTED_KEY] = true
+ }
+ CommonStatusCodes.NETWORK_ERROR -> {
+ // One-tap encountered a network error, try again or just ignore.
+ }
+ else -> {
+ // Couldn't get credential from result.
+ }
+ }
+ }
+ }
+
+ fun oneTapRejected() {
+ events.trySend(OneTapEvent.ShowToast("one tap sign in rejected"))
+ }
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/UserEvent.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/UserEvent.kt
new file mode 100644
index 00000000..aa6c2713
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/UserEvent.kt
@@ -0,0 +1,5 @@
+package com.skyyo.samples.features.oneTap
+
+sealed class UserEvent {
+ class ShowToast(val message: String) : UserEvent()
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/UserScreen.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/UserScreen.kt
new file mode 100644
index 00000000..26e31045
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/UserScreen.kt
@@ -0,0 +1,78 @@
+package com.skyyo.samples.features.oneTap
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.flowWithLifecycle
+import com.google.android.gms.auth.api.identity.Identity
+import com.skyyo.samples.extensions.toast
+import kotlinx.coroutines.flow.receiveAsFlow
+
+@Composable
+fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val events = remember {
+ viewModel.events.receiveAsFlow().flowWithLifecycle(lifecycleOwner.lifecycle)
+ }
+
+ LaunchedEffect(Unit) {
+ events.collect { event ->
+ when (event) {
+ is UserEvent.ShowToast -> context.toast(event.message)
+ }
+ }
+ }
+
+ val user = viewModel.user
+ val client = remember { Identity.getSignInClient(context) }
+ Column(
+ modifier = Modifier.systemBarsPadding(),
+ verticalArrangement = remember { Arrangement.spacedBy(20.dp) }
+ ) {
+ LabeledText(label = "User name: ", text = user.name)
+ LabeledText(label = "User surname: ", text = user.surname)
+ LabeledText(label = "User phone: ", text = user.phone)
+ Button(onClick = { viewModel.signOut(client) }) {
+ Text(text = "sign out")
+ }
+ Button(onClick = { viewModel.signOut(client = client, deleteUser = true) }) {
+ Text(text = "sign out with backend cleaning up")
+ }
+ }
+}
+
+@Composable
+private fun LabeledText(label: String, text: String) {
+ val labelStyle = remember {
+ TextStyle(
+ fontSize = 12.sp,
+ color = Color.DarkGray,
+ fontWeight = FontWeight.W300
+ )
+ }
+ val textStyle = remember {
+ TextStyle(
+ fontSize = 16.sp,
+ color = Color.Black,
+ fontWeight = FontWeight.W600
+ )
+ }
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(text = label, style = labelStyle)
+ Text(modifier = Modifier.padding(start = 10.dp), text = text, style = textStyle)
+ }
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/oneTap/UserViewModel.kt b/app/src/main/java/com/skyyo/samples/features/oneTap/UserViewModel.kt
new file mode 100644
index 00000000..eeecbf49
--- /dev/null
+++ b/app/src/main/java/com/skyyo/samples/features/oneTap/UserViewModel.kt
@@ -0,0 +1,37 @@
+package com.skyyo.samples.features.oneTap
+
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.google.android.gms.auth.api.identity.SignInClient
+import com.skyyo.samples.application.models.OneTapUser
+import com.skyyo.samples.application.network.calls.OneTapCalls
+import com.skyyo.samples.utils.NavigationDispatcher
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.tasks.await
+import javax.inject.Inject
+
+const val USER_KEY = "user"
+
+@HiltViewModel
+class UserViewModel @Inject constructor(
+ handle: SavedStateHandle,
+ private val navigationDispatcher: NavigationDispatcher,
+ private val oneTapCalls: OneTapCalls
+) : ViewModel() {
+ val events = Channel(Channel.UNLIMITED)
+ val user: OneTapUser = handle[USER_KEY]!!
+
+ fun signOut(client: SignInClient, deleteUser: Boolean = false) = viewModelScope.launch(Dispatchers.IO) {
+ if (deleteUser) oneTapCalls.deleteUser(user.id)
+ try {
+ client.signOut().await()
+ } catch (e: Exception) {
+ events.send(UserEvent.ShowToast("sign out failed"))
+ }
+ navigationDispatcher.emit { it.popBackStack() }
+ }
+}
diff --git a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt
index 355f2ff1..2abc4e0c 100644
--- a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt
+++ b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerScreen.kt
@@ -74,6 +74,9 @@ fun SampleContainerScreen(viewModel: SampleContainerViewModel = hiltViewModel())
Button(modifier = Modifier.fillMaxWidth(), onClick = viewModel::goGooglePay) {
Text(text = "google pay")
}
+ Button(modifier = Modifier.fillMaxWidth(), onClick = viewModel::goOneTap) {
+ Text(text = "one tap")
+ }
}
}
diff --git a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt
index b8bfb049..5a88dd51 100644
--- a/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt
+++ b/app/src/main/java/com/skyyo/samples/features/sampleContainer/SampleContainerViewModel.kt
@@ -223,4 +223,8 @@ class SampleContainerViewModel @Inject constructor(
fun goImeAwareLazyColumn() = navigationDispatcher.emit {
it.navigate(Destination.ImeAwareLazyColumn.route)
}
+
+ fun goOneTap() = navigationDispatcher.emit {
+ it.navigate(Destination.OneTap.route)
+ }
}
diff --git a/build.gradle b/build.gradle
index 5a5f2f1b..a76ddd22 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ buildscript {
ext {
kotlin_version = '1.7.10'
compose_version = '1.3.0-beta01'
+ coroutine_version = '1.6.4'
lifecycle_version = '2.6.0-alpha01'
moshi_version = '1.13.0'
retrofit_version = '2.9.0'
diff --git a/gradle.properties b/gradle.properties
index 9cbc3f7e..34c8a77a 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,4 +2,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=false
android.enableResourceOptimizations=true
-kotlin.code.style=official
\ No newline at end of file
+kotlin.code.style=official
+oneTapHost=https://abrupt-tabby-medusaceratops.glitch.me
\ No newline at end of file