diff --git a/example-app/src/main/java/com/hcaptcha/example/MainActivity.java b/example-app/src/main/java/com/hcaptcha/example/MainActivity.java
index 22ca750e..a0da5ac7 100644
--- a/example-app/src/main/java/com/hcaptcha/example/MainActivity.java
+++ b/example-app/src/main/java/com/hcaptcha/example/MainActivity.java
@@ -45,7 +45,8 @@
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
- private static final String SITEKEY = "10000000-ffff-ffff-ffff-000000000001";
+ private static final String SITEKEY_VISUAL = "00000000-0000-0000-0000-000000000000";
+ private static final String SITEKEY_PASSIVE = "10000000-ffff-ffff-ffff-000000000001";
private static final int MAX_AUDIT_LOG_LINES = 100;
private static final int TAB_CONFIGURATION = 0;
private static final int TAB_CUSTOM = 1;
@@ -54,6 +55,9 @@ public class MainActivity extends AppCompatActivity {
private RadioGroup modeGroup;
private RadioGroup sizeGroup;
+ private RadioGroup sitekeyGroup;
+ private TextInputLayout sitekeyInputLayout;
+ private TextInputEditText sitekeyInput;
private CheckBox loading;
private CheckBox disableHardwareAccel;
private CheckBox themeDark;
@@ -99,6 +103,9 @@ protected void onCreate(final Bundle savedInstanceState) {
modeGroup = findViewById(R.id.challenge_mode_group);
sizeGroup = findViewById(R.id.challenge_size_group);
+ sitekeyGroup = findViewById(R.id.sitekey_group);
+ sitekeyInputLayout = findViewById(R.id.sitekeyInputLayout);
+ sitekeyInput = findViewById(R.id.sitekeyInput);
loading = findViewById(R.id.loading);
disableHardwareAccel = findViewById(R.id.hwAccel);
themeDark = findViewById(R.id.themeDark);
@@ -140,6 +147,11 @@ protected void onCreate(final Bundle savedInstanceState) {
updateCustomHostUiState();
});
+ sitekeyGroup.setOnCheckedChangeListener((group, checkedId) -> {
+ sitekeyInputLayout.setVisibility(checkedId == R.id.sitekey_custom ? View.VISIBLE : View.GONE);
+ addAuditLog("Sitekey switched to " + sitekeyLabel(checkedId));
+ });
+
setPhoneModeUi(phoneModeSwitch.isChecked());
phoneModeSwitch.setOnCheckedChangeListener((button, checked) -> {
setPhoneModeUi(checked);
@@ -316,6 +328,28 @@ private HCaptchaSize getSelectedSize() {
return HCaptchaSize.NORMAL;
}
+ private String getSelectedSitekey() {
+ final int checkedId = sitekeyGroup.getCheckedRadioButtonId();
+ if (checkedId == R.id.sitekey_passive) {
+ return SITEKEY_PASSIVE;
+ }
+ if (checkedId == R.id.sitekey_custom) {
+ final String custom = sitekeyInput.getText() != null ? sitekeyInput.getText().toString().trim() : "";
+ return custom.isEmpty() ? SITEKEY_VISUAL : custom;
+ }
+ return SITEKEY_VISUAL;
+ }
+
+ private String sitekeyLabel(final int checkedId) {
+ if (checkedId == R.id.sitekey_passive) {
+ return "Passive";
+ }
+ if (checkedId == R.id.sitekey_custom) {
+ return "Custom";
+ }
+ return "Challenge";
+ }
+
private HCaptcha getConfiguredClient() {
final HCaptcha client = HCaptcha.getClient(this);
client.setEmbeddedContainer(selectedMode == HCaptchaRenderMode.EMBEDDED ? embeddedChallengeContainer : null);
@@ -328,7 +362,7 @@ private HCaptchaConfig getConfig() {
: getSelectedSize();
final boolean isDark = themeDark.isChecked();
return HCaptchaConfig.builder()
- .siteKey(SITEKEY)
+ .siteKey(getSelectedSitekey())
.size(size)
.renderMode(selectedMode)
.loading(selectedMode != HCaptchaRenderMode.EMBEDDED && loading.isChecked())
diff --git a/example-app/src/main/res/layout/activity_main.xml b/example-app/src/main/res/layout/activity_main.xml
index bbae90c6..46743a01 100644
--- a/example-app/src/main/res/layout/activity_main.xml
+++ b/example-app/src/main/res/layout/activity_main.xml
@@ -53,6 +53,62 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
+ android:text="@string/sitekey"
+ android:textColor="@color/hc_text_primary" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example-app/src/main/res/values/strings.xml b/example-app/src/main/res/values/strings.xml
index 6a72bd56..d4c987d7 100644
--- a/example-app/src/main/res/values/strings.xml
+++ b/example-app/src/main/res/values/strings.xml
@@ -30,6 +30,11 @@
Phone input
Country prefix (e.g. 44)
Full phone number (e.g. +44123456789)
+ Sitekey
+ Challenge
+ Passive
+ Custom
+ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Request data (rqdata)
Optional rqdata value
Custom render host showcase
diff --git a/example-compose-app/src/main/java/com/hcaptcha/example/compose/ComposeActivity.kt b/example-compose-app/src/main/java/com/hcaptcha/example/compose/ComposeActivity.kt
index 5036482e..5b26aa64 100644
--- a/example-compose-app/src/main/java/com/hcaptcha/example/compose/ComposeActivity.kt
+++ b/example-compose-app/src/main/java/com/hcaptcha/example/compose/ComposeActivity.kt
@@ -17,10 +17,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt
-import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Refresh
@@ -36,6 +36,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
+import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
@@ -55,6 +56,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -71,8 +73,14 @@ import java.util.Date
import java.util.Locale
class ComposeActivity : ComponentActivity() {
- private enum class ResultState { Idle, Verifying, Success, Error, Setup, Reset, Destroyed }
+ private enum class ResultState { Idle, Verifying, Success, Error, Reset, Destroyed }
private enum class TopTab { Configuration, CustomHost, Result, AuditLog }
+ private enum class SitekeyOption { Visual, Passive, Custom }
+
+ companion object {
+ private const val SITEKEY_VISUAL = "00000000-0000-0000-0000-000000000000"
+ private const val SITEKEY_PASSIVE = "10000000-ffff-ffff-ffff-000000000001"
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -102,6 +110,8 @@ class ComposeActivity : ComponentActivity() {
var selectedTab by remember { mutableStateOf(TopTab.Configuration) }
var selectedMode by remember { mutableStateOf(HCaptchaRenderMode.DIALOG) }
var selectedSize by remember { mutableStateOf(HCaptchaSize.NORMAL) }
+ var selectedSitekeyOption by remember { mutableStateOf(SitekeyOption.Passive) }
+ var customSitekey by remember { mutableStateOf("") }
var loadingEnabled by remember { mutableStateOf(true) }
var webDebugEnabled by remember { mutableStateOf(false) }
@@ -114,7 +124,6 @@ class ComposeActivity : ComponentActivity() {
val auditLog = remember { mutableStateListOf() }
var captchaVisible by remember { mutableStateOf(false) }
- var setupArmed by remember { mutableStateOf(false) }
var captchaRenderKey by remember { mutableIntStateOf(0) }
var customWaitingForOpen by remember { mutableStateOf(false) }
var customStatusMessage by remember { mutableStateOf(null) }
@@ -142,7 +151,6 @@ class ComposeActivity : ComponentActivity() {
fun resetCaptcha() {
captchaVisible = false
captchaRenderKey += 1
- setupArmed = false
customWaitingForOpen = false
customStatusMessage = null
customStatusIsError = false
@@ -150,6 +158,11 @@ class ComposeActivity : ComponentActivity() {
val loadingForConfig = if (selectedMode == HCaptchaRenderMode.EMBEDDED) false else loadingEnabled
val sizeForConfig = if (selectedMode == HCaptchaRenderMode.HEADLESS) HCaptchaSize.INVISIBLE else selectedSize
+ val activeSitekey = when (selectedSitekeyOption) {
+ SitekeyOption.Visual -> SITEKEY_VISUAL
+ SitekeyOption.Passive -> SITEKEY_PASSIVE
+ SitekeyOption.Custom -> customSitekey.ifBlank { SITEKEY_VISUAL }
+ }
val config = remember(
selectedMode,
@@ -157,10 +170,11 @@ class ComposeActivity : ComponentActivity() {
loadingForConfig,
disableHwAccel,
darkTheme,
+ activeSitekey,
captchaRenderKey
) {
HCaptchaConfig.builder()
- .siteKey("10000000-ffff-ffff-ffff-000000000001")
+ .siteKey(activeSitekey)
.size(sizeForConfig)
.renderMode(selectedMode)
.loading(loadingForConfig)
@@ -201,18 +215,6 @@ class ComposeActivity : ComponentActivity() {
label = { Text("Reset") },
colors = navItemColors
)
- NavigationBarItem(
- selected = false,
- onClick = {
- setupArmed = true
- resultState = ResultState.Setup
- resultMessage = "setup armed"
- addAudit("Setup armed")
- },
- icon = { Icon(Icons.Default.Build, contentDescription = null) },
- label = { Text("Setup") },
- colors = navItemColors
- )
NavigationBarItem(
selected = false,
onClick = {
@@ -226,7 +228,7 @@ class ComposeActivity : ComponentActivity() {
customStatusMessage = null
customStatusIsError = false
}
- if (!setupArmed) addAudit("Verify without setup") else addAudit("Verify")
+ addAudit("Verify")
},
icon = { Icon(Icons.Default.CheckCircle, contentDescription = null) },
label = { Text("Verify") },
@@ -310,6 +312,39 @@ class ComposeActivity : ComponentActivity() {
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text("Configuration", style = MaterialTheme.typography.titleMedium, color = hcTextPrimary)
+ Text("Sitekey", color = hcTextPrimary)
+ InlineRadioRow(
+ labels = listOf("Passive", "Challenge", "Custom"),
+ selectedIndex = when (selectedSitekeyOption) {
+ SitekeyOption.Passive -> 0
+ SitekeyOption.Visual -> 1
+ SitekeyOption.Custom -> 2
+ },
+ onSelected = {
+ selectedSitekeyOption = when (it) {
+ 1 -> SitekeyOption.Visual
+ 2 -> SitekeyOption.Custom
+ else -> SitekeyOption.Passive
+ }
+ val label = when (selectedSitekeyOption) {
+ SitekeyOption.Visual -> "Challenge"
+ SitekeyOption.Passive -> "Passive"
+ SitekeyOption.Custom -> "Custom"
+ }
+ addAudit("Sitekey switched to $label")
+ }
+ )
+ if (selectedSitekeyOption == SitekeyOption.Custom) {
+ OutlinedTextField(
+ value = customSitekey,
+ onValueChange = { customSitekey = it },
+ label = { Text("Custom sitekey") },
+ placeholder = { Text("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") },
+ singleLine = true,
+ modifier = Modifier.fillMaxWidth(),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii)
+ )
+ }
Text("Render mode", color = hcTextPrimary)
InlineRadioRow(
labels = listOf("Dialog", "Headless", "Embedded"),
@@ -379,7 +414,6 @@ class ComposeActivity : ComponentActivity() {
HCaptchaCompose(config = config) { response ->
when (response) {
is HCaptchaResponse.Success -> {
- setupArmed = false
captchaVisible = false
customWaitingForOpen = false
customStatusMessage = "Success. See Result tab for details."
@@ -391,7 +425,6 @@ class ComposeActivity : ComponentActivity() {
}
is HCaptchaResponse.Failure -> {
- setupArmed = false
captchaVisible = false
customWaitingForOpen = false
customStatusMessage = "Error. See Result tab for details."
@@ -534,7 +567,6 @@ class ComposeActivity : ComponentActivity() {
HCaptchaCompose(config = config) { response ->
when (response) {
is HCaptchaResponse.Success -> {
- setupArmed = false
captchaVisible = false
resultState = ResultState.Success
lastToken = response.token
@@ -543,7 +575,6 @@ class ComposeActivity : ComponentActivity() {
}
is HCaptchaResponse.Failure -> {
- setupArmed = false
captchaVisible = false
resultState = ResultState.Error
lastToken = null
diff --git a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java
index cfe91c0c..130b1c6a 100644
--- a/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java
+++ b/sdk/src/main/java/com/hcaptcha/sdk/HCaptchaDialogFragment.java
@@ -196,6 +196,7 @@ public View onCreateView(@Nullable LayoutInflater inflater,
public void onDestroy() {
HCaptchaLog.d("DialogFragment.onDestroy");
super.onDestroy();
+ readyForInteraction = false;
if (webViewHelper != null) {
webViewHelper.reset();
}
@@ -210,12 +211,15 @@ public void onStart() {
final Window window = dialog.getWindow();
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
defaultDimAmount = window.getAttributes().dimAmount;
- if (Boolean.FALSE.equals(webViewHelper.getConfig().getLoading())) {
- // Remove dialog shadow to appear completely invisible
+ if (!readyForInteraction && Boolean.FALSE.equals(webViewHelper.getConfig().getLoading())) {
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setDimAmount(0);
}
}
+ if (!readyForInteraction && webViewHelper != null) {
+ HCaptchaLog.d("DialogFragment.onStart: re-triggering onLoaded after reset");
+ onLoaded();
+ }
}
@Override