UI Cetak ulang struk

This commit is contained in:
riz081 2025-06-05 13:03:44 +07:00
parent a30e767adc
commit 5a03fc3aec
6 changed files with 296 additions and 85 deletions

View File

@ -2,15 +2,20 @@ package com.example.bdkipoc;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -26,66 +31,169 @@ import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class TransactionActivity extends AppCompatActivity { public class TransactionActivity extends AppCompatActivity implements TransactionAdapter.OnPrintClickListener {
private RecyclerView recyclerView; private RecyclerView recyclerView;
private TransactionAdapter adapter; private TransactionAdapter adapter;
private List<Transaction> transactionList;
private List<Transaction> filteredList;
private ProgressBar progressBar; private ProgressBar progressBar;
private FloatingActionButton refreshButton; private EditText searchEditText;
private ImageButton searchButton;
// Pagination variables
private int page = 0; private int page = 0;
private final int limit = 10; private final int limit = 10;
private boolean isLoading = false; private boolean isLoading = false;
private boolean isLastPage = false; private boolean isLastPage = false;
private List<Transaction> transactionList = new ArrayList<>(); private String currentSearchQuery = "";
private boolean isRefreshing = false;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transaction); setContentView(R.layout.activity_transaction);
// Set up the toolbar as the action bar // Initialize views
androidx.appcompat.widget.Toolbar toolbar = findViewById(R.id.toolbar); initViews();
setSupportActionBar(toolbar);
// Enable the back button in the action bar // Setup toolbar
setupToolbar();
// Setup RecyclerView
setupRecyclerView();
// Setup search functionality
setupSearch();
// Load initial data
loadTransactions(0);
}
private void initViews() {
recyclerView = findViewById(R.id.recyclerView);
progressBar = findViewById(R.id.progressBar);
searchEditText = findViewById(R.id.searchEditText);
searchButton = findViewById(R.id.searchButton);
}
private void setupToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
} }
}
recyclerView = findViewById(R.id.recyclerView); private void setupRecyclerView() {
progressBar = findViewById(R.id.progressBar); transactionList = new ArrayList<>();
refreshButton = findViewById(R.id.refreshButton); filteredList = new ArrayList<>();
adapter = new TransactionAdapter(transactionList); adapter = new TransactionAdapter(filteredList);
recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter.setPrintClickListener(this);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
refreshButton.setOnClickListener(v -> refreshTransactions()); // Add scroll listener for pagination
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override @Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy); super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1) && !isLoading && !isLastPage) {
// Pagination: Load more when reaching the bottom
if (!recyclerView.canScrollVertically(1) && !isLoading && !isLastPage && currentSearchQuery.isEmpty()) {
loadTransactions(page + 1); loadTransactions(page + 1);
} }
// Detect scroll to top for refresh gesture (double tap on top)
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager != null && layoutManager.findFirstCompletelyVisibleItemPosition() == 0 && dy < 0) {
// User is at top and scrolling up, enable refresh on search button long press
}
} }
}); });
}
loadTransactions(0); 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
searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
currentSearchQuery = s.toString().trim();
filterTransactions(currentSearchQuery);
}
@Override
public void afterTextChanged(Editable s) {}
});
} }
private void refreshTransactions() { private void refreshTransactions() {
if (isRefreshing) return;
isRefreshing = true;
// Clear search when refreshing
searchEditText.setText("");
currentSearchQuery = "";
page = 0; page = 0;
isLastPage = false; isLastPage = false;
transactionList.clear(); transactionList.clear();
filteredList.clear();
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
loadTransactions(0); loadTransactions(0);
} }
private void performSearch() {
String query = searchEditText.getText().toString().trim();
currentSearchQuery = query;
filterTransactions(query);
// Hide keyboard
searchEditText.clearFocus();
}
private void filterTransactions(String query) {
filteredList.clear();
if (query.isEmpty()) {
filteredList.addAll(transactionList);
} else {
for (Transaction transaction : transactionList) {
if (transaction.referenceId.toLowerCase().contains(query.toLowerCase()) ||
transaction.amount.contains(query)) {
filteredList.add(transaction);
}
}
}
adapter.notifyDataSetChanged();
// Scroll to top after filtering
if (!filteredList.isEmpty()) {
recyclerView.scrollToPosition(0);
}
}
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();
} }
@ -149,33 +257,56 @@ public class TransactionActivity extends AppCompatActivity {
@Override @Override
protected void onPostExecute(List<Transaction> transactions) { protected void onPostExecute(List<Transaction> transactions) {
isLoading = false; isLoading = false;
isRefreshing = false;
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
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();
return; return;
} }
if (pageToLoad == 0) { if (pageToLoad == 0) {
transactionList.clear(); transactionList.clear();
} }
transactionList.addAll(transactions); transactionList.addAll(transactions);
adapter.notifyDataSetChanged();
// Update filtered list based on current search
filterTransactions(currentSearchQuery);
page = pageToLoad; page = pageToLoad;
if (transactionList.size() >= total) { if (transactionList.size() >= total) {
isLastPage = true; isLastPage = true;
} }
// Scroll to top if it's a refresh
if (pageToLoad == 0 && !filteredList.isEmpty()) {
recyclerView.scrollToPosition(0);
}
} }
} }
@Override @Override
public boolean onOptionsItemSelected(android.view.MenuItem item) { public void onPrintClick(Transaction transaction) {
// Handle print button click
Toast.makeText(this, "Cetak Ulang: " + transaction.referenceId, Toast.LENGTH_SHORT).show();
// Implement your print logic here
// For example, you might want to:
// 1. Generate receipt
// 2. Send to printer
// 3. Show print preview
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) { if (item.getItemId() == android.R.id.home) {
// Handle the back button click
finish(); finish();
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
// Transaction model class
static class Transaction { static class Transaction {
int id; int id;
String type; String type;

View File

@ -4,6 +4,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -14,11 +15,20 @@ import java.util.Locale;
public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> { public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.TransactionViewHolder> {
private List<TransactionActivity.Transaction> transactionList; private List<TransactionActivity.Transaction> transactionList;
private OnPrintClickListener printClickListener;
public interface OnPrintClickListener {
void onPrintClick(TransactionActivity.Transaction transaction);
}
public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) { public TransactionAdapter(List<TransactionActivity.Transaction> transactionList) {
this.transactionList = transactionList; this.transactionList = transactionList;
} }
public void setPrintClickListener(OnPrintClickListener listener) {
this.printClickListener = listener;
}
@NonNull @NonNull
@Override @Override
public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public TransactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -30,19 +40,24 @@ 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);
// Set reference ID
holder.referenceId.setText(t.referenceId);
// Format the amount as Indonesian Rupiah // Format the amount as Indonesian Rupiah
try { try {
double amountValue = Double.parseDouble(t.amount); double amountValue = Double.parseDouble(t.amount);
NumberFormat rupiahFormat = NumberFormat.getCurrencyInstance(new Locale.Builder().setLanguage("id").setRegion("ID").build()); NumberFormat rupiahFormat = NumberFormat.getInstance(new Locale("id", "ID"));
holder.amount.setText(rupiahFormat.format(amountValue)); holder.amount.setText("Rp. " + rupiahFormat.format(amountValue));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
holder.amount.setText("Rp " + t.amount); holder.amount.setText("Rp. " + t.amount);
} }
holder.status.setText(t.status); // Set click listener for print button
holder.referenceId.setText(t.referenceId); holder.printSection.setOnClickListener(v -> {
holder.merchantName.setText(t.merchantName); if (printClickListener != null) {
holder.createdAt.setText(t.createdAt.replace("T", " ").substring(0, 19)); printClickListener.onPrintClick(t);
}
});
} }
@Override @Override
@ -51,14 +66,14 @@ public class TransactionAdapter extends RecyclerView.Adapter<TransactionAdapter.
} }
static class TransactionViewHolder extends RecyclerView.ViewHolder { static class TransactionViewHolder extends RecyclerView.ViewHolder {
TextView amount, status, referenceId, merchantName, createdAt; TextView amount, referenceId;
LinearLayout printSection;
public TransactionViewHolder(@NonNull View itemView) { public TransactionViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
amount = itemView.findViewById(R.id.textAmount); amount = itemView.findViewById(R.id.textAmount);
status = itemView.findViewById(R.id.textStatus);
referenceId = itemView.findViewById(R.id.textReferenceId); referenceId = itemView.findViewById(R.id.textReferenceId);
merchantName = itemView.findViewById(R.id.textMerchantName); printSection = itemView.findViewById(R.id.printSection);
createdAt = itemView.findViewById(R.id.textCreatedAt);
} }
} }
} }

