Compare commits
30 Commits
master
...
729bdddad4
| Author | SHA1 | Date | |
|---|---|---|---|
| 729bdddad4 | |||
| c56cae64b9 | |||
| d4245c5906 | |||
| eddade3200 | |||
| 13ab6b717e | |||
| 991f77dabe | |||
| da8bcf17cc | |||
| b0ee2e8ee6 | |||
| 4aaa9957e7 | |||
| 99fab68e71 | |||
| 074a4b1f53 | |||
| a1f536b03e | |||
| edca7f92ec | |||
| 3f189f5975 | |||
| 5a03fc3aec | |||
| a30e767adc | |||
| 74f95e0374 | |||
| 1799e7eb0e | |||
| 2a24016637 | |||
| 459d9ab0f1 | |||
| 191966a2e4 | |||
| 46fb81b6a7 | |||
| 290f3015d9 | |||
| f1228db89a | |||
| 810964b4be | |||
| a7fa40d60a | |||
| a07e7a99ac | |||
| c55af6141f | |||
| 6d681f5e41 | |||
| 1ca26371a1 |
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic"
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.example.bdkipoc"
|
applicationId "com.example.bdkipoc"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 30
|
targetSdk 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ dependencies {
|
|||||||
implementation libs.activity
|
implementation libs.activity
|
||||||
implementation libs.constraintlayout
|
implementation libs.constraintlayout
|
||||||
implementation libs.cardview
|
implementation libs.cardview
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||||
testImplementation libs.junit
|
testImplementation libs.junit
|
||||||
androidTestImplementation libs.ext.junit
|
androidTestImplementation libs.ext.junit
|
||||||
androidTestImplementation libs.espresso.core
|
androidTestImplementation libs.espresso.core
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.BDKIPOC"
|
android:theme="@style/Theme.BDKIPOC"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -33,7 +34,27 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".PaymentActivity"
|
android:name=".PaymentActivity"
|
||||||
android:exported="false" />
|
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=".QrisResultActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".SettlementActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".HistoryActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".HistoryDetailActivity"
|
||||||
|
android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
415
app/src/main/java/com/example/bdkipoc/HistoryActivity.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
204
app/src/main/java/com/example/bdkipoc/HistoryDetailActivity.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +1,267 @@
|
|||||||
package com.example.bdkipoc;
|
package com.example.bdkipoc;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import androidx.activity.EdgeToEdge;
|
import androidx.activity.EdgeToEdge;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.cardview.widget.CardView;
|
import androidx.cardview.widget.CardView;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.core.graphics.Insets;
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
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
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
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);
|
super.onCreate(savedInstanceState);
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
setContentView(R.layout.activity_main);
|
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());
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up click listeners for the cards
|
// Initialize views
|
||||||
CardView paymentCard = findViewById(R.id.card_payment);
|
btnLainnya = findViewById(R.id.btn_lainnya);
|
||||||
CardView transactionsCard = findViewById(R.id.card_transactions);
|
|
||||||
|
|
||||||
paymentCard.setOnClickListener(v -> {
|
// Check if we're returning from a completed transaction
|
||||||
// Launch payment activity
|
checkTransactionCompletion();
|
||||||
startActivity(new android.content.Intent(MainActivity.this, PaymentActivity.class));
|
|
||||||
|
// 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 -> {
|
// Set up scan dan bayar card click listener
|
||||||
// Launch transactions activity
|
LinearLayout scanBayarContent = findViewById(R.id.scan_bayar_content);
|
||||||
startActivity(new android.content.Intent(MainActivity.this, TransactionActivity.class));
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
558
app/src/main/java/com/example/bdkipoc/PinActivity.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
894
app/src/main/java/com/example/bdkipoc/QrisActivity.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,50 +9,59 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.*;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import java.io.*;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
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 {
|
public class QrisResultActivity extends AppCompatActivity {
|
||||||
|
// UI Components
|
||||||
private ImageView qrImageView;
|
private ImageView qrImageView;
|
||||||
private TextView amountTextView;
|
private TextView amountTextView, referenceTextView, statusTextView;
|
||||||
private TextView referenceTextView;
|
private TextView timerTextView, qrStatusTextView;
|
||||||
private Button downloadQrisButton;
|
private Button downloadQrisButton, checkStatusButton, returnMainButton;
|
||||||
private Button checkStatusButton;
|
|
||||||
private TextView statusTextView;
|
|
||||||
private Button returnMainButton;
|
|
||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private String orderId;
|
|
||||||
private String grossAmount;
|
// QR Refresh Components
|
||||||
private String referenceId;
|
private Handler qrRefreshHandler;
|
||||||
private String transactionId;
|
private Runnable qrRefreshRunnable;
|
||||||
private String transactionTime;
|
private int countdownSeconds = 60;
|
||||||
private String acquirer;
|
private boolean isQrRefreshActive = true;
|
||||||
private String merchantId;
|
|
||||||
private String backendBase = "https://be-edc.msvc.app";
|
// Transaction Data
|
||||||
private String webhookUrl = "https://be-edc.msvc.app/webhooks/midtrans";
|
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
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_qris_result);
|
setContentView(R.layout.activity_qris_result);
|
||||||
|
|
||||||
|
initializeViews();
|
||||||
|
extractIntentData();
|
||||||
|
validateAndSetupUI();
|
||||||
|
startMonitoring();
|
||||||
|
setupClickListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeViews() {
|
||||||
qrImageView = findViewById(R.id.qrImageView);
|
qrImageView = findViewById(R.id.qrImageView);
|
||||||
amountTextView = findViewById(R.id.amountTextView);
|
amountTextView = findViewById(R.id.amountTextView);
|
||||||
referenceTextView = findViewById(R.id.referenceTextView);
|
referenceTextView = findViewById(R.id.referenceTextView);
|
||||||
@@ -61,10 +70,15 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
statusTextView = findViewById(R.id.statusTextView);
|
statusTextView = findViewById(R.id.statusTextView);
|
||||||
returnMainButton = findViewById(R.id.returnMainButton);
|
returnMainButton = findViewById(R.id.returnMainButton);
|
||||||
progressBar = findViewById(R.id.progressBar);
|
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();
|
Intent intent = getIntent();
|
||||||
String qrImageUrl = intent.getStringExtra("qrImageUrl");
|
currentQrImageUrl = intent.getStringExtra("qrImageUrl");
|
||||||
int amount = intent.getIntExtra("amount", 0);
|
originalAmount = intent.getIntExtra("amount", 0);
|
||||||
referenceId = intent.getStringExtra("referenceId");
|
referenceId = intent.getStringExtra("referenceId");
|
||||||
orderId = intent.getStringExtra("orderId");
|
orderId = intent.getStringExtra("orderId");
|
||||||
grossAmount = intent.getStringExtra("grossAmount");
|
grossAmount = intent.getStringExtra("grossAmount");
|
||||||
@@ -73,215 +87,603 @@ public class QrisResultActivity extends AppCompatActivity {
|
|||||||
acquirer = intent.getStringExtra("acquirer");
|
acquirer = intent.getStringExtra("acquirer");
|
||||||
merchantId = intent.getStringExtra("merchantId");
|
merchantId = intent.getStringExtra("merchantId");
|
||||||
|
|
||||||
|
allOrderIds.add(orderId);
|
||||||
|
Log.d("QrisResult", "Initialized with Order ID: " + orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateAndSetupUI() {
|
||||||
if (orderId == null || transactionId == null) {
|
if (orderId == null || transactionId == null) {
|
||||||
Log.e("QrisResultFlow", "orderId or transactionId is null! Intent extras: " + intent.getExtras());
|
Toast.makeText(this, "Missing transaction details!", Toast.LENGTH_LONG).show();
|
||||||
android.widget.Toast.makeText(this, "Missing transaction details!", android.widget.Toast.LENGTH_LONG).show();
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the exact amount from the grossAmount string value instead of the integer
|
String formattedAmount = formatCurrency(grossAmount != null ? grossAmount : String.valueOf(originalAmount));
|
||||||
String amountStr = "Amount: " + grossAmount;
|
amountTextView.setText(formattedAmount);
|
||||||
amountTextView.setText(amountStr);
|
|
||||||
referenceTextView.setText("Reference ID: " + referenceId);
|
referenceTextView.setText("Reference ID: " + referenceId);
|
||||||
|
|
||||||
// Load QR image
|
loadQrImage(currentQrImageUrl);
|
||||||
new DownloadImageTask(qrImageView).execute(qrImageUrl);
|
|
||||||
|
|
||||||
// Disable check status button initially
|
|
||||||
checkStatusButton.setEnabled(false);
|
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);
|
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> {
|
private void startQrRefreshTimer() {
|
||||||
ImageView bmImage;
|
countdownSeconds = 60;
|
||||||
DownloadImageTask(ImageView bmImage) {
|
isQrRefreshActive = true;
|
||||||
this.bmImage = bmImage;
|
|
||||||
}
|
qrRefreshRunnable = new Runnable() {
|
||||||
protected Bitmap doInBackground(String... urls) {
|
@Override
|
||||||
String urlDisplay = urls[0];
|
public void run() {
|
||||||
Bitmap bitmap = null;
|
if (!isQrRefreshActive) return;
|
||||||
try {
|
|
||||||
URL url = new URI(urlDisplay).toURL();
|
if (countdownSeconds > 0) {
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
timerTextView.setText(String.valueOf(countdownSeconds));
|
||||||
connection.setDoInput(true);
|
countdownSeconds--;
|
||||||
connection.connect();
|
qrRefreshHandler.postDelayed(this, 1000);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
} else {
|
} 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();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate webhook callback
|
private String generateNewQrCode() {
|
||||||
private void simulateWebhook() {
|
try {
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
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(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
JSONObject payload = new JSONObject();
|
for (String checkOrderId : allOrderIds) {
|
||||||
payload.put("transaction_type", "on-us");
|
if (checkOrderId == null || checkOrderId.isEmpty()) continue;
|
||||||
payload.put("transaction_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
|
||||||
payload.put("transaction_status", "settlement");
|
if (checkPaymentStatus(checkOrderId)) {
|
||||||
payload.put("transaction_id", transactionId); // Use the actual transaction_id
|
runOnUiThread(() -> {
|
||||||
payload.put("status_message", "midtrans payment notification");
|
stopQrRefresh();
|
||||||
payload.put("status_code", "200");
|
syncTransactionStatusToBackend("PAID");
|
||||||
payload.put("signature_key", "dummy_signature");
|
showPaymentSuccess();
|
||||||
payload.put("settlement_time", transactionTime != null ? transactionTime : "2025-04-16T06:00:00Z");
|
Toast.makeText(this, "Payment Successful! 🎉", Toast.LENGTH_LONG).show();
|
||||||
payload.put("payment_type", "qris");
|
});
|
||||||
payload.put("order_id", orderId); // Use order_id
|
return;
|
||||||
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
|
} catch (Exception e) {
|
||||||
payload.put("fraud_status", "accept");
|
Log.e("QrisResult", "Payment status check error: " + e.getMessage(), e);
|
||||||
payload.put("currency", "IDR");
|
}
|
||||||
payload.put("acquirer", acquirer != null ? acquirer : "gopay");
|
}).start();
|
||||||
payload.put("shopeepay_reference_number", "");
|
}
|
||||||
payload.put("reference_id", referenceId != null ? referenceId : "DUMMY_REFERENCE_ID");
|
|
||||||
Log.d("QrisResultFlow", "Webhook payload: " + payload.toString());
|
|
||||||
|
|
||||||
URL url = new URL(webhookUrl);
|
private boolean checkPaymentStatus(String checkOrderId) {
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
try {
|
||||||
conn.setRequestMethod("POST");
|
String urlStr = BACKEND_BASE + "/api-logs?request_body_search_strict=" +
|
||||||
conn.setRequestProperty("Content-Type", "application/json");
|
java.net.URLEncoder.encode("{\"order_id\":\"" + checkOrderId + "\"}", "UTF-8");
|
||||||
conn.setDoOutput(true);
|
|
||||||
OutputStream os = conn.getOutputStream();
|
URL url = new URL(urlStr);
|
||||||
os.write(payload.toString().getBytes());
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
os.flush();
|
conn.setRequestMethod("GET");
|
||||||
os.close();
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
int responseCode = conn.getResponseCode();
|
conn.setConnectTimeout(5000);
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(
|
conn.setReadTimeout(5000);
|
||||||
responseCode < 400 ? conn.getInputStream() : conn.getErrorStream()));
|
|
||||||
|
if (conn.getResponseCode() == 200) {
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||||
StringBuilder response = new StringBuilder();
|
StringBuilder response = new StringBuilder();
|
||||||
String line;
|
String line;
|
||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
response.append(line);
|
response.append(line);
|
||||||
}
|
}
|
||||||
Log.d("QrisResultFlow", "Webhook response: " + response.toString());
|
|
||||||
} catch (Exception e) {
|
JSONObject json = new JSONObject(response.toString());
|
||||||
Log.e("QrisResultFlow", "Webhook error: " + e.getMessage(), e);
|
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);
|
progressBar.setVisibility(View.GONE);
|
||||||
// Proceed to show status/result
|
showPaymentSuccess();
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}).start();
|
}).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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
966
app/src/main/java/com/example/bdkipoc/ReceiptActivity.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
354
app/src/main/java/com/example/bdkipoc/SettlementActivity.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
app/src/main/java/com/example/bdkipoc/StyleHelper.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,57 @@
|
|||||||
package com.example.bdkipoc;
|
package com.example.bdkipoc;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
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.util.List;
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.Locale;
|
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> {
|
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
|
||||||
private List<TransactionActivity.Transaction> transactionList;
|
private List<TransactionActivity.Transaction> transactionList;
|
||||||
|
private OnPrintClickListener printClickListener;
|
||||||
|
|
||||||
|
public interface OnPrintClickListener {
|
||||||
|
void onPrintClick(TransactionActivity.Transaction transaction);
|
||||||
|
}
|
||||||
|
|
||||||
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
|
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
|
||||||
this.transactionList = 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
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
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) {
|
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
|
||||||
TransactionActivity.Transaction t = transactionList.get(position);
|
TransactionActivity.Transaction t = transactionList.get(position);
|
||||||
|
|
||||||
// Format the amount as Indonesian Rupiah
|
// ✅ STRIPE TABLE: Set alternating row colors
|
||||||
try {
|
LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer);
|
||||||
double amountValue = Double.parseDouble(t.amount);
|
if (position % 2 == 0) {
|
||||||
NumberFormat rupiahFormat = NumberFormat.getCurrencyInstance(new Locale.Builder().setLanguage("id").setRegion("ID").build());
|
// Even rows - white background
|
||||||
holder.amount.setText(rupiahFormat.format(amountValue));
|
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.white));
|
||||||
} catch (NumberFormatException e) {
|
} else {
|
||||||
holder.amount.setText("Rp " + t.amount);
|
// 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.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
|
@Override
|
||||||
@@ -51,14 +562,17 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class TransactionViewHolder extends RecyclerView.ViewHolder {
|
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) {
|
public TransactionViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
amount = itemView.findViewById(R.id.textAmount);
|
amount = itemView.findViewById(R.id.textAmount);
|
||||||
status = itemView.findViewById(R.id.textStatus);
|
|
||||||
referenceId = itemView.findViewById(R.id.textReferenceId);
|
referenceId = itemView.findViewById(R.id.textReferenceId);
|
||||||
merchantName = itemView.findViewById(R.id.textMerchantName);
|
status = itemView.findViewById(R.id.textStatus);
|
||||||
createdAt = itemView.findViewById(R.id.textCreatedAt);
|
paymentMethod = itemView.findViewById(R.id.textPaymentMethod);
|
||||||
|
createdAt = itemView.findViewById(R.id.textCreatedAt); // ✅ Added createdAt
|
||||||
|
printSection = itemView.findViewById(R.id.printSection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
app/src/main/res/anim/fade_in.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
4
app/src/main/res/anim/fade_out.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
14
app/src/main/res/anim/scale_in.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<scale
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXScale="0.8"
|
||||||
|
android:fromYScale="0.8"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:toXScale="1.0"
|
||||||
|
android:toYScale="1.0" />
|
||||||
|
<alpha
|
||||||
|
android:duration="200"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
|
</set>
|
||||||
14
app/src/main/res/anim/scale_out.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<scale
|
||||||
|
android:duration="200"
|
||||||
|
android:fromXScale="1.0"
|
||||||
|
android:fromYScale="1.0"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:toXScale="0.8"
|
||||||
|
android:toYScale="0.8" />
|
||||||
|
<alpha
|
||||||
|
android:duration="200"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_in_left.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="-100%p"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_in_right.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="100%p"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0.0"
|
||||||
|
android:toAlpha="1.0" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_out_left.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="-100%p" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
|
</set>
|
||||||
10
app/src/main/res/anim/slide_out_right.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="100%p" />
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1.0"
|
||||||
|
android:toAlpha="0.0" />
|
||||||
|
</set>
|
||||||
6
app/src/main/res/drawable/button_active_background.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#DE0701" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
5
app/src/main/res/drawable/button_background_selector.xml
Normal file
@@ -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>
|
||||||
9
app/src/main/res/drawable/button_finish_background.xml
Normal 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>
|
||||||
6
app/src/main/res/drawable/button_inactive_background.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#ECEFF0" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
||||||
13
app/src/main/res/drawable/button_secondary_background.xml
Normal 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>
|
||||||
6
app/src/main/res/drawable/card_background.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#3498DB" />
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
<stroke android:width="0dp" />
|
||||||
|
</shape>
|
||||||
BIN
app/src/main/res/drawable/ic_arrow_back.png
Normal file
|
After Width: | Height: | Size: 220 B |
10
app/src/main/res/drawable/ic_backspace.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<!-- res/drawable/icons/ic_backspace.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#333333"
|
||||||
|
android:pathData="M22,3H7c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/>
|
||||||
|
</vector>
|
||||||
BIN
app/src/main/res/drawable/ic_card_insert.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
10
app/src/main/res/drawable/ic_check_circle.xml
Normal 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>
|
||||||
BIN
app/src/main/res/drawable/ic_credit_card.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
app/src/main/res/drawable/ic_debit_card.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
10
app/src/main/res/drawable/ic_e_money.xml
Normal 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>
|
||||||
BIN
app/src/main/res/drawable/ic_email.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable/ic_help.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable/ic_history.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable/ic_logo_icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
10
app/src/main/res/drawable/ic_notifications.xml
Normal 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>
|
||||||
BIN
app/src/main/res/drawable/ic_print.png
Normal file
|
After Width: | Height: | Size: 586 B |
10
app/src/main/res/drawable/ic_qr_code.xml
Normal 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>
|
||||||
BIN
app/src/main/res/drawable/ic_qris.png
Normal file
|
After Width: | Height: | Size: 607 B |
BIN
app/src/main/res/drawable/ic_refund.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/drawable/ic_reprint.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
10
app/src/main/res/drawable/ic_settlement.xml
Normal 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>
|
||||||
BIN
app/src/main/res/drawable/ic_store_info.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable/ic_success_payment.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
10
app/src/main/res/drawable/ic_transfer.xml
Normal 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>
|
||||||
5
app/src/main/res/drawable/search_background.xml
Normal 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>
|
||||||
4
app/src/main/res/drawable/search_button_background.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<solid android:color="#666666" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
</shape>
|
||||||
11
app/src/main/res/font/inter.xml
Normal 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>
|
||||||
BIN
app/src/main/res/font/inter_medium.ttf
Normal file
BIN
app/src/main/res/font/inter_regular.ttf
Normal file
175
app/src/main/res/layout/activity_history.xml
Normal 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>
|
||||||
46
app/src/main/res/layout/activity_history_detail.xml
Normal 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>
|
||||||
@@ -1,92 +1,701 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
android:fillViewport="true"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:background="#FFFFFF">
|
||||||
|
|
||||||
<TextView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/title_text"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:background="#FFFFFF"
|
||||||
android:text="@string/main_title"
|
tools:context=".MainActivity">
|
||||||
android:textSize="24sp"
|
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<!-- Status Bar Area -->
|
||||||
android:id="@+id/card_payment"
|
<View
|
||||||
android:layout_width="140dp"
|
android:id="@+id/status_bar_background"
|
||||||
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
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="24dp"
|
||||||
android:gravity="center"
|
android:background="#E31937"
|
||||||
android:orientation="vertical"
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
<!-- Red Background Header -->
|
||||||
android:layout_width="64dp"
|
<View
|
||||||
android:layout_height="64dp"
|
android:id="@+id/red_header_background"
|
||||||
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
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="100dp"
|
||||||
android:gravity="center"
|
android:background="#E31937"
|
||||||
android:orientation="vertical"
|
app:layout_constraintTop_toBottomOf="@id/status_bar_background"/>
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
<!-- Merchant Card -->
|
||||||
android:layout_width="64dp"
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_height="64dp"
|
android:id="@+id/merchant_card"
|
||||||
android:src="@android:drawable/ic_menu_recent_history"
|
android:layout_width="match_parent"
|
||||||
android:contentDescription="@string/transactions"
|
android:layout_height="wrap_content"
|
||||||
app:tint="@color/accent_teal" />
|
android:layout_margin="16dp"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_bar_background">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:orientation="vertical"
|
||||||
android:text="@string/transactions2"
|
android:padding="16dp">
|
||||||
android:textColor="@color/accent_teal"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
</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>
|
||||||
@@ -1,227 +1,268 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:background="#FFFFFF">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:background="?attr/colorPrimary"
|
|
||||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
|
||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
|
||||||
app:title="QRIS Payment" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
android:background="#FFFFFF"
|
||||||
|
tools:context=".PaymentActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Red Status Bar (Override purple) -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/red_status_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="24dp"
|
||||||
android:orientation="vertical"
|
android:background="#E31937"
|
||||||
android:padding="16dp">
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<ProgressBar
|
<!-- Red Background Header (Extended height untuk back navigation) -->
|
||||||
android:id="@+id/progressBar"
|
<View
|
||||||
|
android:id="@+id/red_header_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="160dp"
|
||||||
|
android:background="#E31937"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/red_status_bar"/>
|
||||||
|
|
||||||
|
<!-- Back Navigation (Positioned closer to status bar/appbar) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/back_navigation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/red_status_bar">
|
||||||
|
|
||||||
|
<!-- Back Arrow -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backArrow"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@drawable/ic_arrow_back"
|
||||||
|
android:contentDescription="Back" />
|
||||||
|
|
||||||
|
<!-- Title Text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/toolbarTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_marginStart="8dp"
|
||||||
android:visibility="gone" />
|
android:text="Kembali"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
android:textStyle="normal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Payment Card -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/paymentCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="191dp"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
app:cardBackgroundColor="#3498DB"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/back_navigation">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="TOTAL PEMBAYARAN"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="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
|
<TextView
|
||||||
android:id="@+id/statusTextView"
|
android:id="@+id/btn2"
|
||||||
android:layout_width="match_parent"
|
style="@style/NumpadButton"
|
||||||
android:layout_height="wrap_content"
|
android:text="2" />
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Ready to make a payment"
|
|
||||||
android:textSize="18sp" />
|
|
||||||
|
|
||||||
<!-- Initial Payment Form -->
|
<TextView
|
||||||
<LinearLayout
|
android:id="@+id/btn3"
|
||||||
android:layout_width="match_parent"
|
style="@style/NumpadButton"
|
||||||
android:layout_height="wrap_content"
|
android:text="3" />
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<!-- Row 2: 4, 5, 6 -->
|
||||||
android:layout_width="match_parent"
|
<TextView
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/btn4"
|
||||||
android:layout_margin="8dp"
|
style="@style/NumpadButton"
|
||||||
app:cardBackgroundColor="@color/light_blue"
|
android:text="4" />
|
||||||
app:cardCornerRadius="8dp"
|
|
||||||
app:cardElevation="4dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn5"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:orientation="vertical"
|
android:text="5" />
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/btn6"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:text="Amount"
|
android:text="6" />
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<EditText
|
<!-- Row 3: 7, 8, 9 -->
|
||||||
android:id="@+id/editTextAmount"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn7"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:layout_marginTop="8dp"
|
android:text="7" />
|
||||||
android:hint="Enter amount"
|
|
||||||
android:inputType="number"
|
|
||||||
android:maxLength="12"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="24sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:gravity="end" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/btn8"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:layout_marginTop="16dp"
|
android:text="8" />
|
||||||
android:text="Reference ID"
|
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/referenceIdTextView"
|
android:id="@+id/btn9"
|
||||||
android:layout_width="match_parent"
|
style="@style/NumpadButton"
|
||||||
android:layout_height="wrap_content"
|
android:text="9" />
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="ref-abcd1234"
|
|
||||||
android:textColor="@color/primary_blue"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<Button
|
<!-- Row 4: 000, 0, Delete -->
|
||||||
android:id="@+id/initiatePaymentButton"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btn000"
|
||||||
android:layout_height="wrap_content"
|
style="@style/NumpadButton"
|
||||||
android:layout_margin="8dp"
|
android:text="000" />
|
||||||
android:backgroundTint="@color/primary_blue"
|
|
||||||
android:text="Start Payment" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- QR Code and Payment Details -->
|
<TextView
|
||||||
<LinearLayout
|
android:id="@+id/btn0"
|
||||||
android:id="@+id/paymentDetailsLayout"
|
style="@style/NumpadButton"
|
||||||
android:layout_width="match_parent"
|
android:text="0" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btnDelete"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_margin="8dp"
|
android:layout_height="60dp"
|
||||||
app:cardCornerRadius="8dp"
|
android:layout_columnWeight="1"
|
||||||
app:cardElevation="4dp">
|
android:layout_margin="8dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_backspace"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:contentDescription="Delete" />
|
||||||
|
|
||||||
<LinearLayout
|
</GridLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
<!-- Confirmation Button (UPDATED: Menggunakan MaterialButton) -->
|
||||||
android:id="@+id/qrCodeImageView"
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_width="250dp"
|
android:id="@+id/confirmButton"
|
||||||
android:layout_height="250dp"
|
android:layout_width="match_parent"
|
||||||
android:contentDescription="QRIS Code"
|
android:layout_height="48dp"
|
||||||
android:scaleType="fitCenter" />
|
android:text="Konfirmasi"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:fontFamily="@font/inter"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:enabled="false"
|
||||||
|
app:backgroundTint="#DE0701"
|
||||||
|
app:cornerRadius="8dp"
|
||||||
|
app:rippleColor="#B3000000"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/numpad_grid"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="1" />
|
||||||
|
|
||||||
<TextView
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
android:layout_width="match_parent"
|
</ScrollView>
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Scan with your banking app or e-wallet to pay"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/simulatePaymentButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:backgroundTint="@color/accent_green"
|
|
||||||
android:text="Confirm Payment" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Payment Success -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/paymentSuccessLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
app:cardBackgroundColor="@color/light_gray"
|
|
||||||
app:cardCornerRadius="8dp"
|
|
||||||
app:cardElevation="4dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:contentDescription="Success Icon"
|
|
||||||
android:src="@android:drawable/ic_dialog_info"
|
|
||||||
android:tint="@color/accent_green" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Payment Successful!"
|
|
||||||
android:textColor="@color/accent_green"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Your transaction has been completed successfully."
|
|
||||||
android:textSize="14sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/returnToMainButton"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:backgroundTint="@color/primary_blue"
|
|
||||||
android:text="Return to Main" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
298
app/src/main/res/layout/activity_pin.xml
Normal 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>
|
||||||
306
app/src/main/res/layout/activity_qris.xml
Normal 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>
|
||||||
@@ -1,82 +1,223 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="24dp"
|
android:background="#FFFFFF">
|
||||||
android:background="#181824"
|
|
||||||
>
|
|
||||||
|
|
||||||
<TextView
|
<!-- Red Status Bar -->
|
||||||
android:id="@+id/amountTextView"
|
<View
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="44dp"
|
||||||
android:text="@string/amount"
|
android:background="#E31937" />
|
||||||
android:textColor="#2D5DA1"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<!-- Header with Back Navigation -->
|
||||||
android:id="@+id/referenceTextView"
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/reference_id"
|
android:background="#E31937"
|
||||||
android:textColor="#2D5DA1"
|
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:textSize="16sp"
|
||||||
android:layout_gravity="center_horizontal"
|
android:textStyle="normal"
|
||||||
android:layout_marginBottom="16dp"
|
android:background="@android:color/transparent"
|
||||||
android:layout_marginTop="8dp"/>
|
style="?android:attr/borderlessButtonStyle" />
|
||||||
|
|
||||||
<ImageView
|
</LinearLayout>
|
||||||
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"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
547
app/src/main/res/layout/activity_receipt.xml
Normal 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>
|
||||||
160
app/src/main/res/layout/activity_settlement.xml
Normal 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>
|
||||||
@@ -6,26 +6,214 @@
|
|||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
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
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="56dp"
|
||||||
android:background="?attr/colorPrimary"
|
android:background="#F44336"
|
||||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
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>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<!-- ✅ PERBAIKAN: Gunakan LinearLayout dengan weight distribution yang lebih baik -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp"
|
android:background="#f8f9fa"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
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
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -34,21 +222,89 @@
|
|||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="8dp" />
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
<!-- ✅ PERBAIKAN: RecyclerView dengan height yang tepat untuk mencegah pagination terpotong -->
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginTop="8dp" />
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
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>
|
|
||||||
69
app/src/main/res/layout/item_history.xml
Normal 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>
|
||||||
178
app/src/main/res/layout/item_history_detail.xml
Normal 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>
|
||||||
71
app/src/main/res/layout/item_settlement.xml
Normal 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>
|
||||||
@@ -1,44 +1,118 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="12dp"
|
android:orientation="vertical"
|
||||||
android:background="@android:color/white"
|
android:id="@+id/itemContainer">
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:elevation="2dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/textAmount"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Amount"
|
android:orientation="horizontal"
|
||||||
android:textStyle="bold"
|
android:padding="16dp"
|
||||||
android:textSize="18sp" />
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="64dp">
|
||||||
|
|
||||||
<TextView
|
<!-- Column 1: Transaction Info -->
|
||||||
android:id="@+id/textStatus"
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Status"
|
android:layout_weight="2"
|
||||||
android:textColor="#4CAF50"
|
android:orientation="vertical">
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
<!-- Reference ID -->
|
||||||
android:id="@+id/textReferenceId"
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/textReferenceId"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Reference ID" />
|
android:layout_height="wrap_content"
|
||||||
|
android:text="ref-eowu3pin"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="4dp" />
|
||||||
|
|
||||||
<TextView
|
<!-- Status, Payment Method, and Created At Row -->
|
||||||
android:id="@+id/textMerchantName"
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Merchant Name" />
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<TextView
|
<!-- Status -->
|
||||||
android:id="@+id/textCreatedAt"
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/textStatus"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Created At" />
|
android:layout_height="wrap_content"
|
||||||
</LinearLayout>
|
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>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
43
app/src/main/res/layout/modal_layout.xml
Normal 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>
|
||||||
48
app/src/main/res/values/styles.xml
Normal 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>
|
||||||