From 739fad458f197333d4ec083609de720e791080f6 Mon Sep 17 00:00:00 2001 From: Ninjdai Date: Fri, 20 Dec 2024 14:23:08 +0100 Subject: [PATCH] feat: owner input and csv export --- .../dev/ninjdai/balscanner/MainActivity.java | 24 ++- .../java/dev/ninjdai/balscanner/books/Book.kt | 4 +- .../balscanner/ui/home/HomeFragment.java | 145 +++++++++++++++++- .../balscanner/ui/home/UsersItemsAdapter.java | 106 +++++++++++++ .../ui/scanner/ScannerFragment.java | 14 +- app/src/main/res/layout/fragment_home.xml | 32 ++-- app/src/main/res/layout/fragment_scanner.xml | 2 +- app/src/main/res/values/strings.xml | 2 + 8 files changed, 302 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/dev/ninjdai/balscanner/ui/home/UsersItemsAdapter.java diff --git a/app/src/main/java/dev/ninjdai/balscanner/MainActivity.java b/app/src/main/java/dev/ninjdai/balscanner/MainActivity.java index d3a785c..591a094 100644 --- a/app/src/main/java/dev/ninjdai/balscanner/MainActivity.java +++ b/app/src/main/java/dev/ninjdai/balscanner/MainActivity.java @@ -1,31 +1,46 @@ package dev.ninjdai.balscanner; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; import com.google.android.material.bottomnavigation.BottomNavigationView; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import dev.ninjdai.balscanner.books.Book; import dev.ninjdai.balscanner.books.Owner; import dev.ninjdai.balscanner.databinding.ActivityMainBinding; +import dev.ninjdai.balscanner.ui.home.UsersItemsAdapter; +import dev.ninjdai.balscanner.ui.scanner.ScannerItemsAdapter; public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; public static ConcurrentHashMap> books = new ConcurrentHashMap<>(); - public static Owner avril = new Owner("Avril", "Papillon"); + public static Owner current_owner = null; @Override protected void onCreate(Bundle savedInstanceState) { @@ -41,8 +56,5 @@ public class MainActivity extends AppCompatActivity { NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupWithNavController(binding.navView, navController); - - books.put(avril, new ArrayList<>()); - books.get(avril).add(new Book("Test book", "Albert", "69420", avril, 0)); } } \ No newline at end of file diff --git a/app/src/main/java/dev/ninjdai/balscanner/books/Book.kt b/app/src/main/java/dev/ninjdai/balscanner/books/Book.kt index 7dac6e7..bfd52b9 100644 --- a/app/src/main/java/dev/ninjdai/balscanner/books/Book.kt +++ b/app/src/main/java/dev/ninjdai/balscanner/books/Book.kt @@ -2,6 +2,6 @@ package dev.ninjdai.balscanner.books data class Book(val title: String, val author: String, val EAN: String, val owner: Owner, var price: Int) -data class Owner(val firstName: String, val lastName: String) { - val id: String = firstName[0].toString() + lastName[0].toString() +data class Owner(var firstName: String, var lastName: String, var contact: String) { + val id: String = firstName[0].toString().uppercase() + lastName[0].toString().uppercase() } \ No newline at end of file diff --git a/app/src/main/java/dev/ninjdai/balscanner/ui/home/HomeFragment.java b/app/src/main/java/dev/ninjdai/balscanner/ui/home/HomeFragment.java index 2fff92e..5ede79a 100644 --- a/app/src/main/java/dev/ninjdai/balscanner/ui/home/HomeFragment.java +++ b/app/src/main/java/dev/ninjdai/balscanner/ui/home/HomeFragment.java @@ -1,20 +1,49 @@ package dev.ninjdai.balscanner.ui.home; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.net.Uri; import android.os.Bundle; +import android.text.InputType; +import android.util.Log; 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 androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.journeyapps.barcodescanner.ScanOptions; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Objects; + +import dev.ninjdai.balscanner.MainActivity; +import dev.ninjdai.balscanner.R; +import dev.ninjdai.balscanner.books.Book; +import dev.ninjdai.balscanner.books.Owner; import dev.ninjdai.balscanner.databinding.FragmentHomeBinding; public class HomeFragment extends Fragment { private FragmentHomeBinding binding; + private UsersItemsAdapter adapter; + private ActivityResultLauncher documentCreator; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -24,14 +53,126 @@ public class HomeFragment extends Fragment { binding = FragmentHomeBinding.inflate(inflater, container, false); View root = binding.getRoot(); - final TextView textView = binding.textHome; - homeViewModel.getText().observe(getViewLifecycleOwner(), textView::setText); return root; } + public void updateItems() { + adapter.notifyItemChanged(MainActivity.books.keySet().size() - 1); + } + + @SuppressLint("NotifyDataSetChanged") + public void addOwner() { + Owner tmpOwner = new Owner("UNK", "UNK", ""); + Activity activity = getActivity(); + if (activity==null) return; + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle("Prénom"); + + final EditText input = new EditText(getActivity()); + input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME); + builder.setView(input); + + builder.setPositiveButton("Suivant", (dialog, which) -> { + tmpOwner.setFirstName(input.getText().toString()); + + AlertDialog.Builder lastBuilder = new AlertDialog.Builder(activity); + lastBuilder.setTitle("Nom de famille"); + final EditText lastInput = new EditText(getActivity()); + input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME); + lastBuilder.setView(lastInput); + + lastBuilder.setPositiveButton("Suivant", (lDialog, lWhich) -> { + tmpOwner.setLastName(lastInput.getText().toString()); + + AlertDialog.Builder cBuilder = new AlertDialog.Builder(activity); + cBuilder.setTitle("Contact"); + final EditText cInput = new EditText(getActivity()); + input.setInputType(InputType.TYPE_CLASS_TEXT); + cBuilder.setView(cInput); + + cBuilder.setPositiveButton("Valider", (cDialog, cWhich) -> { + tmpOwner.setContact(cInput.getText().toString()); + Owner o = new Owner(tmpOwner.getFirstName().strip(), tmpOwner.getLastName().strip(), tmpOwner.getContact().strip()); + MainActivity.books.put(o, new ArrayList<>()); + MainActivity.current_owner = o; + adapter.setmData(new ArrayList<>(MainActivity.books.keySet())); + adapter.notifyDataSetChanged(); + }); + cBuilder.setNegativeButton("Annuler", (cDialog, cWhich) -> cDialog.cancel()); + cBuilder.show(); + }); + lastBuilder.setNegativeButton("Annuler", (lDialog, lWhich) -> lDialog.cancel()); + lastBuilder.show(); + }); + builder.setNegativeButton("Annuler", (dialog, which) -> dialog.cancel()); + builder.show(); + } + + @SuppressLint("NotifyDataSetChanged") + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + RecyclerView recyclerView = view.findViewById(R.id.users_items); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + adapter = new UsersItemsAdapter(getContext(), new ArrayList<>(MainActivity.books.keySet())); + + adapter.setClickListener((v, position) -> { + Owner o = adapter.getItem(position); + Toast.makeText(getContext(), String.format("Sélectionné %s %s", o.getFirstName(), o.getLastName()), Toast.LENGTH_SHORT).show(); + adapter.notifyDataSetChanged(); + MainActivity.current_owner = o; + }); + adapter.setLongClickListener((v, position) -> { + Toast.makeText(getContext(), "Removed " + adapter.getItem(position), Toast.LENGTH_LONG).show(); + adapter.removeItem(position); + adapter.notifyItemRemoved(position); + return true; + }); + recyclerView.setAdapter(adapter); + + documentCreator = registerForActivityResult(new ActivityResultContracts.CreateDocument("text/csv"), this::onDocumentActivityResult); + + Button userButton = view.findViewById(R.id.user_button); + userButton.setOnClickListener(v -> { + addOwner(); + }); + Button exportButton = view.findViewById(R.id.export_button); + exportButton.setOnClickListener(v -> { + documentCreator.launch("export.csv"); + }); + } + @Override public void onDestroyView() { super.onDestroyView(); binding = null; } + + private void onDocumentActivityResult(Uri result) { + if (result == null) { + Toast.makeText(getContext(), "Export annulé", Toast.LENGTH_LONG).show(); + return; + } + try { + OutputStream stream = getActivity().getContentResolver().openOutputStream(result); + if (stream==null) { + Toast.makeText(getContext(), "Erreur lors de l'ouverture du fichier", Toast.LENGTH_LONG).show(); + return; + } + for (Owner owner: MainActivity.books.keySet()) { + for (Book book: Objects.requireNonNull(MainActivity.books.get(owner))) { + @SuppressLint("DefaultLocale") + String line = String.format(";%s;%s;%s;%d;;%s;;%s;%s%n", + owner.getLastName(), owner.getFirstName(), owner.getContact(), book.getPrice(), book.getEAN(), book.getTitle(), book.getAuthor()); + stream.write(line.getBytes(StandardCharsets.UTF_8)); + } + } + stream.flush(); + stream.close(); + } catch (IOException e) { + Log.e("BALScanner", "onDocumentActivityResult", e); + Toast.makeText(getContext(), "Erreur lors de la création du fichier", Toast.LENGTH_LONG).show(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/dev/ninjdai/balscanner/ui/home/UsersItemsAdapter.java b/app/src/main/java/dev/ninjdai/balscanner/ui/home/UsersItemsAdapter.java new file mode 100644 index 0000000..0dedd95 --- /dev/null +++ b/app/src/main/java/dev/ninjdai/balscanner/ui/home/UsersItemsAdapter.java @@ -0,0 +1,106 @@ +package dev.ninjdai.balscanner.ui.home; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import dev.ninjdai.balscanner.MainActivity; +import dev.ninjdai.balscanner.R; +import dev.ninjdai.balscanner.books.Book; +import dev.ninjdai.balscanner.books.Owner; + +public class UsersItemsAdapter extends RecyclerView.Adapter { + + private List mData; + private final LayoutInflater mInflater; + private ItemClickListener mClickListener; + private ItemLongClickListener mLongClickListener; + + // data is passed into the constructor + public UsersItemsAdapter(Context context, List data) { + this.mInflater = LayoutInflater.from(context); + this.mData = data; + } + + public void setmData(List owners) { + mData = owners; + } + + // inflates the row layout from xml when needed + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.text_row_item, parent, false); + return new ViewHolder(view); + } + + // binds the data to the TextView in each row + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + Owner owner = mData.get(position); + holder.myTextView.setText(String.format("%s%s %s (%s) - %s", owner==MainActivity.current_owner ? "> " : "", owner.getFirstName(), owner.getLastName(), owner.getId(), owner.getContact())); + } + + // total number of rows + @Override + public int getItemCount() { + return mData.size(); + } + + + // stores and recycles views as they are scrolled off screen + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + TextView myTextView; + + ViewHolder(View itemView) { + super(itemView); + myTextView = itemView.findViewById(R.id.bookText); + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } + + @Override + public void onClick(View view) { + if (mClickListener != null) mClickListener.onItemClick(view, getAdapterPosition()); + } + + @Override + public boolean onLongClick(View view) { + if (mClickListener != null) return mLongClickListener.onItemLongClick(view, getAdapterPosition()); + return false; + } + } + + // convenience method for getting data at click position + Owner getItem(int id) { + return mData.get(id); + } + + Owner removeItem(int id) { + MainActivity.books.remove(mData.get(id)); + return mData.remove(id); + } + + // allows clicks events to be caught + void setClickListener(ItemClickListener itemClickListener) { + this.mClickListener = itemClickListener; + } + void setLongClickListener(ItemLongClickListener itemLongClickListener) { + this.mLongClickListener = itemLongClickListener; + } + + // parent activity will implement this method to respond to click events + public interface ItemClickListener { + void onItemClick(View view, int position); + } + public interface ItemLongClickListener { + boolean onItemLongClick(View view, int position); + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/ninjdai/balscanner/ui/scanner/ScannerFragment.java b/app/src/main/java/dev/ninjdai/balscanner/ui/scanner/ScannerFragment.java index 98bd5ae..6c145c8 100644 --- a/app/src/main/java/dev/ninjdai/balscanner/ui/scanner/ScannerFragment.java +++ b/app/src/main/java/dev/ninjdai/balscanner/ui/scanner/ScannerFragment.java @@ -53,7 +53,7 @@ public class ScannerFragment extends Fragment { } public void updateItems() { - adapter.notifyItemChanged(MainActivity.books.get(MainActivity.avril).size()-1); + adapter.notifyItemChanged(MainActivity.books.get(MainActivity.current_owner).size()-1); } private void noBookFoundToast() { @@ -73,7 +73,7 @@ public class ScannerFragment extends Fragment { builder.setPositiveButton("Valider", (dialog, which) -> { book.setPrice(Integer.parseInt(input.getText().toString())); - MainActivity.books.computeIfPresent(MainActivity.avril, ((owner, ownerBooks) -> { + MainActivity.books.computeIfPresent(MainActivity.current_owner, ((owner, ownerBooks) -> { ownerBooks.add(book); return ownerBooks; })); @@ -88,9 +88,11 @@ public class ScannerFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - RecyclerView recyclerView = view.findViewById(R.id.scanner_items); + Toast.makeText(getActivity(), String.format("%s %s", MainActivity.current_owner.getFirstName(), MainActivity.current_owner.getLastName()), Toast.LENGTH_SHORT).show(); + + RecyclerView recyclerView = view.findViewById(R.id.users_items); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - adapter = new ScannerItemsAdapter(getContext(), MainActivity.books.get(MainActivity.avril)); + adapter = new ScannerItemsAdapter(getContext(), MainActivity.books.get(MainActivity.current_owner)); adapter.setClickListener((v, position) -> Toast.makeText(getActivity(), "Book: " + adapter.getItem(position), Toast.LENGTH_LONG).show()); adapter.setLongClickListener((v, position) -> { @@ -125,7 +127,7 @@ public class ScannerFragment extends Fragment { builder.setView(input); builder.setPositiveButton("Suivant", (dialog, which) -> { - Book book = new Book(input.getText().toString(), "Inconnu", ean, MainActivity.avril, 0); + Book book = new Book(input.getText().toString().strip(), "Inconnu", ean, MainActivity.current_owner, 0); addBook(book); }); builder.setNegativeButton("Annuler", (dialog, which) -> dialog.cancel()); @@ -143,7 +145,7 @@ public class ScannerFragment extends Fragment { if (authorRes != null) author = authorRes.getString("name"); } } - Book book = new Book(title, author, ean, MainActivity.avril, 0); + Book book = new Book(title, author, ean, MainActivity.current_owner, 0); addBook(book); } catch (JSONException ex) { Log.e("App", "Failure", ex); diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index f3d9b08..7151fca 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -6,17 +6,29 @@ android:layout_height="match_parent" tools:context=".ui.home.HomeFragment"> - + app:layout_constraintEnd_toEndOf="parent" /> + + + +