Compare commits

..

30 Commits

Author SHA1 Message Date
729bdddad4 safepoint qris result activity 2025-06-13 15:40:56 +07:00
c56cae64b9 safepoint Detail transaksi 2025-06-13 14:41:10 +07:00
d4245c5906 Sorting List 2025-06-13 09:29:35 +07:00
eddade3200 safepoint QRIS 2025-06-12 16:56:26 +07:00
13ab6b717e implement SDK di MainActivity 2025-06-11 23:07:35 +07:00
991f77dabe transaction update 2025-06-10 16:58:42 +07:00
da8bcf17cc fix list 2025-06-10 13:24:32 +07:00
b0ee2e8ee6 memperbaiki list cetak ulang 2025-06-10 12:10:35 +07:00
4aaa9957e7 solved duplicate data 2025-06-09 18:36:52 +07:00
99fab68e71 QRIS FLOW 2025-06-09 12:04:58 +07:00
074a4b1f53 Fix payment dan struk 2025-06-09 01:27:59 +07:00
a1f536b03e midtrans solve 2025-06-08 17:30:07 +07:00
edca7f92ec implement history 2025-06-06 05:29:37 +07:00
3f189f5975 implement menu settlement 2025-06-05 16:19:46 +07:00
5a03fc3aec UI Cetak ulang struk 2025-06-05 13:03:44 +07:00
a30e767adc update UI QRIS Result 2025-06-03 17:17:46 +07:00
74f95e0374 update UI QRIS 2025-06-02 13:16:46 +07:00
1799e7eb0e adjustment 2025-05-30 20:17:43 +07:00
2a24016637 implement menu debit dan qris 2025-05-30 19:27:43 +07:00
459d9ab0f1 improve page struct 2025-05-30 16:13:51 +07:00
191966a2e4 improve payment success UI 2025-05-30 14:59:46 +07:00
46fb81b6a7 initialize success sreen dan receipt screen 2025-05-30 11:47:53 +07:00
290f3015d9 Improve UI Pin Page 2025-05-28 21:31:57 +07:00
f1228db89a UI PIN 2025-05-28 14:33:26 +07:00
810964b4be menambahkan modal 2025-05-28 12:06:20 +07:00
a7fa40d60a UI Kartu Kredit 2025-05-27 15:46:20 +07:00
a07e7a99ac improve button lainnya 2025-05-23 01:04:54 +07:00
c55af6141f improve menu home page 2025-05-22 23:33:20 +07:00
6d681f5e41 Implement PaymentActivity dan TransactionActivity 2025-05-22 17:14:08 +07:00
1ca26371a1 Menu Home dan Icon 2025-05-22 17:03:58 +07:00
72 changed files with 10119 additions and 1210 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "com.example.bdkipoc"
minSdk 21
targetSdk 30
targetSdk 33
versionCode 1
versionName "1.0"
@@ -35,6 +35,7 @@ dependencies {
implementation libs.activity
implementation libs.constraintlayout
implementation libs.cardview
implementation 'androidx.recyclerview:recyclerview:1.3.0'
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core

View File

@@ -17,6 +17,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BDKIPOC"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
@@ -33,7 +34,27 @@
<activity
android:name=".PaymentActivity"
android:exported="false" />
<activity
android:name=".PinActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:exported="false" />
<activity
android:name=".ReceiptActivity"
android:exported="false" />
<activity
android:name=".QrisActivity"
android:exported="false" />
<activity android:name=".QrisResultActivity" />
<activity
android:name=".SettlementActivity"
android:exported="false" />
<activity
android:name=".HistoryActivity"
android:exported="false" />
<activity
android:name=".HistoryDetailActivity"
android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,415 @@
package com.example.bdkipoc;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class HistoryActivity extends AppCompatActivity {
private TextView tvTotalAmount;
private TextView tvTotalTransactions;
private TextView btnLihatDetailTop;
private Button btnLihatDetailBottom;
private RecyclerView recyclerView;
private HistoryAdapter adapter;
private List<HistoryItem> historyList;
private ImageView btnBack;
// Store full data for detail view
private static List<HistoryItem> fullHistoryData = new ArrayList<>();
private String API_URL = "https://be-edc.msvc.app/transactions?page=0&limit=50&sortOrder=DESC&from_date=2025-05-10&to_date=2025-05-21&location_id=0&merchant_id=0&sortColumn=id";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_history);
initViews();
setupRecyclerView();
fetchApiData();
setupClickListeners();
}
private void initViews() {
tvTotalAmount = findViewById(R.id.tv_total_amount);
tvTotalTransactions = findViewById(R.id.tv_total_transactions);
btnLihatDetailTop = findViewById(R.id.btn_lihat_detail);
btnLihatDetailBottom = findViewById(R.id.btn_lihat_detail_bottom);
recyclerView = findViewById(R.id.recycler_view);
btnBack = findViewById(R.id.btn_back);
historyList = new ArrayList<>();
}
private void setupRecyclerView() {
adapter = new HistoryAdapter(historyList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private void setupClickListeners() {
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
View.OnClickListener detailClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Intent intent = new Intent(HistoryActivity.this, HistoryDetailActivity.class);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(HistoryActivity.this, "Error opening detail", Toast.LENGTH_SHORT).show();
}
}
};
btnLihatDetailTop.setOnClickListener(detailClickListener);
btnLihatDetailBottom.setOnClickListener(detailClickListener);
}
private void fetchApiData() {
new ApiTask().execute(API_URL);
}
private void processApiData(JSONArray dataArray) {
try {
historyList.clear();
fullHistoryData.clear(); // Clear static data
final long[] totalAmountArray = {0};
final int[] totalTransactionsArray = {0};
for (int i = 0; i < dataArray.length(); i++) {
JSONObject item = dataArray.getJSONObject(i);
String channelCode = item.getString("channel_code");
String amount = item.getString("amount");
String status = item.getString("status");
String transactionDate = item.getString("transaction_date");
String referenceId = item.getString("reference_id");
// Parse amount safely
double amountValue = 0;
try {
amountValue = Double.parseDouble(amount);
} catch (NumberFormatException e) {
amountValue = 0;
}
// Create history item
HistoryItem historyItem = new HistoryItem();
historyItem.setTime(formatTime(transactionDate));
historyItem.setDate(formatDate(transactionDate));
historyItem.setAmount((long) amountValue);
historyItem.setChannelName(formatChannelName(channelCode));
historyItem.setStatus(status);
historyItem.setReferenceId(referenceId);
historyItem.setFullDate(transactionDate);
historyItem.setChannelCode(channelCode);
// Add to full data
fullHistoryData.add(historyItem);
// Add first 10 to display list
if (i < 10) {
historyList.add(historyItem);
}
totalAmountArray[0] += (long) amountValue;
totalTransactionsArray[0]++;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
updateSummary(totalAmountArray[0], totalTransactionsArray[0]);
adapter.notifyDataSetChanged();
}
});
} catch (JSONException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(HistoryActivity.this, "Error parsing data", Toast.LENGTH_SHORT).show();
loadSampleData();
}
});
}
}
private void updateSummary(long totalAmount, int totalTransactions) {
tvTotalAmount.setText("RP " + formatCurrency(totalAmount));
tvTotalTransactions.setText(String.valueOf(totalTransactions));
}
private void loadSampleData() {
historyList.clear();
fullHistoryData.clear();
// Create sample data
HistoryItem[] sampleData = {
new HistoryItem("03:44", "11-05-2025", 2018619, "Kredit", "FAILED", "197870"),
new HistoryItem("03:10", "12-05-2025", 3974866, "QRIS", "SUCCESS", "053059"),
new HistoryItem("15:17", "13-05-2025", 2418167, "QRIS", "FAILED", "668320"),
new HistoryItem("12:09", "11-05-2025", 3429230, "Debit", "FAILED", "454790"),
new HistoryItem("08:39", "10-05-2025", 4656447, "QRIS", "FAILED", "454248"),
new HistoryItem("00:35", "12-05-2025", 3507704, "QRIS", "FAILED", "301644"),
new HistoryItem("22:43", "13-05-2025", 4277904, "Debit", "SUCCESS", "388709"),
new HistoryItem("18:16", "11-05-2025", 4456904, "Debit", "FAILED", "986861")
};
for (HistoryItem item : sampleData) {
historyList.add(item);
fullHistoryData.add(item);
}
tvTotalAmount.setText("RP 36.166.829");
tvTotalTransactions.setText("10");
adapter.notifyDataSetChanged();
}
private String formatChannelName(String channelCode) {
switch (channelCode) {
case "DEBIT":
return "Debit";
case "QRIS":
return "QRIS";
case "OTHER":
return "Kredit";
default:
return channelCode.substring(0, 1).toUpperCase() +
channelCode.substring(1).toLowerCase();
}
}
private String formatTime(String isoDate) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
SimpleDateFormat outputFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
Date date = inputFormat.parse(isoDate);
return outputFormat.format(date);
} catch (ParseException e) {
return "00:00";
}
}
private String formatDate(String isoDate) {
try {
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
SimpleDateFormat outputFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault());
Date date = inputFormat.parse(isoDate);
return outputFormat.format(date);
} catch (ParseException e) {
return "01-01-2025";
}
}
private String formatCurrency(long amount) {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
}
// Public static method to get full data for detail activity
public static List<HistoryItem> getFullHistoryData() {
return new ArrayList<>(fullHistoryData);
}
// AsyncTask for API call
private class ApiTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
try {
JSONObject jsonResponse = new JSONObject(result);
if (jsonResponse.getInt("status") == 200) {
JSONObject results = jsonResponse.getJSONObject("results");
JSONArray dataArray = results.getJSONArray("data");
processApiData(dataArray);
} else {
Toast.makeText(HistoryActivity.this, "API Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(HistoryActivity.this, "JSON Parse Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
} else {
Toast.makeText(HistoryActivity.this, "Network Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
}
}
}
// HistoryItem class - enhanced with more fields
class HistoryItem {
private String time;
private String date;
private long amount;
private String channelName;
private String status;
private String referenceId;
private String fullDate;
private String channelCode;
public HistoryItem() {}
public HistoryItem(String time, String date, long amount, String channelName, String status, String referenceId) {
this.time = time;
this.date = date;
this.amount = amount;
this.channelName = channelName;
this.status = status;
this.referenceId = referenceId;
}
// Getters and Setters
public String getTime() { return time; }
public void setTime(String time) { this.time = time; }
public String getDate() { return date; }
public void setDate(String date) { this.date = date; }
public long getAmount() { return amount; }
public void setAmount(long amount) { this.amount = amount; }
public String getChannelName() { return channelName; }
public void setChannelName(String channelName) { this.channelName = channelName; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getReferenceId() { return referenceId; }
public void setReferenceId(String referenceId) { this.referenceId = referenceId; }
public String getFullDate() { return fullDate; }
public void setFullDate(String fullDate) { this.fullDate = fullDate; }
public String getChannelCode() { return channelCode; }
public void setChannelCode(String channelCode) { this.channelCode = channelCode; }
}
// HistoryAdapter class - simplified and stable
class HistoryAdapter extends RecyclerView.Adapter<HistoryAdapter.HistoryViewHolder> {
private List<HistoryItem> historyList;
public HistoryAdapter(List<HistoryItem> historyList) {
this.historyList = historyList;
}
@NonNull
@Override
public HistoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_history, parent, false);
return new HistoryViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull HistoryViewHolder holder, int position) {
HistoryItem item = historyList.get(position);
holder.tvTime.setText(item.getTime() + ", " + item.getDate());
holder.tvAmount.setText("Rp. " + formatCurrency(item.getAmount()));
holder.tvChannel.setText(item.getChannelName());
// Set status color
String status = item.getStatus();
if ("SUCCESS".equals(status)) {
holder.tvStatus.setText("Berhasil");
holder.tvStatus.setTextColor(0xFF4CAF50); // Green
} else if ("FAILED".equals(status)) {
holder.tvStatus.setText("Gagal");
holder.tvStatus.setTextColor(0xFFF44336); // Red
} else {
holder.tvStatus.setText("Tertunda");
holder.tvStatus.setTextColor(0xFFFF9800); // Orange
}
}
@Override
public int getItemCount() {
return historyList != null ? historyList.size() : 0;
}
private String formatCurrency(long amount) {
try {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
} catch (Exception e) {
return String.valueOf(amount);
}
}
static class HistoryViewHolder extends RecyclerView.ViewHolder {
TextView tvTime;
TextView tvAmount;
TextView tvChannel;
TextView tvStatus;
public HistoryViewHolder(@NonNull View itemView) {
super(itemView);
tvTime = itemView.findViewById(R.id.tv_time);
tvAmount = itemView.findViewById(R.id.tv_amount);
tvChannel = itemView.findViewById(R.id.tv_channel);
tvStatus = itemView.findViewById(R.id.tv_status);
}
}
}

View File

