Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions compose-sdk/src/main/java/com/hcaptcha/sdk/HCaptchaCompose.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -17,14 +20,17 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogWindowProvider

@Composable
public fun HCaptchaCompose(config: HCaptchaConfig, onResult: (HCaptchaResponse) -> Unit) {
Expand All @@ -38,10 +44,30 @@ public fun HCaptchaCompose(config: HCaptchaConfig, onResult: (HCaptchaResponse)

val helper = remember { mutableStateOf<HCaptchaWebViewHelper?>(null) }
var verificationFinished by remember { mutableStateOf(false) }
var dimBehind by remember { mutableStateOf(config.loading != false) }
val verifier = remember {
HCaptchaComposeVerifier(config, { result ->
if (result is HCaptchaResponse.Success || result is HCaptchaResponse.Failure) {
verificationFinished = true
when (result) {
is HCaptchaResponse.Event -> {
when (result.event) {
HCaptchaEvent.Loaded -> {
if (config.size != HCaptchaSize.INVISIBLE) {
dimBehind = true
}
}

HCaptchaEvent.Opened -> {
if (config.size == HCaptchaSize.INVISIBLE) {
dimBehind = true
}
}
}
}

is HCaptchaResponse.Success,
is HCaptchaResponse.Failure -> {
verificationFinished = true
}
}
onResultState.value(result)
}, helper)
Expand Down Expand Up @@ -90,6 +116,8 @@ public fun HCaptchaCompose(config: HCaptchaConfig, onResult: (HCaptchaResponse)
Dialog(
onDismissRequest = onDismissRequest
) {
HCaptchaDialogWindowSetup(dimBehind = dimBehind)

Column(
modifier = Modifier
.testTag("dialogRoot")
Expand All @@ -110,3 +138,36 @@ public fun HCaptchaCompose(config: HCaptchaConfig, onResult: (HCaptchaResponse)
}
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@Composable
public fun HCaptchaDialogWindowSetup(dimBehind: Boolean) {
val view = LocalView.current
val window = (view.parent as? DialogWindowProvider)?.window ?: return
val defaultDimAmount = remember(window) { window.attributes.dimAmount }
val hadDimBehind = remember(window) {
window.attributes.flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND != 0
}

SideEffect {
setDialogDim(window, enabled = dimBehind, dimAmount = defaultDimAmount)
}

DisposableEffect(window) {
onDispose {
val restoreDimAmount = if (hadDimBehind) defaultDimAmount else 0f
setDialogDim(window, enabled = hadDimBehind, dimAmount = restoreDimAmount)
}
}
}

private fun setDialogDim(window: Window, enabled: Boolean, dimAmount: Float) {
val layoutParams = window.attributes
layoutParams.flags = if (enabled) {
layoutParams.flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND
} else {
layoutParams.flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv()
}
layoutParams.dimAmount = if (enabled) dimAmount else 0f
window.attributes = layoutParams
}
38 changes: 36 additions & 2 deletions example-app/src/main/java/com/hcaptcha/example/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -141,6 +148,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);
Expand Down Expand Up @@ -317,6 +329,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);
Expand All @@ -329,7 +363,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())
Expand Down
56 changes: 56 additions & 0 deletions example-app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />

<RadioGroup
android:id="@+id/sitekey_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">

<RadioButton
android:id="@+id/sitekey_passive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="@string/sitekey_passive" />

<RadioButton
android:id="@+id/sitekey_visual"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sitekey_visual" />

<RadioButton
android:id="@+id/sitekey_custom"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sitekey_custom" />
</RadioGroup>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/sitekeyInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:visibility="gone"
app:boxBackgroundMode="outline"
app:boxStrokeColor="@color/colorPrimary"
app:hintTextColor="@color/hc_text_secondary"
app:placeholderText="@string/sitekey_custom_hint">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/sitekeyInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textColor="@color/hc_text_primary" />
</com.google.android.material.textfield.TextInputLayout>

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/render_mode"
android:textColor="@color/hc_text_primary" />

Expand Down
5 changes: 5 additions & 0 deletions example-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
<string name="phone_input">Phone input</string>
<string name="phone_prefix_hint">Country prefix (e.g. 44)</string>
<string name="phone_number_hint">Full phone number (e.g. +44123456789)</string>
<string name="sitekey">Sitekey</string>
<string name="sitekey_visual">Challenge</string>
<string name="sitekey_passive">Passive</string>
<string name="sitekey_custom">Custom</string>
<string name="sitekey_custom_hint">xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</string>
<string name="rqdata_label">Request data (rqdata)</string>
<string name="rqdata_hint">Optional rqdata value</string>
<string name="embedded_showcase">Custom render host showcase</string>
Expand Down
Loading
Loading