diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java index a4e5e770..300b7e57 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ProductAdapter.java @@ -1,72 +1,57 @@ package com.example.petstoremobile.adapters; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; +import android.view.*; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.petstoremobile.R; -import com.example.petstoremobile.models.Product; +import com.example.petstoremobile.dtos.ProductDTO; import java.util.List; public class ProductAdapter extends RecyclerView.Adapter { - private List productList; - private OnProductClickListener productClickListener; + private List productList; + private OnProductClickListener listener; - // Interface for product click on recycler view public interface OnProductClickListener { void onProductClick(int position); } - // Constructor - public ProductAdapter(List productList, OnProductClickListener productClickListener) { + public ProductAdapter(List productList, OnProductClickListener listener) { this.productList = productList; - this.productClickListener = productClickListener; + this.listener = listener; } - // Get the controls of each row in recycler view public static class ProductViewHolder extends RecyclerView.ViewHolder { - TextView tvProductName, tvProductDesc, tvCategory, tvProductPrice, tvStockQuantity; + TextView tvName, tvCategory, tvDesc, tvPrice; public ProductViewHolder(@NonNull View v) { super(v); - tvProductName = v.findViewById(R.id.tvProductName); - tvProductDesc = v.findViewById(R.id.tvProductDesc); + tvName = v.findViewById(R.id.tvProductName); tvCategory = v.findViewById(R.id.tvProductCategory); - tvProductPrice = v.findViewById(R.id.tvProductPrice); - tvStockQuantity = v.findViewById(R.id.tvStockQuantity); + tvDesc = v.findViewById(R.id.tvProductDesc); + tvPrice = v.findViewById(R.id.tvProductPrice); } } - // Create a new row view @NonNull @Override public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_product, parent, false); + View v = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_product, parent, false); return new ProductViewHolder(v); } - // Populate the row with product data @Override public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) { - Product product = productList.get(position); - - holder.tvProductName.setText(product.getProductName()); - holder.tvProductDesc.setText(product.getProductDesc()); - holder.tvCategory.setText(product.getCategory()); - holder.tvProductPrice.setText("$" + String.format("%.2f", product.getProductPrice())); - holder.tvStockQuantity.setText("Stock: " + product.getStockQuantity()); - - // When a row is clicked, open the detail view - holder.itemView.setOnClickListener(v -> productClickListener.onProductClick(position)); + ProductDTO p = productList.get(position); + holder.tvName.setText(p.getProdName() != null ? p.getProdName() : ""); + holder.tvCategory.setText("Category: " + (p.getCategoryName() != null ? p.getCategoryName() : "")); + holder.tvDesc.setText(p.getProdDesc() != null ? p.getProdDesc() : ""); + holder.tvPrice.setText(p.getProdPrice() != null ? "$" + p.getProdPrice() : ""); + holder.itemView.setOnClickListener(v -> listener.onProductClick(position)); } @Override - public int getItemCount() { - return productList.size(); - } -} - + public int getItemCount() { return productList.size(); } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java new file mode 100644 index 00000000..422a39e5 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/api/ProductApi.java @@ -0,0 +1,27 @@ +package com.example.petstoremobile.api; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; +import retrofit2.Call; +import retrofit2.http.*; + +public interface ProductApi { + + @GET("api/v1/products") + Call> getAllProducts( + @Query("q") String query, + @Query("page") int page, + @Query("size") int size); + + @GET("api/v1/products/{id}") + Call getProductById(@Path("id") Long id); + + @POST("api/v1/products") + Call createProduct(@Body ProductDTO product); + + @PUT("api/v1/products/{id}") + Call updateProduct(@Path("id") Long id, @Body ProductDTO product); + + @DELETE("api/v1/products/{id}") + Call deleteProduct(@Path("id") Long id); +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java new file mode 100644 index 00000000..2b016b28 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/ProductDTO.java @@ -0,0 +1,81 @@ +package com.example.petstoremobile.dtos; + +import java.math.BigDecimal; + +public class ProductDTO { + private Long prodId; + private String prodName; + private Long categoryId; + private String categoryName; + private String prodDesc; + private BigDecimal prodPrice; + private String createdAt; + private String updatedAt; + + public ProductDTO() { + } + + // Constructor for create/update + public ProductDTO(String prodName, Long categoryId, String prodDesc, BigDecimal prodPrice) { + this.prodName = prodName; + this.categoryId = categoryId; + this.prodDesc = prodDesc; + this.prodPrice = prodPrice; + } + + public Long getProdId() { + return prodId; + } + + public void setProdId(Long prodId) { + this.prodId = prodId; + } + + public String getProdName() { + return prodName; + } + + public void setProdName(String prodName) { + this.prodName = prodName; + } + + public Long getCategoryId() { + return categoryId; + } + + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public String getProdDesc() { + return prodDesc; + } + + public void setProdDesc(String prodDesc) { + this.prodDesc = prodDesc; + } + + public BigDecimal getProdPrice() { + return prodPrice; + } + + public void setProdPrice(BigDecimal prodPrice) { + this.prodPrice = prodPrice; + } + + public String getCreatedAt() { + return createdAt; + } + + public String getUpdatedAt() { + return updatedAt; + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java index 65e14d4b..4e72b6cd 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ProductFragment.java @@ -1,88 +1,87 @@ package com.example.petstoremobile.fragments.listfragments; -// Added search/filter bar to filter products by name or category. -// Added pull-to-refresh using SwipeRefreshLayout. - import android.os.Bundle; +import android.text.*; +import android.util.Log; +import android.view.*; +import android.widget.*; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageButton; - import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.ProductAdapter; +import com.example.petstoremobile.api.RetrofitClient; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.listfragments.detailfragments.ProductDetailFragment; -import com.example.petstoremobile.models.Product; import com.google.android.material.floatingactionbutton.FloatingActionButton; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import retrofit2.*; public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener { - private List productList = new ArrayList<>(); - private List filteredList = new ArrayList<>(); + private List productList = new ArrayList<>(); + private List filteredList = new ArrayList<>(); private ProductAdapter adapter; - private SwipeRefreshLayout swipeRefreshLayout; + private SwipeRefreshLayout swipeRefresh; private EditText etSearch; - private ImageButton hamburger; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_product, container, false); - hamburger = view.findViewById(R.id.btnHamburger); - - loadProductData(); // TODO: Replace with actual API call when backend is ready setupRecyclerView(view); setupSearch(view); setupSwipeRefresh(view); + loadProducts(); - FloatingActionButton fabAddProduct = view.findViewById(R.id.fabAddProduct); - fabAddProduct.setOnClickListener(v -> openProductDetails(-1)); + FloatingActionButton fab = view.findViewById(R.id.fabAddProduct); + fab.setOnClickListener(v -> openDetail(-1)); - //Make the hamburger button open the drawer from listFragment + ImageButton hamburger = view.findViewById(R.id.btnHamburgerProduct); hamburger.setOnClickListener(v -> { - ListFragment listFragment = (ListFragment) getParentFragment(); - //if list fragment is found then use its helper function to open the drawer - if (listFragment != null) { - listFragment.openDrawer(); - } + ListFragment lf = (ListFragment) getParentFragment(); + if (lf != null) lf.openDrawer(); }); return view; } - // Filters products by name, description, or category + private void setupRecyclerView(View view) { + RecyclerView rv = view.findViewById(R.id.recyclerViewProducts); + adapter = new ProductAdapter(filteredList, this); + rv.setLayoutManager(new LinearLayoutManager(getContext())); + rv.setAdapter(adapter); + } + private void setupSearch(View view) { etSearch = view.findViewById(R.id.etSearchProduct); etSearch.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) { - filterProducts(s.toString()); + public void beforeTextChanged(CharSequence s, int a, int b, int c) {} + public void afterTextChanged(Editable s) {} + public void onTextChanged(CharSequence s, int a, int b, int c) { + filter(s.toString()); } - @Override public void afterTextChanged(Editable s) {} }); } - private void filterProducts(String query) { + private void setupSwipeRefresh(View view) { + swipeRefresh = view.findViewById(R.id.swipeRefreshProduct); + swipeRefresh.setOnRefreshListener(this::loadProducts); + } + + private void filter(String query) { filteredList.clear(); if (query.isEmpty()) { filteredList.addAll(productList); } else { String lower = query.toLowerCase(); - for (Product p : productList) { - if (p.getProductName().toLowerCase().contains(lower) - || p.getCategory().toLowerCase().contains(lower) - || p.getProductDesc().toLowerCase().contains(lower)) { + for (ProductDTO p : productList) { + if ((p.getProdName() != null && p.getProdName().toLowerCase().contains(lower)) + || (p.getCategoryName() != null && p.getCategoryName().toLowerCase().contains(lower))) { filteredList.add(p); } } @@ -90,73 +89,45 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc adapter.notifyDataSetChanged(); } - private void setupSwipeRefresh(View view) { - swipeRefreshLayout = view.findViewById(R.id.swipeRefreshProduct); - swipeRefreshLayout.setOnRefreshListener(() -> { - loadProductData(); // TODO: Replace with actual API call - filterProducts(etSearch.getText().toString()); - swipeRefreshLayout.setRefreshing(false); - }); + private void loadProducts() { + if (swipeRefresh != null) swipeRefresh.setRefreshing(true); + RetrofitClient.getProductApi(requireContext()).getAllProducts(null, 0, 100) + .enqueue(new Callback>() { + public void onResponse(Call> c, + Response> r) { + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + if (r.isSuccessful() && r.body() != null) { + productList.clear(); + productList.addAll(r.body().getContent()); + filter(etSearch != null ? etSearch.getText().toString() : ""); + } else { + Toast.makeText(getContext(), "Failed to load products", + Toast.LENGTH_SHORT).show(); + } + } + public void onFailure(Call> c, Throwable t) { + if (swipeRefresh != null) swipeRefresh.setRefreshing(false); + Log.e("ProductFragment", t.getMessage()); + } + }); } - private void openProductDetails(int position) { - ProductDetailFragment detailFragment = new ProductDetailFragment(); + private void openDetail(int position) { + ProductDetailFragment detail = new ProductDetailFragment(); Bundle args = new Bundle(); - args.putInt("position", position); - if (position != -1) { - Product product = filteredList.get(position); - int realPosition = productList.indexOf(product); - args.putInt("position", realPosition); - args.putInt("productId", product.getProductId()); - args.putString("productName", product.getProductName()); - args.putString("productDesc", product.getProductDesc()); - args.putString("category", product.getCategory()); - args.putDouble("productPrice", product.getProductPrice()); - args.putInt("stockQuantity", product.getStockQuantity()); + ProductDTO p = filteredList.get(position); + args.putLong("prodId", p.getProdId()); + args.putString("prodName", p.getProdName()); + args.putString("prodDesc", p.getProdDesc() != null ? p.getProdDesc() : ""); + args.putString("prodPrice", p.getProdPrice() != null ? p.getProdPrice().toString() : ""); + args.putLong("categoryId", p.getCategoryId() != null ? p.getCategoryId() : -1); } - - detailFragment.setArguments(args); - detailFragment.setProductFragment(this); - - ListFragment listFragment = (ListFragment) getParentFragment(); - if (listFragment != null) listFragment.loadFragment(detailFragment); - } - - public void onProductSaved(int position, Product product) { - if (position == -1) { - productList.add(product); - } else { - productList.set(position, product); - } - filterProducts(etSearch.getText().toString()); - } - - public void onProductDeleted(int position) { - productList.remove(position); - filterProducts(etSearch.getText().toString()); + detail.setArguments(args); + ListFragment lf = (ListFragment) getParentFragment(); + if (lf != null) lf.loadFragment(detail); } @Override - public void onProductClick(int position) { - openProductDetails(position); - } - - private void loadProductData() { - productList.clear(); - productList.add(new Product(1, "Premium Dog Food", "High protein dry food for adult dogs", "Food", 45.99, 25)); - productList.add(new Product(2, "Cat Toy Bundle", "Set of 5 interactive toys", "Toys", 19.99, 40)); - productList.add(new Product(3, "Pet Shampoo", "Gentle formula for all breeds", "Grooming", 12.99, 60)); - productList.add(new Product(4, "Dog Bed - Large", "Memory foam orthopedic bed", "Bedding", 89.99, 10)); - productList.add(new Product(5, "Aquarium Starter Kit", "20-gallon tank with filter and light", "Aquatic", 129.99, 5)); - filteredList.clear(); - filteredList.addAll(productList); - } - - private void setupRecyclerView(View view) { - RecyclerView recyclerView = view.findViewById(R.id.recyclerViewProducts); - adapter = new ProductAdapter(filteredList, this); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - recyclerView.setAdapter(adapter); - } -} + public void onProductClick(int position) { openDetail(position); } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java index 4179c4f5..29e3473b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java @@ -1,139 +1,190 @@ package com.example.petstoremobile.fragments.listfragments.detailfragments; -// Uses InputValidator for detailed field validation and ActivityLogger to log all changes. - import android.os.Bundle; +import android.util.Log; +import android.view.*; +import android.widget.*; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; import com.example.petstoremobile.R; +import com.example.petstoremobile.api.*; +import com.example.petstoremobile.dtos.*; import com.example.petstoremobile.fragments.ListFragment; -import com.example.petstoremobile.fragments.listfragments.ProductFragment; -import com.example.petstoremobile.models.Product; -import com.example.petstoremobile.utils.ActivityLogger; -import com.example.petstoremobile.utils.InputValidator; +import java.math.BigDecimal; +import java.util.*; +import retrofit2.*; public class ProductDetailFragment extends Fragment { private TextView tvMode, tvProductId; - private EditText etProductName, etProductDesc, etCategory, etProductPrice, etStockQuantity; - private Button btnSaveProduct, btnDeleteProduct, btnBack; - private int productId; - private int position; - private boolean isEditing = false; - private ProductFragment productFragment; + private EditText etProductName, etProductDesc, etProductPrice; + private Spinner spinnerCategory; + private Button btnSave, btnDelete, btnBack; - // Set the product fragment as parent so we refer back when save or delete is done - public void setProductFragment(ProductFragment fragment) { - this.productFragment = fragment; - } + private long prodId = -1; + private boolean isEditing = false; + private long preselectedCategoryId = -1; + + private List categoryList = new ArrayList<>(); @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_product_detail, container, false); - initViews(view); + loadCategories(); handleArguments(); - btnBack.setOnClickListener(v -> { - ListFragment listFragment = (ListFragment) getParentFragment(); - if (listFragment != null) listFragment.getChildFragmentManager().popBackStack(); - }); - btnSaveProduct.setOnClickListener(v -> saveProduct()); - btnDeleteProduct.setOnClickListener(v -> deleteProduct()); - + btnBack.setOnClickListener(v -> navigateBack()); + btnSave.setOnClickListener(v -> saveProduct()); + btnDelete.setOnClickListener(v -> confirmDelete()); return view; } - // Validates all fields using InputValidator, then saves the product - private void saveProduct() { - if (!InputValidator.isNotEmpty(etProductName, "Product Name")) return; - if (!InputValidator.isNotEmpty(etProductDesc, "Description")) return; - if (!InputValidator.isNotEmpty(etCategory, "Category")) return; - if (!InputValidator.isPositiveDecimal(etProductPrice, "Price")) return; - if (!InputValidator.isPositiveInteger(etStockQuantity, "Stock Quantity")) return; - - String productName = etProductName.getText().toString().trim(); - String productDesc = etProductDesc.getText().toString().trim(); - String category = etCategory.getText().toString().trim(); - double productPrice = Double.parseDouble(etProductPrice.getText().toString().trim()); - int stockQuantity = Integer.parseInt(etStockQuantity.getText().toString().trim()); - - try { - if (isEditing) { - // TODO: Replace with actual API PUT call when backend is ready - Product updated = new Product(productId, productName, productDesc, category, productPrice, stockQuantity); - if (productFragment != null) productFragment.onProductSaved(position, updated); - ActivityLogger.logChange(requireContext(), "Product", "UPDATED", productId); - Toast.makeText(getContext(), "Product updated.", Toast.LENGTH_SHORT).show(); - } else { - // TODO: Replace with actual API POST call when backend is ready - Product newProduct = new Product(0, productName, productDesc, category, productPrice, stockQuantity); - if (productFragment != null) productFragment.onProductSaved(-1, newProduct); - ActivityLogger.log(requireContext(), "Added new Product: " + productName); - Toast.makeText(getContext(), "Product added.", Toast.LENGTH_SHORT).show(); - } - ListFragment listFragment = (ListFragment) getParentFragment(); - if (listFragment != null) listFragment.getChildFragmentManager().popBackStack(); - } catch (Exception e) { - ActivityLogger.logException(requireContext(), "ProductDetailFragment.saveProduct", e); - Toast.makeText(getContext(), "Error saving product.", Toast.LENGTH_SHORT).show(); - } + private void initViews(View v) { + tvMode = v.findViewById(R.id.tvProductMode); + tvProductId = v.findViewById(R.id.tvProductId); + etProductName = v.findViewById(R.id.etProductName); + etProductDesc = v.findViewById(R.id.etProductDesc); + etProductPrice = v.findViewById(R.id.etProductPrice); + spinnerCategory = v.findViewById(R.id.spinnerProductCategory); + btnSave = v.findViewById(R.id.btnSaveProduct); + btnDelete = v.findViewById(R.id.btnDeleteProduct); + btnBack = v.findViewById(R.id.btnProductBack); } - // Deletes the product and logs the action - private void deleteProduct() { - try { - // TODO: Replace with actual API DELETE call when backend is ready - if (productFragment != null) productFragment.onProductDeleted(position); - ActivityLogger.logChange(requireContext(), "Product", "DELETED", productId); - Toast.makeText(getContext(), "Product deleted.", Toast.LENGTH_SHORT).show(); - ListFragment listFragment = (ListFragment) getParentFragment(); - if (listFragment != null) listFragment.getChildFragmentManager().popBackStack(); - } catch (Exception e) { - ActivityLogger.logException(requireContext(), "ProductDetailFragment.deleteProduct", e); - Toast.makeText(getContext(), "Error deleting product.", Toast.LENGTH_SHORT).show(); + private void loadCategories() { + RetrofitClient.getCategoryApi(requireContext()).getAllCategories(0, 100) + .enqueue(new Callback>() { + public void onResponse(Call> c, + Response> r) { + if (r.isSuccessful() && r.body() != null) { + categoryList = r.body().getContent(); + populateCategorySpinner(); + } + } + public void onFailure(Call> c, Throwable t) { + Log.e("ProductDetail", "Category load failed: " + t.getMessage()); + } + }); + } + + private void populateCategorySpinner() { + List names = new ArrayList<>(); + names.add("-- Select Category --"); + for (CategoryDTO c : categoryList) names.add(c.getCategoryName()); + spinnerCategory.setAdapter(new ArrayAdapter<>(requireContext(), + android.R.layout.simple_spinner_item, names)); + if (preselectedCategoryId != -1) { + for (int i = 0; i < categoryList.size(); i++) { + if (categoryList.get(i).getCategoryId().equals(preselectedCategoryId)) { + spinnerCategory.setSelection(i + 1); break; + } + } } } private void handleArguments() { - if (getArguments() != null && getArguments().containsKey("productId")) { + Bundle a = getArguments(); + if (a != null && a.containsKey("prodId")) { isEditing = true; - productId = getArguments().getInt("productId"); - position = getArguments().getInt("position"); + prodId = a.getLong("prodId"); + preselectedCategoryId = a.getLong("categoryId", -1); + tvMode.setText("Edit Product"); - tvProductId.setText("ID: " + productId); - etProductName.setText(getArguments().getString("productName")); - etProductDesc.setText(getArguments().getString("productDesc")); - etCategory.setText(getArguments().getString("category")); - etProductPrice.setText(String.valueOf(getArguments().getDouble("productPrice"))); - etStockQuantity.setText(String.valueOf(getArguments().getInt("stockQuantity"))); - btnDeleteProduct.setVisibility(View.VISIBLE); + tvProductId.setText("ID: " + prodId); + tvProductId.setVisibility(View.VISIBLE); + etProductName.setText(a.getString("prodName")); + etProductDesc.setText(a.getString("prodDesc")); + etProductPrice.setText(a.getString("prodPrice")); + btnDelete.setVisibility(View.VISIBLE); } else { - isEditing = false; tvMode.setText("Add Product"); + btnDelete.setVisibility(View.GONE); tvProductId.setVisibility(View.GONE); - btnDeleteProduct.setVisibility(View.GONE); - btnSaveProduct.setText("Add"); } } - private void initViews(View view) { - tvMode = view.findViewById(R.id.tvProductMode); - tvProductId = view.findViewById(R.id.tvProductId); - etProductName = view.findViewById(R.id.etProductName); - etProductDesc = view.findViewById(R.id.etProductDesc); - etCategory = view.findViewById(R.id.etProductCategory); - etProductPrice = view.findViewById(R.id.etProductPrice); - etStockQuantity = view.findViewById(R.id.etStockQuantity); - btnSaveProduct = view.findViewById(R.id.btnSaveProduct); - btnDeleteProduct = view.findViewById(R.id.btnDeleteProduct); - btnBack = view.findViewById(R.id.btnProductBack); + private void saveProduct() { + String name = etProductName.getText().toString().trim(); + String desc = etProductDesc.getText().toString().trim(); + String priceStr = etProductPrice.getText().toString().trim(); + + if (name.isEmpty()) { + etProductName.setError("Enter product name"); return; + } + if (spinnerCategory.getSelectedItemPosition() == 0) { + Toast.makeText(getContext(), "Select a category", Toast.LENGTH_SHORT).show(); return; + } + if (priceStr.isEmpty()) { + etProductPrice.setError("Enter price"); return; + } + + CategoryDTO category = categoryList.get(spinnerCategory.getSelectedItemPosition() - 1); + BigDecimal price; + try { + price = new BigDecimal(priceStr); + } catch (Exception e) { + etProductPrice.setError("Invalid price"); return; + } + + ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price); + + Log.d("PRODUCT_SAVE", "name=" + name + " categoryId=" + category.getCategoryId() + + " price=" + price); + + ProductApi api = RetrofitClient.getProductApi(requireContext()); + if (isEditing) { + api.updateProduct(prodId, dto).enqueue(simpleCallback("Updated")); + } else { + api.createProduct(dto).enqueue(simpleCallback("Saved")); + } } -} + + private Callback simpleCallback(String msg) { + return new Callback<>() { + public void onResponse(Call c, Response r) { + if (r.isSuccessful()) { + Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); + navigateBack(); + } else { + try { + String err = r.errorBody().string(); + Log.e("PRODUCT_SAVE", "Error: " + err); + Toast.makeText(getContext(), "Error " + r.code(), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + Log.e("PRODUCT_SAVE", "Failed to read error"); + } + } + } + public void onFailure(Call c, Throwable t) { + Log.e("PRODUCT_SAVE", "Failure: " + t.getMessage()); + Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); + } + }; + } + + private void confirmDelete() { + new AlertDialog.Builder(requireContext()) + .setTitle("Delete Product?") + .setPositiveButton("Yes", (d, w) -> + RetrofitClient.getProductApi(requireContext()) + .deleteProduct(prodId) + .enqueue(new Callback() { + public void onResponse(Call c, Response r) { + navigateBack(); + } + public void onFailure(Call c, Throwable t) { + Toast.makeText(getContext(), "Delete failed", + Toast.LENGTH_SHORT).show(); + } + })) + .setNegativeButton("No", null).show(); + } + + private void navigateBack() { + ListFragment lf = (ListFragment) getParentFragment(); + if (lf != null) lf.getChildFragmentManager().popBackStack(); + } +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_product.xml b/android/app/src/main/res/layout/fragment_product.xml index 5afd0352..21c9d9ec 100644 --- a/android/app/src/main/res/layout/fragment_product.xml +++ b/android/app/src/main/res/layout/fragment_product.xml @@ -12,7 +12,6 @@ android:orientation="vertical"> + android:textColor="@color/white"/> @@ -65,6 +64,7 @@ android:layout_gravity="end" android:layout_marginBottom="8dp"/> + + + + + + + - - - - + - - - - + android:layout_marginBottom="8dp"/>