@@ -0,0 +1,204 @@
package com.example.bdkipoc;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class HistoryDetailActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private HistoryDetailAdapter adapter;
private List<HistoryItem> detailList;
private ImageView btnBack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_history_detail);
initViews();
setupRecyclerView();
loadData();
setupClickListeners();
}
private void initViews() {
recyclerView = findViewById(R.id.recycler_view);
btnBack = findViewById(R.id.btn_back);
detailList = new ArrayList<>();
}
private void setupRecyclerView() {
adapter = new HistoryDetailAdapter(detailList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private void setupClickListeners() {
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void loadData() {
try {
// Get data from HistoryActivity
List<HistoryItem> fullData = HistoryActivity.getFullHistoryData();
if (fullData != null && !fullData.isEmpty()) {
detailList.clear();
detailList.addAll(fullData);
adapter.notifyDataSetChanged();
} else {
loadSampleDetailData();
}
} catch (Exception e) {
e.printStackTrace();
loadSampleDetailData();
}
}
private void loadSampleDetailData() {
detailList.clear();
// Create sample detail data
HistoryItem[] sampleData = {
new HistoryItem("03:44", "11-05-2025", 2018619, "Kredit", "FAILED", "197870"),
new HistoryItem("03:10", "12-05-2025", 3974866, "QRIS", "SUCCESS", "053059"),
new HistoryItem("15:17", "13-05-2025", 2418167, "QRIS", "FAILED", "668320"),
new HistoryItem("12:09", "11-05-2025", 3429230, "Debit", "FAILED", "454790"),
new HistoryItem("08:39", "10-05-2025", 4656447, "QRIS", "FAILED", "454248"),
new HistoryItem("00:35", "12-05-2025", 3507704, "QRIS", "FAILED", "301644"),
new HistoryItem("22:43", "13-05-2025", 4277904, "Debit", "SUCCESS", "388709"),
new HistoryItem("18:16", "11-05-2025", 4456904, "Debit", "FAILED", "986861"),
new HistoryItem("12:51", "10-05-2025", 3027953, "Kredit", "SUCCESS", "771339"),
new HistoryItem("19:50", "14-05-2025", 4399035, "QRIS", "FAILED", "103478")
};
for (HistoryItem item : sampleData) {
detailList.add(item);
}
adapter.notifyDataSetChanged();
}
}
// HistoryDetailAdapter class - simplified for stability
class HistoryDetailAdapter extends RecyclerView.Adapter<HistoryDetailAdapter.DetailViewHolder> {
private List<HistoryItem> detailList;
public HistoryDetailAdapter(List<HistoryItem> detailList) {
this.detailList = detailList;
}
@NonNull
@Override
public DetailViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_history_detail, parent, false);
return new DetailViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull DetailViewHolder holder, int position) {
HistoryItem item = detailList.get(position);
try {
holder.tvReferenceId.setText("Ref: " + item.getReferenceId());
holder.tvAmount.setText("Rp. " + formatCurrency(item.getAmount()));
holder.tvChannel.setText(item.getChannelName());
holder.tvMerchant.setText("TEST MERCHANT");
holder.tvTime.setText(formatDateTime(item.getTime(), item.getDate()));
holder.tvIssuer.setText("BANK MANDIRI");
// Set status color
String status = item.getStatus();
if ("SUCCESS".equals(status)) {
holder.tvStatus.setText("Berhasil");
holder.tvStatus.setTextColor(0xFF4CAF50); // Green
} else if ("FAILED".equals(status)) {
holder.tvStatus.setText("Gagal");
holder.tvStatus.setTextColor(0xFFF44336); // Red
} else {
holder.tvStatus.setText("Tertunda");
holder.tvStatus.setTextColor(0xFFFF9800); // Orange
}
} catch (Exception e) {
e.printStackTrace();
// Set default values if error occurs
holder.tvReferenceId.setText("Ref: " + position);
holder.tvAmount.setText("Rp. 0");
holder.tvChannel.setText("Unknown");
holder.tvMerchant.setText("TEST MERCHANT");
holder.tvTime.setText("00:00, 01-01-2025");
holder.tvIssuer.setText("UNKNOWN");
holder.tvStatus.setText("Tidak Diketahui");
holder.tvStatus.setTextColor(0xFF666666);
}
}
@Override
public int getItemCount() {
return detailList != null ? detailList.size() : 0;
}
private String formatCurrency(long amount) {
try {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
} catch (Exception e) {
return String.valueOf(amount);
}
}
private String formatDateTime(String time, String date) {
try {
return time + ", " + date;
} catch (Exception e) {
return "00:00, 01-01-2025";
}
}
static class DetailViewHolder extends RecyclerView.ViewHolder {
TextView tvReferenceId;
TextView tvAmount;
TextView tvChannel;
TextView tvMerchant;
TextView tvTime;
TextView tvIssuer;
TextView tvStatus;
public DetailViewHolder(@NonNull View itemView) {
super(itemView);
try {
tvReferenceId = itemView.findViewById(R.id.tv_reference_id);
tvAmount = itemView.findViewById(R.id.tv_amount);
tvChannel = itemView.findViewById(R.id.tv_channel);
tvMerchant = itemView.findViewById(R.id.tv_merchant);
tvTime = itemView.findViewById(R.id.tv_time);
tvIssuer = itemView.findViewById(R.id.tv_issuer);
tvStatus = itemView.findViewById(R.id.tv_status);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@@ -1,41 +1,267 @@
package com.example.bdkipoc;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.WindowManager;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.button.MaterialButton;
public class MainActivity extends AppCompatActivity {
private boolean isExpanded = false; // False = showing only 9 main menus, True = showing all 15 menus
private MaterialButton btnLainnya;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// Enable hardware acceleration for smoother scrolling
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
);
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// Set up click listeners for the cards
CardView paymentCard = findViewById(R.id.card_payment);
CardView transactionsCard = findViewById(R.id.card_transactions);
// Initialize views
btnLainnya = findViewById(R.id.btn_lainnya);
paymentCard.setOnClickListener(v -> {
// Launch payment activity
startActivity(new android.content.Intent(MainActivity.this, PaymentActivity.class));
// Check if we're returning from a completed transaction
checkTransactionCompletion();
// Setup initial state - 9 main menus visible, 6 dummy menus hidden
setupInitialMenuState();
// Setup menu listeners
setupMenuListeners();
}
private void setupInitialMenuState() {
// 9 main menus should always be visible
CardView cardBantuan = findViewById(R.id.card_bantuan);
CardView cardInfoToko = findViewById(R.id.card_info_toko);
if (cardBantuan != null) {
cardBantuan.setVisibility(View.VISIBLE);
}
if (cardInfoToko != null) {
cardInfoToko.setVisibility(View.VISIBLE);
}
// 6 dummy menus should be hidden initially
CardView[] dummyCards = {
findViewById(R.id.card_dummy_menu_1),
findViewById(R.id.card_dummy_menu_2),
findViewById(R.id.card_dummy_menu_3),
findViewById(R.id.card_dummy_menu_4),
findViewById(R.id.card_dummy_menu_5),
findViewById(R.id.card_dummy_menu_6)
};
for (CardView card : dummyCards) {
if (card != null) {
card.setVisibility(View.GONE);
}
}
// Set initial button text
isExpanded = false;
btnLainnya.setText("Lainnya");
}
private void checkTransactionCompletion() {
Intent intent = getIntent();
if (intent != null) {
boolean transactionCompleted = intent.getBooleanExtra("transaction_completed", false);
String transactionAmount = intent.getStringExtra("transaction_amount");
if (transactionCompleted) {
if (transactionAmount != null) {
Toast.makeText(this, "Transaksi berhasil! Jumlah: Rp " + formatCurrency(transactionAmount), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Transaksi berhasil diselesaikan!", Toast.LENGTH_LONG).show();
}
}
}
}
private String formatCurrency(String amount) {
try {
long amountValue = Long.parseLong(amount);
return String.format("%,d", amountValue).replace(',', '.');
} catch (NumberFormatException e) {
return amount;
}
}
private void setupMenuListeners() {
// Card IDs to set up listeners - Total 15 menu items
int[] cardIds = {
// Row 1 (Always visible - 3 items)
R.id.card_kartu_kredit,
R.id.card_kartu_debit,
R.id.card_qris,
// Row 2 (Always visible - 3 items)
R.id.card_uang_elektronik,
R.id.card_cetak_ulang,
R.id.card_settlement,
// Row 3 (Always visible - 3 items)
R.id.card_histori,
R.id.card_bantuan,
R.id.card_info_toko,
// Row 4 (Hidden initially - 3 items)
R.id.card_dummy_menu_1,
R.id.card_dummy_menu_2,
R.id.card_dummy_menu_3,
// Row 5 (Hidden initially - 3 items)
R.id.card_dummy_menu_4,
R.id.card_dummy_menu_5,
R.id.card_dummy_menu_6
};
// Set up click listeners for all cards
for (int cardId : cardIds) {
CardView cardView = findViewById(cardId);
if (cardView != null) {
cardView.setOnClickListener(v -> {
if (cardId == R.id.card_kartu_kredit) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
} else if (cardId == R.id.card_kartu_debit) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
} else if (cardId == R.id.card_qris) {
startActivity(new Intent(MainActivity.this, QrisActivity.class));
} else if (cardId == R.id.card_uang_elektronik) {
startActivity(new Intent(MainActivity.this, PaymentActivity.class));
} else if (cardId == R.id.card_cetak_ulang) {
startActivity(new Intent(MainActivity.this, TransactionActivity.class));
} else if (cardId == R.id.card_settlement) {
Toast.makeText(this, "Settlement - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_histori) {
Toast.makeText(this, "Histori - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_bantuan) {
Toast.makeText(this, "Bantuan - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_info_toko) {
Toast.makeText(this, "Info Toko - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_1) {
Toast.makeText(this, "Dummy Menu 1 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_2) {
Toast.makeText(this, "Dummy Menu 2 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_3) {
Toast.makeText(this, "Dummy Menu 3 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_4) {
Toast.makeText(this, "Dummy Menu 4 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_5) {
Toast.makeText(this, "Dummy Menu 5 - Coming Soon", Toast.LENGTH_SHORT).show();
} else if (cardId == R.id.card_dummy_menu_6) {
Toast.makeText(this, "Dummy Menu 6 - Coming Soon", Toast.LENGTH_SHORT).show();
} else {
// Fallback for any other cards
Toast.makeText(this, "Menu Diklik: " + getResources().getResourceEntryName(cardId), Toast.LENGTH_SHORT).show();
}
});
}
}
// Get references to ONLY the dummy cards that need to be toggled
CardView[] toggleableCards = {
findViewById(R.id.card_dummy_menu_1),
findViewById(R.id.card_dummy_menu_2),
findViewById(R.id.card_dummy_menu_3),
findViewById(R.id.card_dummy_menu_4),
findViewById(R.id.card_dummy_menu_5),
findViewById(R.id.card_dummy_menu_6)
};
// Set up "Lainnya" button click listener
btnLainnya.setOnClickListener(v -> {
isExpanded = !isExpanded;
if (isExpanded) {
// Show the 6 dummy menus with animation
for (CardView card : toggleableCards) {
if (card != null) {
card.setVisibility(View.VISIBLE);
card.setAlpha(0f);
card.animate()
.alpha(1f)
.setDuration(300)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
}
}
btnLainnya.setText("Tampilkan Lebih Sedikit");
} else {
// Hide the 6 dummy menus with animation
for (CardView card : toggleableCards) {
if (card != null) {
card.animate()
.alpha(0f)
.setDuration(300)
.setInterpolator(new AccelerateDecelerateInterpolator())
.withEndAction(() -> card.setVisibility(View.GONE))
.start();
}
}
btnLainnya.setText("Lainnya");
}
});
transactionsCard.setOnClickListener(v -> {
// Launch transactions activity
startActivity(new android.content.Intent(MainActivity.this, TransactionActivity.class));
});
// Set up scan dan bayar card click listener
LinearLayout scanBayarContent = findViewById(R.id.scan_bayar_content);
if (scanBayarContent != null) {
scanBayarContent.setOnClickListener(v -> {
// Navigate to QRIS payment activity
startActivity(new Intent(MainActivity.this, QrisActivity.class));
});
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
// Check for transaction completion when returning to MainActivity
checkTransactionCompletion();
}
@Override
protected void onResume() {
super.onResume();
// Clear any transaction completion flags to avoid repeated messages
getIntent().removeExtra("transaction_completed");
getIntent().removeExtra("transaction_amount");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,558 @@
package com.example.bdkipoc;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class PinActivity extends AppCompatActivity {
// Intent Extra Keys
public static final String EXTRA_TITLE = "extra_title";
public static final String EXTRA_SUBTITLE = "extra_subtitle";
public static final String EXTRA_AMOUNT = "extra_amount";
public static final String EXTRA_SOURCE_ACTIVITY = "extra_source_activity";
// Views
private EditText editTextPin;
private Button confirmButton;
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
// Success screen views
private View successScreen;
private ImageView successIcon;
private TextView successMessage;
// Numpad buttons
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000;
private ImageView btnDelete;
// Data
private StringBuilder currentPin = new StringBuilder();
private static final int MAX_PIN_LENGTH = 6;
private static final int MIN_PIN_LENGTH = 4;
// Extra data from intent
private String sourceActivity;
private String amount;
// Animation
private Handler animationHandler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set status bar color programmatically
setStatusBarColor();
setContentView(R.layout.activity_pin);
// Get intent extras
getIntentExtras();
initializeViews();
setupClickListeners();
setupInitialStates();
setupSuccessScreen();
}
private void setStatusBarColor() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.parseColor("#E31937")); // Red color
// Make status bar icons white (for dark red background)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
View decorView = window.getDecorView();
decorView.setSystemUiVisibility(0); // Clear light status bar flag
}
}
}
private void getIntentExtras() {
Intent intent = getIntent();
if (intent != null) {
sourceActivity = intent.getStringExtra(EXTRA_SOURCE_ACTIVITY);
amount = intent.getStringExtra(EXTRA_AMOUNT);
}
}
private void initializeViews() {
// Main views
editTextPin = findViewById(R.id.editTextPin);
confirmButton = findViewById(R.id.confirmButton);
backNavigation = findViewById(R.id.back_navigation);
backArrow = findViewById(R.id.backArrow);
toolbarTitle = findViewById(R.id.toolbarTitle);
// Success screen views
successScreen = findViewById(R.id.success_screen);
successIcon = findViewById(R.id.success_icon);
successMessage = findViewById(R.id.success_message);
// 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 setupSuccessScreen() {
// Initially hide success screen
if (successScreen != null) {
successScreen.setVisibility(View.GONE);
}
}
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);
handleConfirmPin();
}
});
}
private void navigateBack() {
finish();
}
private void handleNumpadClick(View view, String digit) {
addClickAnimation(view);
addDigit(digit);
}
private void setupInitialStates() {
// Set initial PIN display
editTextPin.setText("");
// Set initial button state
updateButtonState();
// Disable EditText input (only numpad input allowed)
editTextPin.setFocusable(false);
editTextPin.setClickable(false);
editTextPin.setCursorVisible(false);
}
private void addDigit(String digit) {
// Validate input length
if (currentPin.length() >= MAX_PIN_LENGTH) {
showToast("Maksimal " + MAX_PIN_LENGTH + " digit");
return;
}
// Handle special case for 000
if (digit.equals("000")) {
if (currentPin.length() + 3 <= MAX_PIN_LENGTH) {
currentPin.append("000");
} else {
int remainingLength = MAX_PIN_LENGTH - currentPin.length();
if (remainingLength > 0) {
currentPin.append("0".repeat(remainingLength));
}
}
} else {
currentPin.append(digit);
}
updatePinDisplay();
updateButtonState();
addInputFeedback();
}
private void deleteLastDigit() {
if (currentPin.length() > 0) {
String current = currentPin.toString();
// If current ends with 000, remove all three digits
if (current.endsWith("000") && current.length() >= 3) {
currentPin.delete(currentPin.length() - 3, currentPin.length());
} else {
currentPin.deleteCharAt(currentPin.length() - 1);
}
updatePinDisplay();
updateButtonState();
addDeleteFeedback();
}
}
private void updatePinDisplay() {
String pin = currentPin.toString();
if (pin.isEmpty()) {
editTextPin.setText("");
} else {
// Convert digits to asterisks for security
String maskedPin = "*".repeat(pin.length());
editTextPin.setText(maskedPin);
}
}
private void updateButtonState() {
boolean hasValidPin = currentPin.length() >= MIN_PIN_LENGTH;
confirmButton.setEnabled(hasValidPin);
if (hasValidPin) {
// Active state
confirmButton.setBackgroundResource(R.drawable.button_active_background);
confirmButton.setTextColor(Color.WHITE);
confirmButton.setAlpha(1.0f);
} else {
// Inactive state
confirmButton.setBackgroundResource(R.drawable.button_inactive_background);
confirmButton.setTextColor(Color.parseColor("#999999"));
confirmButton.setAlpha(0.6f);
}
}
private void handleConfirmPin() {
String pin = currentPin.toString();
if (TextUtils.isEmpty(pin)) {
showToast("Masukkan PIN");
return;
}
if (pin.length() < MIN_PIN_LENGTH) {
showToast("PIN minimal " + MIN_PIN_LENGTH + " digit");
return;
}
// Process PIN verification
verifyPin(pin);
}
private void verifyPin(String pin) {
// Show loading state
confirmButton.setText("Memverifikasi...");
confirmButton.setEnabled(false);
// Simulate PIN verification
animationHandler.postDelayed(() -> {
// For demo purposes, accept any PIN with length >= 4
// In real implementation, this would call backend API
if (isValidPin(pin)) {
// Show success screen instead of toast
handleSuccessfulVerification();
} else {
showToast("PIN tidak valid. Silakan coba lagi.");
resetPinState();
}
}, 2000);
}
private boolean isValidPin(String pin) {
// Demo validation - in real app, this would validate against backend
// For now, reject simple patterns like "1111", "1234", etc.
return !pin.equals("1111") &&
!pin.equals("1234") &&
!pin.equals("0000") &&
pin.length() >= MIN_PIN_LENGTH;
}
private void handleSuccessfulVerification() {
// Show full screen success message
showSuccessScreen();
// Navigate to receipt page after 2.5 seconds
animationHandler.postDelayed(() -> {
navigateToReceiptPage();
}, 2500);
}
private void showSuccessScreen() {
if (successScreen != null) {
// Hide all other UI components first
hideMainUIComponents();
// Set success message
if (successMessage != null) {
successMessage.setText("Pembayaran Berhasil");
}
// Show success screen with fade in animation
successScreen.setVisibility(View.VISIBLE);
successScreen.setAlpha(0f);
// Fade in the background
ObjectAnimator backgroundFadeIn = ObjectAnimator.ofFloat(successScreen, "alpha", 0f, 1f);
backgroundFadeIn.setDuration(500);
backgroundFadeIn.start();
// Add scale and bounce animation to success icon
if (successIcon != null) {
// Start with invisible icon
successIcon.setScaleX(0f);
successIcon.setScaleY(0f);
successIcon.setAlpha(0f);
// Scale animation with bounce effect
ObjectAnimator scaleX = ObjectAnimator.ofFloat(successIcon, "scaleX", 0f, 1.2f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(successIcon, "scaleY", 0f, 1.2f, 1f);
ObjectAnimator iconFadeIn = ObjectAnimator.ofFloat(successIcon, "alpha", 0f, 1f);
AnimatorSet iconAnimation = new AnimatorSet();
iconAnimation.playTogether(scaleX, scaleY, iconFadeIn);
iconAnimation.setDuration(800);
iconAnimation.setStartDelay(300);
iconAnimation.setInterpolator(new android.view.animation.OvershootInterpolator(1.2f));
iconAnimation.start();
}
// Add slide up animation to success message
if (successMessage != null) {
successMessage.setAlpha(0f);
successMessage.setTranslationY(50f);
ObjectAnimator messageSlideUp = ObjectAnimator.ofFloat(successMessage, "translationY", 50f, 0f);
ObjectAnimator messageFadeIn = ObjectAnimator.ofFloat(successMessage, "alpha", 0f, 1f);
AnimatorSet messageAnimation = new AnimatorSet();
messageAnimation.playTogether(messageSlideUp, messageFadeIn);
messageAnimation.setDuration(600);
messageAnimation.setStartDelay(600);
messageAnimation.setInterpolator(new android.view.animation.DecelerateInterpolator());
messageAnimation.start();
}
}
}
private void hideMainUIComponents() {
// Hide all main UI components to create clean full screen success
if (backNavigation != null) {
backNavigation.setVisibility(View.GONE);
}
// Hide the red header backgrounds
View redStatusBar = findViewById(R.id.red_status_bar);
View redHeaderBackground = findViewById(R.id.red_header_background);
if (redStatusBar != null) {
redStatusBar.setVisibility(View.GONE);
}
if (redHeaderBackground != null) {
redHeaderBackground.setVisibility(View.GONE);
}
// Hide PIN card
View pinCard = findViewById(R.id.pin_card);
if (pinCard != null) {
pinCard.setVisibility(View.GONE);
}
// Hide numpad
View numpadGrid = findViewById(R.id.numpad_grid);
if (numpadGrid != null) {
numpadGrid.setVisibility(View.GONE);
}
// Hide confirm button
if (confirmButton != null) {
confirmButton.setVisibility(View.GONE);
}
}
private void navigateToReceiptPage() {
// Create intent to navigate to receipt/struk page
Intent intent = new Intent(this, ReceiptActivity.class);
// Pass transaction data
intent.putExtra("transaction_amount", amount);
intent.putExtra("pin_verified", true);
intent.putExtra("source_activity", sourceActivity);
// Add transaction details (you can customize these)
intent.putExtra("merchant_name", "TOKO KLONTONG PAK EKO");
intent.putExtra("merchant_location", "Ciputat Baru, Tangsel");
intent.putExtra("transaction_id", generateTransactionId());
intent.putExtra("transaction_date", getCurrentDateTime());
intent.putExtra("payment_method", "Kartu Kredit");
intent.putExtra("card_type", "BCA");
intent.putExtra("tax_percentage", "11%");
intent.putExtra("service_fee", "500");
startActivity(intent);
// Set result for calling activity
Intent resultIntent = new Intent();
resultIntent.putExtra("pin_verified", true);
resultIntent.putExtra("pin_length", currentPin.length());
if (!TextUtils.isEmpty(amount)) {
resultIntent.putExtra(EXTRA_AMOUNT, amount);
}
setResult(RESULT_OK, resultIntent);
// Finish this activity
finish();
}
private String generateTransactionId() {
// Generate a simple transaction ID (in real app, this would come from backend)
return String.valueOf(System.currentTimeMillis() % 1000000000L);
}
private String getCurrentDateTime() {
// Get current date and time (in real app, use proper date formatting)
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd MMMM yyyy HH:mm", java.util.Locale.getDefault());
return sdf.format(new java.util.Date());
}
private void resetPinState() {
currentPin = new StringBuilder();
updatePinDisplay();
updateButtonState();
confirmButton.setText("Konfirmasi");
confirmButton.setEnabled(false);
}
// Animation methods
private void addClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.95f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(150);
animatorSet.start();
}
private void addButtonClickAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0.98f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0.98f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY);
animatorSet.setDuration(200);
animatorSet.start();
}
private void addInputFeedback() {
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(editTextPin, "alpha", 0.7f, 1f);
fadeIn.setDuration(200);
fadeIn.start();
}
private void addDeleteFeedback() {
ObjectAnimator shake = ObjectAnimator.ofFloat(editTextPin, "translationX", 0f, -10f, 10f, 0f);
shake.setDuration(300);
shake.start();
}
// Utility methods
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onBackPressed() {
// Prevent back press when success screen is showing
if (successScreen != null && successScreen.getVisibility() == View.VISIBLE) {
return;
}
navigateBack();
super.onBackPressed();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (animationHandler != null) {
animationHandler.removeCallbacksAndMessages(null);
}
}
// Public methods for testing
public String getCurrentPin() {
return currentPin.toString();
}
public boolean isConfirmButtonEnabled() {
return confirmButton.isEnabled();
}
// Static helper method to launch PinActivity
public static void launch(android.content.Context context, String sourceActivity, String amount) {
Intent intent = new Intent(context, PinActivity.class);
intent.putExtra(EXTRA_SOURCE_ACTIVITY, sourceActivity);
if (!TextUtils.isEmpty(amount)) {
intent.putExtra(EXTRA_AMOUNT, amount);
}
// Launch for result if context is an Activity
if (context instanceof AppCompatActivity) {
((AppCompatActivity) context).startActivityForResult(intent, 100);
} else {
context.startActivity(intent);
}
}
}

View File

@@ -0,0 +1,894 @@
package com.example.bdkipoc;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
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.JSONArray;
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 QrisActivity extends AppCompatActivity {
private ProgressBar progressBar;
private Button initiatePaymentButton;
private TextView statusTextView;
private EditText editTextAmount;
private TextView referenceIdTextView;
private LinearLayout backNavigation;
// Numpad buttons
private TextView btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn0, btn000, btnDelete;
private TextView descriptionText;
private String transactionId;
private String transactionUuid;
private String referenceId;
private int amount;
private JSONObject midtransResponse;
private StringBuilder currentAmount = new StringBuilder();
// ✅ FRONTEND DEDUPLICATION: Add SharedPreferences for tracking
private SharedPreferences transactionPrefs;
private static final String PREF_RECENT_REFERENCES = "recent_references";
private static final String PREF_LAST_TRANSACTION_TIME = "last_transaction_time";
private static final String PREF_CURRENT_REFERENCE = "current_reference";
private static final String PREF_LAST_SUCCESSFUL_TX = "last_successful_tx";
private static final long REFERENCE_COOLDOWN_MS = 60000; // 1 minute cooldown
private static final long TRANSACTION_COOLDOWN_MS = 5000; // 5 second cooldown
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";
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM="; // Replace with your actual key
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qris);
// ✅ Initialize SharedPreferences for duplicate prevention
transactionPrefs = getSharedPreferences("qris_transactions", MODE_PRIVATE);
// Initialize views
progressBar = findViewById(R.id.progressBar);
initiatePaymentButton = findViewById(R.id.initiatePaymentButton);
statusTextView = findViewById(R.id.statusTextView);
editTextAmount = findViewById(R.id.editTextAmount);
referenceIdTextView = findViewById(R.id.referenceIdTextView);
backNavigation = findViewById(R.id.back_navigation);
descriptionText = findViewById(R.id.descriptionText);
// Initialize 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);
// ✅ Generate unique reference ID with duplicate prevention
referenceId = generateUniqueReferenceId();
referenceIdTextView.setText(referenceId);
// Set up click listeners
initiatePaymentButton.setOnClickListener(v -> createTransaction());
backNavigation.setOnClickListener(v -> finish());
// Set up numpad listeners
setupNumpadListeners();
// Initially disable the button
initiatePaymentButton.setEnabled(false);
}
/**
* ✅ FRONTEND DEDUPLICATION: Generate unique reference ID with local tracking
*/
private String generateUniqueReferenceId() {
Log.d("QrisActivity", "🔄 Generating unique reference ID...");
String baseRef = "ref-" + generateRandomString(8);
// Check if this reference was recently created
String recentRefs = transactionPrefs.getString(PREF_RECENT_REFERENCES, "");
long currentTime = System.currentTimeMillis();
// Clean up old references (older than cooldown period)
StringBuilder validRefs = new StringBuilder();
if (!recentRefs.isEmpty()) {
String[] refs = recentRefs.split(",");
for (String refEntry : refs) {
if (refEntry.contains(":")) {
String[] parts = refEntry.split(":");
if (parts.length == 2) {
try {
long timestamp = Long.parseLong(parts[1]);
if (currentTime - timestamp < REFERENCE_COOLDOWN_MS) {
// Reference is still in cooldown period
if (validRefs.length() > 0) validRefs.append(",");
validRefs.append(refEntry);
}
} catch (NumberFormatException e) {
// Skip invalid entries
Log.w("QrisActivity", "Invalid reference entry: " + refEntry);
}
}
}
}
}
// Check if baseRef already exists in recent references
if (validRefs.length() > 0) {
String[] validRefArray = validRefs.toString().split(",");
for (String refEntry : validRefArray) {
if (refEntry.startsWith(baseRef + ":")) {
// Reference already exists, generate a new one
Log.w("QrisActivity", "⚠️ Reference " + baseRef + " recently used, generating new one");
return generateUniqueReferenceId(); // Recursive call with new random string
}
}
}
// Add this reference to recent references
if (validRefs.length() > 0) validRefs.append(",");
validRefs.append(baseRef).append(":").append(currentTime);
// Save updated references
transactionPrefs.edit()
.putString(PREF_RECENT_REFERENCES, validRefs.toString())
.apply();
Log.d("QrisActivity", "✅ Generated unique reference: " + baseRef);
return baseRef;
}
/**
* ✅ FRONTEND DEDUPLICATION: Check if transaction is currently being processed
*/
private boolean isTransactionInProgress() {
long lastTransactionTime = transactionPrefs.getLong(PREF_LAST_TRANSACTION_TIME, 0);
long currentTime = System.currentTimeMillis();
// If last transaction was less than cooldown period, consider it in progress
boolean inProgress = (currentTime - lastTransactionTime) < TRANSACTION_COOLDOWN_MS;
if (inProgress) {
Log.w("QrisActivity", "⏸️ Transaction in progress, cooldown active");
}
return inProgress;
}
/**
* ✅ FRONTEND DEDUPLICATION: Mark transaction processing status
*/
private void markTransactionInProgress(boolean inProgress) {
SharedPreferences.Editor editor = transactionPrefs.edit();
if (inProgress) {
editor.putLong(PREF_LAST_TRANSACTION_TIME, System.currentTimeMillis())
.putString(PREF_CURRENT_REFERENCE, referenceId);
Log.d("QrisActivity", "🔒 Marked transaction in progress: " + referenceId);
} else {
editor.remove(PREF_CURRENT_REFERENCE);
Log.d("QrisActivity", "🔓 Cleared transaction progress status");
}
editor.apply();
}
/**
* ✅ FRONTEND DEDUPLICATION: Save successful transaction for future reference
*/
private void saveSuccessfulTransaction() {
try {
JSONObject txData = new JSONObject();
txData.put("reference_id", referenceId);
txData.put("transaction_uuid", transactionUuid);
txData.put("amount", amount);
txData.put("created_at", System.currentTimeMillis());
// Save to SharedPreferences
transactionPrefs.edit()
.putString(PREF_LAST_SUCCESSFUL_TX, txData.toString())
.putLong("last_success_time", System.currentTimeMillis())
.apply();
Log.d("QrisActivity", "💾 Saved successful transaction: " + referenceId);
} catch (Exception e) {
Log.w("QrisActivity", "Failed to save transaction data: " + e.getMessage());
}
}
/**
* ✅ FRONTEND DEDUPLICATION: Create client info for better backend tracking
*/
private JSONObject createClientInfo() {
try {
JSONObject clientInfo = new JSONObject();
clientInfo.put("app_version", "1.0.0");
clientInfo.put("platform", "android");
clientInfo.put("timestamp", System.currentTimeMillis());
clientInfo.put("session_id", generateRandomString(16));
clientInfo.put("reference_generation_time", System.currentTimeMillis());
return clientInfo;
} catch (JSONException e) {
Log.w("QrisActivity", "Failed to create client info: " + e.getMessage());
return new JSONObject();
}
}
private void setupNumpadListeners() {
View.OnClickListener numberClickListener = v -> {
TextView button = (TextView) v;
String number = button.getText().toString();
appendNumber(number);
};
btn1.setOnClickListener(numberClickListener);
btn2.setOnClickListener(numberClickListener);
btn3.setOnClickListener(numberClickListener);
btn4.setOnClickListener(numberClickListener);
btn5.setOnClickListener(numberClickListener);
btn6.setOnClickListener(numberClickListener);
btn7.setOnClickListener(numberClickListener);
btn8.setOnClickListener(numberClickListener);
btn9.setOnClickListener(numberClickListener);
btn0.setOnClickListener(numberClickListener);
btn000.setOnClickListener(numberClickListener);
btnDelete.setOnClickListener(v -> deleteLastDigit());
}
private void appendNumber(String number) {
currentAmount.append(number);
updateAmountDisplay();
}
private void deleteLastDigit() {
if (currentAmount.length() > 0) {
currentAmount.deleteCharAt(currentAmount.length() - 1);
updateAmountDisplay();
}
}
private void updateAmountDisplay() {
String amountStr = currentAmount.toString();
if (amountStr.isEmpty()) {
editTextAmount.setVisibility(View.GONE);
descriptionText.setText("Pastikan kembali nominal pembayaran pelanggan Anda");
initiatePaymentButton.setEnabled(false);
} else {
editTextAmount.setVisibility(View.VISIBLE);
editTextAmount.setText(formatAmount(amountStr));
descriptionText.setText("Tekan Konfirmasi untuk melanjutkan");
// Enable button if amount is valid
try {
int amt = Integer.parseInt(amountStr);
initiatePaymentButton.setEnabled(amt >= 1000);
} catch (NumberFormatException e) {
initiatePaymentButton.setEnabled(false);
}
}
}
private String formatAmount(String amount) {
if (amount.isEmpty()) return "";
try {
long num = Long.parseLong(amount);
return String.format("%,d", num);
} catch (NumberFormatException e) {
return amount;
}
}
/**
* ✅ ENHANCED: Modified createTransaction with comprehensive duplicate prevention
*/
private void createTransaction() {
if (currentAmount.length() == 0) {
Toast.makeText(this, "Masukkan jumlah pembayaran", Toast.LENGTH_SHORT).show();
return;
}
// ✅ FRONTEND CHECK: Prevent rapid duplicate submissions
if (isTransactionInProgress()) {
Toast.makeText(this, "Transaksi sedang diproses, harap tunggu...", Toast.LENGTH_SHORT).show();
return;
}
Log.d("QrisActivity", "🚀 Starting transaction creation process");
Log.d("QrisActivity", " Reference ID: " + referenceId);
Log.d("QrisActivity", " Amount: " + currentAmount.toString());
progressBar.setVisibility(View.VISIBLE);
initiatePaymentButton.setEnabled(false);
statusTextView.setVisibility(View.VISIBLE);
statusTextView.setText("Creating transaction...");
// Mark transaction as in progress
markTransactionInProgress(true);
new CreateTransactionTask().execute();
}
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() {
try {
// MIDTRANS_AUTH = 'Basic base64string'
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
String decodedString = new String(decoded);
// Format is usually 'SB-Mid-server-xxxx:'. Remove trailing colon if present.
return decodedString.replace(":", "");
} catch (Exception e) {
Log.e("MidtransCharge", "Error decoding server key: " + e.getMessage());
return "";
}
}
private boolean isValidServerKey(String serverKey) {
return serverKey != null &&
serverKey.startsWith("SB-Mid-server-") &&
serverKey.length() > 20;
}
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) {
Log.e("MidtransCharge", "Error generating signature: " + e.getMessage());
return "";
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
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();
// ✅ ENHANCED LOGGING: Better tracking for debugging
Log.d("MidtransCharge", "=== TRANSACTION CREATION START ===");
Log.d("MidtransCharge", "Reference ID: " + referenceId);
Log.d("MidtransCharge", "Transaction UUID: " + transactionUuid);
Log.d("MidtransCharge", "Timestamp: " + System.currentTimeMillis());
// 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);
// ✅ FRONTEND ENHANCEMENT: Add client-side metadata for better tracking
payload.put("client_info", createClientInfo());
payload.put("is_initial_creation", true); // Mark as initial creation
// Get amount from current input
String amountText = currentAmount.toString();
Log.d("MidtransCharge", "Raw amount text: " + amountText);
try {
// Parse amount - expecting integer in lowest denomination (Indonesian Rupiah)
amount = Integer.parseInt(amountText);
// Validate minimum amount
if (amount < 1000) {
errorMessage = "Minimum amount is IDR 1,000";
return false;
}
// Validate maximum amount for testing
if (amount > 10000000) {
errorMessage = "Maximum amount is IDR 10,000,000";
return false;
}
Log.d("MidtransCharge", "Parsed amount: " + amount);
} catch (NumberFormatException e) {
Log.e("MidtransCharge", "Amount parsing error: " + e.getMessage());
errorMessage = "Invalid amount format. Please enter numbers only.";
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");
Log.d("MidtransCharge", "Backend transaction payload: " + payload.toString());
// 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.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
// ✅ FRONTEND ENHANCEMENT: Add client headers for better backend tracking
conn.setRequestProperty("X-Client-Reference", referenceId);
conn.setRequestProperty("X-Client-Timestamp", String.valueOf(System.currentTimeMillis()));
conn.setRequestProperty("X-Client-Version", "1.0.0");
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();
Log.d("MidtransCharge", "Backend response code: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
// Success - process 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());
}
Log.d("MidtransCharge", "Backend success response: " + response.toString());
// Parse the response to get transaction ID
JSONObject jsonResponse = new JSONObject(response.toString());
JSONObject data = jsonResponse.getJSONObject("data");
transactionId = String.valueOf(data.getInt("id"));
Log.d("MidtransCharge", "✅ Created transaction ID: " + transactionId);
// ✅ FRONTEND SUCCESS: Save successful transaction info
saveSuccessfulTransaction();
// Now generate QRIS via Midtrans
return generateQris(amount);
} else if (responseCode == 409 || responseCode == 400) {
// ✅ ENHANCED DUPLICATE HANDLING: Handle gracefully
Log.w("MidtransCharge", "⚠️ Potential duplicate detected (HTTP " + responseCode + ")");
// Try to read and parse error response
try {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8"));
StringBuilder errorResponse = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
errorResponse.append(responseLine.trim());
}
String errorResponseStr = errorResponse.toString();
Log.d("MidtransCharge", "Error response: " + errorResponseStr);
// Check if it's actually a duplicate reference error
if (errorResponseStr.toLowerCase().contains("duplicate") ||
errorResponseStr.toLowerCase().contains("already exists") ||
errorResponseStr.toLowerCase().contains("reference") ||
responseCode == 409) {
Log.i("MidtransCharge", "✅ Confirmed duplicate reference - proceeding with QRIS generation");
// For duplicates, we can still generate QRIS with existing reference
return generateQris(amount);
}
} catch (Exception e) {
Log.w("MidtransCharge", "Could not parse error response: " + e.getMessage());
}
// If we can't determine the exact error, try QRIS generation anyway
Log.i("MidtransCharge", "🔄 Proceeding with QRIS generation despite backend error");
return generateQris(amount);
} else {
// Other HTTP errors
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());
}
String errorResponse = response.toString();
Log.e("MidtransCharge", "❌ Backend error (HTTP " + responseCode + "): " + errorResponse);
errorMessage = "Backend error (" + responseCode + "): " + errorResponse;
return false;
}
} catch (Exception e) {
Log.e("MidtransCharge", "❌ Backend transaction exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
return false;
}
}
private boolean generateQris(int amount) {
try {
// Validate server key first
String serverKey = getServerKey();
if (!isValidServerKey(serverKey)) {
Log.e("MidtransCharge", "Invalid server key format");
errorMessage = "Invalid server key configuration";
return false;
}
Log.d("MidtransCharge", "Using server key: " + serverKey.substring(0, Math.min(20, serverKey.length())) + "...");
// 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);
// Add customer details (recommended for better success rate)
JSONObject customerDetails = new JSONObject();
customerDetails.put("first_name", "Test");
customerDetails.put("last_name", "Customer");
customerDetails.put("email", "test@example.com");
customerDetails.put("phone", "081234567890");
payload.put("customer_details", customerDetails);
// Add item details (optional but recommended)
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
item.put("id", "item1");
item.put("price", amount);
item.put("quantity", 1);
item.put("name", "QRIS Payment - " + referenceId);
itemDetails.put(item);
payload.put("item_details", itemDetails);
// ✅ FRONTEND ENHANCEMENT: Add tracking info for reference linkage
JSONObject customField1 = new JSONObject();
customField1.put("app_reference_id", referenceId);
customField1.put("creation_time", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new java.util.Date()));
customField1.put("client_version", "1.0.0");
payload.put("custom_field1", customField1.toString());
// Log the request details
Log.d("MidtransCharge", "=== MIDTRANS QRIS REQUEST ===");
Log.d("MidtransCharge", "URL: " + MIDTRANS_CHARGE_URL);
Log.d("MidtransCharge", "Authorization: " + MIDTRANS_AUTH);
Log.d("MidtransCharge", "X-Override-Notification: " + WEBHOOK_URL);
Log.d("MidtransCharge", "Reference ID: " + referenceId);
Log.d("MidtransCharge", "Order ID: " + transactionUuid);
Log.d("MidtransCharge", "Amount: " + amount);
Log.d("MidtransCharge", "================================");
// Make the API call to Midtrans
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
conn.setDoOutput(true);
conn.setConnectTimeout(30000); // 30 seconds
conn.setReadTimeout(30000); // 30 seconds
try (OutputStream os = conn.getOutputStream()) {
byte[] input = payload.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d("MidtransCharge", "Midtrans HTTP Response Code: " + responseCode);
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());
}
Log.d("MidtransCharge", "Midtrans Success Response: " + response.toString());
// Parse the response
midtransResponse = new JSONObject(response.toString());
// Check if response contains error within success response
if (midtransResponse.has("status_code")) {
String statusCode = midtransResponse.getString("status_code");
if (!statusCode.equals("201")) {
String statusMessage = midtransResponse.optString("status_message", "Unknown error");
Log.e("MidtransCharge", "Midtrans Error in response: " + statusCode + " - " + statusMessage);
errorMessage = "Midtrans Error: " + statusMessage + " (Code: " + statusCode + ")";
return false;
}
}
// Validate response has required fields
if (!midtransResponse.has("actions") ||
!midtransResponse.has("transaction_id") ||
!midtransResponse.has("gross_amount")) {
Log.e("MidtransCharge", "Missing required fields in Midtrans response");
errorMessage = "Invalid response from Midtrans - missing required fields";
return false;
}
Log.d("MidtransCharge", "✅ QRIS generation successful!");
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", "Midtrans HTTP " + responseCode + ": " + errorResponse.toString());
// Try to parse error JSON for better error message
try {
JSONObject errorJson = new JSONObject(errorResponse.toString());
// Handle different error response formats
String errorMessage = "";
if (errorJson.has("error_messages")) {
errorMessage = errorJson.optString("error_messages", "Unknown error");
} else if (errorJson.has("status_message")) {
errorMessage = errorJson.optString("status_message", "Unknown error");
} else if (errorJson.has("message")) {
errorMessage = errorJson.optString("message", "Unknown error");
} else {
errorMessage = errorResponse.toString();
}
this.errorMessage = "Midtrans Error: " + errorMessage;
} catch (JSONException e) {
this.errorMessage = "HTTP " + responseCode + ": " + errorResponse.toString();
}
} else {
Log.e("MidtransCharge", "HTTP " + responseCode + ": No error stream available");
this.errorMessage = "Error generating QRIS: HTTP " + responseCode + ": No error stream available";
}
return false;
}
} catch (Exception e) {
Log.e("MidtransCharge", "Midtrans QRIS generation exception: " + e.getMessage(), e);
errorMessage = "Network error: " + e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
// ✅ FRONTEND CLEANUP: Always clear in-progress status
markTransactionInProgress(false);
if (success && midtransResponse != null) {
try {
// Extract needed values from midtransResponse
JSONArray actionsArray = midtransResponse.getJSONArray("actions");
if (actionsArray.length() == 0) {
Log.e("MidtransCharge", "No actions found in Midtrans response");
Toast.makeText(QrisActivity.this, "Error: No QR code URL found in response", Toast.LENGTH_LONG).show();
initiatePaymentButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
statusTextView.setVisibility(View.GONE);
return;
}
JSONObject actions = actionsArray.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");
// Send raw amount as string without decimal conversion
String rawAmountString = String.valueOf(amount); // Keep original integer amount
// Log everything before launching activity
Log.d("MidtransCharge", "=== LAUNCHING QRIS RESULT ACTIVITY ===");
Log.d("MidtransCharge", "✅ Transaction created successfully!");
Log.d("MidtransCharge", "qrImageUrl: " + qrImageUrl);
Log.d("MidtransCharge", "amount (raw): " + amount);
Log.d("MidtransCharge", "rawAmountString: " + rawAmountString);
Log.d("MidtransCharge", "referenceId: " + referenceId);
Log.d("MidtransCharge", "transactionUuid (orderId): " + transactionUuid);
Log.d("MidtransCharge", "transaction_id: " + transactionId);
Log.d("MidtransCharge", "transactionTime: " + transactionTime);
Log.d("MidtransCharge", "acquirer: " + acquirer);
Log.d("MidtransCharge", "merchantId: " + merchantId);
Log.d("MidtransCharge", "========================================");
// ✅ FINAL SUCCESS: Update transaction status in preferences
transactionPrefs.edit()
.putString("last_qris_url", qrImageUrl)
.putString("last_qris_reference", referenceId)
.putLong("last_qris_time", System.currentTimeMillis())
.apply();
// Launch QrisResultActivity
Intent intent = new Intent(QrisActivity.this, QrisResultActivity.class);
intent.putExtra("qrImageUrl", qrImageUrl);
intent.putExtra("amount", amount); // Keep as int
intent.putExtra("referenceId", referenceId);
intent.putExtra("orderId", transactionUuid); // Order ID
intent.putExtra("transactionId", transactionId); // Actual Midtrans transaction_id
intent.putExtra("grossAmount", rawAmountString); // Raw amount as string (no decimals)
intent.putExtra("transactionTime", transactionTime); // For timestamp
intent.putExtra("acquirer", acquirer);
intent.putExtra("merchantId", merchantId);
try {
startActivity(intent);
finish(); // Close QrisActivity
Log.d("MidtransCharge", "🎉 Successfully launched QrisResultActivity");
} 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();
// Re-enable button on error
initiatePaymentButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
statusTextView.setVisibility(View.GONE);
}
return;
} catch (JSONException e) {
Log.e("MidtransCharge", "QRIS response JSON error: " + e.getMessage(), e);
Toast.makeText(QrisActivity.this, "Error processing QRIS response: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
} else {
// Handle error case
String message = (errorMessage != null && !errorMessage.isEmpty()) ?
errorMessage : "Unknown error occurred. Please check your connection and try again.";
Log.e("MidtransCharge", "❌ Transaction failed: " + message);
Toast.makeText(QrisActivity.this, message, Toast.LENGTH_LONG).show();
// Re-enable button for retry
initiatePaymentButton.setEnabled(true);
}
// Always hide progress indicators
progressBar.setVisibility(View.GONE);
statusTextView.setVisibility(View.GONE);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// ✅ CLEANUP: Clear any in-progress status when activity is destroyed
markTransactionInProgress(false);
Log.d("QrisActivity", "🧹 QrisActivity destroyed, cleared progress status");
}
@Override
protected void onPause() {
super.onPause();
// Keep progress status when paused (user might come back)
Log.d("QrisActivity", "⏸️ QrisActivity paused");
}
@Override
protected void onResume() {
super.onResume();
Log.d("QrisActivity", "▶️ QrisActivity resumed");
// Check if there's a recent successful transaction
String lastSuccessfulTx = transactionPrefs.getString(PREF_LAST_SUCCESSFUL_TX, "");
if (!lastSuccessfulTx.isEmpty()) {
try {
JSONObject txData = new JSONObject(lastSuccessfulTx);
String lastRef = txData.getString("reference_id");
long lastTime = txData.getLong("created_at");
// If last successful transaction was recent (within 5 minutes) and same reference
if (System.currentTimeMillis() - lastTime < 300000 && lastRef.equals(referenceId)) {
Log.d("QrisActivity", "🔄 Recent successful transaction detected for same reference");
}
} catch (Exception e) {
Log.w("QrisActivity", "Could not parse last successful transaction: " + e.getMessage());
}
}
}
@Override
public void onBackPressed() {
// ✅ CLEANUP: Clear progress status when user goes back
markTransactionInProgress(false);
super.onBackPressed();
}
}

