memperbaiki list cetak ulang
This commit is contained in:
parent
4aaa9957e7
commit
b0ee2e8ee6
106
app/src/main/java/com/example/bdkipoc/StyleHelper.java
Normal file
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
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
import android.widget.TextView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
@ -50,16 +52,25 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
private EditText searchEditText;
|
||||
private ImageButton searchButton;
|
||||
|
||||
// ✅ PAGINATION UI ELEMENTS
|
||||
private LinearLayout infoBar;
|
||||
private TextView textTotalRecords;
|
||||
private TextView textPageInfo;
|
||||
private LinearLayout paginationControls;
|
||||
private LinearLayout pageNumbersContainer;
|
||||
private ImageButton btnFirstPage, btnPrevPage, btnNextPage, btnLastPage;
|
||||
|
||||
// ✅ FRONTEND DEDUPLICATION: Local caching and tracking
|
||||
private Map<String, Transaction> transactionCache = new HashMap<>();
|
||||
private Set<String> processedReferences = new HashSet<>();
|
||||
private SharedPreferences prefs;
|
||||
|
||||
// Pagination variables
|
||||
private int page = 0;
|
||||
private final int limit = 50; // ✅ INCREASED: Fetch more data for better deduplication
|
||||
// ✅ UPDATED PAGINATION VARIABLES
|
||||
private int currentPage = 1; // Start from page 1 instead of 0
|
||||
private final int itemsPerPage = 15; // ✅ 15 items per page as requested
|
||||
private int totalRecords = 0;
|
||||
private int totalPages = 0;
|
||||
private boolean isLoading = false;
|
||||
private boolean isLastPage = false;
|
||||
private String currentSearchQuery = "";
|
||||
private boolean isRefreshing = false;
|
||||
|
||||
@ -83,15 +94,38 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
// Setup search functionality
|
||||
setupSearch();
|
||||
|
||||
// ✅ Setup pagination controls
|
||||
setupPaginationControls();
|
||||
|
||||
// Load initial data
|
||||
loadTransactions(0);
|
||||
loadTransactions(1); // Start from page 1
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
recyclerView = findViewById(R.id.recyclerView);
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
searchEditText = findViewById(R.id.searchEditText);
|
||||
searchButton = findViewById(R.id.searchButton);
|
||||
|
||||
// ✅ APPLY PROGRAMMATIC STYLING
|
||||
LinearLayout searchContainer = findViewById(R.id.searchContainer);
|
||||
LinearLayout filterButton = findViewById(R.id.filterButton);
|
||||
|
||||
StyleHelper.applySearchInputStyle(searchContainer, this);
|
||||
StyleHelper.applyFilterButtonStyle(filterButton, this);
|
||||
|
||||
// ✅ PAGINATION UI ELEMENTS
|
||||
paginationControls = findViewById(R.id.paginationControls);
|
||||
pageNumbersContainer = findViewById(R.id.pageNumbersContainer);
|
||||
btnFirstPage = findViewById(R.id.btnFirstPage);
|
||||
btnPrevPage = findViewById(R.id.btnPrevPage);
|
||||
btnNextPage = findViewById(R.id.btnNextPage);
|
||||
btnLastPage = findViewById(R.id.btnLastPage);
|
||||
|
||||
// Apply pagination button styling (updated sizes)
|
||||
StyleHelper.applyPaginationButtonStyle(btnFirstPage, this, false);
|
||||
StyleHelper.applyPaginationButtonStyle(btnPrevPage, this, false);
|
||||
StyleHelper.applyPaginationButtonStyle(btnNextPage, this, false);
|
||||
StyleHelper.applyPaginationButtonStyle(btnLastPage, this, false);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
@ -114,32 +148,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
// Add scroll listener for pagination
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
|
||||
// Pagination: Load more when reaching the bottom
|
||||
if (!recyclerView.canScrollVertically(1) && !isLoading && !isLastPage && currentSearchQuery.isEmpty()) {
|
||||
loadTransactions(page + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
// ✅ REMOVED: Auto-pagination scroll listener since we use manual pagination now
|
||||
// Manual pagination is better for user control and performance
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
// Search button click listener
|
||||
searchButton.setOnClickListener(v -> performSearch());
|
||||
|
||||
// Search button long press for refresh
|
||||
searchButton.setOnLongClickListener(v -> {
|
||||
refreshTransactions();
|
||||
Toast.makeText(this, "Refreshing data...", Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
|
||||
// Search EditText listener
|
||||
// Search on text change
|
||||
searchEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@ -147,12 +161,89 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
currentSearchQuery = s.toString().trim();
|
||||
// ✅ RESET TO PAGE 1 when searching
|
||||
currentPage = 1;
|
||||
filterTransactions(currentSearchQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
|
||||
// Filter button click
|
||||
findViewById(R.id.filterButton).setOnClickListener(v -> {
|
||||
Toast.makeText(this, "Filter clicked", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Setup pagination controls
|
||||
private void setupPaginationControls() {
|
||||
btnFirstPage.setOnClickListener(v -> {
|
||||
if (currentPage > 1) {
|
||||
goToPage(1);
|
||||
}
|
||||
});
|
||||
|
||||
btnPrevPage.setOnClickListener(v -> {
|
||||
if (currentPage > 1) {
|
||||
goToPage(currentPage - 1);
|
||||
}
|
||||
});
|
||||
|
||||
btnNextPage.setOnClickListener(v -> {
|
||||
if (currentPage < totalPages) {
|
||||
goToPage(currentPage + 1);
|
||||
}
|
||||
});
|
||||
|
||||
btnLastPage.setOnClickListener(v -> {
|
||||
if (currentPage < totalPages) {
|
||||
goToPage(totalPages);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Navigate to specific page
|
||||
private void goToPage(int page) {
|
||||
if (page < 1 || page > totalPages || page == currentPage || isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🔄 Navigating to page " + page);
|
||||
|
||||
if (currentSearchQuery.isEmpty()) {
|
||||
// Load from API
|
||||
loadTransactions(page);
|
||||
} else {
|
||||
// Search mode - just update current page and refresh display
|
||||
currentPage = page;
|
||||
updatePaginationDisplay();
|
||||
displayCurrentPageData();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Display current page data for search results
|
||||
private void displayCurrentPageData() {
|
||||
if (currentSearchQuery.isEmpty()) {
|
||||
return; // This method is only for search results
|
||||
}
|
||||
|
||||
int startIndex = (currentPage - 1) * itemsPerPage;
|
||||
int endIndex = Math.min(startIndex + itemsPerPage, filteredList.size());
|
||||
|
||||
List<Transaction> pageData = new ArrayList<>();
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
pageData.add(filteredList.get(i));
|
||||
}
|
||||
|
||||
// Update adapter with current page data
|
||||
adapter.updateData(pageData, startIndex); // Pass startIndex for numbering
|
||||
|
||||
// Scroll to top
|
||||
recyclerView.scrollToPosition(0);
|
||||
|
||||
Log.d("TransactionActivity", "📄 Displaying search results page " + currentPage +
|
||||
" (items " + (startIndex + 1) + "-" + endIndex + " of " + filteredList.size() + ")");
|
||||
}
|
||||
|
||||
private void refreshTransactions() {
|
||||
@ -166,17 +257,17 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
// Clear search when refreshing
|
||||
searchEditText.setText("");
|
||||
currentSearchQuery = "";
|
||||
page = 0;
|
||||
isLastPage = false;
|
||||
currentPage = 1; // ✅ Reset to page 1
|
||||
transactionList.clear();
|
||||
filteredList.clear();
|
||||
adapter.notifyDataSetChanged();
|
||||
loadTransactions(0);
|
||||
loadTransactions(1);
|
||||
}
|
||||
|
||||
private void performSearch() {
|
||||
String query = searchEditText.getText().toString().trim();
|
||||
currentSearchQuery = query;
|
||||
currentPage = 1; // ✅ Reset to page 1 when searching
|
||||
filterTransactions(query);
|
||||
|
||||
// Hide keyboard
|
||||
@ -187,17 +278,53 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
filteredList.clear();
|
||||
|
||||
if (query.isEmpty()) {
|
||||
// ✅ NO SEARCH: Show current API page data (already sorted)
|
||||
filteredList.addAll(transactionList);
|
||||
totalRecords = totalRecords; // Use API total
|
||||
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
|
||||
|
||||
// ✅ VERIFY FILTERED LIST ORDER
|
||||
Log.d("TransactionActivity", "📋 FILTERED LIST ORDER (no search):");
|
||||
for (int i = 0; i < Math.min(5, filteredList.size()); i++) {
|
||||
Transaction tx = filteredList.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
} else {
|
||||
// ✅ SEARCH MODE: Filter all available data
|
||||
for (Transaction transaction : transactionList) {
|
||||
if (transaction.referenceId.toLowerCase().contains(query.toLowerCase()) ||
|
||||
transaction.amount.contains(query)) {
|
||||
filteredList.add(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ SORT SEARCH RESULTS by date
|
||||
filteredList.sort((t1, t2) -> {
|
||||
try {
|
||||
Date date1 = parseCreatedAtDate(t1.createdAt);
|
||||
Date date2 = parseCreatedAtDate(t2.createdAt);
|
||||
if (date1 != null && date2 != null) {
|
||||
return date2.compareTo(date1); // Newest first
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fallback
|
||||
}
|
||||
return Integer.compare(t2.id, t1.id);
|
||||
});
|
||||
|
||||
// ✅ SEARCH PAGINATION: Calculate pages for filtered results
|
||||
totalRecords = filteredList.size();
|
||||
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
|
||||
|
||||
// ✅ Display current page of search results
|
||||
displayCurrentPageData();
|
||||
updatePaginationDisplay();
|
||||
return; // Early return for search
|
||||
}
|
||||
|
||||
adapter.notifyDataSetChanged();
|
||||
// ✅ For non-search, just update adapter normally
|
||||
adapter.updateData(filteredList, (currentPage - 1) * itemsPerPage);
|
||||
updatePaginationDisplay();
|
||||
|
||||
// Scroll to top after filtering
|
||||
if (!filteredList.isEmpty()) {
|
||||
@ -205,18 +332,104 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Update pagination display
|
||||
private void updatePaginationDisplay() {
|
||||
// Update page info - remove total records display for cleaner look
|
||||
String pageText = "Halaman " + currentPage + " dari " + Math.max(1, totalPages);
|
||||
|
||||
// Show/hide pagination controls based on data availability
|
||||
if (totalRecords > 0) {
|
||||
if (totalPages > 1) {
|
||||
paginationControls.setVisibility(View.VISIBLE);
|
||||
updatePageButtons();
|
||||
createPageNumbers();
|
||||
} else {
|
||||
paginationControls.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
paginationControls.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "📊 Pagination updated: " +
|
||||
"Page " + currentPage + "/" + totalPages + ", Total: " + totalRecords);
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Update pagination button states
|
||||
private void updatePageButtons() {
|
||||
// Enable/disable buttons based on current page
|
||||
btnFirstPage.setEnabled(currentPage > 1);
|
||||
btnPrevPage.setEnabled(currentPage > 1);
|
||||
btnNextPage.setEnabled(currentPage < totalPages);
|
||||
btnLastPage.setEnabled(currentPage < totalPages);
|
||||
|
||||
// Update button opacity for visual feedback
|
||||
float enabledAlpha = 1.0f;
|
||||
float disabledAlpha = 0.3f;
|
||||
|
||||
btnFirstPage.setAlpha(btnFirstPage.isEnabled() ? enabledAlpha : disabledAlpha);
|
||||
btnPrevPage.setAlpha(btnPrevPage.isEnabled() ? enabledAlpha : disabledAlpha);
|
||||
btnNextPage.setAlpha(btnNextPage.isEnabled() ? enabledAlpha : disabledAlpha);
|
||||
btnLastPage.setAlpha(btnLastPage.isEnabled() ? enabledAlpha : disabledAlpha);
|
||||
}
|
||||
|
||||
// ✅ NEW METHOD: Create page number buttons
|
||||
private void createPageNumbers() {
|
||||
pageNumbersContainer.removeAllViews();
|
||||
|
||||
// Calculate which page numbers to show (max 5 numbers)
|
||||
int maxPageButtons = 5;
|
||||
int startPage = Math.max(1, currentPage - 2);
|
||||
int endPage = Math.min(totalPages, startPage + maxPageButtons - 1);
|
||||
|
||||
// Adjust start if we're near the end
|
||||
if (endPage - startPage < maxPageButtons - 1) {
|
||||
startPage = Math.max(1, endPage - maxPageButtons + 1);
|
||||
}
|
||||
|
||||
// ✅ CONSISTENT BUTTON SIZE for all devices (more modern size)
|
||||
int buttonSize = (int) (44 * getResources().getDisplayMetrics().density); // 44dp (iOS standard)
|
||||
|
||||
for (int i = startPage; i <= endPage; i++) {
|
||||
final int pageNumber = i;
|
||||
|
||||
TextView pageButton = new TextView(this);
|
||||
pageButton.setText(String.valueOf(pageNumber));
|
||||
pageButton.setTextSize(16); // Slightly larger text
|
||||
pageButton.setClickable(true);
|
||||
pageButton.setFocusable(true);
|
||||
pageButton.setGravity(android.view.Gravity.CENTER);
|
||||
|
||||
// ✅ USE STYLE HELPER
|
||||
boolean isActive = (pageNumber == currentPage);
|
||||
StyleHelper.applyPaginationButtonStyle(pageButton, this, isActive);
|
||||
|
||||
pageButton.setOnClickListener(v -> goToPage(pageNumber));
|
||||
|
||||
// ✅ IMPROVED: Better spacing like in the image
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
buttonSize, // Consistent width
|
||||
buttonSize // Consistent height
|
||||
);
|
||||
params.setMargins(6, 0, 6, 0); // 6dp margin for better spacing
|
||||
pageButton.setLayoutParams(params);
|
||||
|
||||
pageNumbersContainer.addView(pageButton);
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "🔢 Page buttons created: " + startPage + " to " + endPage +
|
||||
" with size: " + buttonSize + "px");
|
||||
}
|
||||
|
||||
private void loadTransactions(int pageToLoad) {
|
||||
isLoading = true;
|
||||
if (pageToLoad == 0) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
new FetchTransactionsTask(pageToLoad).execute();
|
||||
}
|
||||
|
||||
private class FetchTransactionsTask extends AsyncTask<Void, Void, List<Transaction>> {
|
||||
private int pageToLoad;
|
||||
private boolean error = false;
|
||||
private int total = 0;
|
||||
private int apiTotal = 0;
|
||||
|
||||
FetchTransactionsTask(int page) {
|
||||
this.pageToLoad = page;
|
||||
@ -226,13 +439,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
protected List<Transaction> doInBackground(Void... voids) {
|
||||
List<Transaction> result = new ArrayList<>();
|
||||
try {
|
||||
// ✅ FETCH MORE DATA: Increased limit for better deduplication
|
||||
int fetchLimit = limit * 3; // Get more records to handle all duplicates
|
||||
// ✅ PAGINATION API CALL: Use page-based API call
|
||||
int apiPage = pageToLoad - 1; // API uses 0-based indexing
|
||||
|
||||
String urlString = "https://be-edc.msvc.app/transactions?page=" + pageToLoad +
|
||||
"&limit=" + fetchLimit + "&sortOrder=DESC&from_date=&to_date=&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at";
|
||||
String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage +
|
||||
"&limit=" + itemsPerPage + "&sortOrder=DESC&from_date=&to_date=&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at";
|
||||
|
||||
Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad + " with limit " + fetchLimit);
|
||||
Log.d("TransactionActivity", "🔍 Fetching transactions page " + pageToLoad +
|
||||
" (API page " + apiPage + ") with limit " + itemsPerPage + " - SORT: DESC by created_at");
|
||||
|
||||
URI uri = new URI(urlString);
|
||||
URL url = uri.toURL();
|
||||
@ -256,10 +470,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
JSONObject jsonObject = new JSONObject(response.toString());
|
||||
JSONObject results = jsonObject.getJSONObject("results");
|
||||
total = results.getInt("total");
|
||||
apiTotal = results.getInt("total");
|
||||
JSONArray data = results.getJSONArray("data");
|
||||
|
||||
Log.d("TransactionActivity", "📊 Raw API response: " + data.length() + " records");
|
||||
Log.d("TransactionActivity", "📊 API response: " + data.length() +
|
||||
" records, total: " + apiTotal);
|
||||
|
||||
// ✅ STEP 1: Parse all transactions from API
|
||||
List<Transaction> rawTransactions = new ArrayList<>();
|
||||
@ -304,64 +519,109 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
if (error) {
|
||||
Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
|
||||
updatePaginationDisplay(); // Show current state even on error
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageToLoad == 0) {
|
||||
// ✅ UPDATE PAGINATION DATA
|
||||
currentPage = pageToLoad;
|
||||
totalRecords = apiTotal;
|
||||
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
|
||||
|
||||
// ✅ UPDATE TRANSACTION LIST
|
||||
transactionList.clear();
|
||||
transactionCache.clear(); // Clear cache on refresh
|
||||
}
|
||||
transactionList.addAll(transactions);
|
||||
|
||||
// ✅ SMART MERGE: Only add truly new transactions
|
||||
int addedCount = 0;
|
||||
for (Transaction newTx : transactions) {
|
||||
String refId = newTx.referenceId;
|
||||
// ✅ CRITICAL: FORCE SORT AGAIN after adding to main list
|
||||
transactionList.sort((t1, t2) -> {
|
||||
try {
|
||||
Date date1 = parseCreatedAtDate(t1.createdAt);
|
||||
Date date2 = parseCreatedAtDate(t2.createdAt);
|
||||
|
||||
// Check if we already have a better version of this transaction
|
||||
Transaction cachedTx = transactionCache.get(refId);
|
||||
if (cachedTx == null || isBetterTransaction(newTx, cachedTx)) {
|
||||
// Update cache with better transaction
|
||||
transactionCache.put(refId, newTx);
|
||||
if (date1 != null && date2 != null) {
|
||||
int comparison = date2.compareTo(date1); // Newest first
|
||||
Log.d("TransactionActivity", "🔄 Final sort: " + t1.createdAt + " vs " + t2.createdAt + " = " + comparison);
|
||||
return comparison;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionActivity", "Date comparison error: " + e.getMessage());
|
||||
}
|
||||
return Integer.compare(t2.id, t1.id); // Fallback by ID
|
||||
});
|
||||
|
||||
// Update or add to main list
|
||||
boolean updated = false;
|
||||
for (int i = 0; i < transactionList.size(); i++) {
|
||||
if (transactionList.get(i).referenceId.equals(refId)) {
|
||||
transactionList.set(i, newTx);
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.d("TransactionActivity", "📋 Page " + currentPage + " loaded and sorted: " +
|
||||
transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages");
|
||||
|
||||
if (!updated) {
|
||||
transactionList.add(newTx);
|
||||
addedCount++;
|
||||
// ✅ LOG FINAL ORDER VERIFICATION
|
||||
Log.d("TransactionActivity", "📋 FINAL DISPLAY ORDER:");
|
||||
for (int i = 0; i < Math.min(10, transactionList.size()); i++) {
|
||||
Transaction tx = transactionList.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". " + tx.createdAt + " - " + tx.referenceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("TransactionActivity", "📋 Added " + addedCount + " new unique transactions. Total: " + transactionList.size());
|
||||
|
||||
// Update filtered list based on current search
|
||||
filterTransactions(currentSearchQuery);
|
||||
|
||||
page = pageToLoad;
|
||||
if (transactions.size() < limit) { // No more pages if returned less than requested
|
||||
isLastPage = true;
|
||||
}
|
||||
|
||||
// Scroll to top if it's a refresh
|
||||
if (pageToLoad == 0 && !filteredList.isEmpty()) {
|
||||
// Scroll to top
|
||||
if (!filteredList.isEmpty()) {
|
||||
recyclerView.scrollToPosition(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ ENHANCED DATE PARSING: Handle multiple date formats from API
|
||||
*/
|
||||
private Date parseCreatedAtDate(String rawDate) {
|
||||
if (rawDate == null || rawDate.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// List of possible date formats from API
|
||||
String[] possibleFormats = {
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", // ISO format with milliseconds
|
||||
"yyyy-MM-dd'T'HH:mm:ss'Z'", // ISO format without milliseconds
|
||||
"yyyy-MM-dd HH:mm:ss.SSS", // Standard format with milliseconds
|
||||
"yyyy-MM-dd HH:mm:ss", // Standard format
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'" // ISO format with microseconds
|
||||
};
|
||||
|
||||
for (String format : possibleFormats) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
|
||||
return sdf.parse(rawDate);
|
||||
} catch (Exception e) {
|
||||
// Continue to next format
|
||||
}
|
||||
}
|
||||
|
||||
// Manual parsing fallback for complex formats
|
||||
try {
|
||||
String cleanedDate = rawDate.replace("T", " ").replace("Z", "");
|
||||
|
||||
// Remove microseconds/milliseconds if present
|
||||
if (cleanedDate.contains(".")) {
|
||||
cleanedDate = cleanedDate.substring(0, cleanedDate.indexOf("."));
|
||||
}
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
return sdf.parse(cleanedDate);
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionActivity", "❌ Could not parse date: " + rawDate);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
|
||||
*/
|
||||
private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
|
||||
Log.d("TransactionActivity", "🧠 Starting advanced deduplication...");
|
||||
Log.d("TransactionActivity", "📥 Input transactions order (first 5):");
|
||||
for (int i = 0; i < Math.min(5, rawTransactions.size()); i++) {
|
||||
Transaction tx = rawTransactions.get(i);
|
||||
Log.d("TransactionActivity", " " + (i+1) + ". ID:" + tx.id + " Date:" + tx.createdAt + " Ref:" + tx.referenceId);
|
||||
}
|
||||
|
||||
// Strategy 1: Group by reference_id
|
||||
Map<String, List<Transaction>> groupedByRef = new HashMap<>();
|
||||
@ -384,13 +644,26 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
deduplicatedList.add(group.get(0));
|
||||
Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId);
|
||||
} else {
|
||||
// Multiple transactions with same reference_id
|
||||
// Multiple transactions with same reference_id - sort group by date first
|
||||
group.sort((t1, t2) -> {
|
||||
try {
|
||||
Date date1 = parseCreatedAtDate(t1.createdAt);
|
||||
Date date2 = parseCreatedAtDate(t2.createdAt);
|
||||
if (date1 != null && date2 != null) {
|
||||
return date2.compareTo(date1); // Newest first in group
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fallback to ID
|
||||
}
|
||||
return Integer.compare(t2.id, t1.id);
|
||||
});
|
||||
|
||||
Transaction bestTransaction = selectBestTransactionAdvanced(group, referenceId);
|
||||
deduplicatedList.add(bestTransaction);
|
||||
duplicatesRemoved += (group.size() - 1);
|
||||
|
||||
Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId +
|
||||
" (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ")");
|
||||
" (kept ID: " + bestTransaction.id + ", status: " + bestTransaction.status + ", date: " + bestTransaction.createdAt + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,10 +683,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
|
||||
Transaction bestTransaction = duplicates.get(0);
|
||||
int bestPriority = getStatusPriority(bestTransaction.status);
|
||||
Date bestDate = parseCreatedAtDate(bestTransaction.createdAt);
|
||||
|
||||
// Detailed analysis of each candidate
|
||||
for (Transaction tx : duplicates) {
|
||||
int currentPriority = getStatusPriority(tx.status);
|
||||
Date currentDate = parseCreatedAtDate(tx.createdAt);
|
||||
|
||||
Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id +
|
||||
", Status=" + tx.status + " (priority=" + currentPriority + ")" +
|
||||
@ -429,20 +704,24 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
}
|
||||
// Rule 2: Same priority, choose newer timestamp
|
||||
else if (currentPriority == bestPriority) {
|
||||
if (isNewerTransaction(tx, bestTransaction)) {
|
||||
if (currentDate != null && bestDate != null && currentDate.after(bestDate)) {
|
||||
shouldSelect = true;
|
||||
reason = "newer timestamp";
|
||||
}
|
||||
// Rule 3: Same priority and time, choose higher ID
|
||||
else if (tx.createdAt.equals(bestTransaction.createdAt) && tx.id > bestTransaction.id) {
|
||||
else if ((currentDate == null && bestDate == null) ||
|
||||
(currentDate != null && bestDate != null && currentDate.equals(bestDate))) {
|
||||
if (tx.id > bestTransaction.id) {
|
||||
shouldSelect = true;
|
||||
reason = "higher ID";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSelect) {
|
||||
bestTransaction = tx;
|
||||
bestPriority = currentPriority;
|
||||
bestDate = currentDate;
|
||||
Log.d("TransactionActivity", " ⭐ NEW BEST selected: " + reason);
|
||||
}
|
||||
}
|
||||
@ -454,19 +733,18 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ TIMESTAMP COMPARISON: Smart date comparison
|
||||
* ✅ TIMESTAMP COMPARISON: Smart date comparison using enhanced parsing
|
||||
*/
|
||||
private boolean isNewerTransaction(Transaction tx1, Transaction tx2) {
|
||||
try {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
|
||||
Date date1 = sdf.parse(tx1.createdAt);
|
||||
Date date2 = sdf.parse(tx2.createdAt);
|
||||
Date date1 = parseCreatedAtDate(tx1.createdAt);
|
||||
Date date2 = parseCreatedAtDate(tx2.createdAt);
|
||||
|
||||
if (date1 != null && date2 != null) {
|
||||
return date1.after(date2);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("TransactionActivity", "Date parsing error, falling back to ID comparison");
|
||||
Log.w("TransactionActivity", "Date comparison error, falling back to ID comparison");
|
||||
}
|
||||
|
||||
// Fallback: higher ID usually means newer
|
||||
@ -474,7 +752,7 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ COMPARISON HELPER: Check if one transaction is better than another
|
||||
* ✅ COMPARISON HELPER: Check if one transaction is better than another using enhanced parsing
|
||||
*/
|
||||
private boolean isBetterTransaction(Transaction newTx, Transaction existingTx) {
|
||||
int newPriority = getStatusPriority(newTx.status);
|
||||
|
@ -11,18 +11,16 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
// ✅ TAMBAHKAN MISSING IMPORTS INI:
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
// ✅ TAMBAHKAN JSON IMPORTS:
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@ -43,6 +41,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
this.printClickListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data without numbering (removed as per request)
|
||||
*/
|
||||
public void updateData(List<TransactionActivity.Transaction> newData, int startIndex) {
|
||||
this.transactionList = newData;
|
||||
notifyDataSetChanged();
|
||||
|
||||
Log.d("TransactionAdapter", "📋 Data updated: " + newData.size() + " items");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
@ -54,6 +62,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
public void onBindViewHolder(@NonNull TransactionViewHolder holder, int position) {
|
||||
TransactionActivity.Transaction t = transactionList.get(position);
|
||||
|
||||
// ✅ STRIPE TABLE: Set alternating row colors
|
||||
LinearLayout itemContainer = holder.itemView.findViewById(R.id.itemContainer);
|
||||
if (position % 2 == 0) {
|
||||
// Even rows - white background
|
||||
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.white));
|
||||
} else {
|
||||
// Odd rows - light gray background
|
||||
itemContainer.setBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), android.R.color.background_light));
|
||||
}
|
||||
|
||||
Log.d("TransactionAdapter", "📋 Binding transaction " + position + ":");
|
||||
Log.d("TransactionAdapter", " Reference: " + t.referenceId);
|
||||
Log.d("TransactionAdapter", " Status: " + t.status);
|
||||
@ -87,8 +105,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
if (t.referenceId != null && !t.referenceId.isEmpty()) {
|
||||
// Show checking state
|
||||
holder.status.setText("CHECKING...");
|
||||
holder.status.setTextColor(ContextCompat.getColor(holder.itemView.getContext(),
|
||||
android.R.color.holo_orange_dark));
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING");
|
||||
|
||||
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId);
|
||||
|
||||
@ -97,13 +114,13 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
} else {
|
||||
// No reference ID to check
|
||||
holder.status.setText(displayStatus.toUpperCase());
|
||||
setStatusColor(holder.status, displayStatus);
|
||||
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());
|
||||
setStatusColor(holder.status, displayStatus);
|
||||
StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
|
||||
Log.d("TransactionAdapter", "✅ Using confirmed status: " + displayStatus);
|
||||
}
|
||||
|
||||
@ -111,6 +128,12 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
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) {
|
||||
@ -188,35 +211,6 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
return "Rp. " + formatted;
|
||||
}
|
||||
|
||||
private void setStatusColor(TextView statusTextView, String status) {
|
||||
String statusLower = status.toLowerCase();
|
||||
int color;
|
||||
|
||||
if (statusLower.equals("failed") || statusLower.equals("failure") ||
|
||||
statusLower.equals("error") || statusLower.equals("declined") ||
|
||||
statusLower.equals("expire") || statusLower.equals("cancel")) {
|
||||
// Red for failed/error statuses
|
||||
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_red_dark);
|
||||
} else if (statusLower.equals("success") || statusLower.equals("paid") ||
|
||||
statusLower.equals("settlement") || statusLower.equals("completed") ||
|
||||
statusLower.equals("capture")) {
|
||||
// Green for successful statuses
|
||||
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_green_dark);
|
||||
} else if (statusLower.equals("pending") || statusLower.equals("processing") ||
|
||||
statusLower.equals("waiting") || statusLower.equals("checking...")) {
|
||||
// Orange for pending/processing statuses
|
||||
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_orange_dark);
|
||||
} else if (statusLower.equals("init")) {
|
||||
// Yellow for init status
|
||||
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.holo_orange_light);
|
||||
} else {
|
||||
// Default gray for unknown statuses
|
||||
color = ContextCompat.getColor(statusTextView.getContext(), android.R.color.darker_gray);
|
||||
}
|
||||
|
||||
statusTextView.setTextColor(color);
|
||||
}
|
||||
|
||||
private void checkMidtransStatus(String referenceId, TextView statusTextView) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
@ -344,7 +338,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText(finalStatus);
|
||||
setStatusColor(statusTextView, finalStatus);
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), finalStatus);
|
||||
|
||||
Log.d("TransactionAdapter", "🎨 UI UPDATED:");
|
||||
Log.d("TransactionAdapter", " Reference: " + referenceId);
|
||||
@ -356,7 +350,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("ERROR");
|
||||
setStatusColor(statusTextView, "ERROR");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "ERROR");
|
||||
});
|
||||
}
|
||||
|
||||
@ -364,12 +358,108 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e);
|
||||
statusTextView.post(() -> {
|
||||
statusTextView.setText("INIT");
|
||||
setStatusColor(statusTextView, "INIT");
|
||||
StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT");
|
||||
});
|
||||
}
|
||||
}).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";
|
||||
@ -408,7 +498,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
}
|
||||
|
||||
static class TransactionViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView amount, referenceId, status, paymentMethod;
|
||||
TextView amount, referenceId, status, paymentMethod, createdAt; // ✅ Added createdAt
|
||||
LinearLayout printSection;
|
||||
|
||||
public TransactionViewHolder(@NonNull View itemView) {
|
||||
@ -417,6 +507,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
|
||||
referenceId = itemView.findViewById(R.id.textReferenceId);
|
||||
status = itemView.findViewById(R.id.textStatus);
|
||||
paymentMethod = itemView.findViewById(R.id.textPaymentMethod);
|
||||
createdAt = itemView.findViewById(R.id.textCreatedAt); // ✅ Added createdAt
|
||||
printSection = itemView.findViewById(R.id.printSection);
|
||||
}
|
||||
}
|
||||
|
@ -23,39 +23,120 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#f5f5f5"
|
||||
android:background="#f8f9fa"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<!-- Search Section -->
|
||||
<!-- Search and Filter Section -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:padding="20dp"
|
||||
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="16dp">
|
||||
|
||||
<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="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/search_background"
|
||||
android:hint="Cari dengan nomor struk..."
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:textSize="14sp"
|
||||
android:textColorHint="#999999" />
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="Search"
|
||||
android:textSize="16sp"
|
||||
android:textColorHint="#999999"
|
||||
android:textColor="#333333"
|
||||
android:maxLines="1"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/searchButton"
|
||||
android:layout_width="48dp"
|
||||
</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="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/ic_menu_sort_by_size"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:alpha="0.5" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Filter"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#666666" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Table Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/search_button_background"
|
||||
android:src="@android:drawable/ic_menu_search"
|
||||
android:contentDescription="Search"
|
||||
app:tint="@android:color/white" />
|
||||
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>
|
||||
|
||||
@ -70,8 +151,73 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white" />
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#ffffff" />
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<LinearLayout
|
||||
android:id="@+id/paginationControls"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@android:color/white"
|
||||
android:padding="20dp"
|
||||
android:gravity="center"
|
||||
android:elevation="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<!-- First Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnFirstPage"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:src="@android:drawable/ic_media_previous"
|
||||
android:contentDescription="First Page"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="centerInside" />
|
||||
|
||||
<!-- Previous Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnPrevPage"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:src="@android:drawable/ic_media_rew"
|
||||
android:contentDescription="Previous Page"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:scaleType="centerInside" />
|
||||
|
||||
<!-- 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" />
|
||||
|
||||
<!-- Next Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnNextPage"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:src="@android:drawable/ic_media_ff"
|
||||
android:contentDescription="Next Page"
|
||||
android:layout_marginStart="12dp"
|
||||
android:scaleType="centerInside" />
|
||||
|
||||
<!-- Last Page Button -->
|
||||
<ImageButton
|
||||
android:id="@+id/btnLastPage"
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
android:contentDescription="Last Page"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="centerInside" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -1,23 +1,23 @@
|
||||
<?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:orientation="vertical"
|
||||
android:background="@android:color/white">
|
||||
android:id="@+id/itemContainer">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp"
|
||||
android:gravity="center_vertical">
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="64dp">
|
||||
|
||||
<!-- Kolom 1: Transaction Info -->
|
||||
<!-- Column 1: Transaction Info -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_weight="2"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Reference ID -->
|
||||
@ -28,16 +28,17 @@
|
||||
android:text="ref-eowu3pin"
|
||||
android:textColor="#333333"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="4dp" />
|
||||
|
||||
<!-- Status and Payment Method -->
|
||||
<!-- Status, Payment Method, and Created At Row -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:id="@+id/textStatus"
|
||||
android:layout_width="wrap_content"
|
||||
@ -45,75 +46,73 @@
|
||||
android:text="SUCCESS"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#4CAF50" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=" • "
|
||||
android:textSize="12sp"
|
||||
android:textColor="#999999"
|
||||
android:layout_marginHorizontal="4dp" />
|
||||
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="#333333" />
|
||||
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>
|
||||
|
||||
<!-- Kolom 2: Amount -->
|
||||
<!-- 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="#666666"
|
||||
android:textColor="#333333"
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="textEnd" />
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- Kolom 3: Print Button -->
|
||||
<!-- 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_vertical|end"
|
||||
android:gravity="center"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:padding="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/ic_print"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:tint="#666666" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cetak Ulang"
|
||||
android:textColor="#666666"
|
||||
android:textSize="14sp" />
|
||||
android:textSize="12sp"
|
||||
android:drawableLeft="@android:drawable/ic_menu_edit"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Bottom Border -->
|
||||
<View
|
||||
android:id="@+id/bottomBorder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#e0e0e0" />
|
||||
|
||||
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user