diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7923c09..1b115fe 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,6 +34,11 @@
android:name=".PaymentActivity"
android:exported="false" />
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java
index a129e28..da30b41 100644
--- a/app/src/main/java/com/example/bdkipoc/PaymentActivity.java
+++ b/app/src/main/java/com/example/bdkipoc/PaymentActivity.java
@@ -121,10 +121,14 @@ public class PaymentActivity extends AppCompatActivity {
Handler modalHandler = new Handler(Looper.getMainLooper());
paymentModal.setOnShowListener(dialog -> {
modalHandler.postDelayed(() -> {
- if (paymentModal.isShowing()) {
- dismissModal();
- // Simulate successful card processing
- processCardPayment();
+ if (paymentModal != null && paymentModal.isShowing()) {
+ // First dismiss modal, then navigate
+ paymentModal.dismiss();
+
+ // Add small delay to ensure modal is fully dismissed
+ animationHandler.postDelayed(() -> {
+ navigateToPinActivity();
+ }, 100);
}
}, 3000);
});
@@ -324,6 +328,9 @@ public class PaymentActivity extends AppCompatActivity {
}
private void showModalWithAnimation() {
+ // Add debug log
+ showToast("Showing card modal...");
+
paymentModal.show();
// Add slide-up animation
@@ -371,23 +378,47 @@ public class PaymentActivity extends AppCompatActivity {
// This method is no longer needed since modal auto-dismisses
}
+ private void navigateToPinActivity() {
+ String amount = currentAmount.toString();
+
+ // Add debug log
+ showToast("Navigating to PIN Activity...");
+
+ try {
+ long amountValue = Long.parseLong(amount);
+
+ // Launch PIN Activity with amount data
+ Intent intent = new Intent(this, PinActivity.class);
+ intent.putExtra(PinActivity.EXTRA_SOURCE_ACTIVITY, "PaymentActivity");
+ intent.putExtra(PinActivity.EXTRA_AMOUNT, String.valueOf(amountValue));
+ startActivityForResult(intent, 100);
+
+ } catch (NumberFormatException e) {
+ showToast("Format jumlah tidak valid: " + e.getMessage());
+ } catch (Exception e) {
+ showToast("Error navigating to PIN: " + e.getMessage());
+ }
+ }
+
private void processCardPayment() {
+ // This method is called after PIN verification is successful
+ // Now process the actual payment
String amount = currentAmount.toString();
try {
long amountValue = Long.parseLong(amount);
- // Show processing with card payment
- showToast("Memproses pembayaran dengan kartu...");
+ // Show processing message
+ showToast("PIN berhasil diverifikasi! Memproses pembayaran...");
- // Process payment
+ // Process the final payment
processPayment(amountValue);
} catch (NumberFormatException e) {
showToast("Format jumlah tidak valid");
}
}
-
+
private void processPayment(long amount) {
// Show loading state
confirmButton.setText("Memproses...");
@@ -398,12 +429,12 @@ public class PaymentActivity extends AppCompatActivity {
// Show success message
showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount)));
- // Reset state and go back
+ // Reset state and go back (this is final step after PIN verification)
resetPaymentState();
navigateBack();
}, 2000);
}
-
+
private void resetPaymentState() {
currentAmount = new StringBuilder();
updateAmountDisplay();
@@ -459,6 +490,20 @@ public class PaymentActivity extends AppCompatActivity {
}
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ // Handle result from PIN Activity
+ if (resultCode == RESULT_OK && data != null) {
+ boolean pinVerified = data.getBooleanExtra("pin_verified", false);
+ if (pinVerified) {
+ // PIN verification successful, process payment
+ processCardPayment();
+ }
+ }
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/app/src/main/java/com/example/bdkipoc/PinActivity.java b/app/src/main/java/com/example/bdkipoc/PinActivity.java
new file mode 100644
index 0000000..b2915d5
--- /dev/null
+++ b/app/src/main/java/com/example/bdkipoc/PinActivity.java
@@ -0,0 +1,408 @@
+package com.example.bdkipoc;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class PinActivity extends AppCompatActivity {
+
+ // Intent Extra Keys
+ public static final String EXTRA_TITLE = "extra_title";
+ public static final String EXTRA_SUBTITLE = "extra_subtitle";
+ public static final String EXTRA_AMOUNT = "extra_amount";
+ public static final String EXTRA_SOURCE_ACTIVITY = "extra_source_activity";
+
+ // Views
+ private EditText editTextPin;
+ private Button confirmButton;
+ private LinearLayout backNavigation;
+ private ImageView backArrow;
+ private TextView backText;
+
+ // Numpad buttons
+ private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
+ private ImageView btnDelete;
+
+ // Data
+ private StringBuilder currentPin = new StringBuilder();
+ private static final int MAX_PIN_LENGTH = 6;
+ private static final int MIN_PIN_LENGTH = 4;
+
+ // Extra data from intent
+ private String sourceActivity;
+ private String amount;
+
+ // Animation
+ private Handler animationHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Set status bar color programmatically
+ setStatusBarColor();
+
+ setContentView(R.layout.activity_pin);
+
+ // Get intent extras
+ getIntentExtras();
+
+ initializeViews();
+ setupClickListeners();
+ setupInitialStates();
+ }
+
+ private void setStatusBarColor() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Window window = getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+ window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
+
+ // Make status bar icons white (for dark red background)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ View decorView = window.getDecorView();
+ decorView.setSystemUiVisibility(0); // Clear light status bar flag
+ }
+ }
+ }
+
+ private void getIntentExtras() {
+ Intent intent = getIntent();
+ if (intent != null) {
+ sourceActivity = intent.getStringExtra(EXTRA_SOURCE_ACTIVITY);
+ amount = intent.getStringExtra(EXTRA_AMOUNT);
+ }
+ }
+
+ private void initializeViews() {
+ // Main views
+ editTextPin = findViewById(R.id.editTextPin);
+ confirmButton = findViewById(R.id.confirmButton);
+ backNavigation = findViewById(R.id.back_navigation);
+ backArrow = findViewById(R.id.backArrow);
+ backText = findViewById(R.id.back_text);
+
+ // Numpad buttons
+ btn1 = findViewById(R.id.btn1);
+ btn2 = findViewById(R.id.btn2);
+ btn3 = findViewById(R.id.btn3);
+ btn4 = findViewById(R.id.btn4);
+ btn5 = findViewById(R.id.btn5);
+ btn6 = findViewById(R.id.btn6);
+ btn7 = findViewById(R.id.btn7);
+ btn8 = findViewById(R.id.btn8);
+ btn9 = findViewById(R.id.btn9);
+ btn0 = findViewById(R.id.btn0);
+ btn000 = findViewById(R.id.btn000);
+ btnDelete = findViewById(R.id.btnDelete);
+ }
+
+ private void setupClickListeners() {
+ // Back navigation - entire LinearLayout is clickable
+ backNavigation.setOnClickListener(v -> {
+ addClickAnimation(v);
+ navigateBack();
+ });
+
+ // Individual back arrow (for additional touch area)
+ backArrow.setOnClickListener(v -> {
+ addClickAnimation(v);
+ navigateBack();
+ });
+
+ // Back text (also clickable for back navigation)
+ backText.setOnClickListener(v -> {
+ addClickAnimation(v);
+ navigateBack();
+ });
+
+ // Numpad listeners
+ btn1.setOnClickListener(v -> handleNumpadClick(v, "1"));
+ btn2.setOnClickListener(v -> handleNumpadClick(v, "2"));
+ btn3.setOnClickListener(v -> handleNumpadClick(v, "3"));
+ btn4.setOnClickListener(v -> handleNumpadClick(v, "4"));
+ btn5.setOnClickListener(v -> handleNumpadClick(v, "5"));
+ btn6.setOnClickListener(v -> handleNumpadClick(v, "6"));
+ btn7.setOnClickListener(v -> handleNumpadClick(v, "7"));
+ btn8.setOnClickListener(v -> handleNumpadClick(v, "8"));
+ btn9.setOnClickListener(v -> handleNumpadClick(v, "9"));
+ btn0.setOnClickListener(v -> handleNumpadClick(v, "0"));
+ btn000.setOnClickListener(v -> handleNumpadClick(v, "000"));
+
+ // Delete button
+ btnDelete.setOnClickListener(v -> {
+ addClickAnimation(v);
+ deleteLastDigit();
+ });
+
+ // Confirm button
+ confirmButton.setOnClickListener(v -> {
+ if (confirmButton.isEnabled()) {
+ addButtonClickAnimation(v);
+ handleConfirmPin();
+ }
+ });
+ }
+
+ private void navigateBack() {
+ finish();
+ }
+
+ private void handleNumpadClick(View view, String digit) {
+ addClickAnimation(view);
+ addDigit(digit);
+ }
+
+ private void setupInitialStates() {
+ // Set initial PIN display
+ editTextPin.setText("");
+
+ // Set initial button state
+ updateButtonState();
+
+ // Disable EditText input (only numpad input allowed)
+ editTextPin.setFocusable(false);
+ editTextPin.setClickable(false);
+ editTextPin.setCursorVisible(false);
+ }
+
+ private void addDigit(String digit) {
+ // Validate input length
+ if (currentPin.length() >= MAX_PIN_LENGTH) {
+ showToast("Maksimal " + MAX_PIN_LENGTH + " digit");
+ return;
+ }
+
+ // Handle special case for 000
+ if (digit.equals("000")) {
+ if (currentPin.length() + 3 <= MAX_PIN_LENGTH) {
+ currentPin.append("000");
+ } else {
+ int remainingLength = MAX_PIN_LENGTH - currentPin.length();
+ if (remainingLength > 0) {
+ currentPin.append("0".repeat(remainingLength));
+ }
+ }
+ } else {
+ currentPin.append(digit);
+ }
+
+ updatePinDisplay();
+ updateButtonState();
+ addInputFeedback();
+ }
+
+ private void deleteLastDigit() {
+ if (currentPin.length() > 0) {
+ String current = currentPin.toString();
+
+ // If current ends with 000, remove all three digits
+ if (current.endsWith("000") && current.length() >= 3) {
+ currentPin.delete(currentPin.length() - 3, currentPin.length());
+ } else {
+ currentPin.deleteCharAt(currentPin.length() - 1);
+ }
+
+ updatePinDisplay();
+ updateButtonState();
+ addDeleteFeedback();
+ }
+ }
+
+ private void updatePinDisplay() {
+ String pin = currentPin.toString();
+
+ if (pin.isEmpty()) {
+ editTextPin.setText("");
+ } else {
+ // Convert digits to asterisks for security
+ String maskedPin = "*".repeat(pin.length());
+ editTextPin.setText(maskedPin);
+ }
+ }
+
+ private void updateButtonState() {
+ boolean hasValidPin = currentPin.length() >= MIN_PIN_LENGTH;
+
+ confirmButton.setEnabled(hasValidPin);
+
+ if (hasValidPin) {
+ // Active state
+ confirmButton.setBackgroundResource(R.drawable.button_active_background);
+ confirmButton.setTextColor(Color.WHITE);
+ confirmButton.setAlpha(1.0f);
+ } else {
+ // Inactive state
+ confirmButton.setBackgroundResource(R.drawable.button_inactive_background);
+ confirmButton.setTextColor(Color.parseColor("#999999"));
+ confirmButton.setAlpha(0.6f);
+ }
+ }
+
+ private void handleConfirmPin() {
+ String pin = currentPin.toString();
+
+ if (TextUtils.isEmpty(pin)) {
+ showToast("Masukkan PIN");
+ return;
+ }
+
+ if (pin.length() < MIN_PIN_LENGTH) {
+ showToast("PIN minimal " + MIN_PIN_LENGTH + " digit");
+ return;
+ }
+
+ // Process PIN verification
+ verifyPin(pin);
+ }
+
+ private void verifyPin(String pin) {
+ // Show loading state
+ confirmButton.setText("Memverifikasi...");
+ confirmButton.setEnabled(false);
+
+ // Simulate PIN verification
+ animationHandler.postDelayed(() -> {
+ // For demo purposes, accept any PIN with length >= 4
+ // In real implementation, this would call backend API
+
+ if (isValidPin(pin)) {
+ showToast("PIN berhasil diverifikasi!");
+ handleSuccessfulVerification();
+ } else {
+ showToast("PIN tidak valid. Silakan coba lagi.");
+ resetPinState();
+ }
+ }, 2000);
+ }
+
+ private boolean isValidPin(String pin) {
+ // Demo validation - in real app, this would validate against backend
+ // For now, reject simple patterns like "1111", "1234", etc.
+ return !pin.equals("1111") &&
+ !pin.equals("1234") &&
+ !pin.equals("0000") &&
+ pin.length() >= MIN_PIN_LENGTH;
+ }
+
+ private void handleSuccessfulVerification() {
+ // Return result to calling activity
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra("pin_verified", true);
+ resultIntent.putExtra("pin_length", currentPin.length());
+
+ if (!TextUtils.isEmpty(amount)) {
+ resultIntent.putExtra(EXTRA_AMOUNT, amount);
+ }
+
+ setResult(RESULT_OK, resultIntent);
+
+ // Navigate back or to next screen based on source activity
+ if ("PaymentActivity".equals(sourceActivity)) {
+ // Could navigate to payment success screen
+ showToast("Pembayaran berhasil diproses!");
+ }
+
+ finish();
+ }
+
+ private void resetPinState() {
+ currentPin = new StringBuilder();
+ updatePinDisplay();
+ updateButtonState();
+ confirmButton.setText("Konfirmasi");
+ confirmButton.setEnabled(false);
+ }
+
+ // Animation methods
+ private void addClickAnimation(View view) {
+ ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
+ ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(scaleX, scaleY);
+ animatorSet.setDuration(150);
+ animatorSet.start();
+ }
+
+ private void addButtonClickAnimation(View view) {
+ ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f);
+ ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f);
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(scaleX, scaleY);
+ animatorSet.setDuration(200);
+ animatorSet.start();
+ }
+
+ private void addInputFeedback() {
+ ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextPin, "alpha", 0.7f, 1f);
+ fadeIn.setDuration(200);
+ fadeIn.start();
+ }
+
+ private void addDeleteFeedback() {
+ ObjectAnimator shake = ObjectAnimator.ofFloat(editTextPin, "translationX", 0f, -10f, 10f, 0f);
+ shake.setDuration(300);
+ shake.start();
+ }
+
+ // Utility methods
+ private void showToast(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onBackPressed() {
+ navigateBack();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (animationHandler != null) {
+ animationHandler.removeCallbacksAndMessages(null);
+ }
+ }
+
+ // Public methods for testing
+ public String getCurrentPin() {
+ return currentPin.toString();
+ }
+
+ public boolean isConfirmButtonEnabled() {
+ return confirmButton.isEnabled();
+ }
+
+ // Static helper method to launch PinActivity
+ public static void launch(android.content.Context context, String sourceActivity, String amount) {
+ Intent intent = new Intent(context, PinActivity.class);
+ intent.putExtra(EXTRA_SOURCE_ACTIVITY, sourceActivity);
+ if (!TextUtils.isEmpty(amount)) {
+ intent.putExtra(EXTRA_AMOUNT, amount);
+ }
+
+ // Launch for result if context is an Activity
+ if (context instanceof AppCompatActivity) {
+ ((AppCompatActivity) context).startActivityForResult(intent, 100);
+ } else {
+ context.startActivity(intent);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_pin.xml b/app/src/main/res/layout/activity_pin.xml
new file mode 100644
index 0000000..1f3300d
--- /dev/null
+++ b/app/src/main/res/layout/activity_pin.xml
@@ -0,0 +1,243 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file