View File

@@ -9,50 +9,59 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.*;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class QrisResultActivity extends AppCompatActivity {
// UI Components
private ImageView qrImageView;
private TextView amountTextView;
private TextView referenceTextView;
private Button downloadQrisButton;
private Button checkStatusButton;
private TextView statusTextView;
private Button returnMainButton;
private TextView amountTextView, referenceTextView, statusTextView;
private TextView timerTextView, qrStatusTextView;
private Button downloadQrisButton, checkStatusButton, returnMainButton;
private ProgressBar progressBar;
private String orderId;
private String grossAmount;
private String referenceId;
private String transactionId;
private String transactionTime;
private String acquirer;
private String merchantId;
private String backendBase = "https://be-edc.msvc.app";
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
// QR Refresh Components
private Handler qrRefreshHandler;
private Runnable qrRefreshRunnable;
private int countdownSeconds = 60;
private boolean isQrRefreshActive = true;
// Transaction Data
private String orderId, grossAmount, referenceId, transactionId;
private String transactionTime, acquirer, merchantId, currentQrImageUrl;
private int originalAmount;
private List<String> allOrderIds = new ArrayList<>();
// Configuration
private static final String BACKEND_BASE = "https://be-edc.msvc.app";
private static final String WEBHOOK_URL = "https://be-edc.msvc.app/webhooks/midtrans";
private static final String MIDTRANS_AUTH = "Basic U0ItTWlkLXNlcnZlci1JM2RJWXdIRzVuamVMeHJCMVZ5endWMUM=";
private static final String MIDTRANS_CHARGE_URL = "https://api.sandbox.midtrans.com/v2/charge";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qris_result);
initializeViews();
extractIntentData();
validateAndSetupUI();
startMonitoring();
setupClickListeners();
}
private void initializeViews() {
qrImageView = findViewById(R.id.qrImageView);
amountTextView = findViewById(R.id.amountTextView);
referenceTextView = findViewById(R.id.referenceTextView);
@@ -61,10 +70,15 @@ public class QrisResultActivity extends AppCompatActivity {
statusTextView = findViewById(R.id.statusTextView);
returnMainButton = findViewById(R.id.returnMainButton);
progressBar = findViewById(R.id.progressBar);
timerTextView = findViewById(R.id.timerTextView);
qrStatusTextView = findViewById(R.id.qrStatusTextView);
qrRefreshHandler = new Handler(Looper.getMainLooper());
}
private void extractIntentData() {
Intent intent = getIntent();
String qrImageUrl = intent.getStringExtra("qrImageUrl");
int amount = intent.getIntExtra("amount", 0);
currentQrImageUrl = intent.getStringExtra("qrImageUrl");
originalAmount = intent.getIntExtra("amount", 0);
referenceId = intent.getStringExtra("referenceId");
orderId = intent.getStringExtra("orderId");
grossAmount = intent.getStringExtra("grossAmount");
@@ -73,215 +87,603 @@ public class QrisResultActivity extends AppCompatActivity {
acquirer = intent.getStringExtra("acquirer");
merchantId = intent.getStringExtra("merchantId");
allOrderIds.add(orderId);
Log.d("QrisResult", "Initialized with Order ID: " + orderId);
}
private void validateAndSetupUI() {
if (orderId == null || transactionId == null) {
Log.e("QrisResultFlow", "orderId or transactionId is null! Intent extras: " + intent.getExtras());
android.widget.Toast.makeText(this, "Missing transaction details!", android.widget.Toast.LENGTH_LONG).show();
Toast.makeText(this, "Missing transaction details!", Toast.LENGTH_LONG).show();
finish();
return;
}
// Get the exact amount from the grossAmount string value instead of the integer
String amountStr = "Amount: " + grossAmount;
amountTextView.setText(amountStr);
String formattedAmount = formatCurrency(grossAmount != null ? grossAmount : String.valueOf(originalAmount));
amountTextView.setText(formattedAmount);
referenceTextView.setText("Reference ID: " + referenceId);
// Load QR image
new DownloadImageTask(qrImageView).execute(qrImageUrl);
loadQrImage(currentQrImageUrl);
// Disable check status button initially
checkStatusButton.setEnabled(false);
// Start polling for pending payment log
statusTextView.setText("Waiting for payment...");
qrStatusTextView.setText("QR Code akan refresh dalam");
}
private void startMonitoring() {
startQrRefreshTimer();
startPaymentMonitoring();
pollPendingPaymentLog(orderId);
// Download QRIS button
downloadQrisButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
qrImageView.setDrawingCacheEnabled(true);
Bitmap bitmap = qrImageView.getDrawingCache();
if (bitmap != null) {
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
}
qrImageView.setDrawingCacheEnabled(false);
}
});
// Check Payment Status button
checkStatusButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
simulateWebhook();
}
});
// Return to Main Screen button
returnMainButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(QrisResultActivity.this, com.example.bdkipoc.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finishAffinity();
}
});
}
private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
DownloadImageTask(ImageView bmImage) {
this.bmImage = bmImage;
}
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();
InputStream input = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(input);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
protected void onPostExecute(Bitmap result) {
if (result != null) {
bmImage.setImageBitmap(result);
}
}
}
private void startQrRefreshTimer() {
countdownSeconds = 60;
isQrRefreshActive = true;
// Save bitmap to gallery
private void saveImageToGallery(Bitmap bitmap, String fileName) {
try {
String savedImageURL = android.provider.MediaStore.Images.Media.insertImage(
getContentResolver(), bitmap, fileName, "QRIS Payment QR Code");
if (savedImageURL != null) {
android.widget.Toast.makeText(this, "QRIS saved to gallery", android.widget.Toast.LENGTH_SHORT).show();
} else {
android.widget.Toast.makeText(this, "Failed to save QRIS", android.widget.Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
android.widget.Toast.makeText(this, "Error saving QRIS: " + e.getMessage(), android.widget.Toast.LENGTH_LONG).show();
}
}
qrRefreshRunnable = new Runnable() {
@Override
public void run() {
if (!isQrRefreshActive) return;
private void pollPendingPaymentLog(final String orderId) {
Log.d("QrisResultFlow", "Polling for orderId (transaction_uuid): " + orderId);
progressBar.setVisibility(View.VISIBLE);
new Thread(() -> {
int maxAttempts = 10;
int intervalMs = 1500;
int attempt = 0;
boolean found = false;
while (attempt < maxAttempts && !found) {
try {
String urlStr = backendBase + "/api-logs?request_body_search_strict={\"order_id\":\"" + orderId + "\"}";
Log.d("QrisResultFlow", "Polling URL: " + urlStr);
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
JSONObject json = new JSONObject(response.toString());
JSONArray results = json.optJSONArray("results");
if (results != null && results.length() > 0) {
for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null && "pending".equals(reqBody.optString("transaction_status"))) {
found = true;
break;
}
}
}
}
} catch (Exception e) {
Log.e("QrisResultFlow", "Polling error: " + e.getMessage(), e);
}
if (!found) {
attempt++;
try { Thread.sleep(intervalMs); } catch (InterruptedException ignored) {}
}
}
final boolean logFound = found;
new Handler(Looper.getMainLooper()).post(() -> {
progressBar.setVisibility(View.GONE);
if (logFound) {
checkStatusButton.setEnabled(true);
android.widget.Toast.makeText(QrisResultActivity.this, "Pending payment log found!", android.widget.Toast.LENGTH_SHORT).show();
if (countdownSeconds > 0) {
timerTextView.setText(String.valueOf(countdownSeconds));
countdownSeconds--;
qrRefreshHandler.postDelayed(this, 1000);
} else {
android.widget.Toast.makeText(QrisResultActivity.this, "Pending payment log NOT found.", android.widget.Toast.LENGTH_LONG).show();
refreshQrCode();
}
});
}
};
qrRefreshHandler.post(qrRefreshRunnable);
}
private void refreshQrCode() {
if (!isQrRefreshActive) return;
timerTextView.setText("...");
qrStatusTextView.setText("Generating new QR Code...");
new Thread(() -> {
try {
String newQrUrl = generateNewQrCode();
runOnUiThread(() -> {
if (newQrUrl != null) {
currentQrImageUrl = newQrUrl;
loadQrImage(newQrUrl);
allOrderIds.add(orderId);
updateUIAfterRefresh();
countdownSeconds = 60;
qrStatusTextView.setText("QR Code akan refresh dalam");
Toast.makeText(this, "QR Code refreshed", Toast.LENGTH_SHORT).show();
} else {
qrStatusTextView.setText("Failed to refresh QR - trying again in 30s");
countdownSeconds = 30;
}
qrRefreshHandler.postDelayed(qrRefreshRunnable, 1000);
});
} catch (Exception e) {
Log.e("QrisResult", "QR refresh error: " + e.getMessage(), e);
runOnUiThread(() -> {
qrStatusTextView.setText("QR refresh error - retrying in 30s");
countdownSeconds = 30;
qrRefreshHandler.postDelayed(qrRefreshRunnable, 1000);
});
}
}).start();
}
// Simulate webhook callback
private void simulateWebhook() {
progressBar.setVisibility(View.VISIBLE);
private String generateNewQrCode() {
try {
String newOrderId = java.util.UUID.randomUUID().toString();
JSONObject customField = new JSONObject();
customField.put("refresh_of", orderId);
customField.put("refresh_time", getCurrentISOTime());
customField.put("original_reference", referenceId);
JSONObject payload = createQrisPayload(newOrderId, customField);
String response = sendMidtransRequest(payload);
if (response != null) {
JSONObject jsonResponse = new JSONObject(response);
if (jsonResponse.has("actions")) {
JSONArray actions = jsonResponse.getJSONArray("actions");
if (actions.length() > 0) {
String newQrUrl = actions.getJSONObject(0).getString("url");
// Update transaction info
this.transactionId = jsonResponse.optString("transaction_id", transactionId);
this.transactionTime = jsonResponse.optString("transaction_time", transactionTime);
this.orderId = newOrderId;
return newQrUrl;
}
}
}
} catch (Exception e) {
Log.e("QrisResult", "Generate QR error: " + e.getMessage(), e);
}
return null;
}
private JSONObject createQrisPayload(String orderIdParam, JSONObject customField) throws Exception {
JSONObject payload = new JSONObject();
payload.put("payment_type", "qris");
JSONObject transactionDetails = new JSONObject();
transactionDetails.put("order_id", orderIdParam);
transactionDetails.put("gross_amount", originalAmount);
payload.put("transaction_details", transactionDetails);
JSONObject customerDetails = new JSONObject();
customerDetails.put("first_name", "Test");
customerDetails.put("last_name", "Customer");
customerDetails.put("email", "test@example.com");
customerDetails.put("phone", "081234567890");
payload.put("customer_details", customerDetails);
JSONArray itemDetails = new JSONArray();
JSONObject item = new JSONObject();
item.put("id", "item1_refresh_" + System.currentTimeMillis());
item.put("price", originalAmount);
item.put("quantity", 1);
item.put("name", "QRIS Payment - Refreshed (Ref: " + referenceId + ")");
itemDetails.put(item);
payload.put("item_details", itemDetails);
payload.put("custom_field1", customField.toString());
return payload;
}
private String sendMidtransRequest(JSONObject payload) {
try {
URL url = new URI(MIDTRANS_CHARGE_URL).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", MIDTRANS_AUTH);
conn.setRequestProperty("X-Override-Notification", WEBHOOK_URL);
conn.setDoOutput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
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) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line.trim());
}
return response.toString();
}
} catch (Exception e) {
Log.e("QrisResult", "Midtrans request error: " + e.getMessage(), e);
}
return null;
}
private void startPaymentMonitoring() {
Handler paymentHandler = new Handler(Looper.getMainLooper());
Runnable paymentRunnable = new Runnable() {
@Override
public void run() {
checkAllOrderIdsStatus();
if (!isFinishing() && isQrRefreshActive) {
paymentHandler.postDelayed(this, 3000);
}
}
};
paymentHandler.post(paymentRunnable);
}
private void checkAllOrderIdsStatus() {
new Thread(() -> {
try {
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
payload.put("transaction_status", "settlement");
payload.put("transaction_id", transactionId); // Use the actual transaction_id
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", "dummy_signature");
payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
payload.put("payment_type", "qris");
payload.put("order_id", orderId); // Use order_id
payload.put("merchant_id", merchantId != null ? merchantId : "DUMMY_MERCHANT_ID");
payload.put("issuer", acquirer != null ? acquirer : "gopay");
payload.put("gross_amount", grossAmount); // Use exact gross amount
payload.put("fraud_status", "accept");
payload.put("currency", "IDR");
payload.put("acquirer", acquirer != null ? acquirer : "gopay");
payload.put("shopeepay_reference_number", "");
payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID");
Log.d("QrisResultFlow", "Webhook payload: " + payload.toString());
for (String checkOrderId : allOrderIds) {
if (checkOrderId == null || checkOrderId.isEmpty()) continue;
URL url = new URL(webhookUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(payload.toString().getBytes());
os.flush();
os.close();
int responseCode = conn.getResponseCode();
BufferedReader br = new BufferedReader(new InputStreamReader(
responseCode < 400 ? conn.getInputStream() : conn.getErrorStream()));
if (checkPaymentStatus(checkOrderId)) {
runOnUiThread(() -> {
stopQrRefresh();
syncTransactionStatusToBackend("PAID");
showPaymentSuccess();
Toast.makeText(this, "Payment Successful! 🎉", Toast.LENGTH_LONG).show();
});
return;
}
}
} catch (Exception e) {
Log.e("QrisResult", "Payment status check error: " + e.getMessage(), e);
}
}).start();
}
private boolean checkPaymentStatus(String checkOrderId) {
try {
String urlStr = BACKEND_BASE + "/api-logs?request_body_search_strict=" +
java.net.URLEncoder.encode("{\"order_id\":\"" + checkOrderId + "\"}", "UTF-8");
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if (conn.getResponseCode() == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
Log.d("QrisResultFlow", "Webhook response: " + response.toString());
} catch (Exception e) {
Log.e("QrisResultFlow", "Webhook error: " + e.getMessage(), e);
JSONObject json = new JSONObject(response.toString());
JSONArray results = json.optJSONArray("results");
if (results != null && results.length() > 0) {
for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null) {
String transactionStatus = reqBody.optString("transaction_status");
String logOrderId = reqBody.optString("order_id");
if (checkOrderId.equals(logOrderId) &&
(transactionStatus.equals("settlement") ||
transactionStatus.equals("capture") ||
transactionStatus.equals("success"))) {
return true;
}
}
}
}
}
new Handler(Looper.getMainLooper()).post(() -> {
} catch (Exception e) {
Log.e("QrisResult", "Payment check error: " + e.getMessage(), e);
}
return false;
}
private void loadQrImage(String qrImageUrl) {
if (qrImageUrl != null && !qrImageUrl.isEmpty()) {
new DownloadImageTask(qrImageView).execute(qrImageUrl);
} else {
qrImageView.setVisibility(View.GONE);
downloadQrisButton.setEnabled(false);
}
}
private void setupClickListeners() {
downloadQrisButton.setOnClickListener(v -> downloadQrCode());
checkStatusButton.setOnClickListener(v -> {
stopQrRefresh();
simulateWebhook();
});
returnMainButton.setOnClickListener(v -> returnToMain());
}
private void stopQrRefresh() {
isQrRefreshActive = false;
if (qrRefreshHandler != null && qrRefreshRunnable != null) {
qrRefreshHandler.removeCallbacks(qrRefreshRunnable);
}
timerTextView.setVisibility(View.GONE);
qrStatusTextView.setVisibility(View.GONE);
}
private void showPaymentSuccess() {
stopQrRefresh();
qrImageView.setVisibility(View.GONE);
amountTextView.setVisibility(View.GONE);
referenceTextView.setVisibility(View.GONE);
downloadQrisButton.setVisibility(View.GONE);
checkStatusButton.setVisibility(View.GONE);
statusTextView.setText("✅ Payment Successful!\n\nTransaction ID: " + transactionId +
"\nReference: " + referenceId +
"\nAmount: " + formatCurrency(grossAmount));
returnMainButton.setVisibility(View.VISIBLE);
new Handler(Looper.getMainLooper()).postDelayed(this::launchReceiptActivity, 2000);
}
private void syncTransactionStatusToBackend(String status) {
new Thread(() -> {
try {
JSONObject updatePayload = new JSONObject();
updatePayload.put("status", status);
updatePayload.put("transaction_status", status);
updatePayload.put("updated_at", getCurrentISOTime());
updatePayload.put("settlement_time", getCurrentISOTime());
JSONObject body = new JSONObject();
body.put("reference_id", referenceId);
body.put("update_data", updatePayload);
String updateUrl = BACKEND_BASE + "/transactions/update-by-reference";
URL url = new URI(updateUrl).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = body.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d("QrisResult", "Backend sync response: " + responseCode);
} catch (Exception e) {
Log.e("QrisResult", "Backend sync error: " + e.getMessage(), e);
}
}).start();
}
private void simulateWebhook() {
progressBar.setVisibility(View.VISIBLE);
statusTextView.setText("Simulating payment...");
checkStatusButton.setEnabled(false);
stopQrRefresh();
new Thread(() -> {
try {
JSONObject payload = createWebhookPayload();
sendWebhookRequest(payload);
Thread.sleep(2000);
} catch (Exception e) {
Log.e("QrisResult", "Webhook simulation error: " + e.getMessage(), e);
}
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
// Proceed to show status/result
qrImageView.setVisibility(View.GONE);
amountTextView.setVisibility(View.GONE);
referenceTextView.setVisibility(View.GONE);
downloadQrisButton.setVisibility(View.GONE);
checkStatusButton.setVisibility(View.GONE);
statusTextView.setVisibility(View.VISIBLE);
returnMainButton.setVisibility(View.VISIBLE);
showPaymentSuccess();
});
}).start();
}
private JSONObject createWebhookPayload() throws Exception {
String serverKey = getServerKey();
String signatureKey = generateSignature(orderId, "200", grossAmount, serverKey);
JSONObject payload = new JSONObject();
payload.put("transaction_type", "on-us");
payload.put("transaction_time", transactionTime != null ? transactionTime : getCurrentISOTime());
payload.put("transaction_status", "settlement");
payload.put("transaction_id", transactionId);
payload.put("status_message", "midtrans payment notification");
payload.put("status_code", "200");
payload.put("signature_key", signatureKey);
payload.put("payment_type", "qris");
payload.put("order_id", orderId);
payload.put("gross_amount", grossAmount);
payload.put("reference_id", referenceId);
return payload;
}
private void sendWebhookRequest(JSONObject payload) {
try {
URL url = new URL(WEBHOOK_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(payload.toString().getBytes());
}
Log.d("QrisResult", "Webhook response: " + conn.getResponseCode());
} catch (Exception e) {
Log.e("QrisResult", "Webhook request error: " + e.getMessage(), e);
}
}
// Utility Methods
private String formatCurrency(String amount) {
try {
double amountDouble = Double.parseDouble(amount);
NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
return formatter.format(amountDouble);
} catch (NumberFormatException e) {
return "IDR " + amount;
}
}
private String getCurrentISOTime() {
return new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
.format(new java.util.Date());
}
private String getServerKey() {
try {
String base64 = MIDTRANS_AUTH.replace("Basic ", "");
byte[] decoded = android.util.Base64.decode(base64, android.util.Base64.DEFAULT);
return new String(decoded).replace(":", "");
} catch (Exception e) {
return "";
}
}
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 (Exception e) {
return "dummy_signature";
}
}
// Navigation and Lifecycle
private void returnToMain() {
stopQrRefresh();
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finishAffinity();
}
private void launchReceiptActivity() {
Intent intent = new Intent(this, ReceiptActivity.class);
intent.putExtra("calling_activity", "QrisResultActivity");
intent.putExtra("transaction_id", transactionId);
intent.putExtra("reference_id", referenceId);
intent.putExtra("order_id", orderId);
intent.putExtra("transaction_amount", String.valueOf(originalAmount));
intent.putExtra("gross_amount", grossAmount != null ? grossAmount : String.valueOf(originalAmount));
intent.putExtra("created_at", getCurrentISOTime());
intent.putExtra("payment_method", "QRIS");
intent.putExtra("acquirer", acquirer != null ? acquirer : "qris");
intent.putExtra("mid", "71000026521");
intent.putExtra("tid", "73001500");
startActivity(intent);
}
private void downloadQrCode() {
try {
qrImageView.setDrawingCacheEnabled(true);
qrImageView.buildDrawingCache();
Bitmap bitmap = qrImageView.getDrawingCache();
if (bitmap != null) {
saveImageToGallery(bitmap, "qris_code_" + System.currentTimeMillis());
} else {
Toast.makeText(this, "Unable to capture QR code image", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(this, "Error downloading QR code", Toast.LENGTH_LONG).show();
} finally {
qrImageView.setDrawingCacheEnabled(false);
}
}
private void saveImageToGallery(Bitmap bitmap, String fileName) {
try {
String savedImageURL = android.provider.MediaStore.Images.Media.insertImage(
getContentResolver(), bitmap, fileName, "QRIS Payment QR Code");
if (savedImageURL != null) {
Toast.makeText(this, "QRIS saved to gallery", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Failed to save QRIS", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(this, "Error saving QRIS", Toast.LENGTH_LONG).show();
}
}
private void updateUIAfterRefresh() {
String refreshTime = new java.text.SimpleDateFormat("HH:mm:ss").format(new java.util.Date());
referenceTextView.setText("Reference ID: " + referenceId + " (Refreshed at " + refreshTime + ")");
}
private void pollPendingPaymentLog(String orderId) {
progressBar.setVisibility(View.VISIBLE);
statusTextView.setText("Checking payment status...");
new Thread(() -> {
int maxAttempts = 12;
boolean found = false;
for (int attempt = 0; attempt < maxAttempts && !found; attempt++) {
try {
found = checkPaymentStatus(orderId);
if (!found && attempt < maxAttempts - 1) {
Thread.sleep(2000);
}
} catch (Exception e) {
Log.e("QrisResult", "Polling error: " + e.getMessage());
}
}
final boolean logFound = found;
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
if (logFound) {
checkStatusButton.setEnabled(true);
statusTextView.setText("Ready to simulate payment");
Toast.makeText(this, "Payment log found!", Toast.LENGTH_SHORT).show();
} else {
statusTextView.setText("Payment log not found");
checkStatusButton.setEnabled(true);
}
});
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
stopQrRefresh();
}
@Override
public void onBackPressed() {
stopQrRefresh();
returnToMain();
super.onBackPressed();
}
// AsyncTask for downloading QR image
private static class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
private String errorMessage;
DownloadImageTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... urls) {
try {
URL url = new URI(urls[0]).toURL();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.setConnectTimeout(10000);
connection.setReadTimeout(10000);
connection.connect();
if (connection.getResponseCode() == 200) {
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} else {
errorMessage = "Failed to download QR code";
}
} catch (Exception e) {
errorMessage = "Error downloading QR code: " + e.getMessage();
}
return null;
}
@Override
protected void onPostExecute(Bitmap result) {
if (result != null) {
imageView.setImageBitmap(result);
} else {
imageView.setImageResource(android.R.drawable.ic_menu_report_image);
if (errorMessage != null && imageView.getContext() != null) {
Toast.makeText(imageView.getContext(), errorMessage, Toast.LENGTH_LONG).show();
}
}
}
}
}

View File

@@ -0,0 +1,966 @@
package com.example.bdkipoc;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.io.OutputStream;
import java.net.URL;
import java.net.URI;
public class ReceiptActivity extends AppCompatActivity {
// Views
private LinearLayout backNavigation;
private ImageView backArrow;
private TextView toolbarTitle;
// Receipt details
private TextView merchantName;
private TextView merchantLocation;
private TextView midText;
private TextView tidText;
private TextView transactionNumber;
private TextView transactionDate;
private TextView paymentMethod;
private TextView cardType;
private TextView transactionTotal;
private TextView taxPercentage;
private TextView serviceFee;
private TextView finalTotal;
// Action buttons
private LinearLayout printButton;
private LinearLayout emailButton;
private Button finishButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set status bar color
setStatusBarColor();
setContentView(R.layout.activity_receipt);
initializeViews();
setupClickListeners();
loadTransactionData();
}
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() {
// Navigation
backNavigation = findViewById(R.id.back_navigation);
backArrow = findViewById(R.id.backArrow);
toolbarTitle = findViewById(R.id.toolbarTitle);
// Receipt details
merchantName = findViewById(R.id.merchant_name);
merchantLocation = findViewById(R.id.merchant_location);
midText = findViewById(R.id.mid_text);
tidText = findViewById(R.id.tid_text);
transactionNumber = findViewById(R.id.transaction_number);
transactionDate = findViewById(R.id.transaction_date);
paymentMethod = findViewById(R.id.payment_method);
cardType = findViewById(R.id.card_type);
transactionTotal = findViewById(R.id.transaction_total);
taxPercentage = findViewById(R.id.tax_percentage);
serviceFee = findViewById(R.id.service_fee);
finalTotal = findViewById(R.id.final_total);
// Action buttons
printButton = findViewById(R.id.print_button);
emailButton = findViewById(R.id.email_button);
finishButton = findViewById(R.id.finish_button);
}
private void setupClickListeners() {
// Back navigation - Goes back to previous activity
backNavigation.setOnClickListener(v -> handleBackNavigation());
backArrow.setOnClickListener(v -> handleBackNavigation());
toolbarTitle.setOnClickListener(v -> handleBackNavigation());
// Action buttons
printButton.setOnClickListener(v -> handlePrintReceipt());
emailButton.setOnClickListener(v -> handleEmailReceipt());
finishButton.setOnClickListener(v -> handleFinish());
}
private void loadTransactionData() {
Intent intent = getIntent();
if (intent != null) {
Log.d("ReceiptActivity", "=== LOADING TRANSACTION DATA ===");
// Get all available data from intent
String amount = intent.getStringExtra("transaction_amount");
String grossAmount = intent.getStringExtra("gross_amount");
String merchantNameStr = intent.getStringExtra("merchant_name");
String merchantLocationStr = intent.getStringExtra("merchant_location");
String transactionId = intent.getStringExtra("transaction_id");
String referenceId = intent.getStringExtra("reference_id");
String orderId = intent.getStringExtra("order_id");
String transactionDateStr = intent.getStringExtra("transaction_date");
String createdAt = intent.getStringExtra("created_at");
String paymentMethodStr = intent.getStringExtra("payment_method");
String cardTypeStr = intent.getStringExtra("card_type");
String channelCode = intent.getStringExtra("channel_code");
String channelCategory = intent.getStringExtra("channel_category");
String acquirer = intent.getStringExtra("acquirer");
String mid = intent.getStringExtra("mid");
String tid = intent.getStringExtra("tid");
// Log received data for debugging
Log.d("ReceiptActivity", "🔍 RECEIVED DATA:");
Log.d("ReceiptActivity", " amount: " + amount);
Log.d("ReceiptActivity", " referenceId: " + referenceId);
Log.d("ReceiptActivity", " orderId: " + orderId);
Log.d("ReceiptActivity", " channelCode: " + channelCode);
Log.d("ReceiptActivity", " acquirer (from intent): " + acquirer);
Log.d("ReceiptActivity", " createdAt: " + createdAt);
// 1. Set merchant data with defaults
merchantName.setText(merchantNameStr != null ? merchantNameStr : "Marcel Panjaitan");
merchantLocation.setText(merchantLocationStr != null ? merchantLocationStr : "Jakarta, Indonesia");
// 2. Set MID and TID
midText.setText(mid != null ? mid : "71000026521");
tidText.setText(tid != null ? tid : "73001500");
// 3. Set transaction number
String displayTransactionNumber = null;
if (referenceId != null && !referenceId.isEmpty()) {
displayTransactionNumber = referenceId;
} else if (transactionId != null && !transactionId.isEmpty()) {
displayTransactionNumber = transactionId;
} else if (orderId != null && !orderId.isEmpty()) {
displayTransactionNumber = orderId;
}
transactionNumber.setText(displayTransactionNumber != null ? displayTransactionNumber : "N/A");
// 4. Set transaction date
String displayDate = null;
if (createdAt != null && !createdAt.isEmpty()) {
displayDate = formatDateFromCreatedAt(createdAt);
} else if (transactionDateStr != null && !transactionDateStr.isEmpty()) {
displayDate = transactionDateStr;
} else {
displayDate = getCurrentDateTime();
}
transactionDate.setText(displayDate);
// 5. Set payment method
String displayPaymentMethod = getPaymentMethodFromChannelCode(channelCode, paymentMethodStr);
paymentMethod.setText(displayPaymentMethod);
// 6. ✅ IMPROVED: Enhanced card type detection for QRIS
String displayCardType = null;
if (channelCode != null && channelCode.equalsIgnoreCase("QRIS")) {
Log.d("ReceiptActivity", "🔍 QRIS transaction detected - searching for real acquirer");
// For QRIS, try to get real acquirer from webhook data
if (referenceId != null && !referenceId.isEmpty()) {
String realAcquirer = fetchRealAcquirerSync(referenceId);
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
displayCardType = getCardTypeFromAcquirer(realAcquirer, null, null);
Log.d("ReceiptActivity", "✅ QRIS real acquirer found: " + realAcquirer + " -> " + displayCardType);
} else {
Log.w("ReceiptActivity", "⚠️ QRIS real acquirer not found, using generic QRIS");
displayCardType = "QRIS";
// Start async search for better results
fetchRealAcquirerFromWebhook(referenceId);
}
} else {
displayCardType = "QRIS";
}
} else {
// Non-QRIS transaction
displayCardType = getCardTypeFromAcquirer(acquirer, channelCode, cardTypeStr);
}
cardType.setText(displayCardType);
Log.d("ReceiptActivity", "💳 FINAL CARD TYPE: " + displayCardType);
// 7. Format and set amounts
setAmountData(amount, grossAmount);
Log.d("ReceiptActivity", "=== TRANSACTION DATA LOADED ===");
}
}
private String formatDateFromCreatedAt(String createdAt) {
try {
// Input format from database: "yyyy-MM-dd HH:mm:ss"
SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// Output format for receipt: "dd/MM/yyyy HH:mm"
SimpleDateFormat outputFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID"));
Date date = inputFormat.parse(createdAt);
String formatted = outputFormat.format(date);
Log.d("ReceiptActivity", "Date formatting: '" + createdAt + "' -> '" + formatted + "'");
return formatted;
} catch (Exception e) {
Log.e("ReceiptActivity", "Error formatting date: " + createdAt, e);
// Fallback: try alternative formats or return as-is
return createdAt;
}
}
/**
* Get payment method name from channel_code with comprehensive mapping
*/
private String getPaymentMethodFromChannelCode(String channelCode, String fallbackPaymentMethod) {
if (channelCode != null && !channelCode.isEmpty()) {
String code = channelCode.toUpperCase();
switch (code) {
case "QRIS":
return "QRIS";
case "DEBIT":
return "Kartu Debit";
case "CREDIT":
return "Kartu Kredit";
case "BCA":
return "BCA";
case "MANDIRI":
return "Bank Mandiri";
case "BNI":
return "Bank BNI";
case "BRI":
return "Bank BRI";
case "PERMATA":
return "Bank Permata";
case "CIMB":
return "CIMB Niaga";
case "DANAMON":
return "Bank Danamon";
case "BSI":
return "Bank Syariah Indonesia";
case "CASH":
return "Tunai";
case "EDC":
return "EDC";
case "ALFAMART":
return "Alfamart";
case "INDOMARET":
return "Indomaret";
case "AKULAKU":
return "Akulaku";
default:
return code; // Return the code as-is if not mapped
}
}
// Fallback to provided payment method or default
return fallbackPaymentMethod != null ? fallbackPaymentMethod : "QRIS";
}
private String getCardTypeFromAcquirer(String acquirer, String channelCode, String fallbackCardType) {
// STEP 1: If we have a valid acquirer that's not generic "qris", use it
if (acquirer != null && !acquirer.isEmpty() && !acquirer.equalsIgnoreCase("qris")) {
String acq = acquirer.toLowerCase().trim();
Log.d("ReceiptActivity", "🔍 Mapping acquirer: '" + acquirer + "' -> '" + acq + "'");
// ✅ COMPREHENSIVE acquirer mapping (case-insensitive)
switch (acq) {
// E-Wallet acquirers (most common for QRIS)
case "gopay":
case "go-pay":
case "gojek": return "GoPay";
case "shopeepay":
case "shopee_pay":
case "shopee": return "ShopeePay";
case "ovo": return "OVO";
case "dana": return "DANA";
case "linkaja":
case "link_aja":
case "tcash": return "LinkAja";
case "jenius":
case "btpn": return "Jenius";
case "kaspro":
case "kas_pro": return "KasPro";
case "sakuku":
case "saku_ku": return "SakuKu";
case "doku":
case "doku_wallet": return "DOKU";
case "paymi":
case "pay_mi": return "PayMi";
case "isaku":
case "i_saku": return "i.Saku";
// Bank acquirers
case "bca":
case "bank_bca": return "BCA";
case "mandiri":
case "bank_mandiri":
case "mandiri_bill": return "Mandiri";
case "bni":
case "bank_bni":
case "bni_va": return "BNI";
case "bri":
case "bank_bri":
case "bri_va": return "BRI";
case "permata":
case "bank_permata":
case "permata_va": return "Permata";
case "cimb":
case "cimb_niaga":
case "bank_cimb":
case "cimb_va": return "CIMB Niaga";
case "danamon":
case "bank_danamon":
case "danamon_va": return "Danamon";
case "bsi":
case "bank_bsi":
case "bsi_va":
case "syariah_indonesia": return "BSI";
case "maybank":
case "bank_maybank": return "Maybank";
case "bca_digital":
case "blu": return "BCA Digital";
case "jago":
case "bank_jago": return "Bank Jago";
case "seabank":
case "sea_bank": return "SeaBank";
// Credit card acquirers
case "visa": return "Visa";
case "mastercard":
case "master_card": return "Mastercard";
case "jcb": return "JCB";
case "amex":
case "american_express": return "American Express";
case "discover": return "Discover";
case "unionpay":
case "union_pay": return "UnionPay";
// Over-the-counter
case "alfamart":
case "alfa_mart": return "Alfamart";
case "indomaret":
case "indo_maret": return "Indomaret";
case "pos_indonesia": return "Pos Indonesia";
// Buy now pay later
case "akulaku": return "Akulaku";
case "kredivo": return "Kredivo";
case "indodana":
case "indo_dana": return "Indodana";
case "traveloka_paylater":
case "traveloka": return "Traveloka PayLater";
case "atome": return "Atome";
default:
// Return capitalized version of acquirer if not in mapping
String capitalized = capitalizeFirstLetter(acquirer);
Log.d("ReceiptActivity", "🔤 Using capitalized acquirer: " + capitalized);
return capitalized;
}
}
// STEP 2: Fallback based on channel code
if (channelCode != null && !channelCode.isEmpty()) {
String code = channelCode.toUpperCase();
Log.d("ReceiptActivity", "🔍 Using channel code fallback: " + code);
switch (code) {
case "QRIS": return "QRIS"; // Generic QRIS
case "DEBIT": return "Debit";
case "CREDIT": return "Credit";
case "BCA": return "BCA";
case "MANDIRI": return "Mandiri";
case "BNI": return "BNI";
case "BRI": return "BRI";
default: return code;
}
}
// STEP 3: Final fallback
String result = fallbackCardType != null ? fallbackCardType : "Unknown";
Log.d("ReceiptActivity", "🔍 Using final fallback: " + result);
return result;
}
private String fetchRealAcquirerSync(String referenceId) {
try {
Log.d("ReceiptActivity", "🔍 Sync search for acquirer: " + referenceId);
// ✅ IMPROVED: Search with broader scope for better matches
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=100&sortOrder=DESC&sortColumn=created_at";
URL url = new URL(queryUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
conn.setConnectTimeout(8000); // Slightly longer timeout for better results
conn.setReadTimeout(8000);
if (conn.getResponseCode() == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
org.json.JSONObject json = new org.json.JSONObject(response.toString());
org.json.JSONArray results = json.optJSONArray("results");
if (results != null && results.length() > 0) {
Log.d("ReceiptActivity", "📊 Sync analyzing " + results.length() + " webhook logs");
// ✅ PRIORITY SEARCH: Look for settlement first, then pending, then any
String[] searchPriority = {"settlement", "capture", "success", "pending", ""};
for (String status : searchPriority) {
String[] targetStatuses = status.isEmpty() ? new String[]{} : new String[]{status};
String foundAcquirer = searchLogsByStatus(results, referenceId, targetStatuses);
if (foundAcquirer != null && !foundAcquirer.isEmpty() && !foundAcquirer.equalsIgnoreCase("qris")) {
Log.d("ReceiptActivity", "🎯 Sync found acquirer: " + foundAcquirer +
" (priority: " + (status.isEmpty() ? "any" : status) + ")");
return foundAcquirer;
}
}
Log.w("ReceiptActivity", "⚠️ Sync search completed but no valid acquirer found");
}
} else {
Log.w("ReceiptActivity", "⚠️ Sync API call failed with code: " + conn.getResponseCode());
}
} catch (Exception e) {
Log.e("ReceiptActivity", "❌ Sync acquirer fetch error: " + e.getMessage());
}
return null; // No acquirer found
}
private void fetchRealAcquirerFromWebhook(String referenceId) {
new Thread(() -> {
try {
Log.d("ReceiptActivity", "🔍 Async search for real acquirer: " + referenceId);
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at";
URL url = new URL(queryUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
conn.setConnectTimeout(15000);
conn.setReadTimeout(15000);
if (conn.getResponseCode() == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
org.json.JSONObject json = new org.json.JSONObject(response.toString());
org.json.JSONArray results = json.optJSONArray("results");
if (results != null && results.length() > 0) {
Log.d("ReceiptActivity", "📊 Async analyzing " + results.length() + " webhook logs");
String realAcquirer = searchForRealAcquirer(results, referenceId);
if (realAcquirer != null && !realAcquirer.isEmpty() && !realAcquirer.equalsIgnoreCase("qris")) {
Log.d("ReceiptActivity", "✅ Async found real acquirer: " + realAcquirer);
// Update UI on main thread
final String displayAcquirer = getCardTypeFromAcquirer(realAcquirer, null, null);
runOnUiThread(() -> {
if (cardType != null) {
cardType.setText(displayAcquirer);
Log.d("ReceiptActivity", "🎨 UI UPDATED: QRIS -> " + displayAcquirer);
// Show a subtle indication that the data was updated
showToast("Card type updated: " + displayAcquirer);
}
});
} else {
Log.w("ReceiptActivity", "⚠️ Async search found no valid acquirer for: " + referenceId);
}
} else {
Log.w("ReceiptActivity", "⚠️ Async search returned no results");
}
} else {
Log.w("ReceiptActivity", "⚠️ Async API call failed with code: " + conn.getResponseCode());
}
} catch (Exception e) {
Log.e("ReceiptActivity", "❌ Async acquirer fetch error: " + e.getMessage(), e);
}
}).start();
}
private String searchForRealAcquirer(org.json.JSONArray results, String referenceId) {
try {
Log.d("ReceiptActivity", "🔍 Analyzing " + results.length() + " webhook logs for acquirer");
Log.d("ReceiptActivity", "🎯 Target reference ID: " + referenceId);
// Get order_id from intent for additional matching
String orderId = getIntent().getStringExtra("order_id");
Log.d("ReceiptActivity", "🎯 Target order ID: " + orderId);
// Strategy 1: Look for settlement/success transactions first (highest priority)
Log.d("ReceiptActivity", "🔍 Strategy 1: Searching for settlement/success status");
String acquirerFromSettlement = searchLogsByStatus(results, referenceId, new String[]{"settlement", "capture", "success"});
if (acquirerFromSettlement != null) {
Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from settlement: " + acquirerFromSettlement);
return acquirerFromSettlement;
}
// Strategy 2: Look for pending transactions
Log.d("ReceiptActivity", "🔍 Strategy 2: Searching for pending status");
String acquirerFromPending = searchLogsByStatus(results, referenceId, new String[]{"pending"});
if (acquirerFromPending != null) {
Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from pending: " + acquirerFromPending);
return acquirerFromPending;
}
// Strategy 3: Look for any transaction with this reference (broadest search)
Log.d("ReceiptActivity", "🔍 Strategy 3: Searching for any status");
String acquirerFromAny = searchLogsByStatus(results, referenceId, new String[]{});
if (acquirerFromAny != null) {
Log.d("ReceiptActivity", "🎯 SUCCESS: Found acquirer from any status: " + acquirerFromAny);
return acquirerFromAny;
}
Log.w("ReceiptActivity", "❌ FAILED: No acquirer found in webhook logs for reference: " + referenceId);
// Strategy 4: Debug logging - show what we actually found
Log.d("ReceiptActivity", "🔍 DEBUG: Showing first 5 log entries for analysis:");
for (int i = 0; i < Math.min(5, results.length()); i++) {
try {
org.json.JSONObject log = results.getJSONObject(i);
org.json.JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null) {
String logOrderId = reqBody.optString("order_id", "");
String logRefId = reqBody.optString("reference_id", "");
String logStatus = reqBody.optString("transaction_status", "");
String logAcquirer = reqBody.optString("acquirer", "");
String logIssuer = reqBody.optString("issuer", "");
Log.d("ReceiptActivity", " Log " + (i+1) + ": " +
"order_id=" + logOrderId +
", ref_id=" + logRefId +
", status=" + logStatus +
", acquirer=" + logAcquirer +
", issuer=" + logIssuer);
}
} catch (Exception e) {
Log.w("ReceiptActivity", "Error parsing debug log " + i + ": " + e.getMessage());
}
}
return null;
} catch (Exception e) {
Log.e("ReceiptActivity", "❌ Error analyzing webhook logs: " + e.getMessage(), e);
return null;
}
}
private String searchLogsByStatus(org.json.JSONArray results, String referenceId, String[] targetStatuses) {
try {
for (int i = 0; i < results.length(); i++) {
org.json.JSONObject log = results.getJSONObject(i);
org.json.JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null) {
String logReferenceId = reqBody.optString("reference_id", "");
String logTransactionStatus = reqBody.optString("transaction_status", "");
String logAcquirer = reqBody.optString("acquirer", "");
String logIssuer = reqBody.optString("issuer", "");
String logOrderId = reqBody.optString("order_id", "");
Log.d("ReceiptActivity", "🔍 Checking log: order_id=" + logOrderId +
", status=" + logTransactionStatus +
", acquirer=" + logAcquirer +
", issuer=" + logIssuer);
// Check for direct reference match
boolean isDirectMatch = referenceId.equals(logReferenceId);
// ✅ IMPROVED: Also check order_id match from QrisResultActivity
boolean isOrderMatch = false;
String orderId = getIntent().getStringExtra("order_id");
if (orderId != null && !orderId.isEmpty() && orderId.equals(logOrderId)) {
isOrderMatch = true;
Log.d("ReceiptActivity", "✅ Found order_id match: " + orderId);
}
// Check custom_field1 for refresh tracking
boolean isRefreshMatch = false;
String customField1 = reqBody.optString("custom_field1", "");
if (!customField1.isEmpty()) {
try {
org.json.JSONObject customData = new org.json.JSONObject(customField1);
String originalReference = customData.optString("original_reference", "");
String appReferenceId = customData.optString("app_reference_id", "");
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
isRefreshMatch = true;
}
} catch (org.json.JSONException e) {
// Ignore parsing errors
}
}
// Check if this log matches our reference
if (isDirectMatch || isRefreshMatch || isOrderMatch) {
Log.d("ReceiptActivity", "🎯 TRANSACTION MATCH FOUND!");
Log.d("ReceiptActivity", " Match type: " +
(isDirectMatch ? "DIRECT " : "") +
(isRefreshMatch ? "REFRESH " : "") +
(isOrderMatch ? "ORDER" : ""));
// If target statuses specified, check status
if (targetStatuses.length > 0) {
boolean statusMatches = false;
for (String targetStatus : targetStatuses) {
if (logTransactionStatus.equalsIgnoreCase(targetStatus)) {
statusMatches = true;
break;
}
}
if (!statusMatches) {
Log.d("ReceiptActivity", " Status doesn't match target: " + logTransactionStatus);
continue; // Skip if status doesn't match
}
}
// ✅ CRITICAL FIX: For QRIS transactions, prioritize issuer over acquirer
String foundAcquirer = null;
// Check if this is a QRIS transaction by payment_type
String paymentType = reqBody.optString("payment_type", "");
boolean isQrisTransaction = "qris".equalsIgnoreCase(paymentType);
if (isQrisTransaction) {
// For QRIS: issuer contains the actual payment provider (LinkAja, DANA, etc.)
// acquirer is usually just "gopay" (the QRIS aggregator)
if (!logIssuer.isEmpty() && !logIssuer.equalsIgnoreCase("qris")) {
foundAcquirer = logIssuer;
Log.d("ReceiptActivity", "📱 QRIS: Using issuer as acquirer: " + foundAcquirer);
} else if (!logAcquirer.isEmpty() && !logAcquirer.equalsIgnoreCase("qris")) {
foundAcquirer = logAcquirer;
Log.d("ReceiptActivity", "📱 QRIS: Fallback to acquirer: " + foundAcquirer);
}
} else {
// For non-QRIS: prefer acquirer over issuer (traditional behavior)
if (!logAcquirer.isEmpty() && !logAcquirer.equalsIgnoreCase("qris")) {
foundAcquirer = logAcquirer;
Log.d("ReceiptActivity", "💳 Non-QRIS: Using acquirer: " + foundAcquirer);
} else if (!logIssuer.isEmpty() && !logIssuer.equalsIgnoreCase("qris")) {
foundAcquirer = logIssuer;
Log.d("ReceiptActivity", "💳 Non-QRIS: Fallback to issuer: " + foundAcquirer);
}
}
if (foundAcquirer != null && !foundAcquirer.isEmpty()) {
Log.d("ReceiptActivity", "✅ FINAL ACQUIRER FOUND: " + foundAcquirer +
" (status: " + logTransactionStatus +
", payment_type: " + paymentType + ")");
return foundAcquirer;
} else {
Log.w("ReceiptActivity", "⚠️ Transaction matched but no valid acquirer found");
}
}
}
}
} catch (Exception e) {
Log.e("ReceiptActivity", "❌ Error searching logs by status: " + e.getMessage(), e);
}
return null; // No acquirer found for specified criteria
}
private String capitalizeFirstLetter(String input) {
if (input == null || input.isEmpty()) {
return input;
}
// Handle special cases
String cleaned = input.trim();
// Special handling for common patterns
if (cleaned.toLowerCase().contains("pay")) {
// Keep "Pay" capitalized in payment methods
return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase()
.replace("pay", "Pay");
}
return cleaned.substring(0, 1).toUpperCase() + cleaned.substring(1).toLowerCase();
}
private void setAmountData(String amount, String grossAmount) {
// Prioritize 'amount' over 'grossAmount' for transaction data
String amountToUse = amount != null ? amount : grossAmount;
Log.d("ReceiptActivity", "Setting amount data - amount: " + amount +
", grossAmount: " + grossAmount + ", using: " + amountToUse);
if (amountToUse != null) {
try {
// Clean and parse the amount
String cleanAmount = cleanAmountString(amountToUse);
Log.d("ReceiptActivity", "Cleaned amount: " + cleanAmount);
// Parse as long integer (Indonesian Rupiah doesn't use decimal cents)
long amountLong = Long.parseLong(cleanAmount);
// Set transaction total
transactionTotal.setText("Rp " + formatCurrency(amountLong));
// Calculate tax and service fee (for QRIS, typically no additional fees)
long tax = 0; // QRIS usually doesn't have tax
long serviceFeeValue = 0; // QRIS usually doesn't have service fee
long total = amountLong + tax + serviceFeeValue;
// Set calculated values
taxPercentage.setText("Rp 0");
serviceFee.setText("Rp 0");
finalTotal.setText("Rp " + formatCurrency(total));
Log.d("ReceiptActivity", "Amount formatting successful: " + amountLong + " -> Rp " + formatCurrency(total));
} catch (NumberFormatException e) {
Log.e("ReceiptActivity", "Error parsing amount: " + amountToUse, e);
// Fallback if parsing fails
transactionTotal.setText("Rp " + amountToUse);
taxPercentage.setText("Rp 0");
serviceFee.setText("Rp 0");
finalTotal.setText("Rp " + amountToUse);
}
} else {
// Default values if no amount provided
transactionTotal.setText("Rp 0");
taxPercentage.setText("Rp 0");
serviceFee.setText("Rp 0");
finalTotal.setText("Rp 0");
}
}
/**
* Clean amount string to extract raw number correctly
* Input examples: "1000", "1000.00", "Rp 1.000", "1.000"
* Output: "1000"
*/
private String cleanAmountString(String amount) {
if (amount == null || amount.isEmpty()) {
return "0";
}
// Remove currency symbols and spaces first
String cleaned = amount
.replace("Rp. ", "")
.replace("Rp ", "")
.replace("IDR ", "")
.replace(" ", "")
.trim();
Log.d("ReceiptActivity", "After currency removal: '" + cleaned + "'");
// Handle decimal cases properly
if (cleaned.contains(".")) {
// Check if it contains decimal cents (like "1000.00") or thousand separator (like "1.000")
String[] parts = cleaned.split("\\.");
if (parts.length == 2) {
String beforeDot = parts[0];
String afterDot = parts[1];
// If after dot is "00" or "0", it's decimal cents - remove it
if (afterDot.equals("00") || afterDot.equals("0")) {
cleaned = beforeDot;
Log.d("ReceiptActivity", "Removed decimal cents: '" + cleaned + "'");
}
// If after dot has 3 digits, it's thousand separator - combine them
else if (afterDot.length() == 3) {
cleaned = beforeDot + afterDot;
Log.d("ReceiptActivity", "Combined thousand separator: '" + cleaned + "'");
}
// For other cases, try to determine based on length
else {
// If beforeDot is short (1-3 digits) and afterDot is 3 digits, it's thousand separator
if (beforeDot.length() <= 3 && afterDot.length() == 3) {
cleaned = beforeDot + afterDot;
} else {
// Otherwise, it's likely decimal - remove the decimal part
cleaned = beforeDot;
}
Log.d("ReceiptActivity", "Processed mixed format: '" + cleaned + "'");
}
} else {
// Multiple dots - remove all dots (treat as thousand separators)
cleaned = cleaned.replace(".", "");
Log.d("ReceiptActivity", "Removed multiple dots: '" + cleaned + "'");
}
}
// Remove any remaining commas (some locales use comma as thousand separator)
cleaned = cleaned.replace(",", "");
Log.d("ReceiptActivity", "Final cleaned amount: '" + amount + "' -> '" + cleaned + "'");
return cleaned;
}
private String formatCurrency(long amount) {
// Use Indonesian locale formatting with dots as thousand separators
return String.format("%,d", amount).replace(',', '.');
}
private String getCurrentDateTime() {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", new Locale("id", "ID"));
return sdf.format(new Date());
}
private void handlePrintReceipt() {
// Handle print receipt action
// In real app, this would integrate with printer
showToast("Mencetak struk...");
}
private void handleEmailReceipt() {
// Handle email receipt action
// In real app, this would open email intent
showToast("Mengirim email...");
}
private void handleFinish() {
// Navigate to MainActivity/Home Page when "Selesai" button is pressed
navigateToHomePage();
}
private void handleBackNavigation() {
// Smart back navigation - go to the actual previous activity
// Check if we have a calling activity in the intent
String callingActivity = getIntent().getStringExtra("calling_activity");
if (callingActivity != null) {
switch (callingActivity) {
case "TransactionActivity":
// Go back to transaction list
Intent transactionIntent = new Intent(this, TransactionActivity.class);
transactionIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(transactionIntent);
break;
case "QrisResultActivity":
// Go back to main menu since QrisResultActivity is typically finished
navigateToHomePage();
break;
case "PaymentActivity":
case "QrisActivity":
// Go back to payment/qris activity
Intent paymentIntent = new Intent(this, QrisActivity.class);
paymentIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(paymentIntent);
break;
default:
// Default: use system back navigation
super.onBackPressed();
break;
}
} else {
// No calling activity specified, use system back navigation
super.onBackPressed();
}
finish();
}
private void navigateToHomePage() {
// Navigate to MainActivity/Home Page
Intent intent = new Intent(this, MainActivity.class);
// Clear all previous activities from the stack and start fresh
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
// Optional: Add success message to show in MainActivity
intent.putExtra("transaction_completed", true);
intent.putExtra("transaction_amount", getIntent().getStringExtra("transaction_amount"));
startActivity(intent);
finish();
// Show success message
showToast("Transaksi berhasil diselesaikan!");
}
private void showToast(String message) {
android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show();
}
@Override
public void onBackPressed() {
// Use the smart back navigation
handleBackNavigation();
super.onBackPressed();
}
}

View File

@@ -0,0 +1,354 @@
package com.example.bdkipoc;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class SettlementActivity extends AppCompatActivity {
private TextView tvTotalAmount;
private TextView tvTotalTransactions;
private RecyclerView recyclerView;
private SettlementAdapter adapter;
private List<SettlementItem> settlementList;
private ImageView btnBack;
private String API_URL = "https://be-edc.msvc.app/transactions/performa-chanel-pembayaran?from_date=2025-01-01&to_date=2025-06-04&location_id=0&merchant_id=0";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settlement);
initViews();
setupRecyclerView();
fetchApiData();
setupClickListeners();
}
private void fetchApiData() {
// Execute network call in background thread
new ApiTask().execute(API_URL);
}
private void processApiData(JSONArray dataArray) {
try {
settlementList.clear();
final long[] totalAmountArray = {0}; // Using array to make it effectively final
final int[] totalTransactionsArray = {0}; // Using array to make it effectively final
// Process each channel individually (no grouping)
for (int i = 0; i < dataArray.length(); i++) {
JSONObject item = dataArray.getJSONObject(i);
String channelCode = item.getString("channel_code");
int transactions = item.getInt("total_transactions");
long maxAmount = item.getLong("max_transastions");
// Use channel code directly as display name with some formatting
String displayName = formatChannelName(channelCode);
int iconResource = getChannelIcon(channelCode);
settlementList.add(new SettlementItem(
displayName,
maxAmount,
transactions,
iconResource
));
totalAmountArray[0] += maxAmount;
totalTransactionsArray[0] += transactions;
}
// Update UI on main thread
runOnUiThread(new Runnable() {
@Override
public void run() {
updateSummary(totalAmountArray[0], totalTransactionsArray[0]);
adapter.notifyDataSetChanged();
}
});
} catch (JSONException e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(SettlementActivity.this, "Error parsing data", Toast.LENGTH_SHORT).show();
loadSampleData(); // Fallback to sample data
}
});
}
}
private void updateSummary(long totalAmount, int totalTransactions) {
tvTotalAmount.setText(formatCurrency(totalAmount));
tvTotalTransactions.setText(String.valueOf(totalTransactions));
}
private void initViews() {
tvTotalAmount = findViewById(R.id.tv_total_amount);
tvTotalTransactions = findViewById(R.id.tv_total_transactions);
recyclerView = findViewById(R.id.recycler_view);
btnBack = findViewById(R.id.btn_back);
settlementList = new ArrayList<>();
}
private void setupRecyclerView() {
adapter = new SettlementAdapter(settlementList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private void setupClickListeners() {
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
private void loadSampleData() {
// Sample data as fallback
settlementList.clear();
settlementList.add(new SettlementItem("Kartu Kredit", 200000, 13, android.R.drawable.ic_menu_recent_history));
settlementList.add(new SettlementItem("Kartu Debit", 200000, 13, android.R.drawable.ic_menu_manage));
settlementList.add(new SettlementItem("Transfer", 200000, 13, android.R.drawable.ic_menu_send));
settlementList.add(new SettlementItem("Uang Elektronik", 200000, 13, android.R.drawable.ic_menu_gallery));
settlementList.add(new SettlementItem("QRIS", 200000, 13, android.R.drawable.ic_menu_camera));
// Update summary
tvTotalAmount.setText(formatCurrency(3506500));
tvTotalTransactions.setText("65");
adapter.notifyDataSetChanged();
}
private String formatChannelName(String channelCode) {
// Format channel code to be more readable
switch (channelCode) {
case "GO-PAY":
return "GoPay";
case "SHOPEEPAY":
return "ShopeePay";
case "LINKAJA":
return "LinkAja";
case "MASTERCARD":
return "Mastercard";
case "VISA":
return "Visa";
case "QRIS":
return "QRIS";
case "DANA":
return "Dana";
case "OVO":
return "OVO";
case "DEBIT":
return "Kartu Debit";
case "GPN":
return "GPN";
case "OTHER":
return "Lainnya";
default:
// Capitalize first letter and make rest lowercase
return channelCode.substring(0, 1).toUpperCase() +
channelCode.substring(1).toLowerCase();
}
}
private String getChannelDisplayName(String channelCode) {
// Deprecated - keeping for backward compatibility
return formatChannelName(channelCode);
}
private int getChannelIcon(String channelCode) {
// Dynamic icon assignment based on channel type
switch (channelCode) {
case "DEBIT":
return android.R.drawable.ic_menu_manage;
case "VISA":
case "MASTERCARD":
return android.R.drawable.ic_menu_recent_history;
case "QRIS":
return android.R.drawable.ic_menu_camera;
case "DANA":
case "GO-PAY":
case "OVO":
case "SHOPEEPAY":
case "LINKAJA":
return android.R.drawable.ic_menu_gallery;
case "GPN":
return android.R.drawable.ic_menu_send;
case "OTHER":
return android.R.drawable.ic_menu_info_details;
default:
return android.R.drawable.ic_menu_help;
}
}
private String formatCurrency(long amount) {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
}
// Deprecated helper class - no longer needed
// private static class ChannelData { ... }
// AsyncTask for API call
private class ApiTask extends AsyncTask<String, Void, String> {
@Override
protected String doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
return response.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
if (result != null) {
try {
JSONObject jsonResponse = new JSONObject(result);
if (jsonResponse.getInt("status") == 200) {
JSONArray dataArray = jsonResponse.getJSONArray("data");
processApiData(dataArray);
} else {
Toast.makeText(SettlementActivity.this, "API Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(SettlementActivity.this, "JSON Parse Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
} else {
Toast.makeText(SettlementActivity.this, "Network Error", Toast.LENGTH_SHORT).show();
loadSampleData();
}
}
}
}
// SettlementItem class - combined in same file
class SettlementItem {
private String channelName;
private long amount;
private int transactionCount;
private int iconResource;
public SettlementItem() {}
public SettlementItem(String channelName, long amount, int transactionCount, int iconResource) {
this.channelName = channelName;
this.amount = amount;
this.transactionCount = transactionCount;
this.iconResource = iconResource;
}
// Getters and Setters
public String getChannelName() { return channelName; }
public void setChannelName(String channelName) { this.channelName = channelName; }
public long getAmount() { return amount; }
public void setAmount(long amount) { this.amount = amount; }
public int getTransactionCount() { return transactionCount; }
public void setTransactionCount(int transactionCount) { this.transactionCount = transactionCount; }
public int getIconResource() { return iconResource; }
public void setIconResource(int iconResource) { this.iconResource = iconResource; }
}
// SettlementAdapter class - combined in same file
class SettlementAdapter extends RecyclerView.Adapter<SettlementAdapter.SettlementViewHolder> {
private List<SettlementItem> settlementList;
public SettlementAdapter(List<SettlementItem> settlementList) {
this.settlementList = settlementList;
}
@NonNull
@Override
public SettlementViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_settlement, parent, false);
return new SettlementViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SettlementViewHolder holder, int position) {
SettlementItem item = settlementList.get(position);
holder.ivIcon.setImageResource(item.getIconResource());
holder.tvChannelName.setText(item.getChannelName());
holder.tvAmount.setText("Rp. " + formatCurrency(item.getAmount()));
holder.tvTransactionCount.setText(item.getTransactionCount() + " Transaksi");
}
@Override
public int getItemCount() {
return settlementList.size();
}
private String formatCurrency(long amount) {
NumberFormat formatter = NumberFormat.getNumberInstance(new Locale("id", "ID"));
return formatter.format(amount);
}
static class SettlementViewHolder extends RecyclerView.ViewHolder {
ImageView ivIcon;
TextView tvChannelName;
TextView tvAmount;
TextView tvTransactionCount;
public SettlementViewHolder(@NonNull View itemView) {
super(itemView);
ivIcon = itemView.findViewById(R.id.iv_icon);
tvChannelName = itemView.findViewById(R.id.tv_channel_name);
tvAmount = itemView.findViewById(R.id.tv_amount);
tvTransactionCount = itemView.findViewById(R.id.tv_transaction_count);
}
}
}

View File

@@ -0,0 +1,106 @@
package com.example.bdkipoc;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
public class StyleHelper {
/**
* Create rounded rectangle drawable programmatically
*/
public static GradientDrawable createRoundedDrawable(int color, int strokeColor, int strokeWidth, int radius) {
GradientDrawable drawable = new GradientDrawable();
drawable.setShape(GradientDrawable.RECTANGLE);
drawable.setColor(color);
drawable.setStroke(strokeWidth, strokeColor);
drawable.setCornerRadius(radius);
return drawable;
}
/**
* Apply search input styling
*/
public static void applySearchInputStyle(View view, Context context) {
int white = ContextCompat.getColor(context, android.R.color.white);
int lightGrey = ContextCompat.getColor(context, android.R.color.darker_gray);
// ✅ IMPROVED: Larger corner radius and lighter border like in the image
GradientDrawable drawable = createRoundedDrawable(white, lightGrey, 1, 75); // 25dp radius, thinner border
view.setBackground(drawable);
}
/**
* Apply filter button styling
*/
public static void applyFilterButtonStyle(View view, Context context) {
int white = ContextCompat.getColor(context, android.R.color.white);
int lightGrey = ContextCompat.getColor(context, android.R.color.darker_gray);
// ✅ IMPROVED: Larger corner radius like in the image
GradientDrawable drawable = createRoundedDrawable(white, lightGrey, 1, 75); // 25dp radius, thinner border
view.setBackground(drawable);
}
/**
* Apply pagination button styling (simple version)
*/
public static void applyPaginationButtonStyle(View view, Context context, boolean isActive) {
int backgroundColor, strokeColor;
if (isActive) {
backgroundColor = ContextCompat.getColor(context, android.R.color.holo_red_dark);
strokeColor = ContextCompat.getColor(context, android.R.color.holo_red_dark);
} else {
backgroundColor = ContextCompat.getColor(context, android.R.color.white);
strokeColor = ContextCompat.getColor(context, android.R.color.transparent);
}
// ✅ IMPROVED: Larger corner radius for modern look (like in the image)
GradientDrawable drawable = createRoundedDrawable(backgroundColor, strokeColor, 0, 48); // 16dp radius
view.setBackground(drawable);
// Set text color if it's a TextView
if (view instanceof TextView) {
int textColor = isActive ?
ContextCompat.getColor(context, android.R.color.white) :
ContextCompat.getColor(context, android.R.color.black);
((TextView) view).setTextColor(textColor);
}
}
/**
* Apply status text color only (no background badge)
*/
public static void applyStatusTextColor(TextView textView, Context context, String status) {
String statusLower = status != null ? status.toLowerCase() : "";
int textColor;
if (statusLower.equals("failed") || statusLower.equals("failure") ||
statusLower.equals("error") || statusLower.equals("declined") ||
statusLower.equals("expire") || statusLower.equals("cancel")) {
// Red text for failed/error statuses
textColor = ContextCompat.getColor(context, android.R.color.holo_red_dark);
} else if (statusLower.equals("success") || statusLower.equals("paid") ||
statusLower.equals("settlement") || statusLower.equals("completed") ||
statusLower.equals("capture")) {
// Green text for successful statuses
textColor = ContextCompat.getColor(context, android.R.color.holo_green_dark);
} else if (statusLower.equals("pending") || statusLower.equals("processing") ||
statusLower.equals("waiting") || statusLower.equals("checking...") ||
statusLower.equals("checking")) {
// Orange text for pending/processing statuses
textColor = ContextCompat.getColor(context, android.R.color.holo_orange_dark);
} else if (statusLower.equals("init")) {
// Blue text for init status
textColor = ContextCompat.getColor(context, android.R.color.holo_blue_dark);
} else {
// Default gray text for unknown statuses
textColor = ContextCompat.getColor(context, android.R.color.darker_gray);
}
textView.setTextColor(textColor);
textView.setBackground(null); // Remove any background
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,57 @@
package com.example.bdkipoc;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.core.content.ContextCompat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream; // ✅ ADDED: Missing import
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.text.NumberFormat;
import java.util.Locale;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
private List<TransactionActivity.Transaction> transactionList;
private OnPrintClickListener printClickListener;
public interface OnPrintClickListener {
void onPrintClick(TransactionActivity.Transaction transaction);
}
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
this.transactionList = transactionList;
}
public void setPrintClickListener(OnPrintClickListener listener) {
this.printClickListener = listener;
}
/**
* Update data without numbering (removed as per request)
*/
public void updateData(List<TransactionActivity.Transaction> newData, int startIndex) {
this.transactionList = newData;
notifyDataSetChanged();
Log.d("TransactionAdapter", "📋 Data updated: " + newData.size() + " items");
}
@NonNull
@Override
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@@ -30,19 +63,497 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
TransactionActivity.Transaction t = transactionList.get(position);
// Format the amount as Indonesian Rupiah
try {
double amountValue = Double.parseDouble(t.amount);
NumberFormat rupiahFormat = NumberFormat.getCurrencyInstance(new Locale.Builder().setLanguage("id").setRegion("ID").build());
holder.amount.setText(rupiahFormat.format(amountValue));
} catch (NumberFormatException e) {
holder.amount.setText("Rp " + t.amount);
// ✅ STRIPE TABLE: Set alternating row colors
LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer);
if (position % 2 == 0) {
// Even rows - white background
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.white));
} else {
// Odd rows - light gray background
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.background_light));
}
holder.status.setText(t.status);
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":");
Log.d("TransactionAdapter", " Reference: " + t.referenceId);
Log.d("TransactionAdapter", " Status: " + t.status);
Log.d("TransactionAdapter", " Amount: " + t.amount);
// Set reference ID
holder.referenceId.setText(t.referenceId);
holder.merchantName.setText(t.merchantName);
holder.createdAt.setText(t.createdAt.replace("T", " ").substring(0, 19));
// Format the amount as Indonesian Rupiah
try {
String cleanAmount = cleanAmountString(t.amount);
long amountValue = Long.parseLong(cleanAmount);
String formattedAmount = formatRupiah(amountValue);
holder.amount.setText(formattedAmount);
Log.d("TransactionAdapter", "💰 Amount processed: '" + t.amount + "' -> '" + formattedAmount + "'");
} catch (NumberFormatException e) {
Log.e("TransactionAdapter", "❌ Amount format error: " + t.amount, e);
String fallback = t.amount.startsWith("Rp") ? t.amount : "Rp " + t.amount;
holder.amount.setText(fallback);
}
// ✅ ENHANCED STATUS HANDLING dengan comprehensive checking
String displayStatus = t.status;
Log.d("TransactionAdapter", "🔍 Checking status for: " + t.referenceId + " (current: " + displayStatus + ")");
// Jika status adalah INIT atau PENDING, lakukan comprehensive check
if ("INIT".equalsIgnoreCase(t.status) || "PENDING".equalsIgnoreCase(t.status)) {
if (t.referenceId != null && !t.referenceId.isEmpty()) {
// Show checking state
holder.status.setText("CHECKING...");
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING");
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId);
// Check real status dari semua kemungkinan sources
checkMidtransStatus(t.referenceId, holder.status);
} else {
// No reference ID to check
holder.status.setText(displayStatus.toUpperCase());
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
Log.w("TransactionAdapter", "⚠️ No reference ID for status check");
}
} else {
// Use existing status yang sudah confirmed
holder.status.setText(displayStatus.toUpperCase());
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
Log.d("TransactionAdapter", "✅ Using confirmed status: " + displayStatus);
}
// Set payment method
String paymentMethod = getPaymentMethodName(t.channelCode, t.channelCategory);
holder.paymentMethod.setText(paymentMethod);
// ✅ FORMAT AND DISPLAY CREATED AT
String formattedDate = formatCreatedAtDate(t.createdAt);
holder.createdAt.setText(formattedDate);
Log.d("TransactionAdapter", "📅 Created at: " + t.createdAt + " -> " + formattedDate);
// Set click listeners
holder.itemView.setOnClickListener(v -> {
if (printClickListener != null) {
printClickListener.onPrintClick(t);
}
});
holder.printSection.setOnClickListener(v -> {
if (printClickListener != null) {
printClickListener.onPrintClick(t);
}
});
Log.d("TransactionAdapter", "✅ Transaction binding complete for: " + t.referenceId);
}
private String cleanAmountString(String amount) {
if (amount == null || amount.isEmpty()) {
return "0";
}
Log.d("TransactionAdapter", "Cleaning amount: '" + amount + "'");
// Remove currency symbols and spaces
String cleaned = amount
.replace("Rp. ", "")
.replace("Rp ", "")
.replace("IDR ", "")
.replace(" ", "")
.trim();
// Handle dots properly
if (cleaned.contains(".")) {
// Split by dots
String[] parts = cleaned.split("\\.");
if (parts.length == 2) {
String beforeDot = parts[0];
String afterDot = parts[1];
// Check if it's decimal format (like "1000.00") or thousand separator (like "1.000")
if (afterDot.length() <= 2 && (afterDot.equals("00") || afterDot.equals("0"))) {
// It's decimal format - keep only the integer part
cleaned = beforeDot;
} else if (afterDot.length() == 3) {
// It's thousand separator format - combine parts
cleaned = beforeDot + afterDot;
} else {
// Ambiguous case - assume thousand separator if beforeDot is short
if (beforeDot.length() <= 3) {
cleaned = beforeDot + afterDot;
} else {
cleaned = beforeDot; // Assume decimal
}
}
} else if (parts.length > 2) {
// Multiple dots - assume all are thousand separators
cleaned = String.join("", parts);
}
}
// Remove any commas
cleaned = cleaned.replace(",", "");
Log.d("TransactionAdapter", "Cleaned result: '" + cleaned + "'");
return cleaned;
}
/**
* Format long amount to Indonesian Rupiah format
*/
private String formatRupiah(long amount) {
// Use dots as thousand separators (Indonesian format)
String formatted = String.format("%,d", amount).replace(',', '.');
return "Rp. " + formatted;
}
// ✅ FIXED: Enhanced status checking with comprehensive search
private void checkMidtransStatus(String referenceId, TextView statusTextView) {
new Thread(() -> {
try {
Log.d("TransactionAdapter", "🔍 Comprehensive status check for reference: " + referenceId);
// STEP 1: Query webhook logs untuk semua order_id yang terkait
String queryUrl = "https://be-edc.msvc.app/api-logs?limit=200&sortOrder=DESC&sortColumn=created_at";
URL url = new URL(queryUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
if (conn.getResponseCode() == 200) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
JSONObject json = new JSONObject(response.toString());
JSONArray results = json.optJSONArray("results");
String finalStatus = "INIT"; // Default
String foundOrderId = null;
String foundAcquirer = null;
if (results != null && results.length() > 0) {
Log.d("TransactionAdapter", "📊 Processing " + results.length() + " log entries");
// STEP 2: Comprehensive search dengan multiple matching strategies
for (int i = 0; i < results.length(); i++) {
JSONObject log = results.getJSONObject(i);
JSONObject reqBody = log.optJSONObject("request_body");
if (reqBody != null) {
String logOrderId = reqBody.optString("order_id", "");
String logTransactionStatus = reqBody.optString("transaction_status", "");
String logReferenceId = reqBody.optString("reference_id", "");
String logAcquirer = reqBody.optString("acquirer", "");
// ✅ METHOD 1: Direct reference_id match
boolean isDirectMatch = referenceId.equals(logReferenceId);
// ✅ METHOD 2: Check custom_field1 untuk QR refresh tracking
boolean isRefreshMatch = false;
String customField1 = reqBody.optString("custom_field1", "");
if (!customField1.isEmpty()) {
try {
JSONObject customData = new JSONObject(customField1);
String originalReference = customData.optString("original_reference", "");
String appReferenceId = customData.optString("app_reference_id", "");
if (referenceId.equals(originalReference) || referenceId.equals(appReferenceId)) {
isRefreshMatch = true;
Log.d("TransactionAdapter", "🔄 Found refresh match: " + logOrderId);
}
} catch (JSONException e) {
// Ignore custom field parsing errors
}
}
// ✅ METHOD 3: Check item details untuk backup tracking
boolean isItemMatch = false;
JSONArray itemDetails = reqBody.optJSONArray("item_details");
if (itemDetails != null && itemDetails.length() > 0) {
for (int j = 0; j < itemDetails.length(); j++) {
JSONObject item = itemDetails.optJSONObject(j);
if (item != null) {
String itemName = item.optString("name", "");
if (itemName.contains("(Ref: " + referenceId + ")") ||
itemName.contains("- " + referenceId)) {
isItemMatch = true;
Log.d("TransactionAdapter", "📦 Found item match: " + logOrderId);
break;
}
}
}
}
// ✅ COMPREHENSIVE MATCH: Any of the three methods
boolean isRelatedTransaction = isDirectMatch || isRefreshMatch || isItemMatch;
if (isRelatedTransaction) {
Log.d("TransactionAdapter", "🎯 MATCH FOUND!");
Log.d("TransactionAdapter", " Order ID: " + logOrderId);
Log.d("TransactionAdapter", " Status: " + logTransactionStatus);
Log.d("TransactionAdapter", " Acquirer: " + logAcquirer);
Log.d("TransactionAdapter", " Match Type: " +
(isDirectMatch ? "DIRECT " : "") +
(isRefreshMatch ? "REFRESH " : "") +
(isItemMatch ? "ITEM" : ""));
// ✅ PRIORITY SYSTEM: settlement > capture > success > pending > init
if (logTransactionStatus.equals("settlement") ||
logTransactionStatus.equals("capture") ||
logTransactionStatus.equals("success")) {
finalStatus = "PAID";
foundOrderId = logOrderId;
foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "✅ PAYMENT CONFIRMED: " + logOrderId + " -> " + logTransactionStatus);
break; // Found paid status, stop searching
} else if (logTransactionStatus.equals("pending") && finalStatus.equals("INIT")) {
finalStatus = "PENDING";
foundOrderId = logOrderId;
foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "⏳ PENDING found: " + logOrderId);
} else if (logTransactionStatus.equals("expire") || logTransactionStatus.equals("cancel")) {
if (finalStatus.equals("INIT")) { // Only update if no better status found
finalStatus = "FAILED";
foundOrderId = logOrderId;
foundAcquirer = logAcquirer;
Log.d("TransactionAdapter", "❌ FAILED status: " + logOrderId + " -> " + logTransactionStatus);
}
}
}
}
}
Log.d("TransactionAdapter", "🔍 FINAL RESULT for " + referenceId + ":");
Log.d("TransactionAdapter", " Status: " + finalStatus);
Log.d("TransactionAdapter", " Order ID: " + (foundOrderId != null ? foundOrderId : "N/A"));
Log.d("TransactionAdapter", " Acquirer: " + (foundAcquirer != null ? foundAcquirer : "N/A"));
}
// STEP 3: Update UI di main thread
final String displayStatus = finalStatus;
final String detectedAcquirer = foundAcquirer;
statusTextView.post(() -> {
statusTextView.setText(displayStatus);
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), displayStatus);
Log.d("TransactionAdapter", "🎨 UI UPDATED:");
Log.d("TransactionAdapter", " Reference: " + referenceId);
Log.d("TransactionAdapter", " Display Status: " + displayStatus);
Log.d("TransactionAdapter", " Detected Acquirer: " + (detectedAcquirer != null ? detectedAcquirer : "Unknown"));
});
// ✅ BONUS: Update backend jika status berubah ke PAID
if (finalStatus.equals("PAID")) {
updateBackendTransactionStatus(referenceId, finalStatus, foundOrderId, detectedAcquirer);
}
} else {
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
statusTextView.post(() -> {
statusTextView.setText("ERROR");
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR");
});
}
} catch (IOException | JSONException e) {
Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e);
statusTextView.post(() -> {
statusTextView.setText("INIT");
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT");
});
}
}).start();
}
/**
* ✅ NEW METHOD: Update backend transaction status when payment confirmed
*/
private void updateBackendTransactionStatus(String referenceId, String status, String orderId, String acquirer) {
new Thread(() -> {
try {
Log.d("TransactionAdapter", "🔄 Updating backend status for reference: " + referenceId);
JSONObject updatePayload = new JSONObject();
updatePayload.put("status", status);
updatePayload.put("payment_status", status);
updatePayload.put("paid_order_id", orderId);
updatePayload.put("detected_acquirer", acquirer);
updatePayload.put("updated_at", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new Date()));
updatePayload.put("settlement_time", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new Date()));
String updateUrl = "https://be-edc.msvc.app/transactions/update-by-reference";
URL url = new URL(updateUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("User-Agent", "BDKIPOCApp/1.0");
conn.setDoOutput(true);
conn.setConnectTimeout(15000);
conn.setReadTimeout(15000);
JSONObject requestBody = new JSONObject();
requestBody.put("reference_id", referenceId);
requestBody.put("update_data", updatePayload);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = requestBody.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
Log.d("TransactionAdapter", "📥 Backend update response: " + responseCode);
if (responseCode == 200 || responseCode == 201) {
Log.d("TransactionAdapter", "✅ Backend status updated successfully");
} else {
Log.e("TransactionAdapter", "❌ Backend update failed: " + responseCode);
}
} catch (Exception e) {
Log.e("TransactionAdapter", "❌ Backend update error: " + e.getMessage(), e);
}
}).start();
}
/**
* Format created_at date to readable format
*/
private String formatCreatedAtDate(String rawDate) {
if (rawDate == null || rawDate.isEmpty()) {
return "N/A";
}
Log.d("TransactionAdapter", "📅 Input date: '" + rawDate + "'");
try {
// Handle different possible input formats from API
SimpleDateFormat inputFormat;
String cleanedDate = rawDate;
if (rawDate.contains("T")) {
// ISO format: "2025-06-10T04:31:19.565Z"
cleanedDate = rawDate.replace("T", " ").replace("Z", "");
// Remove microseconds if present
if (cleanedDate.contains(".")) {
cleanedDate = cleanedDate.substring(0, cleanedDate.indexOf("."));
}
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
} else if (rawDate.length() > 19 && rawDate.contains(".")) {
// Format with microseconds: "2025-06-10 04:31:19.565"
cleanedDate = rawDate.substring(0, 19); // Cut off microseconds
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
} else {
// Standard format: "2025-06-10 04:31:19"
inputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
}
Log.d("TransactionAdapter", "📅 Cleaned date: '" + cleanedDate + "'");
// Output format: d/M/yyyy H:mm:ss
SimpleDateFormat outputFormat = new SimpleDateFormat("d/M/yyyy H:mm:ss", Locale.getDefault());
Date date = inputFormat.parse(cleanedDate);
if (date != null) {
String formatted = outputFormat.format(date);
Log.d("TransactionAdapter", "📅 Date formatted: " + rawDate + " -> " + formatted);
return formatted;
}
} catch (Exception e) {
Log.e("TransactionAdapter", "❌ Date formatting error for: " + rawDate, e);
}
// Fallback: Manual parsing
try {
// Handle format like "2025-06-10T04:31:19.565Z" manually
String workingDate = rawDate.replace("T", " ").replace("Z", "");
// Remove microseconds if present
if (workingDate.contains(".")) {
workingDate = workingDate.substring(0, workingDate.indexOf("."));
}
Log.d("TransactionAdapter", "📅 Manual parsing attempt: '" + workingDate + "'");
// Split into date and time parts
String[] parts = workingDate.split(" ");
if (parts.length >= 2) {
String datePart = parts[0]; // "2025-06-10"
String timePart = parts[1]; // "04:31:19"
String[] dateComponents = datePart.split("-");
if (dateComponents.length == 3) {
String year = dateComponents[0];
String month = dateComponents[1];
String day = dateComponents[2];
// Remove leading zeros and format as d/M/yyyy H:mm:ss
int dayInt = Integer.parseInt(day);
int monthInt = Integer.parseInt(month);
// Parse time to remove leading zeros from hour
String[] timeComponents = timePart.split(":");
if (timeComponents.length >= 3) {
int hour = Integer.parseInt(timeComponents[0]);
String minute = timeComponents[1];
String second = timeComponents[2];
String result = dayInt + "/" + monthInt + "/" + year + " " + hour + ":" + minute + ":" + second;
Log.d("TransactionAdapter", "📅 Manual format result: " + result);
return result;
}
}
}
} catch (Exception e) {
Log.w("TransactionAdapter", "❌ Manual date formatting failed: " + e.getMessage());
}
Log.w("TransactionAdapter", "📅 Using fallback - returning original date: " + rawDate);
return rawDate;
}
private String getPaymentMethodName(String channelCode, String channelCategory) {
// Convert channel code to readable payment method name
if (channelCode == null) return "Unknown";
switch (channelCode.toUpperCase()) {
case "QRIS":
return "QRIS";
case "DEBIT":
return "Kartu Debit";
case "CREDIT":
return "Kartu Kredit";
case "BCA":
return "BCA";
case "MANDIRI":
return "Mandiri";
case "BNI":
return "BNI";
case "BRI":
return "BRI";
case "CASH":
return "Tunai";
case "EDC":
return "EDC";
default:
// If channel category is available, use it as fallback
if (channelCategory != null && !channelCategory.isEmpty()) {
return channelCategory.toUpperCase();
}
return channelCode.toUpperCase();
}
}
@Override
@@ -51,14 +562,17 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
}
static class TransactionViewHolder extends RecyclerView.ViewHolder {
TextView amount, status, referenceId, merchantName, createdAt;
TextView amount, referenceId, status, paymentMethod, createdAt; // ✅ Added createdAt
LinearLayout printSection;
public TransactionViewHolder(@NonNull View itemView) {
super(itemView);
amount = itemView.findViewById(R.id.textAmount);
status = itemView.findViewById(R.id.textStatus);
referenceId = itemView.findViewById(R.id.textReferenceId);
merchantName = itemView.findViewById(R.id.textMerchantName);
createdAt = itemView.findViewById(R.id.textCreatedAt);
status = itemView.findViewById(R.id.textStatus);
paymentMethod = itemView.findViewById(R.id.textPaymentMethod);
createdAt = itemView.findViewById(R.id.textCreatedAt); // ✅ Added createdAt
printSection = itemView.findViewById(R.id.printSection);
}
}
}

