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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +