UI PIN
This commit is contained in:
parent
810964b4be
commit
f1228db89a
@ -34,6 +34,11 @@
|
|||||||
android:name=".PaymentActivity"
|
android:name=".PaymentActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<activity android:name=".QrisResultActivity" />
|
<activity android:name=".QrisResultActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".PinActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
|
android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -121,10 +121,14 @@ public class PaymentActivity extends AppCompatActivity {
|
|||||||
Handler modalHandler = new Handler(Looper.getMainLooper());
|
Handler modalHandler = new Handler(Looper.getMainLooper());
|
||||||
paymentModal.setOnShowListener(dialog -> {
|
paymentModal.setOnShowListener(dialog -> {
|
||||||
modalHandler.postDelayed(() -> {
|
modalHandler.postDelayed(() -> {
|
||||||
if (paymentModal.isShowing()) {
|
if (paymentModal != null && paymentModal.isShowing()) {
|
||||||
dismissModal();
|
// First dismiss modal, then navigate
|
||||||
// Simulate successful card processing
|
paymentModal.dismiss();
|
||||||
processCardPayment();
|
|
||||||
|
// Add small delay to ensure modal is fully dismissed
|
||||||
|
animationHandler.postDelayed(() -> {
|
||||||
|
navigateToPinActivity();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
});
|
});
|
||||||
@ -324,6 +328,9 @@ public class PaymentActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showModalWithAnimation() {
|
private void showModalWithAnimation() {
|
||||||
|
// Add debug log
|
||||||
|
showToast("Showing card modal...");
|
||||||
|
|
||||||
paymentModal.show();
|
paymentModal.show();
|
||||||
|
|
||||||
// Add slide-up animation
|
// Add slide-up animation
|
||||||
@ -371,16 +378,40 @@ public class PaymentActivity extends AppCompatActivity {
|
|||||||
// This method is no longer needed since modal auto-dismisses
|
// 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() {
|
private void processCardPayment() {
|
||||||
|
// This method is called after PIN verification is successful
|
||||||
|
// Now process the actual payment
|
||||||
String amount = currentAmount.toString();
|
String amount = currentAmount.toString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
long amountValue = Long.parseLong(amount);
|
long amountValue = Long.parseLong(amount);
|
||||||
|
|
||||||
// Show processing with card payment
|
// Show processing message
|
||||||
showToast("Memproses pembayaran dengan kartu...");
|
showToast("PIN berhasil diverifikasi! Memproses pembayaran...");
|
||||||
|
|
||||||
// Process payment
|
// Process the final payment
|
||||||
processPayment(amountValue);
|
processPayment(amountValue);
|
||||||
|
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@ -398,7 +429,7 @@ public class PaymentActivity extends AppCompatActivity {
|
|||||||
// Show success message
|
// Show success message
|
||||||
showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount)));
|
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();
|
resetPaymentState();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
@ -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
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
408
app/src/main/java/com/example/bdkipoc/PinActivity.java
Normal file
408
app/src/main/java/com/example/bdkipoc/PinActivity.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
243
app/src/main/res/layout/activity_pin.xml
Normal file
243
app/src/main/res/layout/activity_pin.xml
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:background="#F5F5F5">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#F5F5F5"
|
||||||
|
tools:context=".PinActivity">
|
||||||
|
|
||||||
|
<!-- Status Bar Area -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/status_bar_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:background="#E31937"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<!-- Header with Back Navigation -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/header_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="#E31937"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_bar_background">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/back_navigation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:background="?android:attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backArrow"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/ic_arrow_back"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
android:layout_marginEnd="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/back_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Kembali"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="16sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- PIN Card -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/pin_card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:cardBackgroundColor="#4A90E2"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/header_container">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="SILAKAN MASUKAN PIN"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<!-- PIN Input Display -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:layout_marginEnd="8dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/editTextPin"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_weight="3"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textColorHint="#CCFFFFFF"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:inputType="numberPassword"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="false"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:letterSpacing="0.5"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Numpad Grid -->
|
||||||
|
<GridLayout
|
||||||
|
android:id="@+id/numpad_grid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:columnCount="3"
|
||||||
|
android:rowCount="4"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/pin_card">
|
||||||
|
|
||||||
|
<!-- Row 1: 1, 2, 3 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn1"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="1"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn2"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="2"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn3"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="3"/>
|
||||||
|
|
||||||
|
<!-- Row 2: 4, 5, 6 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn4"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="4"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn5"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="5"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn6"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="6"/>
|
||||||
|
|
||||||
|
<!-- Row 3: 7, 8, 9 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn7"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="7"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn8"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="8"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn9"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="9"/>
|
||||||
|
|
||||||
|
<!-- Row 4: 000, 0, Delete -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn000"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="000"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn0"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="0"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_columnWeight="1"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/btnDelete"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@drawable/ic_backspace"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:tint="#666666"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</GridLayout>
|
||||||
|
|
||||||
|
<!-- Confirmation Button -->
|
||||||
|
<Button
|
||||||
|
android:id="@+id/confirmButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:text="Konfirmasi"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:background="@drawable/button_inactive_background"
|
||||||
|
android:enabled="false"
|
||||||
|
android:alpha="0.6"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
Loading…
x
Reference in New Issue
Block a user