View 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" />

View 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" />

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#3498DB" />
<corners android:radius="8dp" />
</shape>

View 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>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F5F5F5" />
<stroke
android:width="1dp"
android:color="#E0E0E0" />
<corners android:radius="8dp" />
</shape>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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="#000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,16h2v-1h1c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1h-3v-1h4V8h-2V7h-2v1h-1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1h3v1H9v2h2v1z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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="#FFFFFF"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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="#FFFFFF"
android:pathData="M3,11h8L11,3L3,3v8zM5,5h4v4L5,9L5,5zM13,3v8h8L21,3h-8zM19,9h-4L15,5h4v4zM3,21h8v-8L3,13v8zM5,15h4v4L5,19v-4zM13,13v8h8v-8h-8zM19,19h-4v-4h4v4z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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="#000000"
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM12,7c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM18,19L6,19v-1.4c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1L18,19z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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="#000000"
android:pathData="M16,17.01V10h-2v7.01h-3L15,20l4,-3h-3zM9,3L5,6h3v7.01h2V6h3L9,3z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#f5f5f5" />
<corners android:radius="4dp" />
<stroke android:width="1dp" android:color="#e0e0e0" />
</shape>

View File

@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#666666" />
<corners android:radius="4dp" />
</shape>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font
app:font="@font/inter_regular"
app:fontWeight="400"
app:fontStyle="normal"/>
<font
app:font="@font/inter_medium"
app:fontWeight="500"
app:fontStyle="normal"/>
</font-family>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:orientation="vertical">
<!-- Header with red background -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#E53E3E">
<!-- Back button -->
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
android:src="@android:drawable/ic_menu_revert"
app:tint="@android:color/white" />
<!-- Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_toEndOf="@id/btn_back"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="16sp" />
<!-- History Card -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="16dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4299E1"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Transaksi Hari Ini"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Total Transaksi"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_total_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="RP 4.500.000"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Jumlah Transaksi"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_total_transactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="30"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="@+id/btn_lihat_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="8dp"
android:text="Lihat Detail"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<!-- Content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Transaksi Terbaru"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
<!-- Transaction List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<!-- Bottom Button -->
<Button
android:id="@+id/btn_lihat_detail_bottom"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:background="#6B46C1"
android:text="Lihat Detail"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:orientation="vertical">
<!-- Header -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="#E53E3E"
android:padding="16dp">
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:src="@android:drawable/ic_menu_revert"
app:tint="@android:color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Detail Transaksi"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
</RelativeLayout>
<!-- Content -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" />
</LinearLayout>

