update UI QRIS

This commit is contained in:
riz081 2025-06-02 13:16:46 +07:00
parent 1799e7eb0e
commit 74f95e0374
2 changed files with 653 additions and 441 deletions

View File

@ -1,24 +1,33 @@
package com.example.bdkipoc;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import org.json.JSONException;
import org.json.JSONObject;
@ -37,22 +46,34 @@ import java.util.UUID;
public class QrisActivity extends AppCompatActivity {
private ProgressBar progressBar;
private Button initiatePaymentButton;
private Button simulatePaymentButton;
private ImageView qrCodeImageView;
private TextView statusTextView;
// Views
private EditText editTextAmount;
private Button initiatePaymentButton;
private LinearLayout backNavigation;
private TextView toolbarTitle;
private TextView descriptionText;
// Numpad buttons
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
private TextView btnDelete; // Changed from ImageView to TextView
// Only needed views for loading state
private ProgressBar progressBar;
private TextView statusTextView;
private TextView referenceIdTextView;
private View paymentDetailsLayout;
private View paymentSuccessLayout;
private Button returnToMainButton;
private LinearLayout initialPaymentLayout;
// Data
private StringBuilder currentAmount = new StringBuilder();
private static final int MAX_AMOUNT_LENGTH = 12;
private String transactionId;
private String transactionUuid;
private String referenceId;
private int amount;
private JSONObject midtransResponse;
// Animation
private Handler animationHandler = new Handler(Looper.getMainLooper());
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
@ -62,80 +83,286 @@ public class QrisActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set status bar color programmatically
setStatusBarColor();
setContentView(R.layout.activity_qris);
// Set up the toolbar
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setTitle("QRIS Payment");
}
// Initialize views
progressBar = findViewById(R.id.progressBar);
initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
simulatePaymentButton = findViewById(R.id.simulatePaymentButton);
qrCodeImageView = findViewById(R.id.qrCodeImageView);
statusTextView = findViewById(R.id.statusTextView);
editTextAmount = findViewById(R.id.editTextAmount);
referenceIdTextView = findViewById(R.id.referenceIdTextView);
paymentDetailsLayout = findViewById(R.id.paymentDetailsLayout);
paymentSuccessLayout = findViewById(R.id.paymentSuccessLayout);
returnToMainButton = findViewById(R.id.returnToMainButton);
// Generate a random amount between 100,000 and 999,999
amount = new Random().nextInt(900000) + 100000;
// Format and display the amount
editTextAmount.setText("");
editTextAmount.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(editTextAmount, InputMethodManager.SHOW_IMPLICIT);
}
initializeViews();
setupClickListeners();
setupInitialStates();
// Generate reference ID
referenceId = "ref-" + generateRandomString(8);
referenceIdTextView.setText(referenceId);
// Set up click listeners
initiatePaymentButton.setOnClickListener(v -> createTransaction());
simulatePaymentButton.setOnClickListener(v -> simulateWebhook());
returnToMainButton.setOnClickListener(v -> finish());
// Initially hide the progress and status views
progressBar.setVisibility(View.GONE);
statusTextView.setVisibility(View.GONE);
initialPaymentLayout.setVisibility(View.VISIBLE);
}
// Initially hide the QR code and payment success views
paymentDetailsLayout.setVisibility(View.GONE);
paymentSuccessLayout.setVisibility(View.GONE);
simulatePaymentButton.setVisibility(View.GONE);
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 initializeViews() {
// New UI components (similar to PaymentActivity)
editTextAmount = findViewById(R.id.editTextAmount);
initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
backNavigation = findViewById(R.id.back_navigation);
toolbarTitle = findViewById(R.id.toolbarTitle);
descriptionText = findViewById(R.id.descriptionText);
// 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); // Now TextView
// Only needed views for loading state
progressBar = findViewById(R.id.progressBar);
statusTextView = findViewById(R.id.statusTextView);
referenceIdTextView = findViewById(R.id.referenceIdTextView);
// Main content layout (replaces initialPaymentLayout)
initialPaymentLayout = findViewById(R.id.mainContentLayout);
}
private void setupClickListeners() {
// Back navigation
backNavigation.setOnClickListener(v -> {
addClickAnimation(v);
navigateBack();
});
toolbarTitle.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();
});
// Original QRIS buttons - only initiate payment needed
initiatePaymentButton.setOnClickListener(v -> {
if (initiatePaymentButton.isEnabled()) {
addButtonClickAnimation(v);
createTransaction();
}
});
}
private void setupInitialStates() {
// Initially hide amount input and show description
editTextAmount.setVisibility(View.GONE);
descriptionText.setVisibility(View.VISIBLE);
// Set initial button state
updateButtonState();
// Disable EditText input (only numpad input allowed)
editTextAmount.setFocusable(false);
editTextAmount.setClickable(false);
editTextAmount.setCursorVisible(false);
}
private void navigateBack() {
finish();
}
private void handleNumpadClick(View view, String digit) {
addClickAnimation(view);
addDigit(digit);
}
private void addDigit(String digit) {
// Validate input length
if (currentAmount.length() >= MAX_AMOUNT_LENGTH) {
showToast("Maksimal " + MAX_AMOUNT_LENGTH + " digit");
return;
}
// Handle leading zeros
if (currentAmount.length() == 0) {
if (digit.equals("000")) {
// Don't allow 000 as first input
return;
}
currentAmount.append(digit);
} else if (currentAmount.length() == 1 && currentAmount.toString().equals("0")) {
if (!digit.equals("000")) {
// Replace single 0 with new digit
currentAmount = new StringBuilder(digit);
} else {
return;
}
} else {
currentAmount.append(digit);
}
updateAmountDisplay();
updateButtonState();
addInputFeedback();
}
private void deleteLastDigit() {
if (currentAmount.length() > 0) {
String current = currentAmount.toString();
// If current ends with 000, remove all three digits
if (current.endsWith("000") && current.length() >= 3) {
currentAmount.delete(currentAmount.length() - 3, currentAmount.length());
} else {
currentAmount.deleteCharAt(currentAmount.length() - 1);
}
updateAmountDisplay();
updateButtonState();
addDeleteFeedback();
}
}
private void updateAmountDisplay() {
String amount = currentAmount.toString();
if (amount.isEmpty() || amount.equals("0")) {
// Show description text, hide amount input
editTextAmount.setVisibility(View.GONE);
descriptionText.setVisibility(View.VISIBLE);
} else {
// Show amount input, hide description text
String formattedAmount = formatCurrency(amount);
editTextAmount.setText(formattedAmount);
editTextAmount.setVisibility(View.VISIBLE);
descriptionText.setVisibility(View.GONE);
}
}
private String formatCurrency(String amount) {
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
return "";
}
try {
long number = Long.parseLong(amount);
return String.format("%,d", number).replace(',', '.');
} catch (NumberFormatException e) {
return amount;
}
}
private void updateButtonState() {
boolean hasValidAmount = currentAmount.length() > 0 &&
!currentAmount.toString().equals("0") &&
!currentAmount.toString().isEmpty();
initiatePaymentButton.setEnabled(hasValidAmount);
// Use MaterialButton's backgroundTint property
com.google.android.material.button.MaterialButton materialButton =
(com.google.android.material.button.MaterialButton) initiatePaymentButton;
if (hasValidAmount) {
// Active state - red background like in the XML
materialButton.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#DE0701")));
materialButton.setTextColor(Color.WHITE);
materialButton.setAlpha(1.0f);
} else {
// Inactive state - gray background
materialButton.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#E8E8E8")));
materialButton.setTextColor(Color.parseColor("#999999"));
materialButton.setAlpha(0.6f);
}
}
private void createTransaction() {
progressBar.setVisibility(View.VISIBLE);
initiatePaymentButton.setEnabled(false);
statusTextView.setText("Creating transaction...");
String amountText = currentAmount.toString();
new CreateTransactionTask().execute();
if (TextUtils.isEmpty(amountText) || amountText.equals("0")) {
showToast("Masukkan jumlah pembayaran");
return;
}
try {
long amountValue = Long.parseLong(amountText);
// Validate minimum amount
if (amountValue < 1000) {
showToast("Minimal pembayaran Rp 1.000");
return;
}
// Validate maximum amount
if (amountValue > 999999999L) {
showToast("Maksimal pembayaran Rp 999.999.999");
return;
}
// Set amount for transaction
amount = (int) amountValue;
// Show loading state
progressBar.setVisibility(View.VISIBLE);
statusTextView.setText("Creating transaction...");
statusTextView.setVisibility(View.VISIBLE);
initiatePaymentButton.setEnabled(false);
new CreateTransactionTask().execute();
} catch (NumberFormatException e) {
showToast("Format jumlah tidak valid");
}
}
private void displayQrCode(String qrImageUrl) {
new DownloadImageTask().execute(qrImageUrl);
// This method is no longer needed since we navigate to QrisResultActivity
// Keeping it for compatibility but it won't be called
}
private void simulateWebhook() {
progressBar.setVisibility(View.VISIBLE);
simulatePaymentButton.setEnabled(false);
statusTextView.setText("Processing payment...");
new SimulateWebhookTask().execute();
// This method is no longer needed since we navigate to QrisResultActivity
// QrisResultActivity handles webhook simulation
}
private void showSuccessScreen() {
paymentDetailsLayout.setVisibility(View.GONE);
paymentSuccessLayout.setVisibility(View.VISIBLE);
statusTextView.setText("Payment successful!");
progressBar.setVisibility(View.GONE);
// This method is no longer needed since we navigate to QrisResultActivity
// QrisResultActivity handles success display
}
private String generateRandomString(int length) {
@ -174,13 +401,48 @@ public class QrisActivity extends AppCompatActivity {
}
}
// 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(editTextAmount, "alpha", 0.7f, 1f);
fadeIn.setDuration(200);
fadeIn.start();
}
private void addDeleteFeedback() {
ObjectAnimator shake = ObjectAnimator.ofFloat(editTextAmount, "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 boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
public void onBackPressed() {
// Simple back navigation
navigateBack();
}
private class CreateTransactionTask extends AsyncTask<Void, Void, Boolean> {
@ -199,19 +461,7 @@ public class QrisActivity extends AppCompatActivity {
payload.put("channel_code", "QRIS");
payload.put("reference_id", referenceId);
// Read amount from EditText and log it
String amountText = editTextAmount.getText().toString().trim();
Log.d("MidtransCharge", "Raw amount text: " + amountText);
try {
// Parse amount - expecting integer in lowest denomination (Indonesian Rupiah)
amount = Integer.parseInt(amountText);
Log.d("MidtransCharge", "Parsed amount: " + amount);
} catch (NumberFormatException e) {
Log.e("MidtransCharge", "Amount parsing error: " + e.getMessage());
errorMessage = "Invalid amount format";
return false;
}
Log.d("MidtransCharge", "Amount for transaction: " + amount);
payload.put("amount", amount);
payload.put("cashflow", "MONEY_IN");
@ -373,7 +623,7 @@ public class QrisActivity extends AppCompatActivity {
Log.d("MidtransCharge", "transaction_id: " + transactionId);
Log.d("MidtransCharge", "exactGrossAmount: " + exactGrossAmount);
// Instead of showing QR inline, launch QrisResultActivity
// Launch QrisResultActivity instead of showing QR inline
Intent intent = new Intent(QrisActivity.this, QrisResultActivity.class);
intent.putExtra("qrImageUrl", qrImageUrl);
intent.putExtra("amount", amount);
@ -387,171 +637,54 @@ public class QrisActivity extends AppCompatActivity {
try {
startActivity(intent);
// Finish this activity so user can't go back to input form
finish();
} catch (Exception e) {
Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e);
Toast.makeText(QrisActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show();
resetToInitialState();
}
return;
} catch (JSONException e) {
Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
Toast.makeText(QrisActivity.this, "Error processing QRIS response", Toast.LENGTH_LONG).show();
resetToInitialState();
}
} else {
String message = (errorMessage != null && !errorMessage.isEmpty()) ? errorMessage : "Unknown error occurred. Please check Logcat for details.";
Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
initiatePaymentButton.setEnabled(true);
resetToInitialState();
}
progressBar.setVisibility(View.GONE);
}
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected Bitmap doInBackground(String... urls) {
String urlDisplay = urls[0];
Bitmap bitmap = null;
try {
URL url = new URI(urlDisplay).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
java.io.InputStream input = connection.getInputStream();
bitmap = android.graphics.BitmapFactory.decodeStream(input);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
private void resetToInitialState() {
// Reset to show the input form again
progressBar.setVisibility(View.GONE);
statusTextView.setVisibility(View.GONE);
initiatePaymentButton.setEnabled(true);
updateButtonState();
}
// Remove DownloadImageTask - no longer needed
// Remove SimulateWebhookTask - no longer needed
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
qrCodeImageView.setImageBitmap(result);
} else {
Toast.makeText(QrisActivity.this, "Error loading QR code image", Toast.LENGTH_LONG).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (animationHandler != null) {
animationHandler.removeCallbacksAndMessages(null);
}
}
private class SimulateWebhookTask extends AsyncTask<Void, Void, Boolean> {
private String errorMessage;
@Override
protected Boolean doInBackground(Void... voids) {
try {
// Wait a moment to simulate real-world timing
Thread.sleep(1500);
// Get server key and prepare signature
String serverKey = getServerKey();
String grossAmount = String.valueOf(amount) + ".00";
String signatureKey = generateSignature(
transactionUuid,
"200",
grossAmount,
serverKey
);
// Create webhook payload
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", midtransResponse.getString("transaction_time"));
payload.put("transaction_status", "settlement");
payload.put("transaction_id", midtransResponse.getString("transaction_id"));
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", signatureKey);
payload.put("settlement_time", midtransResponse.getString("transaction_time"));
payload.put("payment_type", "qris");
payload.put("order_id", transactionUuid);
payload.put("merchant_id", midtransResponse.getString("merchant_id"));
payload.put("issuer", midtransResponse.getString("acquirer"));
payload.put("gross_amount", grossAmount);
payload.put("fraud_status", "accept");
payload.put("currency", "IDR");
payload.put("acquirer", midtransResponse.getString("acquirer"));
payload.put("shopeepay_reference_number", "");
payload.put("reference_id", referenceId);
// Call the webhook URL
URL url = new URI(WEBHOOK_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
if (responseCode == 200 || responseCode == 201) {
// Wait briefly to allow the backend to process
Thread.sleep(2000);
return checkTransactionStatus();
} else {
// Read error response
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
errorMessage = "Error simulating payment: " + response.toString();
return false;
}
} catch (Exception e) {
errorMessage = "Error: " + e.getMessage();
return false;
}
}
private boolean checkTransactionStatus() {
try {
// Check transaction status
URL url = new URI(BACKEND_BASE + "/transactions/" + transactionId).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
// Read the response
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// Parse the response
JSONObject jsonResponse = new JSONObject(response.toString());
JSONObject data = jsonResponse.getJSONObject("data");
String status = data.getString("status");
return status.equalsIgnoreCase("SUCCESS");
} else {
errorMessage = "Error checking transaction status. HTTP response code: " + responseCode;
return false;
}
} catch (Exception e) {
errorMessage = "Error checking transaction status: " + e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
showSuccessScreen();
} else {
String message = (errorMessage != null && !errorMessage.isEmpty()) ? errorMessage : "Unknown error occurred. Please check Logcat for details.";
Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
simulatePaymentButton.setEnabled(true);
}
progressBar.setVisibility(View.GONE);
}
// Public methods for testing
public String getCurrentAmount() {
return currentAmount.toString();
}
public boolean isInitiateButtonEnabled() {
return initiatePaymentButton.isEnabled();
}
}

View File

@ -1,227 +1,306 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#FFFFFF">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="QRIS Payment" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
android:background="#FFFFFF"
tools:context=".QrisActivity">
<LinearLayout
<!-- Red Status Bar -->
<View
android:id="@+id/red_status_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<ProgressBar
android:id="@+id/progressBar"
<!-- Red Background Header -->
<View
android:id="@+id/red_header_background"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="#E31937"
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
<!-- Back Navigation -->
<LinearLayout
android:id="@+id/back_navigation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginBottom="5dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
<!-- Back Arrow -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone" />
android:text=""
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginEnd="8dp" />
<!-- Title Text -->
<TextView
android:id="@+id/statusTextView"
android:layout_width="match_parent"
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="Ready to make a payment"
android:textSize="18sp" />
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="normal" />
<!-- Initial Payment Form -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/light_blue"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
<EditText
android:id="@+id/editTextAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Enter amount"
android:inputType="number"
android:maxLength="12"
android:importantForAutofill="no"
android:singleLine="true"
android:textColor="@color/primary_blue"
android:textSize="24sp"
android:textStyle="bold"
android:gravity="end" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Reference ID"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
<TextView
android:id="@+id/referenceIdTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="ref-abcd1234"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/initiatePaymentButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/primary_blue"
android:text="Start Payment" />
</LinearLayout>
<!-- QR Code and Payment Details -->
<LinearLayout
android:id="@+id/paymentDetailsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:id="@+id/qrCodeImageView"
android:layout_width="250dp"
android:layout_height="250dp"
android:contentDescription="QRIS Code"
android:scaleType="fitCenter" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="Scan with your banking app or e-wallet to pay"
android:textSize="14sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/simulatePaymentButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/accent_green"
android:text="Confirm Payment" />
</LinearLayout>
<!-- Payment Success -->
<LinearLayout
android:id="@+id/paymentSuccessLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/light_gray"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:contentDescription="Success Icon"
android:src="@android:drawable/ic_dialog_info"
android:tint="@color/accent_green" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="Payment Successful!"
android:textColor="@color/accent_green"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Your transaction has been completed successfully."
android:textSize="14sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/returnToMainButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:backgroundTint="@color/primary_blue"
android:text="Return to Main" />
</LinearLayout>
<!-- Hidden back arrow for Java compatibility -->
<ImageView
android:id="@+id/backArrow"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- Payment Card -->
<androidx.cardview.widget.CardView
android:id="@+id/paymentCard"
android:layout_width="match_parent"
android:layout_height="191dp"
android:layout_margin="16dp"
android:layout_marginTop="5dp"
app:cardBackgroundColor="#3498DB"
app:cardCornerRadius="12dp"
app:cardElevation="8dp"
app:layout_constraintTop_toBottomOf="@id/back_navigation">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<!-- Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TOTAL PEMBAYARAN"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="24dp"
android:gravity="center" />
<!-- RP Label -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="RP"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<!-- Amount Input Field (initially hidden) -->
<EditText
android:id="@+id/editTextAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:inputType="none"
android:focusable="false"
android:clickable="false"
android:cursorVisible="false"
android:text=""
android:gravity="start"
android:paddingBottom="4dp"
android:visibility="gone" />
<!-- Description Text (always visible initially) -->
<TextView
android:id="@+id/descriptionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Pastikan kembali nominal pembayaran pelanggan Anda"
android:textColor="@android:color/white"
android:textSize="12sp"
android:alpha="0.9"
android:layout_marginBottom="8dp" />
<!-- White Underline -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@android:color/white" />
<!-- Hidden Reference ID for internal use -->
<TextView
android:id="@+id/referenceIdTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ref-abcd1234"
android:textColor="@android:color/white"
android:textSize="12sp"
android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Main Content Layout -->
<LinearLayout
android:id="@+id/mainContentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/paymentCard"
app:layout_constraintBottom_toTopOf="@id/initiatePaymentButton">
<!-- Numpad -->
<GridLayout
android:id="@+id/numpad_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<!-- 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" />
<TextView
android:id="@+id/btnDelete"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_columnWeight="1"
android:layout_margin="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="center"
android:text="⌫"
android:textColor="@android:color/black"
android:textSize="28sp"
android:clickable="true"
android:focusable="true"
android:contentDescription="Delete" />
</GridLayout>
</LinearLayout>
<!-- Confirmation Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/initiatePaymentButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Konfirmasi"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
android:enabled="false"
app:backgroundTint="#DE0701"
app:cornerRadius="8dp"
app:rippleColor="#B3000000"
app:layout_constraintBottom_toBottomOf="parent" />
<!-- Progress Bar (For create transaction loading) -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Status Text (For create transaction status) -->
<TextView
android:id="@+id/statusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:gravity="center"
android:text="Ready to make a payment"
android:textSize="18sp"
android:textColor="@android:color/black"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/progressBar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>