UI Kartu Kredit
This commit is contained in:
parent
a07e7a99ac
commit
a7fa40d60a
@ -1,557 +1,372 @@
|
|||||||
package com.example.bdkipoc;
|
package com.example.bdkipoc;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.content.Intent;
|
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.Bundle;
|
||||||
import android.util.Log;
|
import android.os.Handler;
|
||||||
import android.view.MenuItem;
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class PaymentActivity extends AppCompatActivity {
|
public class PaymentActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ProgressBar progressBar;
|
// Views
|
||||||
private Button initiatePaymentButton;
|
|
||||||
private Button simulatePaymentButton;
|
|
||||||
private ImageView qrCodeImageView;
|
|
||||||
private TextView statusTextView;
|
|
||||||
private EditText editTextAmount;
|
private EditText editTextAmount;
|
||||||
private TextView referenceIdTextView;
|
private Button confirmButton;
|
||||||
private View paymentDetailsLayout;
|
private LinearLayout backNavigation;
|
||||||
private View paymentSuccessLayout;
|
private ImageView backArrow;
|
||||||
private Button returnToMainButton;
|
private TextView toolbarTitle;
|
||||||
|
|
||||||
private String transactionId;
|
// Numpad buttons
|
||||||
private String transactionUuid;
|
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
|
||||||
private String referenceId;
|
private ImageView btnDelete;
|
||||||
private int amount;
|
|
||||||
private JSONObject midtransResponse;
|
// Data
|
||||||
|
private StringBuilder currentAmount = new StringBuilder();
|
||||||
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
|
private static final int MAX_AMOUNT_LENGTH = 12;
|
||||||
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
|
|
||||||
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key
|
// Animation
|
||||||
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
|
private Handler animationHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Set status bar color programmatically
|
||||||
|
setStatusBarColor();
|
||||||
|
|
||||||
setContentView(R.layout.activity_payment);
|
setContentView(R.layout.activity_payment);
|
||||||
|
|
||||||
// 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
|
initializeViews();
|
||||||
editTextAmount.setText("");
|
setupClickListeners();
|
||||||
editTextAmount.requestFocus();
|
setupInitialStates();
|
||||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
// REMOVED: addAnimations() - No more card sliding animation
|
||||||
if (imm != null) {
|
|
||||||
imm.showSoftInput(editTextAmount, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 QR code and payment success views
|
|
||||||
paymentDetailsLayout.setVisibility(View.GONE);
|
|
||||||
paymentSuccessLayout.setVisibility(View.GONE);
|
|
||||||
simulatePaymentButton.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTransaction() {
|
private void setStatusBarColor() {
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
initiatePaymentButton.setEnabled(false);
|
Window window = getWindow();
|
||||||
statusTextView.setText("Creating transaction...");
|
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||||
|
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
|
||||||
new CreateTransactionTask().execute();
|
|
||||||
}
|
// Make status bar icons white (for dark red background)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
private void displayQrCode(String qrImageUrl) {
|
View decorView = window.getDecorView();
|
||||||
new DownloadImageTask().execute(qrImageUrl);
|
decorView.setSystemUiVisibility(0); // Clear light status bar flag
|
||||||
}
|
|
||||||
|
|
||||||
private void simulateWebhook() {
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
simulatePaymentButton.setEnabled(false);
|
|
||||||
statusTextView.setText("Processing payment...");
|
|
||||||
|
|
||||||
new SimulateWebhookTask().execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSuccessScreen() {
|
|
||||||
paymentDetailsLayout.setVisibility(View.GONE);
|
|
||||||
paymentSuccessLayout.setVisibility(View.VISIBLE);
|
|
||||||
statusTextView.setText("Payment successful!");
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateRandomString(int length) {
|
|
||||||
String chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
Random random = new Random();
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
int index = random.nextInt(chars.length());
|
|
||||||
sb.append(chars.charAt(index));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getServerKey() {
|
|
||||||
// MIDTRANS_AUTH = 'Basic base64string'
|
|
||||||
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
|
|
||||||
String decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT).toString();
|
|
||||||
// Format is usually 'SB-Mid-server-xxxx:'. Remove trailing colon if present.
|
|
||||||
return decoded.replace(":\n", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateSignature(String orderId, String statusCode, String grossAmount, String serverKey) {
|
|
||||||
String input = orderId + statusCode + grossAmount + serverKey;
|
|
||||||
try {
|
|
||||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-512");
|
|
||||||
byte[] messageDigest = md.digest(input.getBytes());
|
|
||||||
StringBuilder hexString = new StringBuilder();
|
|
||||||
for (byte b : messageDigest) {
|
|
||||||
String hex = Integer.toHexString(0xff & b);
|
|
||||||
if (hex.length() == 1) hexString.append('0');
|
|
||||||
hexString.append(hex);
|
|
||||||
}
|
}
|
||||||
return hexString.toString();
|
}
|
||||||
} catch (java.security.NoSuchAlgorithmException e) {
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
|
// Main views
|
||||||
|
editTextAmount = findViewById(R.id.editTextAmount);
|
||||||
|
confirmButton = findViewById(R.id.confirmButton);
|
||||||
|
backNavigation = findViewById(R.id.back_navigation);
|
||||||
|
backArrow = findViewById(R.id.backArrow);
|
||||||
|
toolbarTitle = findViewById(R.id.toolbarTitle);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toolbar title (also clickable for back navigation)
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confirm button
|
||||||
|
confirmButton.setOnClickListener(v -> {
|
||||||
|
if (confirmButton.isEnabled()) {
|
||||||
|
addButtonClickAnimation(v);
|
||||||
|
handleConfirmPayment();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateBack() {
|
||||||
|
// Simple back navigation without card animation
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNumpadClick(View view, String digit) {
|
||||||
|
addClickAnimation(view);
|
||||||
|
addDigit(digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupInitialStates() {
|
||||||
|
// Set initial amount display
|
||||||
|
editTextAmount.setText("");
|
||||||
|
|
||||||
|
// Set initial button state
|
||||||
|
updateButtonState();
|
||||||
|
|
||||||
|
// Disable EditText input (only numpad input allowed)
|
||||||
|
editTextAmount.setFocusable(false);
|
||||||
|
editTextAmount.setClickable(false);
|
||||||
|
editTextAmount.setCursorVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// REMOVED: addAnimations() method - No card sliding animation on startup
|
||||||
|
|
||||||
|
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")) {
|
||||||
|
editTextAmount.setText("");
|
||||||
|
} else {
|
||||||
|
String formattedAmount = formatCurrency(amount);
|
||||||
|
editTextAmount.setText(formattedAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatCurrency(String amount) {
|
||||||
|
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
|
||||||
return "";
|
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();
|
||||||
|
|
||||||
|
confirmButton.setEnabled(hasValidAmount);
|
||||||
|
|
||||||
|
if (hasValidAmount) {
|
||||||
|
// 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 handleConfirmPayment() {
|
||||||
|
String amount = currentAmount.toString();
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(amount) || amount.equals("0")) {
|
||||||
|
showToast("Masukkan jumlah pembayaran");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long amountValue = Long.parseLong(amount);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process payment
|
||||||
|
processPayment(amountValue);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
showToast("Format jumlah tidak valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processPayment(long amount) {
|
||||||
|
// Show loading state
|
||||||
|
confirmButton.setText("Memproses...");
|
||||||
|
confirmButton.setEnabled(false);
|
||||||
|
|
||||||
|
// Simulate payment processing
|
||||||
|
animationHandler.postDelayed(() -> {
|
||||||
|
// Show success message
|
||||||
|
showToast("Pembayaran berhasil! Jumlah: Rp " + formatCurrency(String.valueOf(amount)));
|
||||||
|
|
||||||
|
// Reset state and go back
|
||||||
|
resetPaymentState();
|
||||||
|
navigateBack();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetPaymentState() {
|
||||||
|
currentAmount = new StringBuilder();
|
||||||
|
updateAmountDisplay();
|
||||||
|
updateButtonState();
|
||||||
|
confirmButton.setText("Konfirmasi");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation methods (only for numpad interactions)
|
||||||
|
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
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public void onBackPressed() {
|
||||||
if (item.getItemId() == android.R.id.home) {
|
navigateBack();
|
||||||
finish();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CreateTransactionTask extends AsyncTask<Void, Void, Boolean> {
|
|
||||||
private String errorMessage;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(Void... voids) {
|
|
||||||
try {
|
|
||||||
// Generate a UUID for the transaction
|
|
||||||
transactionUuid = UUID.randomUUID().toString();
|
|
||||||
|
|
||||||
// Create transaction JSON payload
|
|
||||||
JSONObject payload = new JSONObject();
|
|
||||||
payload.put("type", "PAYMENT");
|
|
||||||
payload.put("channel_category", "RETAIL_OUTLET");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.put("amount", amount);
|
|
||||||
payload.put("cashflow", "MONEY_IN");
|
|
||||||
payload.put("status", "INIT");
|
|
||||||
payload.put("device_id", 1);
|
|
||||||
payload.put("transaction_uuid", transactionUuid);
|
|
||||||
payload.put("transaction_time_seconds", 0.0);
|
|
||||||
payload.put("device_code", "PB4K252T00021");
|
|
||||||
payload.put("merchant_name", "Marcel Panjaitan");
|
|
||||||
payload.put("mid", "71000026521");
|
|
||||||
payload.put("tid", "73001500");
|
|
||||||
|
|
||||||
// Make the API call
|
|
||||||
URL url = new URI(BACKEND_BASE + "/transactions").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) {
|
|
||||||
// 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 to get transaction ID
|
|
||||||
JSONObject jsonResponse = new JSONObject(response.toString());
|
|
||||||
JSONObject data = jsonResponse.getJSONObject("data");
|
|
||||||
transactionId = String.valueOf(data.getInt("id"));
|
|
||||||
|
|
||||||
// Now generate QRIS via Midtrans
|
|
||||||
return generateQris(amount);
|
|
||||||
} 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 creating transaction: " + response.toString();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("MidtransCharge", "Exception: " + e.getMessage(), e);
|
|
||||||
errorMessage = "Unexpected error: " + e.getMessage();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean generateQris(int amount) {
|
|
||||||
try {
|
|
||||||
// Create QRIS charge JSON payload
|
|
||||||
JSONObject payload = new JSONObject();
|
|
||||||
payload.put("payment_type", "qris");
|
|
||||||
|
|
||||||
JSONObject transactionDetails = new JSONObject();
|
|
||||||
transactionDetails.put("order_id", transactionUuid);
|
|
||||||
transactionDetails.put("gross_amount", amount);
|
|
||||||
payload.put("transaction_details", transactionDetails);
|
|
||||||
|
|
||||||
// Log the request details
|
|
||||||
Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
|
|
||||||
Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
|
|
||||||
Log.d("MidtransCharge", "Accept: application/json");
|
|
||||||
Log.d("MidtransCharge", "Content-Type: application/json");
|
|
||||||
Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
|
|
||||||
Log.d("MidtransCharge", "Payload: " + payload.toString());
|
|
||||||
|
|
||||||
// Make the API call to Midtrans
|
@Override
|
||||||
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
|
protected void onDestroy() {
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
super.onDestroy();
|
||||||
conn.setRequestMethod("POST");
|
if (animationHandler != null) {
|
||||||
conn.setRequestProperty("Accept", "application/json");
|
animationHandler.removeCallbacksAndMessages(null);
|
||||||
conn.setRequestProperty("Content-Type", "application/json");
|
|
||||||
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
|
|
||||||
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
|
|
||||||
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) {
|
|
||||||
InputStream inputStream = conn.getInputStream();
|
|
||||||
if (inputStream != null) {
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
|
|
||||||
StringBuilder response = new StringBuilder();
|
|
||||||
String responseLine;
|
|
||||||
while ((responseLine = br.readLine()) != null) {
|
|
||||||
response.append(responseLine.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the response
|
|
||||||
midtransResponse = new JSONObject(response.toString());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": No input stream available");
|
|
||||||
errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No input stream available";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
InputStream errorStream = conn.getErrorStream();
|
|
||||||
if (errorStream != null) {
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(errorStream, "utf-8"));
|
|
||||||
StringBuilder errorResponse = new StringBuilder();
|
|
||||||
String responseLine;
|
|
||||||
while ((responseLine = br.readLine()) != null) {
|
|
||||||
errorResponse.append(responseLine.trim());
|
|
||||||
}
|
|
||||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": " + errorResponse.toString());
|
|
||||||
errorMessage = "Error generating QRIS: HTTP " + responseCode + ": " + errorResponse.toString();
|
|
||||||
} else {
|
|
||||||
Log.e("MidtransCharge", "HTTP " + responseCode + ": No error stream available");
|
|
||||||
errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No error stream available";
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("MidtransCharge", "Exception: " + e.getMessage(), e);
|
|
||||||
errorMessage = "Unexpected error: " + e.getMessage();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean success) {
|
|
||||||
if (success && midtransResponse != null) {
|
|
||||||
try {
|
|
||||||
// Extract needed values from midtransResponse
|
|
||||||
JSONObject actions = midtransResponse.getJSONArray("actions").getJSONObject(0);
|
|
||||||
String qrImageUrl = actions.getString("url");
|
|
||||||
|
|
||||||
// Extract transaction_id
|
|
||||||
String transactionId = midtransResponse.getString("transaction_id");
|
|
||||||
String transactionTime = midtransResponse.getString("transaction_time");
|
|
||||||
String acquirer = midtransResponse.getString("acquirer");
|
|
||||||
String merchantId = midtransResponse.getString("merchant_id");
|
|
||||||
String exactGrossAmount = midtransResponse.getString("gross_amount");
|
|
||||||
|
|
||||||
// Log everything before launching activity
|
|
||||||
Log.d("MidtransCharge", "Creating QrisResultActivity intent with:");
|
|
||||||
Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl);
|
|
||||||
Log.d("MidtransCharge", "amount: " + amount);
|
|
||||||
Log.d("MidtransCharge", "referenceId: " + referenceId);
|
|
||||||
Log.d("MidtransCharge", "transactionUuid (orderId): " + transactionUuid);
|
|
||||||
Log.d("MidtransCharge", "transaction_id: " + transactionId);
|
|
||||||
Log.d("MidtransCharge", "exactGrossAmount: " + exactGrossAmount);
|
|
||||||
|
|
||||||
// Instead of showing QR inline, launch QrisResultActivity
|
|
||||||
Intent intent = new Intent(PaymentActivity.this, QrisResultActivity.class);
|
|
||||||
intent.putExtra("qrImageUrl", qrImageUrl);
|
|
||||||
intent.putExtra("amount", amount);
|
|
||||||
intent.putExtra("referenceId", referenceId);
|
|
||||||
intent.putExtra("orderId", transactionUuid); // Order ID
|
|
||||||
intent.putExtra("transactionId", transactionId); // Actual Midtrans transaction_id
|
|
||||||
intent.putExtra("grossAmount", exactGrossAmount); // Exact gross amount from response
|
|
||||||
intent.putExtra("transactionTime", transactionTime); // For timestamp
|
|
||||||
intent.putExtra("acquirer", acquirer);
|
|
||||||
intent.putExtra("merchantId", merchantId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
startActivity(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("MidtransCharge", "Failed to start QrisResultActivity: " + e.getMessage(), e);
|
|
||||||
Toast.makeText(PaymentActivity.this, "Error launching QR display: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
|
|
||||||
Toast.makeText(PaymentActivity.this, "Error processing QRIS response", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String message = (errorMessage != null && !errorMessage.isEmpty()) ? errorMessage : "Unknown error occurred. Please check Logcat for details.";
|
|
||||||
Toast.makeText(PaymentActivity.this, message, Toast.LENGTH_LONG).show();
|
|
||||||
initiatePaymentButton.setEnabled(true);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// Public methods for testing
|
||||||
protected void onPostExecute(Bitmap result) {
|
public String getCurrentAmount() {
|
||||||
if (result != null) {
|
return currentAmount.toString();
|
||||||
qrCodeImageView.setImageBitmap(result);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(PaymentActivity.this, "Error loading QR code image", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SimulateWebhookTask extends AsyncTask<Void, Void, Boolean> {
|
public boolean isConfirmButtonEnabled() {
|
||||||
private String errorMessage;
|
return confirmButton.isEnabled();
|
||||||
|
|
||||||
@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(PaymentActivity.this, message, Toast.LENGTH_LONG).show();
|
|
||||||
simulatePaymentButton.setEnabled(true);
|
|
||||||
}
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
4
app/src/main/res/anim/fade_in.xml
Normal file
4
app/src/main/res/anim/fade_in.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
4
app/src/main/res/anim/fade_out.xml
Normal file
4
app/src/main/res/anim/fade_out.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
14
app/src/main/res/anim/scale_in.xml
Normal file
14
app/src/main/res/anim/scale_in.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<scale
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXScale="0.8"
|
||||||
|
android:fromYScale="0.8"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:toXScale="1.0"
|
||||||
|
android:toYScale="1.0" />
|
||||||
|
<alpha
|
||||||
|
android:duration="200"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
|
</set>
|
14
app/src/main/res/anim/scale_out.xml
Normal file
14
app/src/main/res/anim/scale_out.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<scale
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXScale="1.0"
|
||||||
|
android:fromYScale="1.0"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:toXScale="0.8"
|
||||||
|
android:toYScale="0.8" />
|
||||||
|
<alpha
|
||||||
|
android:duration="200"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
|
</set>
|
10
app/src/main/res/anim/slide_in_left.xml
Normal file
10
app/src/main/res/anim/slide_in_left.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="-100%p"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
|
</set>
|
10
app/src/main/res/anim/slide_in_right.xml
Normal file
10
app/src/main/res/anim/slide_in_right.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="100%p"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
|
</set>
|
10
app/src/main/res/anim/slide_out_left.xml
Normal file
10
app/src/main/res/anim/slide_out_left.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="-100%p" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
|
</set>
|
10
app/src/main/res/anim/slide_out_right.xml
Normal file
10
app/src/main/res/anim/slide_out_right.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="100%p" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
|
</set>
|
6
app/src/main/res/drawable/button_active_background.xml
Normal file
6
app/src/main/res/drawable/button_active_background.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#DE0701" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
5
app/src/main/res/drawable/button_background_selector.xml
Normal file
5
app/src/main/res/drawable/button_background_selector.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/button_active_background" android:state_enabled="true"/>
|
||||||
|
<item android:drawable="@drawable/button_inactive_background" android:state_enabled="false"/>
|
||||||
|
</selector>
|
6
app/src/main/res/drawable/button_inactive_background.xml
Normal file
6
app/src/main/res/drawable/button_inactive_background.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#ECEFF0" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
6
app/src/main/res/drawable/card_background.xml
Normal file
6
app/src/main/res/drawable/card_background.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#3498DB" />
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
<stroke android:width="0dp" />
|
||||||
|
</shape>
|
BIN
app/src/main/res/drawable/ic_arrow_back.png
Normal file
BIN
app/src/main/res/drawable/ic_arrow_back.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 B |
10
app/src/main/res/drawable/ic_backspace.xml
Normal file
10
app/src/main/res/drawable/ic_backspace.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!-- res/drawable/icons/ic_backspace.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#333333"
|
||||||
|
android:pathData="M22,3H7c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/>
|
||||||
|
</vector>
|
@ -1,227 +1,268 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
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
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
android:background="#FFFFFF"
|
||||||
|
tools:context=".PaymentActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Red Status Bar (Override purple) -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/red_status_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="24dp"
|
||||||
android:orientation="vertical"
|
android:background="#E31937"
|
||||||
android:padding="16dp">
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<ProgressBar
|
<!-- Red Background Header (Extended height untuk back navigation) -->
|
||||||
android:id="@+id/progressBar"
|
<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 (Positioned closer to status bar/appbar) -->
|
||||||
|
<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 -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backArrow"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@drawable/ic_arrow_back"
|
||||||
|
android:contentDescription="Back" />
|
||||||
|
|
||||||
|
<!-- Title Text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/toolbarTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_marginStart="8dp"
|
||||||
android:visibility="gone" />
|
android:text="Kembali"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
android:textStyle="normal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 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="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
android:layout_marginBottom="24dp" />
|
||||||
|
|
||||||
|
<!-- Amount Input Section -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="RP"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_gravity="bottom" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/editTextAmount"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textColorHint="#80FFFFFF"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
android:hint=""
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="false"
|
||||||
|
android:cursorVisible="false"
|
||||||
|
android:text=""
|
||||||
|
android:gravity="start"
|
||||||
|
android:paddingBottom="4dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- White Underline -->
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="2dp"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<TextView
|
||||||
|
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:fontFamily="@font/inter"
|
||||||
|
android:alpha="0.9" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- 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="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/paymentCard">
|
||||||
|
|
||||||
|
<!-- Row 1: 1, 2, 3 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn1"
|
||||||
|
style="@style/NumpadButton"
|
||||||
|
android:text="1" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/statusTextView"
|
android:id="@+id/btn2"
|
||||||
android:layout_width="match_parent"
|
style="@style/NumpadButton"
|
||||||
android:layout_height="wrap_content"
|
android:text="2" />
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Ready to make a payment"
|
|
||||||
android:textSize="18sp" />
|
|
||||||
|
|
||||||
<!-- Initial Payment Form -->
|
<TextView
|
||||||
<LinearLayout
|
android:id="@+id/btn3"
|
||||||
android:layout_width="match_parent"
|
style="@style/NumpadButton"
|
||||||
android:layout_height="wrap_content"
|
android:text="3" />
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<!-- Row 2: 4, 5, 6 -->
|
||||||
android:layout_width="match_parent"
|
<TextView
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/btn4"
|
||||||
android:layout_margin="8dp"
|
style="@style/NumpadButton"
|
||||||
app:cardBackgroundColor="@color/light_blue"
|
android:text="4" />
|
||||||
app:cardCornerRadius="8dp"
|
|
||||||
app:cardElevation="4dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn5"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:orientation="vertical"
|
android:text="5" />
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/btn6"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:text="Amount"
|
android:text="6" />
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<EditText
|
<!-- Row 3: 7, 8, 9 -->
|
||||||
android:id="@+id/editTextAmount"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn7"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:layout_marginTop="8dp"
|
android:text="7" />
|
||||||
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/btn8"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:layout_marginTop="16dp"
|
android:text="8" />
|
||||||
android:text="Reference ID"
|
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/referenceIdTextView"
|
android:id="@+id/btn9"
|
||||||
android:layout_width="match_parent"
|
style="@style/NumpadButton"
|
||||||
android:layout_height="wrap_content"
|
android:text="9" />
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="ref-abcd1234"
|
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<Button
|
<!-- Row 4: 000, 0, Delete -->
|
||||||
android:id="@+id/initiatePaymentButton"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn000"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:layout_margin="8dp"
|
android:text="000" />
|
||||||
android:backgroundTint="@color/primary_blue"
|
|
||||||
android:text="Start Payment" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- QR Code and Payment Details -->
|
<TextView
|
||||||
<LinearLayout
|
android:id="@+id/btn0"
|
||||||
android:id="@+id/paymentDetailsLayout"
|
style="@style/NumpadButton"
|
||||||
android:layout_width="match_parent"
|
android:text="0" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btnDelete"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_height="60dp"
|
||||||
app:cardCornerRadius="8dp"
|
android:layout_columnWeight="1"
|
||||||
app:cardElevation="4dp">
|
android:layout_margin="8dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_backspace"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:contentDescription="Delete" />
|
||||||
|
|
||||||
<LinearLayout
|
</GridLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
<!-- Confirmation Button (UPDATED: Menggunakan MaterialButton) -->
|
||||||
android:id="@+id/qrCodeImageView"
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="250dp"
|
android:id="@+id/confirmButton"
|
||||||
android:layout_height="250dp"
|
android:layout_width="match_parent"
|
||||||
android:contentDescription="QRIS Code"
|
android:layout_height="48dp"
|
||||||
android:scaleType="fitCenter" />
|
android:text="Konfirmasi"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
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_constraintTop_toBottomOf="@id/numpad_grid"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="1" />
|
||||||
|
|
||||||
<TextView
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
android:layout_width="match_parent"
|
</ScrollView>
|
||||||
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>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,12 +1,48 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<!-- Style yang sudah ada -->
|
||||||
<style name="MenuCardTitle">
|
<style name="MenuCardTitle">
|
||||||
<item name="fontFamily">@font/inter</item>
|
<item name="fontFamily">@font/inter</item>
|
||||||
<item name="android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
<item name="android:textColor">#DD0701</item>
|
<item name="android:textColor">#DD0701</item>
|
||||||
<item name="android:textAlignment">center</item>
|
<item name="android:textAlignment">center</item>
|
||||||
<item name="android:lineHeight">12sp</item>
|
<item name="android:lineSpacingExtra">0dp</item>
|
||||||
|
<item name="android:lineSpacingMultiplier">1.0</item>
|
||||||
<item name="android:letterSpacing">0</item>
|
<item name="android:letterSpacing">0</item>
|
||||||
<item name="android:textStyle">normal</item>
|
<item name="android:textStyle">normal</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Style baru untuk Toolbar Title -->
|
||||||
|
<style name="ToolbarTitleStyle">
|
||||||
|
<item name="fontFamily">@font/inter</item>
|
||||||
|
<item name="android:textSize">18sp</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:lineSpacingExtra">6dp</item>
|
||||||
|
<item name="android:lineSpacingMultiplier">1.0</item>
|
||||||
|
<item name="android:letterSpacing">0</item>
|
||||||
|
<item name="android:textColor">@android:color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Custom Toolbar Theme -->
|
||||||
|
<style name="CustomToolbarTheme" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||||
|
<item name="android:textAppearance">@style/ToolbarTitleStyle</item>
|
||||||
|
<item name="titleTextAppearance">@style/ToolbarTitleStyle</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Numpad Button Style -->
|
||||||
|
<style name="NumpadButton">
|
||||||
|
<item name="android:layout_width">0dp</item>
|
||||||
|
<item name="android:layout_height">60dp</item>
|
||||||
|
<item name="android:layout_columnWeight">1</item>
|
||||||
|
<item name="android:layout_margin">8dp</item>
|
||||||
|
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
|
||||||
|
<item name="android:textSize">28sp</item>
|
||||||
|
<item name="android:textColor">#333333</item>
|
||||||
|
<item name="android:textStyle">normal</item>
|
||||||
|
<item name="android:fontFamily">@font/inter</item>
|
||||||
|
<item name="android:gravity">center</item>
|
||||||
|
<item name="android:clickable">true</item>
|
||||||
|
<item name="android:focusable">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user