View File

@@ -1,92 +1,701 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#FFFFFF">
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/main_title"
android:textSize="24sp"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".MainActivity">
<androidx.cardview.widget.CardView
android:id="@+id/card_payment"
android:layout_width="140dp"
android:layout_height="140dp"
android:layout_marginTop="64dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/light_blue"
app:layout_constraintEnd_toStartOf="@+id/card_transactions"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_text">
<LinearLayout
<!-- Status Bar Area -->
<View
android:id="@+id/status_bar_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@android:drawable/ic_menu_send"
android:contentDescription="@string/payment"
app:tint="@color/primary_blue" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/payment2"
android:textColor="@color/primary_blue"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_transactions"
android:layout_width="140dp"
android:layout_height="140dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/light_gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/card_payment"
app:layout_constraintTop_toTopOf="@+id/card_payment">
<LinearLayout
<!-- Red Background Header -->
<View
android:id="@+id/red_header_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
android:layout_height="100dp"
android:background="#E31937"
app:layout_constraintTop_toBottomOf="@id/status_bar_background"/>
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@android:drawable/ic_menu_recent_history"
android:contentDescription="@string/transactions"
app:tint="@color/accent_teal" />
<!-- Merchant Card -->
<androidx.cardview.widget.CardView
android:id="@+id/merchant_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:layout_constraintTop_toBottomOf="@id/status_bar_background">
<TextView
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/transactions2"
android:textColor="@color/accent_teal"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
android:orientation="vertical"
android:padding="16dp">
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:layout_width="144dp"
android:layout_height="36dp"
android:src="@drawable/ic_logo_icon"
android:scaleType="fitStart"
android:adjustViewBounds="true"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TOKO KLONTONG PAK EKO"
android:textColor="#061D28"
android:textSize="18sp"
android:textStyle="bold"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Ciputat Baru, Tangsel"
android:textColor="#9FA4A9"
android:textSize="14sp"
android:layout_gravity="center_horizontal"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:background="#EEEEEE"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MID: 12345678901"
android:textColor="#9FA4A9"
android:textSize="14sp"
android:layout_marginEnd="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TID: 12345678901"
android:textColor="#9FA4A9"
android:textSize="14sp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Menu Grid -->
<GridLayout
android:id="@+id/menu_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="5"
android:background="@android:color/white"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/merchant_card">
<!-- Row 1: Kartu Kredit, Kartu Debit, QRIS -->
<androidx.cardview.widget.CardView
android:id="@+id/card_kartu_kredit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_credit_card"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Kartu Kredit"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_kartu_debit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_debit_card"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Kartu Debit"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_qris"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qris"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="QRIS"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Row 2: Uang Elektronik, Cetak Ulang, Settlement -->
<androidx.cardview.widget.CardView
android:id="@+id/card_uang_elektronik"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_e_money"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Uang Elektronik"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_cetak_ulang"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_reprint"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Cetak Ulang"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_settlement"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_settlement"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Settlement"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Row 3: Histori, Bantuan, Info Toko -->
<androidx.cardview.widget.CardView
android:id="@+id/card_histori"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_history"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Histori"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_bantuan"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="visible"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_help"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Bantuan"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_info_toko"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="visible"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_store_info"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Info Toko"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Row 4: Dummy Menu 1, 2, 3 (Hidden initially) -->
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 1"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 2"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 3"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Row 5: Dummy Menu 4, 5, 6 (Hidden initially) -->
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 4"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 5"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_dummy_menu_6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_rowWeight="1"
android:layout_margin="8dp"
android:visibility="gone"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#F3F4F3">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="#E31937"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dummy Menu 6"
style="@style/MenuCardTitle"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</GridLayout>
<!-- Lainnya Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_lainnya"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tampilkan Lebih Sedikit"
android:textColor="#DE0701"
android:backgroundTint="#FFFFFF"
android:textAllCaps="false"
app:strokeColor="#DE0701"
app:strokeWidth="1dp"
app:cornerRadius="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/menu_grid"/>
<!-- Scan dan Bayar Card -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardBackgroundColor="#E31937"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_lainnya"
app:layout_constraintVertical_bias="0">
<LinearLayout
android:id="@+id/scan_bayar_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="16dp">
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_qr_code"
app:tint="@android:color/white"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SCAN DAN BAYAR"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Terima pembayaran dengan QRIS secara mudah"
android:textColor="@android:color/white"
android:textSize="12sp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -1,227 +1,268 @@
<?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=".PaymentActivity">
<LinearLayout
<!-- Red Status Bar (Override purple) -->
<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 (Extended height untuk back navigation) -->
<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_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone" />
android:layout_marginStart="8dp"
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="18sp"
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
android:id="@+id/statusTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="center"
android:text="Ready to make a payment"
android:textSize="18sp" />
android:id="@+id/btn2"
style="@style/NumpadButton"
android:text="2" />
<!-- Initial Payment Form -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/btn3"
style="@style/NumpadButton"
android:text="3" />
<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">
<!-- Row 2: 4, 5, 6 -->
<TextView
android:id="@+id/btn4"
style="@style/NumpadButton"
android:text="4" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/btn5"
style="@style/NumpadButton"
android:text="5" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount"
android:textColor="@color/primary_blue"
android:textSize="16sp" />
<TextView
android:id="@+id/btn6"
style="@style/NumpadButton"
android:text="6" />
<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" />
<!-- Row 3: 7, 8, 9 -->
<TextView
android:id="@+id/btn7"
style="@style/NumpadButton"
android:text="7" />
<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/btn8"
style="@style/NumpadButton"
android:text="8" />
<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>
<TextView
android:id="@+id/btn9"
style="@style/NumpadButton"
android:text="9" />
<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>
<!-- Row 4: 000, 0, Delete -->
<TextView
android:id="@+id/btn000"
style="@style/NumpadButton"
android:text="000" />
<!-- 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">
<TextView
android:id="@+id/btn0"
style="@style/NumpadButton"
android:text="0" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<ImageView
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:src="@drawable/ic_backspace"
android:scaleType="center"
android:contentDescription="Delete" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp">
</GridLayout>
<ImageView
android:id="@+id/qrCodeImageView"
android:layout_width="250dp"
android:layout_height="250dp"
android:contentDescription="QRIS Code"
android:scaleType="fitCenter" />
<!-- Confirmation Button (UPDATED: Menggunakan MaterialButton) -->
<com.google.android.material.button.MaterialButton
android:id="@+id/confirmButton"
android:layout_width="match_parent"
android:layout_height="48dp"
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
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>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,298 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#F5F5F5">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".PinActivity">
<!-- Red Status Bar (Override purple) -->
<View
android:id="@+id/red_status_bar"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<!-- Red Background Header (Extended height untuk back navigation) -->
<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"/>
<!-- Header with 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 -->
<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_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- PIN Card -->
<androidx.cardview.widget.CardView
android:id="@+id/pin_card"
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 Text -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SILAKAN MASUKAN PIN"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginBottom="24dp"
android:gravity="center"
android:textAlignment="center"/>
<!-- PIN Input Display -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:layout_marginEnd="8dp"
android:layout_gravity="center_vertical" />
<EditText
android:id="@+id/editTextPin"
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="center"
android:textAlignment="center"
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" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Numpad Grid -->
<GridLayout
android:id="@+id/numpad_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="4"
android:layout_margin="16dp"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/pin_card">
<!-- Row 1: 1, 2, 3 -->
<TextView
android:id="@+id/btn1"
style="@style/NumpadButton"
android:text="1"/>
<TextView
android:id="@+id/btn2"
style="@style/NumpadButton"
android:text="2"/>
<TextView
android:id="@+id/btn3"
style="@style/NumpadButton"
android:text="3"/>
<!-- Row 2: 4, 5, 6 -->
<TextView
android:id="@+id/btn4"
style="@style/NumpadButton"
android:text="4"/>
<TextView
android:id="@+id/btn5"
style="@style/NumpadButton"
android:text="5"/>
<TextView
android:id="@+id/btn6"
style="@style/NumpadButton"
android:text="6"/>
<!-- Row 3: 7, 8, 9 -->
<TextView
android:id="@+id/btn7"
style="@style/NumpadButton"
android:text="7"/>
<TextView
android:id="@+id/btn8"
style="@style/NumpadButton"
android:text="8"/>
<TextView
android:id="@+id/btn9"
style="@style/NumpadButton"
android:text="9"/>
<!-- Row 4: 000, 0, Delete -->
<TextView
android:id="@+id/btn000"
style="@style/NumpadButton"
android:text="000"/>
<TextView
android:id="@+id/btn0"
style="@style/NumpadButton"
android:text="0"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_margin="8dp"
android:gravity="center">
<ImageView
android:id="@+id/btnDelete"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_backspace"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="12dp"
app:tint="#666666"/>
</LinearLayout>
</GridLayout>
<!-- Confirmation Button -->
<Button
android:id="@+id/confirmButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:layout_marginTop="32dp"
android:text="Konfirmasi"
android:textColor="#999999"
android:textSize="16sp"
android:textStyle="bold"
android:background="@drawable/button_inactive_background"
android:enabled="false"
android:alpha="0.6"
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"/>
<!-- Success Screen (Full Screen Overlay) - IMPROVED VERSION -->
<LinearLayout
android:id="@+id/success_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#E31937"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<!-- Success Icon -->
<ImageView
android:id="@+id/success_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_success_payment"
android:layout_marginBottom="32dp"
android:scaleType="centerInside"/>
<!-- Success Message -->
<TextView
android:id="@+id/success_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pembayaran Berhasil"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:gravity="center"
android:letterSpacing="0.02"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,306 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#FFFFFF">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
tools:context=".QrisActivity">
<!-- Red Status Bar -->
<View
android:id="@+id/red_status_bar"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<!-- 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:text=""
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginEnd="8dp" />
<!-- Title Text -->
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="12sp"
android:textStyle="normal" />
<!-- Hidden back arrow for Java compatibility -->
<ImageView
android:id="@+id/backArrow"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
</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="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>

