diff --git a/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java b/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java index f9b74d6..7922332 100644 --- a/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java +++ b/app/src/main/java/com/example/bdkipoc/kredit/EmvTransactionActivity.java @@ -9,7 +9,11 @@ import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -42,20 +46,29 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * EmvTransactionActivity - Handle card reading and EMV processing + * EmvTransactionActivity - Handle card reading and EMV processing with Modal */ public class EmvTransactionActivity extends AppCompatActivity { private static final String TAG = "EmvTransaction"; - // UI Components - SIMPLIFIED + // UI Components - SIMPLIFIED + MODAL + private LinearLayout mainContent; + private FrameLayout modalOverlay; private TextView tvStatus; + private TextView modalText; + private ImageView modalIcon; private ProgressBar progressBar; private ImageView ivCardReader; + // Animation + private Animation fadeIn; + private Animation fadeOut; + // Transaction Data private String transactionAmount; private boolean isEMVMode; private boolean isProcessing = false; + private boolean isModalShowing = false; // EMV Components private EMVOptV2 mEMVOptV2; @@ -85,6 +98,11 @@ public class EmvTransactionActivity extends AppCompatActivity { private static final int PIN_CLICK_CONFIRM = 52; private static final int PIN_CLICK_CANCEL = 53; private static final int PIN_ERROR = 54; + + // Modal Display Messages + private static final int SHOW_MODAL_SCAN_CARD = 101; + private static final int SHOW_MODAL_PROCESSING = 102; + private static final int HIDE_MODAL = 103; private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override @@ -110,10 +128,12 @@ public class EmvTransactionActivity extends AppCompatActivity { case EMV_SHOW_PIN_PAD: android.util.Log.d(TAG, "Initializing PIN pad..."); + hideModal(); initPinPad(); break; case EMV_ONLINE_PROCESS: + showModalProcessing("Memproses Otorisasi Online..."); mockOnlineProcess(); break; @@ -149,6 +169,7 @@ public class EmvTransactionActivity extends AppCompatActivity { Log.e(TAG, "EMV Transaction failed: " + errorDesc + " (Code: " + errorCode + ")"); + hideModal(); if (errorCode == -50009) { // EMV process conflict - reset and retry Log.d(TAG, "EMV process conflict detected - resetting..."); @@ -163,8 +184,22 @@ public class EmvTransactionActivity extends AppCompatActivity { case EMV_TRANS_SUCCESS: // Navigate to results screen + hideModal(); navigateToResults(); break; + + case SHOW_MODAL_SCAN_CARD: + showModalScanCard(); + break; + + case SHOW_MODAL_PROCESSING: + String processingMsg = (String) msg.obj; + showModalProcessing(processingMsg); + break; + + case HIDE_MODAL: + hideModal(); + break; } } }; @@ -176,6 +211,7 @@ public class EmvTransactionActivity extends AppCompatActivity { getIntentData(); initViews(); + initAnimations(); initEMVComponents(); initEMVData(); @@ -208,12 +244,96 @@ public class EmvTransactionActivity extends AppCompatActivity { } // Initialize UI components + mainContent = findViewById(R.id.main_content); + modalOverlay = findViewById(R.id.modal_overlay); tvStatus = findViewById(R.id.tv_status); progressBar = findViewById(R.id.progress_bar); ivCardReader = findViewById(R.id.iv_card_reader); + modalText = findViewById(R.id.modal_text); + modalIcon = findViewById(R.id.modal_icon); // Set initial state updateStatusUI("Initializing scanner...", true); + + // Setup modal overlay click to close + modalOverlay.setOnClickListener(v -> { + // Optional: close modal when clicking outside + // hideModal(); + }); + } + + // ====== ANIMATION INITIALIZATION ====== + private void initAnimations() { + // Create fade animations programmatically since we want to avoid new resources + fadeIn = AnimationUtils.loadAnimation(this, android.R.anim.fade_in); + fadeOut = AnimationUtils.loadAnimation(this, android.R.anim.fade_out); + + // Set animation duration + fadeIn.setDuration(300); + fadeOut.setDuration(300); + } + + // ====== MODAL METHODS ====== + private void showModalScanCard() { + if (isModalShowing) return; + + runOnUiThread(() -> { + modalText.setText("Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"); + modalIcon.setImageResource(R.drawable.ic_card_insert); + + modalOverlay.setVisibility(View.VISIBLE); + modalOverlay.startAnimation(fadeIn); + + isModalShowing = true; + + Log.d(TAG, "Modal scan card shown"); + }); + } + + private void showModalProcessing(String message) { + if (!isModalShowing) { + runOnUiThread(() -> { + modalText.setText(message); + modalIcon.setImageResource(R.drawable.ic_card_insert); + + modalOverlay.setVisibility(View.VISIBLE); + modalOverlay.startAnimation(fadeIn); + + isModalShowing = true; + + Log.d(TAG, "Modal processing shown: " + message); + }); + } else { + // Just update text if modal already showing + runOnUiThread(() -> { + modalText.setText(message); + Log.d(TAG, "Modal text updated: " + message); + }); + } + } + + private void hideModal() { + if (!isModalShowing) return; + + runOnUiThread(() -> { + fadeOut.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + + @Override + public void onAnimationEnd(Animation animation) { + modalOverlay.setVisibility(View.GONE); + isModalShowing = false; + } + + @Override + public void onAnimationRepeat(Animation animation) {} + }); + + modalOverlay.startAnimation(fadeOut); + + Log.d(TAG, "Modal hidden"); + }); } // ====== NEW METHOD TO UPDATE STATUS UI ====== @@ -314,6 +434,9 @@ public class EmvTransactionActivity extends AppCompatActivity { updateStatusUI("Ready for card...\n\nPlease insert, swipe, or tap your card\n\nScanning will start automatically", true); + // Show modal for card scanning + mHandler.obtainMessage(SHOW_MODAL_SCAN_CARD).sendToTarget(); + // Start scanning automatically startCardCheck(); } @@ -396,6 +519,9 @@ public class EmvTransactionActivity extends AppCompatActivity { updateStatusUI("Scan error: " + errorMessage + "\n\nRetrying in 2 seconds...", false); showToast(errorMessage); + // Hide modal on error + hideModal(); + // Auto-restart scanning after error restartAutoScanning(); } @@ -460,39 +586,65 @@ public class EmvTransactionActivity extends AppCompatActivity { @Override public void findMagCard(Bundle info) throws RemoteException { android.util.Log.d(TAG, "Simple Mode: findMagCard callback triggered"); - runOnUiThread(() -> handleSimpleCardResult(info, "MAGNETIC")); + runOnUiThread(() -> { + showModalProcessing("Kartu Ditemukan - Memproses..."); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + handleSimpleCardResult(info, "MAGNETIC"); + }, 1500); + }); } @Override public void findICCard(String atr) throws RemoteException { Bundle info = new Bundle(); info.putString("atr", atr); - runOnUiThread(() -> handleSimpleCardResult(info, "IC")); + runOnUiThread(() -> { + showModalProcessing("Kartu IC Ditemukan - Memproses..."); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + handleSimpleCardResult(info, "IC"); + }, 1500); + }); } @Override public void findRFCard(String uuid) throws RemoteException { Bundle info = new Bundle(); info.putString("uuid", uuid); - runOnUiThread(() -> handleSimpleCardResult(info, "NFC")); + runOnUiThread(() -> { + showModalProcessing("Kartu NFC Ditemukan - Memproses..."); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + handleSimpleCardResult(info, "NFC"); + }, 1500); + }); } @Override public void onError(int code, String message) throws RemoteException { runOnUiThread(() -> { showToast("Card error: " + message); + hideModal(); stopCardCheck(); }); } @Override public void findICCardEx(Bundle info) throws RemoteException { - runOnUiThread(() -> handleSimpleCardResult(info, "IC")); + runOnUiThread(() -> { + showModalProcessing("Kartu IC Ditemukan - Memproses..."); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + handleSimpleCardResult(info, "IC"); + }, 1500); + }); } @Override public void findRFCardEx(Bundle info) throws RemoteException { - runOnUiThread(() -> handleSimpleCardResult(info, "NFC")); + runOnUiThread(() -> { + showModalProcessing("Kartu NFC Ditemukan - Memproses..."); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + handleSimpleCardResult(info, "NFC"); + }, 1500); + }); } @Override @@ -500,6 +652,7 @@ public class EmvTransactionActivity extends AppCompatActivity { runOnUiThread(() -> { String msg = info.getString("message", "Unknown error"); showToast("Card error: " + msg); + hideModal(); stopCardCheck(); }); } @@ -509,26 +662,38 @@ public class EmvTransactionActivity extends AppCompatActivity { private final CheckCardCallbackV2 mEMVCheckCardCallback = new CheckCardCallbackV2.Stub() { @Override public void findMagCard(Bundle info) throws RemoteException { - runOnUiThread(() -> handleSimpleCardResult(info, "MAGNETIC")); + runOnUiThread(() -> { + showModalProcessing("Kartu Magnetik Ditemukan - Memproses..."); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + handleSimpleCardResult(info, "MAGNETIC"); + }, 1500); + }); } @Override public void findICCard(String atr) throws RemoteException { MyApplication.app.basicOptV2.buzzerOnDevice(1, 2750, 200, 0); mCardType = AidlConstantsV2.CardType.IC.getValue(); - runOnUiThread(() -> startEMVTransactionProcess()); + runOnUiThread(() -> { + showModalProcessing("Kartu IC Ditemukan - Memulai EMV..."); + startEMVTransactionProcess(); + }); } @Override public void findRFCard(String uuid) throws RemoteException { mCardType = AidlConstantsV2.CardType.NFC.getValue(); - runOnUiThread(() -> startEMVTransactionProcess()); + runOnUiThread(() -> { + showModalProcessing("Kartu NFC Ditemukan - Memulai EMV..."); + startEMVTransactionProcess(); + }); } @Override public void onError(int code, String message) throws RemoteException { runOnUiThread(() -> { showToast("EMV Error: " + message); + hideModal(); stopCardCheck(); }); } @@ -536,13 +701,19 @@ public class EmvTransactionActivity extends AppCompatActivity { @Override public void findICCardEx(Bundle info) throws RemoteException { mCardType = AidlConstantsV2.CardType.IC.getValue(); - runOnUiThread(() -> startEMVTransactionProcess()); + runOnUiThread(() -> { + showModalProcessing("Kartu IC Ditemukan - Memulai EMV..."); + startEMVTransactionProcess(); + }); } @Override public void findRFCardEx(Bundle info) throws RemoteException { mCardType = AidlConstantsV2.CardType.NFC.getValue(); - runOnUiThread(() -> startEMVTransactionProcess()); + runOnUiThread(() -> { + showModalProcessing("Kartu NFC Ditemukan - Memulai EMV..."); + startEMVTransactionProcess(); + }); } @Override @@ -550,6 +721,7 @@ public class EmvTransactionActivity extends AppCompatActivity { runOnUiThread(() -> { String msg = info.getString("message", "Unknown error"); showToast("EMV Error: " + msg); + hideModal(); stopCardCheck(); }); } @@ -601,12 +773,14 @@ public class EmvTransactionActivity extends AppCompatActivity { bundle.putInt("cardType", mCardType); updateStatusUI("EMV processing started...\nReading card data...", true); + showModalProcessing("Memproses Data Kartu EMV..."); Log.d(TAG, "Starting transactProcessEx with reset EMV"); mEMVOptV2.transactProcessEx(bundle, mEMVListener); } catch (Exception e) { Log.e(TAG, "Error in delayed EMV start: " + e.getMessage(), e); + hideModal(); resetScanningState(); showToast("Error starting EMV: " + e.getMessage()); } @@ -615,6 +789,7 @@ public class EmvTransactionActivity extends AppCompatActivity { } catch (Exception e) { android.util.Log.e(TAG, "Error starting EMV transaction: " + e.getMessage()); e.printStackTrace(); + hideModal(); resetScanningState(); showToast("Error starting EMV transaction: " + e.getMessage()); } @@ -628,6 +803,7 @@ public class EmvTransactionActivity extends AppCompatActivity { android.util.Log.d(TAG, "onWaitAppSelect isFirstSelect:" + isFirstSelect); mProcessStep = EMV_APP_SELECT; String[] candidateNames = getCandidateNames(appNameList); + runOnUiThread(() -> hideModal()); mHandler.obtainMessage(EMV_APP_SELECT, candidateNames).sendToTarget(); } @@ -751,6 +927,7 @@ public class EmvTransactionActivity extends AppCompatActivity { String displayText = "Card Number Detected:\n" + maskCardNumber(mCardNo) + "\n\nConfirming automatically..."; updateStatusUI(displayText, true); + showModalProcessing("Mengkonfirmasi Nomor Kartu..."); // Auto-confirm after short delay new Handler(Looper.getMainLooper()).postDelayed(() -> { @@ -767,6 +944,7 @@ public class EmvTransactionActivity extends AppCompatActivity { String displayText = "Certificate Verification:\n" + mCertInfo + "\n\nProcessing automatically..."; updateStatusUI(displayText, true); + showModalProcessing("Memverifikasi Sertifikat..."); // Auto-confirm after short delay new Handler(Looper.getMainLooper()).postDelayed(() -> { @@ -793,7 +971,7 @@ public class EmvTransactionActivity extends AppCompatActivity { PinPadConfigV2 pinPadConfig = new PinPadConfigV2(); pinPadConfig.setPinPadType(0); pinPadConfig.setPinType(mPinType); - pinPadConfig.setOrderNumKey(true); + pinPadConfig.setOrderNumKey(true); // Set to true for normal order, false for random String panForPin = mCardNo.substring(mCardNo.length() - 13, mCardNo.length() - 1); byte[] panBytes = panForPin.getBytes("US-ASCII"); @@ -837,6 +1015,7 @@ public class EmvTransactionActivity extends AppCompatActivity { runOnUiThread(() -> { updateStatusUI("PIN confirmed, processing...", true); + showModalProcessing("PIN Dikonfirmasi - Memproses..."); }); if (pinBlock != null) { @@ -855,6 +1034,7 @@ public class EmvTransactionActivity extends AppCompatActivity { runOnUiThread(() -> { updateStatusUI("PIN input cancelled", false); + hideModal(); }); mHandler.obtainMessage(PIN_CLICK_CANCEL).sendToTarget(); @@ -867,6 +1047,7 @@ public class EmvTransactionActivity extends AppCompatActivity { runOnUiThread(() -> { updateStatusUI("PIN error: " + msg, false); + hideModal(); }); mHandler.obtainMessage(PIN_ERROR, code, code, msg).sendToTarget(); @@ -926,6 +1107,7 @@ public class EmvTransactionActivity extends AppCompatActivity { mProcessStep = 0; updateStatusUI("Ready for card...\n\nPlease insert, swipe, or tap your card", true); + hideModal(); } // ====== HELPER METHODS ====== @@ -1066,6 +1248,8 @@ public class EmvTransactionActivity extends AppCompatActivity { isProcessing = false; mProcessStep = 0; + hideModal(); + } catch (Exception e) { Log.e(TAG, "Error cleaning up on back press: " + e.getMessage()); } @@ -1092,6 +1276,8 @@ public class EmvTransactionActivity extends AppCompatActivity { mEMVOptV2.initEmvProcess(); } + hideModal(); + } catch (Exception e) { Log.e(TAG, "Error cleaning up EMV: " + e.getMessage()); } diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml new file mode 100644 index 0000000..102c5e7 --- /dev/null +++ b/app/src/main/res/anim/slide_down.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml new file mode 100644 index 0000000..bfb5615 --- /dev/null +++ b/app/src/main/res/anim/slide_up.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_emv_transaction.xml b/app/src/main/res/layout/activity_emv_transaction.xml index bd4f73b..b23ea86 100644 --- a/app/src/main/res/layout/activity_emv_transaction.xml +++ b/app/src/main/res/layout/activity_emv_transaction.xml @@ -1,35 +1,123 @@ - - - - - + + android:orientation="vertical"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -41,67 +129,32 @@ android:padding="32dp" android:gravity="center"> - - - - + - + - - - - - - + android:lineSpacingExtra="4dp" /> + - + - \ No newline at end of file + \ No newline at end of file