View File

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

View File

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

View File

@ -12,10 +12,10 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" android:background="#F44336"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:title="Transactions History" /> app:title="CETAK ULANG STRUK" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
@ -23,9 +23,42 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp" android:background="#f5f5f5"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!-- Search Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white"
android:gravity="center_vertical">
<EditText
android:id="@+id/searchEditText"
android:layout_width="0dp"
android:layout_height="48dp"
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" />
<ImageButton
android:id="@+id/searchButton"
android:layout_width="48dp"
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" />
</LinearLayout>
<ProgressBar <ProgressBar
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -38,17 +71,8 @@
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="match_parent"
android:layout_marginTop="8dp" /> android:background="@android:color/white" />
</LinearLayout> </LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/refreshButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="Refresh"
android:src="@android:drawable/ic_popup_sync"
app:tint="@android:color/white" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,44 +1,76 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" 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:padding="12dp" android:orientation="vertical"
android:background="@android:color/white" android:background="@android:color/white">
android:layout_marginBottom="8dp"
android:elevation="2dp">
<TextView <LinearLayout
android:id="@+id/textAmount" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Amount" android:orientation="horizontal"
android:textStyle="bold" android:padding="16dp"
android:textSize="18sp" /> android:gravity="center_vertical">
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status"
android:textColor="#4CAF50"
android:textStyle="bold" />
<!-- Kolom 1: Reference ID -->
<TextView <TextView
android:id="@+id/textReferenceId" android:id="@+id/textReferenceId"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Reference ID" /> android:layout_weight="1"
android:text="ref-eowu3pin"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
<!-- Kolom 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:textSize="14sp"
android:textAlignment="textEnd" />
<!-- Kolom 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: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 <TextView
android:id="@+id/textMerchantName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Merchant Name" /> android:text="Cetak Ulang"
android:textColor="#666666"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
<!-- Bottom Border -->
<View
android:id="@+id/bottomBorder"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e0e0e0" />
<TextView
android:id="@+id/textCreatedAt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Created At" />
</LinearLayout> </LinearLayout>