View File

@@ -1,82 +1,223 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:background="#181824"
>
android:background="#FFFFFF">
<TextView
android:id="@+id/amountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/amount"
android:textColor="#2D5DA1"
android:textSize="20sp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"/>
<!-- Red Status Bar -->
<View
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="#E31937" />
<TextView
android:id="@+id/referenceTextView"
android:layout_width="wrap_content"
<!-- Header with Back Navigation -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/reference_id"
android:textColor="#2D5DA1"
android:background="#E31937"
android:paddingBottom="16dp">
<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:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:clickable="true"
android:focusable="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginEnd="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<!-- White Card Container -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginTop="0dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center_horizontal">
<!-- Generate QR Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Generate QR"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<!-- QRIS Logo Text -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QRIS"
android:textColor="@android:color/black"
android:textSize="32sp"
android:textStyle="bold"
android:fontFamily="monospace"
android:layout_marginBottom="24dp" />
<!-- QR Code -->
<ImageView
android:id="@+id/qrImageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:contentDescription="QRIS QR Code"
android:scaleType="fitCenter"
android:background="#F0F0F0"
android:layout_marginBottom="24dp" />
<!-- Amount Display -->
<TextView
android:id="@+id/amountTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RP.200.000"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<!-- Timer/Counter -->
<TextView
android:id="@+id/timerTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="60"
android:textColor="#E31937"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="24dp" />
<!-- QR Refresh Status -->
<TextView
android:id="@+id/qrStatusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QR Code akan refresh dalam"
android:textColor="#666666"
android:textSize="12sp"
android:layout_marginBottom="16dp"
android:visibility="visible" />
<!-- Action Buttons Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp">
<!-- Download QRIS Button -->
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:text="Download QRIS"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#4CAF50"
android:visibility="visible" />
<!-- Check Payment Status Button -->
<Button
android:id="@+id/checkStatusButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:text="Check Payment Status"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#2196F3"
android:visibility="visible" />
<!-- Return to Main Button -->
<Button
android:id="@+id/returnMainButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="Return to Main"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#FF9800"
android:visibility="visible" />
</LinearLayout>
<!-- Hidden views for compatibility -->
<TextView
android:id="@+id/referenceTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reference ID: ref-12345"
android:textColor="@android:color/black"
android:textSize="14sp"
android:visibility="gone" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Payment Status Success"
android:textColor="@android:color/black"
android:textSize="18sp"
android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Spacer to push button to bottom -->
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<!-- Bottom Cancel Button -->
<Button
android:id="@+id/cancelButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:text="Batalkan"
android:textColor="#E31937"
android:textSize="16sp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="16dp"
android:layout_marginTop="8dp"/>
<ImageView
android:id="@+id/qrImageView"
android:layout_width="280dp"
android:layout_height="280dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:contentDescription="@string/qr_code"/>
<Button
android:id="@+id/downloadQrisButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download_qris"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"/>
<Button
android:id="@+id/checkStatusButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/check_payment_status"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone"/>
<TextView
android:id="@+id/statusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/payment_status_success"
android:textColor="#2D5DA1"
android:textSize="20sp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:visibility="gone"/>
<Button
android:id="@+id/returnMainButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/return_main"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:visibility="gone"/>
android:textStyle="normal"
android:background="@android:color/transparent"
style="?android:attr/borderlessButtonStyle" />
</LinearLayout>

