From 74f95e0374c4b4bfefd6fd4155051ca5b888a75b Mon Sep 17 00:00:00 2001 From: riz081 Date: Mon, 2 Jun 2025 13:16:46 +0700 Subject: [PATCH] update UI QRIS --- .../com/example/bdkipoc/QrisActivity.java | 593 +++++++++++------- app/src/main/res/layout/activity_qris.xml | 501 ++++++++------- 2 files changed, 653 insertions(+), 441 deletions(-) diff --git a/app/src/main/java/com/example/bdkipoc/QrisActivity.java b/app/src/main/java/com/example/bdkipoc/QrisActivity.java index 66a996d..674b77b 100644 --- a/app/src/main/java/com/example/bdkipoc/QrisActivity.java +++ b/app/src/main/java/com/example/bdkipoc/QrisActivity.java @@ -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 { @@ -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 { - @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 { - 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(); } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_qris.xml b/app/src/main/res/layout/activity_qris.xml index dc89329..62f831b 100644 --- a/app/src/main/res/layout/activity_qris.xml +++ b/app/src/main/res/layout/activity_qris.xml @@ -1,227 +1,306 @@ - + android:layout_height="match_parent" + android:fillViewport="true" + android:overScrollMode="never" + android:scrollbars="none" + android:background="#FFFFFF"> - - - - - - - + android:background="#FFFFFF" + tools:context=".QrisActivity"> - + + android:layout_height="24dp" + android:background="#E31937" + app:layout_constraintTop_toTopOf="parent"/> - + + + + + + + + android:text="‹" + android:textColor="@android:color/white" + android:textSize="18sp" + android:textStyle="bold" + android:layout_marginEnd="8dp" /> + + android:text="Kembali" + android:textColor="@android:color/white" + android:textSize="12sp" + android:textStyle="normal" /> - - - - - - - - - - - - - - - - - -