memperbaiki list cetak ulang

This commit is contained in:
riz081 2025-06-10 12:10:35 +07:00
parent 4aaa9957e7
commit b0ee2e8ee6
5 changed files with 814 additions and 194 deletions

View File

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

View File

@ -11,6 +11,8 @@ import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import android.widget.TextView;
import android.widget.LinearLayout;
import android.content.Intent; import android.content.Intent;
import android.util.Log; import android.util.Log;
@ -50,16 +52,25 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
private EditText searchEditText; private EditText searchEditText;
private ImageButton searchButton; 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 // FRONTEND DEDUPLICATION: Local caching and tracking
private Map<String, Transaction> transactionCache = new HashMap<>(); private Map<String, Transaction> transactionCache = new HashMap<>();
private Set<String> processedReferences = new HashSet<>(); private Set<String> processedReferences = new HashSet<>();
private SharedPreferences prefs; private SharedPreferences prefs;
// Pagination variables // UPDATED PAGINATION VARIABLES
private int page = 0; private int currentPage = 1; // Start from page 1 instead of 0
private final int limit = 50; // INCREASED: Fetch more data for better deduplication 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 isLoading = false;
private boolean isLastPage = false;
private String currentSearchQuery = ""; private String currentSearchQuery = "";
private boolean isRefreshing = false; private boolean isRefreshing = false;
@ -83,15 +94,38 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
// Setup search functionality // Setup search functionality
setupSearch(); setupSearch();
// Setup pagination controls
setupPaginationControls();
// Load initial data // Load initial data
loadTransactions(0); loadTransactions(1); // Start from page 1
} }
private void initViews() { private void initViews() {
recyclerView = findViewById(R.id.recyclerView); recyclerView = findViewById(R.id.recyclerView);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
searchEditText = findViewById(R.id.searchEditText); 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() { private void setupToolbar() {
@ -114,32 +148,12 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
// Add scroll listener for pagination // REMOVED: Auto-pagination scroll listener since we use manual pagination now
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { // Manual pagination is better for user control and performance
@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);
}
}
});
} }
private void setupSearch() { private void setupSearch() {
// Search button click listener // Search on text change
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
searchEditText.addTextChangedListener(new TextWatcher() { searchEditText.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@ -147,12 +161,89 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
currentSearchQuery = s.toString().trim(); currentSearchQuery = s.toString().trim();
// RESET TO PAGE 1 when searching
currentPage = 1;
filterTransactions(currentSearchQuery); filterTransactions(currentSearchQuery);
} }
@Override @Override
public void afterTextChanged(Editable s) {} 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() { private void refreshTransactions() {
@ -166,17 +257,17 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
// Clear search when refreshing // Clear search when refreshing
searchEditText.setText(""); searchEditText.setText("");
currentSearchQuery = ""; currentSearchQuery = "";
page = 0; currentPage = 1; // Reset to page 1
isLastPage = false;
transactionList.clear(); transactionList.clear();
filteredList.clear(); filteredList.clear();
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
loadTransactions(0); loadTransactions(1);
} }
private void performSearch() { private void performSearch() {
String query = searchEditText.getText().toString().trim(); String query = searchEditText.getText().toString().trim();
currentSearchQuery = query; currentSearchQuery = query;
currentPage = 1; // Reset to page 1 when searching
filterTransactions(query); filterTransactions(query);
// Hide keyboard // Hide keyboard
@ -187,17 +278,53 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
filteredList.clear(); filteredList.clear();
if (query.isEmpty()) { if (query.isEmpty()) {
// NO SEARCH: Show current API page data (already sorted)
filteredList.addAll(transactionList); 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 { } else {
// SEARCH MODE: Filter all available data
for (Transaction transaction : transactionList) { for (Transaction transaction : transactionList) {
if (transaction.referenceId.toLowerCase().contains(query.toLowerCase()) || if (transaction.referenceId.toLowerCase().contains(query.toLowerCase()) ||
transaction.amount.contains(query)) { transaction.amount.contains(query)) {
filteredList.add(transaction); 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 // Scroll to top after filtering
if (!filteredList.isEmpty()) { 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) { private void loadTransactions(int pageToLoad) {
isLoading = true; isLoading = true;
if (pageToLoad == 0) {
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
}
new FetchTransactionsTask(pageToLoad).execute(); new FetchTransactionsTask(pageToLoad).execute();
} }
private class FetchTransactionsTask extends AsyncTask<Void, Void, List<Transaction>> { private class FetchTransactionsTask extends AsyncTask<Void, Void, List<Transaction>> {
private int pageToLoad; private int pageToLoad;
private boolean error = false; private boolean error = false;
private int total = 0; private int apiTotal = 0;
FetchTransactionsTask(int page) { FetchTransactionsTask(int page) {
this.pageToLoad = page; this.pageToLoad = page;
@ -226,13 +439,14 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
protected List<Transaction> doInBackground(Void... voids) { protected List<Transaction> doInBackground(Void... voids) {
List<Transaction> result = new ArrayList<>(); List<Transaction> result = new ArrayList<>();
try { try {
// FETCH MORE DATA: Increased limit for better deduplication // PAGINATION API CALL: Use page-based API call
int fetchLimit = limit * 3; // Get more records to handle all duplicates int apiPage = pageToLoad - 1; // API uses 0-based indexing
String urlString = "https://be-edc.msvc.app/transactions?page=" + pageToLoad + String urlString = "https://be-edc.msvc.app/transactions?page=" + apiPage +
"&limit=" + fetchLimit + "&sortOrder=DESC&from_date=&to_date=&location_id=0&merchant_id=0&tid=73001500&mid=71000026521&sortColumn=created_at"; "&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); URI uri = new URI(urlString);
URL url = uri.toURL(); URL url = uri.toURL();
@ -256,10 +470,11 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
JSONObject jsonObject = new JSONObject(response.toString()); JSONObject jsonObject = new JSONObject(response.toString());
JSONObject results = jsonObject.getJSONObject("results"); JSONObject results = jsonObject.getJSONObject("results");
total = results.getInt("total"); apiTotal = results.getInt("total");
JSONArray data = results.getJSONArray("data"); 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 // STEP 1: Parse all transactions from API
List<Transaction> rawTransactions = new ArrayList<>(); List<Transaction> rawTransactions = new ArrayList<>();
@ -304,64 +519,109 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
if (error) { if (error) {
Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show(); Toast.makeText(TransactionActivity.this, "Failed to fetch transactions", Toast.LENGTH_SHORT).show();
updatePaginationDisplay(); // Show current state even on error
return; return;
} }
if (pageToLoad == 0) { // UPDATE PAGINATION DATA
currentPage = pageToLoad;
totalRecords = apiTotal;
totalPages = (int) Math.ceil((double) totalRecords / itemsPerPage);
// UPDATE TRANSACTION LIST
transactionList.clear(); transactionList.clear();
transactionCache.clear(); // Clear cache on refresh transactionList.addAll(transactions);
}
// SMART MERGE: Only add truly new transactions // CRITICAL: FORCE SORT AGAIN after adding to main list
int addedCount = 0; transactionList.sort((t1, t2) -> {
for (Transaction newTx : transactions) { try {
String refId = newTx.referenceId; Date date1 = parseCreatedAtDate(t1.createdAt);
Date date2 = parseCreatedAtDate(t2.createdAt);
// Check if we already have a better version of this transaction if (date1 != null && date2 != null) {
Transaction cachedTx = transactionCache.get(refId); int comparison = date2.compareTo(date1); // Newest first
if (cachedTx == null || isBetterTransaction(newTx, cachedTx)) { Log.d("TransactionActivity", "🔄 Final sort: " + t1.createdAt + " vs " + t2.createdAt + " = " + comparison);
// Update cache with better transaction return comparison;
transactionCache.put(refId, newTx); }
} 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 Log.d("TransactionActivity", "📋 Page " + currentPage + " loaded and sorted: " +
boolean updated = false; transactions.size() + " transactions. Total: " + totalRecords + "/" + totalPages + " pages");
for (int i = 0; i < transactionList.size(); i++) {
if (transactionList.get(i).referenceId.equals(refId)) {
transactionList.set(i, newTx);
updated = true;
break;
}
}
if (!updated) { // LOG FINAL ORDER VERIFICATION
transactionList.add(newTx); Log.d("TransactionActivity", "📋 FINAL DISPLAY ORDER:");
addedCount++; 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 // Update filtered list based on current search
filterTransactions(currentSearchQuery); filterTransactions(currentSearchQuery);
page = pageToLoad; // Scroll to top
if (transactions.size() < limit) { // No more pages if returned less than requested if (!filteredList.isEmpty()) {
isLastPage = true;
}
// Scroll to top if it's a refresh
if (pageToLoad == 0 && !filteredList.isEmpty()) {
recyclerView.scrollToPosition(0); 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 * ADVANCED DEDUPLICATION: Enhanced algorithm with multiple strategies
*/ */
private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) { private List<Transaction> applyAdvancedDeduplication(List<Transaction> rawTransactions) {
Log.d("TransactionActivity", "🧠 Starting advanced deduplication..."); 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 // Strategy 1: Group by reference_id
Map<String, List<Transaction>> groupedByRef = new HashMap<>(); Map<String, List<Transaction>> groupedByRef = new HashMap<>();
@ -384,13 +644,26 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
deduplicatedList.add(group.get(0)); deduplicatedList.add(group.get(0));
Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId); Log.d("TransactionActivity", "✅ Unique transaction: " + referenceId);
} else { } 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); Transaction bestTransaction = selectBestTransactionAdvanced(group, referenceId);
deduplicatedList.add(bestTransaction); deduplicatedList.add(bestTransaction);
duplicatesRemoved += (group.size() - 1); duplicatesRemoved += (group.size() - 1);
Log.d("TransactionActivity", "🔄 Deduplicated " + group.size() + " → 1 for ref: " + referenceId + 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); Transaction bestTransaction = duplicates.get(0);
int bestPriority = getStatusPriority(bestTransaction.status); int bestPriority = getStatusPriority(bestTransaction.status);
Date bestDate = parseCreatedAtDate(bestTransaction.createdAt);
// Detailed analysis of each candidate // Detailed analysis of each candidate
for (Transaction tx : duplicates) { for (Transaction tx : duplicates) {
int currentPriority = getStatusPriority(tx.status); int currentPriority = getStatusPriority(tx.status);
Date currentDate = parseCreatedAtDate(tx.createdAt);
Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id + Log.d("TransactionActivity", " 📊 Candidate: ID=" + tx.id +
", Status=" + tx.status + " (priority=" + currentPriority + ")" + ", Status=" + tx.status + " (priority=" + currentPriority + ")" +
@ -429,20 +704,24 @@ public class TransactionActivity extends AppCompatActivity implements Transactio
} }
// Rule 2: Same priority, choose newer timestamp // Rule 2: Same priority, choose newer timestamp
else if (currentPriority == bestPriority) { else if (currentPriority == bestPriority) {
if (isNewerTransaction(tx, bestTransaction)) { if (currentDate != null && bestDate != null && currentDate.after(bestDate)) {
shouldSelect = true; shouldSelect = true;
reason = "newer timestamp"; reason = "newer timestamp";
} }
// Rule 3: Same priority and time, choose higher ID // 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; shouldSelect = true;
reason = "higher ID"; reason = "higher ID";
} }
} }
}
if (shouldSelect) { if (shouldSelect) {
bestTransaction = tx; bestTransaction = tx;
bestPriority = currentPriority; bestPriority = currentPriority;
bestDate = currentDate;
Log.d("TransactionActivity", " ⭐ NEW BEST selected: " + reason); 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) { private boolean isNewerTransaction(Transaction tx1, Transaction tx2) {
try { try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); Date date1 = parseCreatedAtDate(tx1.createdAt);
Date date1 = sdf.parse(tx1.createdAt); Date date2 = parseCreatedAtDate(tx2.createdAt);
Date date2 = sdf.parse(tx2.createdAt);
if (date1 != null && date2 != null) { if (date1 != null && date2 != null) {
return date1.after(date2); return date1.after(date2);
} }
} catch (Exception e) { } 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 // 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) { private boolean isBetterTransaction(Transaction newTx, Transaction existingTx) {
int newPriority = getStatusPriority(newTx.status); int newPriority = getStatusPriority(newTx.status);

View File

@ -11,18 +11,16 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
// TAMBAHKAN MISSING IMPORTS INI:
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder;
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;
// TAMBAHKAN JSON IMPORTS:
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -43,6 +41,16 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
this.printClickListener = 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) {
@ -54,6 +62,16 @@ 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);
// 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", "📋 Binding transaction " + position + ":");
Log.d("TransactionAdapter", " Reference: " + t.referenceId); Log.d("TransactionAdapter", " Reference: " + t.referenceId);
Log.d("TransactionAdapter", " Status: " + t.status); Log.d("TransactionAdapter", " Status: " + t.status);
@ -87,8 +105,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
if (t.referenceId != null && !t.referenceId.isEmpty()) { if (t.referenceId != null && !t.referenceId.isEmpty()) {
// Show checking state // Show checking state
holder.status.setText("CHECKING..."); holder.status.setText("CHECKING...");
holder.status.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), "CHECKING");
android.R.color.holo_orange_dark));
Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId); Log.d("TransactionAdapter", "🔄 Starting comprehensive check for: " + t.referenceId);
@ -97,13 +114,13 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} else { } else {
// No reference ID to check // No reference ID to check
holder.status.setText(displayStatus.toUpperCase()); 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"); Log.w("TransactionAdapter", "⚠️ No reference ID for status check");
} }
} else { } else {
// Use existing status yang sudah confirmed // Use existing status yang sudah confirmed
holder.status.setText(displayStatus.toUpperCase()); holder.status.setText(displayStatus.toUpperCase());
setStatusColor(holder.status, displayStatus); StyleHelper.applyStatusTextColor(holder.status, holder.itemView.getContext(), displayStatus);
Log.d("TransactionAdapter", "✅ Using confirmed status: " + 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); String paymentMethod = getPaymentMethodName(t.channelCode, t.channelCategory);
holder.paymentMethod.setText(paymentMethod); 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 // Set click listeners
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (printClickListener != null) { if (printClickListener != null) {
@ -188,35 +211,6 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
return "Rp. " + formatted; 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) { private void checkMidtransStatus(String referenceId, TextView statusTextView) {
new Thread(() -> { new Thread(() -> {
try { try {
@ -344,7 +338,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
statusTextView.post(() -> { statusTextView.post(() -> {
statusTextView.setText(finalStatus); statusTextView.setText(finalStatus);
setStatusColor(statusTextView, finalStatus); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), finalStatus);
Log.d("TransactionAdapter", "🎨 UI UPDATED:"); Log.d("TransactionAdapter", "🎨 UI UPDATED:");
Log.d("TransactionAdapter", " Reference: " + referenceId); 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()); Log.w("TransactionAdapter", "⚠️ API call failed with code: " + conn.getResponseCode());
statusTextView.post(() -> { statusTextView.post(() -> {
statusTextView.setText("ERROR"); 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); Log.e("TransactionAdapter", "❌ Comprehensive status check error: " + e.getMessage(), e);
statusTextView.post(() -> { statusTextView.post(() -> {
statusTextView.setText("INIT"); statusTextView.setText("INIT");
setStatusColor(statusTextView, "INIT"); StyleHelper.applyStatusTextColor(statusTextView, statusTextView.getContext(), "INIT");
}); });
} }
}).start(); }).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) { private String getPaymentMethodName(String channelCode, String channelCategory) {
// Convert channel code to readable payment method name // Convert channel code to readable payment method name
if (channelCode == null) return "Unknown"; if (channelCode == null) return "Unknown";
@ -408,7 +498,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} }
static class TransactionViewHolder extends RecyclerView.ViewHolder { static class TransactionViewHolder extends RecyclerView.ViewHolder {
TextView amount, referenceId, status, paymentMethod; TextView amount, referenceId, status, paymentMethod, createdAt; // Added createdAt
LinearLayout printSection; LinearLayout printSection;
public TransactionViewHolder(@NonNull View itemView) { public TransactionViewHolder(@NonNull View itemView) {
@ -417,6 +507,7 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
referenceId = itemView.findViewById(R.id.textReferenceId); referenceId = itemView.findViewById(R.id.textReferenceId);
status = itemView.findViewById(R.id.textStatus); status = itemView.findViewById(R.id.textStatus);
paymentMethod = itemView.findViewById(R.id.textPaymentMethod); paymentMethod = itemView.findViewById(R.id.textPaymentMethod);
createdAt = itemView.findViewById(R.id.textCreatedAt); // Added createdAt
printSection = itemView.findViewById(R.id.printSection); printSection = itemView.findViewById(R.id.printSection);
} }
} }

View File

@ -23,39 +23,120 @@
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:background="#f5f5f5" android:background="#f8f9fa"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Search Section --> <!-- Search and Filter Section -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="16dp" android:padding="20dp"
android:background="@android:color/white" android:background="@android:color/white"
android:gravity="center_vertical"> 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 <EditText
android:id="@+id/searchEditText" android:id="@+id/searchEditText"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="48dp" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/search_background" android:background="@android:color/transparent"
android:hint="Cari dengan nomor struk..." android:hint="Search"
android:paddingStart="16dp" android:textSize="16sp"
android:paddingEnd="16dp" android:textColorHint="#999999"
android:textSize="14sp" android:textColor="#333333"
android:textColorHint="#999999" /> android:maxLines="1"
android:gravity="center_vertical" />
<ImageButton </LinearLayout>
android:id="@+id/searchButton"
android:layout_width="48dp" <!-- 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_height="48dp"
android:layout_marginStart="8dp" android:orientation="horizontal"
android:background="@drawable/search_button_background" android:background="#f5f5f5"
android:src="@android:drawable/ic_menu_search" android:gravity="center_vertical"
android:contentDescription="Search" android:paddingLeft="16dp"
app:tint="@android:color/white" /> 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> </LinearLayout>
@ -70,8 +151,73 @@
<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:background="@android:color/white" /> 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> </LinearLayout>

View File

@ -1,23 +1,23 @@
<?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="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:background="@android:color/white"> android:id="@+id/itemContainer">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:padding="16dp" android:padding="16dp"
android:gravity="center_vertical"> android:gravity="center_vertical"
android:minHeight="64dp">
<!-- Kolom 1: Transaction Info --> <!-- Column 1: Transaction Info -->
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="2"
android:orientation="vertical"> android:orientation="vertical">
<!-- Reference ID --> <!-- Reference ID -->
@ -28,16 +28,17 @@
android:text="ref-eowu3pin" android:text="ref-eowu3pin"
android:textColor="#333333" android:textColor="#333333"
android:textSize="16sp" 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 <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginTop="4dp"
android:gravity="center_vertical"> android:gravity="center_vertical">
<!-- Status -->
<TextView <TextView
android:id="@+id/textStatus" android:id="@+id/textStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -45,75 +46,73 @@
android:text="SUCCESS" android:text="SUCCESS"
android:textSize="12sp" android:textSize="12sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="#4CAF50" /> android:textColor="@android:color/white"
android:paddingLeft="8dp"
<TextView android:paddingRight="8dp"
android:layout_width="wrap_content" android:paddingTop="2dp"
android:layout_height="wrap_content" android:paddingBottom="2dp" />
android:text=" • "
android:textSize="12sp"
android:textColor="#999999"
android:layout_marginHorizontal="4dp" />
<!-- Payment Method -->
<TextView <TextView
android:id="@+id/textPaymentMethod" android:id="@+id/textPaymentMethod"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="QRIS" android:text="QRIS"
android:textSize="12sp" android:textSize="12sp"
android:textColor="#333333" /> android:textColor="#666666"
android:layout_marginStart="12dp" />
</LinearLayout> </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> </LinearLayout>
<!-- Kolom 2: Amount --> <!-- Column 2: Amount -->
<TextView <TextView
android:id="@+id/textAmount" android:id="@+id/textAmount"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="Rp. 1.111" android:text="Rp. 1.111"
android:textColor="#666666" android:textColor="#333333"
android:textSize="14sp" android:textSize="14sp"
android:textAlignment="textEnd" /> android:textStyle="bold"
android:gravity="center" />
<!-- Kolom 3: Print Button --> <!-- Column 3: Print Button -->
<LinearLayout <LinearLayout
android:id="@+id/printSection" android:id="@+id/printSection"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical|end" android:gravity="center"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:padding="8dp" android:padding="8dp"
android:clickable="true" android:clickable="true"
android:focusable="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 <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Cetak Ulang" android:text="Cetak Ulang"
android:textColor="#666666" 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>
</LinearLayout> </LinearLayout>
<!-- Bottom Border -->
<View
android:id="@+id/bottomBorder"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0" />
</LinearLayout> </LinearLayout>