View File

@@ -0,0 +1,547 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:overScrollMode="never"
android:scrollbars="none"
android:background="#F5F5F5">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
tools:context=".ReceiptActivity">
<!-- Red Status Bar (Override purple) -->
<View
android:id="@+id/red_status_bar"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#E31937"
app:layout_constraintTop_toTopOf="parent"/>
<!-- Red Background Header -->
<View
android:id="@+id/red_header_background"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="#E31937"
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
<!-- Header with 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_marginTop="16dp"
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="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_arrow_back"
app:tint="@android:color/white"
android:contentDescription="Kembali" />
<!-- Title Text -->
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="16sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Receipt Card - Positioned to overlap header by half -->
<androidx.cardview.widget.CardView
android:id="@+id/receipt_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="60dp"
app:cardBackgroundColor="@android:color/white"
app:cardCornerRadius="12dp"
app:cardElevation="8dp"
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Header Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:layout_marginBottom="16dp">
<!-- EDC Merchant Logo/Text -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="EDC "
android:textColor="#E31937"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Merchant"
android:textColor="#3F51B5"
android:textSize="24sp"
android:textStyle="bold"
android:fontFamily="@font/inter"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BANK BRI"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter"
android:layout_marginTop="2dp"/>
</LinearLayout>
<!-- Merchant Info -->
<TextView
android:id="@+id/merchant_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TOKO KLONTONG PAK EKO"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold"
android:fontFamily="@font/inter"
android:gravity="center"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/merchant_location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ciputat Baru, Tangsel"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"
android:gravity="center"
android:layout_marginBottom="16dp"/>
<!-- Separator Line -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
android:layout_marginBottom="16dp"/>
<!-- Transaction Details -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="MID: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/mid_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="1234567890"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TID: "
android:textColor="#666666"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/tid_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="1234567890"
android:textColor="#333333"
android:textSize="12sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Separator Line -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
android:layout_marginVertical="12dp"/>
<!-- Transaction Number -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Nomor transaksi"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/transaction_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3429483635"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Transaction Date -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Tanggal transaksi"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/transaction_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="13 Januari 2025 13:46"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Payment Method -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Metode pembayaran"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/payment_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kartu Kredit"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Card Type -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Jenis Kartu"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/card_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BCA"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Separator Line -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#E0E0E0"
android:layout_marginBottom="16dp"/>
<!-- Transaction Amount -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Total transaksi"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/transaction_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3.500.000"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Tax -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Pajak (%)"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/tax_percentage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="11%"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Service Fee -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Biaya Layanan"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/service_fee"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="500"
android:textColor="#333333"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Separator Line -->
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#E0E0E0"
android:layout_marginBottom="16dp"/>
<!-- Final Total -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="20dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TOTAL"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter"/>
<TextView
android:id="@+id/final_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3.506.500"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold"
android:fontFamily="@font/inter"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Action Buttons (Print & Email) -->
<LinearLayout
android:id="@+id/action_buttons_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@id/receipt_card">
<!-- Print Button -->
<LinearLayout
android:id="@+id/print_button"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:orientation="horizontal"
android:gravity="center"
android:background="@drawable/button_secondary_background"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_print"
app:tint="#666666"
android:layout_marginEnd="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cetak Ulang"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
<!-- Email Button -->
<LinearLayout
android:id="@+id/email_button"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:orientation="horizontal"
android:gravity="center"
android:background="@drawable/button_secondary_background"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_email"
app:tint="#666666"
android:layout_marginEnd="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Email"
android:textColor="#666666"
android:textSize="14sp"
android:fontFamily="@font/inter"/>
</LinearLayout>
</LinearLayout>
<!-- Finish Button -->
<Button
android:id="@+id/finish_button"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="24dp"
android:text="Selesai"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
android:background="@drawable/button_finish_background"
android:fontFamily="@font/inter"
app:layout_constraintTop_toBottomOf="@id/action_buttons_container"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:orientation="vertical">
<!-- Header with solid red background -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#E53E3E">
<!-- Back button -->
<ImageView
android:id="@+id/btn_back"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
android:src="@android:drawable/ic_menu_revert"
app:tint="@android:color/white" />
<!-- Title -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_toEndOf="@id/btn_back"
android:text="Kembali"
android:textColor="@android:color/white"
android:textSize="16sp" />
<!-- Settlement Card -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginStart="16dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4299E1"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SETTLEMENT"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Rekap dan kirim transaksi hari ini"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_total_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="3.506.500"
android:textColor="@android:color/white"
android:textSize="24sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Jumlah Transaksi"
android:textColor="#E0FFFFFF"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/tv_total_transactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="65"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</RelativeLayout>
<!-- Content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Informasi Ringkasan Transaksi Hari Ini"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Pastikan Anda melakukan settlement untuk menyelesaikan transaksi harian."
android:textColor="#666666"
android:textSize="12sp" />
<!-- Transaction List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<!-- Bottom Button -->
<Button
android:id="@+id/btn_continue"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_margin="16dp"
android:background="#E53E3E"
android:text="Selanjutnya"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>

View File

@@ -6,26 +6,214 @@
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:elevation="0dp"
app:elevation="0dp">
<!-- Status Bar Background -->
<View
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="#F44336" />
<!-- Main Toolbar -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:layout_height="56dp"
android:background="#F44336"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="Transactions History" />
app:contentInsetStart="0dp"
app:contentInsetLeft="0dp">
<!-- Custom toolbar content -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<!-- Back Button -->
<ImageView
android:id="@+id/backButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_revert"
android:layout_marginEnd="16dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="4dp"
android:clickable="true"
android:focusable="true"
android:rotation="180" />
<!-- Title -->
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cetak Ulang Struk"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="normal"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<!-- Title Section (outside toolbar) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cetak Ulang Struk"
android:textColor="#333333"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<!-- ✅ PERBAIKAN: Gunakan LinearLayout dengan weight distribution yang lebih baik -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="#f8f9fa"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Search and Filter Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<!-- Search Bar -->
<LinearLayout
android:id="@+id/searchContainer"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:layout_marginEnd="12dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_search"
android:layout_marginEnd="12dp"
android:alpha="0.5" />
<EditText
android:id="@+id/searchEditText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:hint="Search"
android:textSize="16sp"
android:textColorHint="#999999"
android:textColor="#333333"
android:maxLines="1"
android:gravity="center_vertical" />
</LinearLayout>
<!-- Filter Button -->
<LinearLayout
android:id="@+id/filterButton"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:orientation="horizontal"
android:gravity="center"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:clickable="true"
android:focusable="true"
android:minWidth="120dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@android:drawable/ic_menu_my_calendar"
android:layout_marginEnd="6dp"
android:alpha="0.5" />
<TextView
android:id="@+id/filterButtonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Filter"
android:textSize="14sp"
android:textColor="#666666"
android:maxLines="2"
android:ellipsize="end"
android:gravity="center" />
</LinearLayout>
</LinearLayout>
<!-- Table Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:background="#f5f5f5"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Nomor Struk"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#666666" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Amount"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#666666"
android:gravity="center" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Action"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#666666"
android:gravity="center" />
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
@@ -34,21 +222,89 @@
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp" />
<!-- ✅ PERBAIKAN: RecyclerView dengan height yang tepat untuk mencegah pagination terpotong -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp" />
android:layout_height="0dp"
android:layout_weight="1"
android:background="#ffffff"
android:clipToPadding="false"
android:paddingBottom="8dp" />
<!-- ✅ PERBAIKAN: Pagination Controls dengan padding dan margin yang lebih baik -->
<LinearLayout
android:id="@+id/paginationControls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:color/white"
android:paddingTop="16dp"
android:paddingBottom="20dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:gravity="center"
android:elevation="4dp"
android:visibility="gone"
android:layout_marginTop="2dp">
<!-- First Page Button -->
<ImageButton
android:id="@+id/btnFirstPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_media_previous"
android:contentDescription="First Page"
android:layout_marginEnd="8dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
<!-- Previous Page Button -->
<ImageButton
android:id="@+id/btnPrevPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_media_rew"
android:contentDescription="Previous Page"
android:layout_marginEnd="12dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
<!-- Page Numbers Container -->
<LinearLayout
android:id="@+id/pageNumbersContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:minHeight="48dp" />
<!-- Next Page Button -->
<ImageButton
android:id="@+id/btnNextPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_media_ff"
android:contentDescription="Next Page"
android:layout_marginStart="12dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
<!-- Last Page Button -->
<ImageButton
android:id="@+id/btnLastPage"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_media_next"
android:contentDescription="Last Page"
android:layout_marginStart="8dp"
android:scaleType="centerInside"
android:background="?android:attr/selectableItemBackgroundBorderless" />
</LinearLayout>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/refreshButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="Refresh"
android:src="@android:drawable/ic_popup_sync"
app:tint="@android:color/white" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<!-- Left Content -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="09:00, 07-05-2025"
android:textColor="#333333"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Rp. 78.000"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Center Content -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kredit"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Right Content -->
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dp"
android:text="Berhasil"
android:textColor="#4CAF50"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>

View File

@@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Header Row -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_reference_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Ref: 197870"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SUCCESS"
android:textColor="#4CAF50"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Amount -->
<TextView
android:id="@+id/tv_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Rp. 2.018.619"
android:textColor="#333333"
android:textSize="18sp"
android:textStyle="bold" />
<!-- Details Grid -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="vertical">
<!-- Row 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Channel"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Other Payment"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Waktu"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="16/05/2025 05:40"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<!-- Row 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Merchant"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_merchant"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TEST MERCHANT"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Issuer"
android:textColor="#666666"
android:textSize="12sp" />
<TextView
android:id="@+id/tv_issuer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BANK MANDIRI"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<!-- Icon -->
<ImageView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:background="#F7FAFC"
android:padding="8dp"
android:src="@android:drawable/ic_menu_gallery"
app:tint="#E53E3E" />
<!-- Content -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kartu Kredit"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Amount and Count -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tv_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="Rp. 200.000"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_transaction_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="2dp"
android:text="13 Transaksi"
android:textColor="#666666"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,44 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="@android:color/white"
android:layout_marginBottom="8dp"
android:elevation="2dp">
android:orientation="vertical"
android:id="@+id/itemContainer">
<TextView
android:id="@+id/textAmount"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Amount"
android:textStyle="bold"
android:textSize="18sp" />
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical"
android:minHeight="64dp">
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status"
android:textColor="#4CAF50"
android:textStyle="bold" />
<!-- Column 1: Transaction Info -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:orientation="vertical">
<TextView
android:id="@+id/textReferenceId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reference ID" />
<!-- Reference ID -->
<TextView
android:id="@+id/textReferenceId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ref-eowu3pin"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/textMerchantName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Merchant Name" />
<!-- Status, Payment Method, and Created At Row -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<!-- Status -->
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SUCCESS"
android:textSize="12sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="2dp"
android:paddingBottom="2dp" />
<!-- Payment Method -->
<TextView
android:id="@+id/textPaymentMethod"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="QRIS"
android:textSize="12sp"
android:textColor="#666666"
android:layout_marginStart="12dp" />
</LinearLayout>
<!-- Created At Date -->
<TextView
android:id="@+id/textCreatedAt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="01 Jan 2024 14:30"
android:textSize="11sp"
android:textColor="#999999"
android:layout_marginTop="2dp" />
</LinearLayout>
<!-- Column 2: Amount -->
<TextView
android:id="@+id/textAmount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Rp. 1.111"
android:textColor="#333333"
android:textSize="14sp"
android:textStyle="bold"
android:gravity="center" />
<!-- Column 3: Print Button -->
<LinearLayout
android:id="@+id/printSection"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:gravity="center"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp"
android:clickable="true"
android:focusable="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cetak Ulang"
android:textColor="#666666"
android:textSize="12sp"
android:drawableLeft="@android:drawable/ic_menu_edit"
android:drawablePadding="4dp"
android:gravity="center_vertical" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/textCreatedAt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Created At" />
</LinearLayout>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- res/layout/modal_layout.xml -->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="32dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="#FFFFFF">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<!-- Card Icon -->
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_card_insert"
android:layout_marginBottom="24dp"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>
<!-- Main Text -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Silakan Tempelkan / Gesekkan / Masukkan Kartu ke Perangkat"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="normal"
android:textAlignment="center"
android:gravity="center"
android:lineSpacingExtra="4dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Style yang sudah ada -->
<style name="MenuCardTitle">
<item name="fontFamily">@font/inter</item>
<item name="android:textSize">12sp</item>
<item name="android:textColor">#DD0701</item>
<item name="android:textAlignment">center</item>
<item name="android:lineSpacingExtra">0dp</item>
<item name="android:lineSpacingMultiplier">1.0</item>
<item name="android:letterSpacing">0</item>
<item name="android:textStyle">normal</item>
</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>

0
gradlew vendored Normal file → Executable file
View File