feat: initial functional release of Stock Pignon

This commit is contained in:
2026-01-14 18:42:53 +01:00
commit 4dcb0b1b5a
106 changed files with 2147 additions and 0 deletions

View File

@@ -0,0 +1,304 @@
package com.stock.pignon;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.GridLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
/**
* Main Activity: Central Dashboard for the application.
* Manages category navigation and item selection within a single screen.
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
// UI Components
private LinearLayout cartList;
private LinearLayout homeLayout;
private LinearLayout categoryItemsLayout;
private LinearLayout categoriesLayout;
private GridLayout gridPieces;
private ImageView mainImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Hide Android bar
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
// View Binding
cartList = findViewById(R.id.cartList);
homeLayout = findViewById(R.id.homeLayout);
categoryItemsLayout = findViewById(R.id.categoryItemsLayout);
categoriesLayout = findViewById(R.id.categoriesLayout);
gridPieces = findViewById(R.id.gridPieces);
mainImage = findViewById(R.id.mainImage);
// Setup Back Button
Button backBtn = findViewById(R.id.backToHomeBtn);
if (backBtn != null) {
backBtn.setOnClickListener(v -> showHome());
}
// Copy assets to sd card if not founded
copyAssetsIfEmpty();
// Get data from sd card
DataLoader.loadData();
// Initial UI Setup
setupCategoriesMenu();
loadMainImage();
// Initial cart visual sync
CartViewHelper.updateCartView(cartList, this);
}
/**
* Builds the left sidebar with category buttons using DataLoader's cache.
*/
private void setupCategoriesMenu() {
List<Category> categories = DataLoader.getCategories();
if (categories == null || categories.isEmpty()) {
Log.w(TAG, "No categories found to display.");
return;
}
categoriesLayout.removeAllViews();
for (Category category : categories) {
Button btn = createCategoryButton(category);
// On click, we show the items for this specific category
btn.setOnClickListener(v -> showCategory(category.getName()));
categoriesLayout.addView(btn);
}
}
/**
* Styles category buttons based on the new Category getters.
*/
private Button createCategoryButton(Category category) {
Button btn = new Button(this);
btn.setText(category.getName());
btn.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
float density = getResources().getDisplayMetrics().density;
float radiusPx = 3 * density;
// Apply background color with transparency (70% alpha)
if (category.getBgColor() != null) {
try {
int color = Color.parseColor(category.getBgColor());
int alpha = (int) (0.7 * 255);
color = (color & 0x00FFFFFF) | (alpha << 24);
GradientDrawable shape = new GradientDrawable();
shape.setColor(color);
shape.setCornerRadius(radiusPx);
btn.setBackground(shape);
} catch (Exception e) {
Log.e(TAG, "Error parsing bgColor for: " + category.getName());
}
}
// Apply text color
if (category.getTextColor() != null) {
try {
btn.setTextColor(Color.parseColor(category.getTextColor()));
} catch (Exception e) {
Log.e(TAG, "Error parsing textColor for: " + category.getName());
}
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 5, 0, 5);
btn.setLayoutParams(params);
return btn;
}
/**
* Switches center view to display the grid of items.
* Uses DataLoader.getItemsForCategory() to get the merged list.
*/
private void showCategory(String categoryName) {
homeLayout.setVisibility(View.GONE);
categoryItemsLayout.setVisibility(View.VISIBLE);
gridPieces.removeAllViews();
// Calculate item width for a 4-column grid
int gridWidth = getResources().getDisplayMetrics().widthPixels - dpToPx(370);
int itemWidth = gridWidth / 4;
// Fetch the PRE-MERGED list (globals + specifics) from DataLoader
List<Item> itemsToDisplay = DataLoader.getItemsForCategory(categoryName);
for (Item item : itemsToDisplay) {
View itemView = LayoutInflater.from(this).inflate(R.layout.item_piece, gridPieces, false);
// Apply width to the inflated view
GridLayout.LayoutParams params = (GridLayout.LayoutParams) itemView.getLayoutParams();
if (params == null) {
params = new GridLayout.LayoutParams();
}
params.width = itemWidth;
itemView.setLayoutParams(params);
bindItemToView(itemView, item);
gridPieces.addView(itemView);
}
}
/**
* Connects Item data to the inflated layout piece.
*/
private void bindItemToView(View itemView, Item item) {
TextView nom = itemView.findViewById(R.id.itemName);
TextView prix = itemView.findViewById(R.id.itemPrice);
ImageView img = itemView.findViewById(R.id.itemImage);
TextView qtyView = itemView.findViewById(R.id.qtyView);
nom.setText(item.getName());
prix.setText(getString(R.string.price_range, item.getMinPrice(), item.getMaxPrice(), ""));
// Use your existing ImageLoader helper
ImageLoader.loadImage(img, item.getImage(),200,200);
// Sync visual quantity with CartManager
CartItem existing = CartManager.getItemByName(item.getName());
int currentQty = (existing != null) ? existing.getQuantity() : 0;
qtyView.setText(String.valueOf(currentQty));
// Listeners for + and - buttons
itemView.findViewById(R.id.plusBtn).setOnClickListener(v -> updateItemQty(item, qtyView, 1));
itemView.findViewById(R.id.minusBtn).setOnClickListener(v -> updateItemQty(item, qtyView, -1));
}
/**
* Updates the local counter and the global cart state simultaneously.
*/
private void updateItemQty(Item item, TextView qtyView, int delta) {
int current = Integer.parseInt(qtyView.getText().toString());
int next = Math.max(0, current + delta);
qtyView.setText(String.valueOf(next));
// Central data update
CartManager.addOrUpdateItem(item.getName(), item.getMinPrice(), item.getMaxPrice(), next, item.getImage());
// Refresh sidebar cart list
CartViewHelper.updateCartView(cartList, this);
}
private void showHome() {
categoryItemsLayout.setVisibility(View.GONE);
homeLayout.setVisibility(View.VISIBLE);
}
private void loadMainImage() {
// Fallback for home screen image
ImageLoader.loadImage(mainImage, "_velo", 800,800);
}
// --- Button Actions (linked via android:onClick in XML) ---
public void emptyCart(View view) {
CartActionHelper.emptyCart(cartList, this);
}
public void validateCart(View view) {
CartActionHelper.validateCart(cartList, this);
}
private int dpToPx(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
@Override
protected void onResume() {
super.onResume();
// Ensure cart is up to date if returning to the activity
CartViewHelper.updateCartView(cartList, this);
}
private void copyAssetsIfEmpty() {
File folder = new File(Environment.getExternalStorageDirectory(), Config.EXTERNAL_DIR_NAME);
// First safety : is folder already in sdcard ?
if (!folder.exists()) {
// Second safety : are we able to create folder ?
if (folder.mkdirs()) {
// Copy JSON file
copyFileFromAssets(Config.PIECES_FILE_NAME, new File(folder, Config.PIECES_FILE_NAME));
// Copy images subfolder
File imgFolder = new File(folder, Config.IMAGES_SUBDIR_NAME);
if (imgFolder.mkdirs()) {
copyFolderFromAssets(Config.IMAGES_SUBDIR_NAME, imgFolder);
}
}
}
}
private void copyFolderFromAssets(String assetDirName, File destDir) {
try {
String[] files = getAssets().list(assetDirName);
if (files != null) {
// For every image
for (String fileName : files) {
// Create source path
String assetPath = assetDirName + "/" + fileName;
// Create dest path
File destFile = new File(destDir, fileName);
// Copy
copyFileFromAssets(assetPath, destFile);
}
}
} catch (IOException e) {
Log.e("MainActivity", "Erreur listing assets: " + assetDirName, e);
}
}
private void copyFileFromAssets(String assetName, File destFile) {
// Optimized read
// Try-with-resources ensures streams are automatically closed, avoid memory leaks
try (InputStream in = getAssets().open(assetName);
OutputStream out = new FileOutputStream(destFile)) {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
Log.d("MainActivity", "Succès : " + assetName + " copié.");
} catch (IOException e) {
Log.e("MainActivity", "Erreur copie asset: " + assetName, e);
}
}
}