ticket fomattage
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
package com.example.quiz;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -16,10 +21,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -42,11 +44,9 @@ import com.example.quiz.data.model.MiseInitiale;
|
||||
import com.example.quiz.data.model.Pari;
|
||||
import com.example.quiz.data.model.PariMise;
|
||||
import com.example.quiz.data.model.ParisResponse;
|
||||
import com.example.quiz.data.model.Reunion;
|
||||
|
||||
import com.example.quiz.data.model.TypeOfBet;
|
||||
import com.example.quiz.data.model.dtos.NotifPayload;
|
||||
import com.example.quiz.data.model.dtos.PariCourseDto;
|
||||
import com.example.quiz.data.model.enums.PariStatut;
|
||||
import com.example.quiz.data.remote.StompManager;
|
||||
import com.example.quiz.databinding.FragmentBetValidationBinding;
|
||||
import com.example.quiz.utils.BluetoothUtils;
|
||||
@@ -54,6 +54,7 @@ import com.example.quiz.utils.LoaderDialog;
|
||||
import com.example.quiz.utils.MessageDialog;
|
||||
import com.example.quiz.utils.Result;
|
||||
import com.example.quiz.utils.SharedPrefsHelper;
|
||||
import com.example.quiz.utils.SunmiPrinterManager;
|
||||
import com.example.quiz.viewModel.LogsViewModel;
|
||||
import com.example.quiz.viewModel.PariMiseViewModel;
|
||||
import com.example.quiz.viewModel.PariViewModel;
|
||||
@@ -67,15 +68,17 @@ import com.google.zxing.WriterException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Observable;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -93,8 +96,10 @@ public class BetValidation extends Fragment {
|
||||
|
||||
FragmentBetValidationBinding binding;
|
||||
|
||||
|
||||
SharedViewModel shared;
|
||||
|
||||
|
||||
PariMiseViewModel pariMiseViewModel;
|
||||
|
||||
List<MiseInitiale> misesInitiales;
|
||||
@@ -104,6 +109,8 @@ public class BetValidation extends Fragment {
|
||||
@Inject
|
||||
StompManager stompManager;
|
||||
|
||||
SunmiPrinterManager sunmiPrinterManager;
|
||||
|
||||
private int nombreX;
|
||||
|
||||
LogsViewModel logsViewModel;
|
||||
@@ -113,16 +120,20 @@ public class BetValidation extends Fragment {
|
||||
|
||||
private long mise;
|
||||
|
||||
String mobileName;
|
||||
|
||||
|
||||
private TypeOfBet typeOfBet;
|
||||
|
||||
MutableLiveData<Boolean> isPrinterReady = new MutableLiveData<>(false);
|
||||
|
||||
SharedPrefsHelper prefsHelper;
|
||||
|
||||
|
||||
private Course course;
|
||||
|
||||
LoaderDialog loader;
|
||||
|
||||
LoaderDialog loader;
|
||||
|
||||
|
||||
private int coeff;
|
||||
@@ -135,12 +146,11 @@ public class BetValidation extends Fragment {
|
||||
PariViewModel pariViewModel;
|
||||
|
||||
|
||||
|
||||
public BetValidation() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
public static BetValidation newInstance() {
|
||||
public static BetValidation newInstance() {
|
||||
BetValidation fragment = new BetValidation();
|
||||
Bundle args = new Bundle();
|
||||
fragment.setArguments(args);
|
||||
@@ -151,8 +161,16 @@ public class BetValidation extends Fragment {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
prefsHelper = SharedPrefsHelper.getInstance(getContext());
|
||||
mobileName = Build.MANUFACTURER;
|
||||
sunmiPrinterManager = SunmiPrinterManager.getInstance(requireContext());
|
||||
if(mobileName.toLowerCase().contains("sunmi")){
|
||||
sunmiPrinterManager.connectPrinter(status ->{
|
||||
isPrinterReady.setValue(status);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -161,7 +179,7 @@ public class BetValidation extends Fragment {
|
||||
logsViewModel = new ViewModelProvider(this).get(LogsViewModel.class);
|
||||
binding.coeff.setText(String.valueOf(1));
|
||||
coeff = Integer.parseInt(binding.coeff.getText().toString());
|
||||
binding.coeff.addTextChangedListener(new TextWatcher(){
|
||||
binding.coeff.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
@@ -169,11 +187,11 @@ public class BetValidation extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
if(charSequence.toString().isEmpty()){
|
||||
if (charSequence.toString().isEmpty()) {
|
||||
binding.coeff.setError("Le coefficient est obligatoire");
|
||||
return;
|
||||
}
|
||||
if(Integer.parseInt(charSequence.toString())<1){
|
||||
if (Integer.parseInt(charSequence.toString()) < 1) {
|
||||
binding.coeff.setError("Le coefficient doit être supérieur ou égal à 1 ");
|
||||
return;
|
||||
}
|
||||
@@ -194,7 +212,7 @@ public class BetValidation extends Fragment {
|
||||
binding.gridNumbers.removeAllViews();
|
||||
int columns = 8;
|
||||
grid.setColumnCount(columns);
|
||||
if(shared.selectedCourse.getValue() != null){
|
||||
if (shared.selectedCourse.getValue() != null) {
|
||||
for (int i = 1; i <= shared.selectedCourse.getValue().getNombrePartants(); i++) {
|
||||
createNumberItem(String.valueOf(i));
|
||||
grid.addView(createNumberItem(String.valueOf(i)));
|
||||
@@ -203,7 +221,6 @@ public class BetValidation extends Fragment {
|
||||
createNumberItem("X");
|
||||
grid.addView(createNumberItem("X"));
|
||||
}
|
||||
|
||||
private TextView createNumberItem(String horse) {
|
||||
TextView textView = new TextView(requireContext());
|
||||
textView.setText(horse);
|
||||
@@ -215,53 +232,53 @@ public class BetValidation extends Fragment {
|
||||
textView.setWidth(80);
|
||||
textView.setHeight(100);
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
textView.setBackgroundResource(Objects.equals(horse, "X") ?R.drawable.x_background: R.drawable.number_background);
|
||||
if(!Objects.equals(horse, "X") && shared.selectedCourse.getValue().getNonPartants() != null && shared.selectedCourse.getValue().getNonPartants().contains(Integer.valueOf(horse))){
|
||||
textView.setBackgroundResource(Objects.equals(horse, "X") ? R.drawable.x_background : R.drawable.number_background);
|
||||
if (!Objects.equals(horse, "X") && shared.selectedCourse.getValue().getNonPartants() != null && shared.selectedCourse.getValue().getNonPartants().contains(Integer.valueOf(horse))) {
|
||||
textView.setTextColor(getResources().getColor(R.color.white));
|
||||
textView.setBackgroundResource(R.drawable.number_background_grey);
|
||||
}
|
||||
textView.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
textView.setOnClickListener(v -> {
|
||||
if(!Objects.equals(horse, "X") && shared.selectedCourse.getValue().getNonPartants() != null && shared.selectedCourse.getValue().getNonPartants().contains(Integer.valueOf(horse))){
|
||||
if (!Objects.equals(horse, "X") && shared.selectedCourse.getValue().getNonPartants() != null && shared.selectedCourse.getValue().getNonPartants().contains(Integer.valueOf(horse))) {
|
||||
Toast.makeText(getContext(), "Ce cheval n'est pas partant", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final List<String> horses = new ArrayList<>(selectedHorses.getValue());
|
||||
if(horses.size() >= shared.typeOfBet.getValue().getNumberOfHorse() && horse.equals("X")){
|
||||
Toast.makeText(getContext(), "Vous ne pouvez plus sélectionner X", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if(_notClickable(horses) && horse.equals("X")){
|
||||
Toast.makeText(getContext(), "Vous ne pouvez plus sélectionner X", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (horses.contains(horse) && !horse.equals("X")) {
|
||||
horses.remove(horse);
|
||||
selectedHorses.setValue(horses);
|
||||
v.setSelected(false);
|
||||
v.setBackgroundResource(R.drawable.number_background);
|
||||
} else {
|
||||
horses.add(horse);
|
||||
selectedHorses.setValue(horses);
|
||||
v.setSelected(true);
|
||||
v.setBackgroundResource(R.drawable.number_selected_background);
|
||||
}
|
||||
String combinationText = selectedHorses.getValue().stream()
|
||||
.map(h -> h)
|
||||
.collect(Collectors.joining("-"));
|
||||
binding.combination.setText( combinationText);
|
||||
});
|
||||
if (horses.size() >= shared.typeOfBet.getValue().getNumberOfHorse() && horse.equals("X")) {
|
||||
Toast.makeText(getContext(), "Vous ne pouvez plus sélectionner X", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (_notClickable(horses) && horse.equals("X")) {
|
||||
Toast.makeText(getContext(), "Vous ne pouvez plus sélectionner X", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (horses.contains(horse) && !horse.equals("X")) {
|
||||
horses.remove(horse);
|
||||
selectedHorses.setValue(horses);
|
||||
v.setSelected(false);
|
||||
v.setBackgroundResource(R.drawable.number_background);
|
||||
} else {
|
||||
horses.add(horse);
|
||||
selectedHorses.setValue(horses);
|
||||
v.setSelected(true);
|
||||
v.setBackgroundResource(R.drawable.number_selected_background);
|
||||
}
|
||||
String combinationText = selectedHorses.getValue().stream()
|
||||
.map(h -> h)
|
||||
.collect(Collectors.joining("-"));
|
||||
binding.combination.setText(combinationText);
|
||||
});
|
||||
return textView;
|
||||
}
|
||||
|
||||
|
||||
boolean _notClickable(List<String> selectedHorses){
|
||||
boolean _notClickable(List<String> selectedHorses) {
|
||||
int numberOfElement = 0;
|
||||
if(shared.typeOfBet.getValue().getNumberOfHorse() < 2){
|
||||
if (shared.typeOfBet.getValue().getNumberOfHorse() < 2) {
|
||||
return true;
|
||||
}
|
||||
for(String horse: selectedHorses){
|
||||
numberOfElement = horse.equals("X")?numberOfElement+1:numberOfElement;
|
||||
for (String horse : selectedHorses) {
|
||||
numberOfElement = horse.equals("X") ? numberOfElement + 1 : numberOfElement;
|
||||
}
|
||||
nombreX = numberOfElement;
|
||||
return numberOfElement == shared.typeOfBet.getValue().getNumberOfHorse() - 1;
|
||||
@@ -274,8 +291,8 @@ public class BetValidation extends Fragment {
|
||||
pariMiseViewModel = new ViewModelProvider(this).get(PariMiseViewModel.class);
|
||||
pariMiseViewModel.getBetInitMise().observe(
|
||||
getViewLifecycleOwner(),
|
||||
result->{
|
||||
switch (result.status){
|
||||
result -> {
|
||||
switch (result.status) {
|
||||
case LOADING: {
|
||||
loader.show("Chargement des mise");
|
||||
break;
|
||||
@@ -294,12 +311,13 @@ public class BetValidation extends Fragment {
|
||||
}
|
||||
}
|
||||
);
|
||||
if(shared.selectedCourse.getValue() != null){
|
||||
stompManager.subscribe("courses/"+shared.selectedCourse.getValue().getId(), json->{
|
||||
Type type = new TypeToken<NotifPayload<Course>>(){}.getType();
|
||||
if (shared.selectedCourse.getValue() != null) {
|
||||
stompManager.subscribe("courses/" + shared.selectedCourse.getValue().getId(), json -> {
|
||||
Type type = new TypeToken<NotifPayload<Course>>() {
|
||||
}.getType();
|
||||
NotifPayload<Course> notif = new Gson().fromJson(json, type);
|
||||
Course updatedCourse = notif.getPayload();
|
||||
if(shared.selectedCourse.getValue().getId() == updatedCourse.getId()){
|
||||
if (shared.selectedCourse.getValue().getId() == updatedCourse.getId()) {
|
||||
shared.setSelectedCourse(updatedCourse);
|
||||
setupNumberGrid(binding.gridNumbers);
|
||||
}
|
||||
@@ -310,12 +328,12 @@ public class BetValidation extends Fragment {
|
||||
typeOfBet = shared.typeOfBet.getValue();
|
||||
course = shared.selectedCourse.getValue();
|
||||
AppCompatActivity activity = (AppCompatActivity) getActivity();
|
||||
if(activity != null){
|
||||
if (activity != null) {
|
||||
MaterialToolbar toolbar = activity.findViewById(R.id.toolbar);
|
||||
toolbar.setBackgroundColor( getResources().getColor(R.color.primary_green));
|
||||
toolbar.setBackgroundColor(getResources().getColor(R.color.primary_green));
|
||||
activity.setSupportActionBar(toolbar);
|
||||
if(activity.getSupportActionBar() != null){
|
||||
activity.getSupportActionBar().setTitle("Pari "+shared.selectedCourse.getValue().getNom());
|
||||
if (activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar().setTitle("Pari " + shared.selectedCourse.getValue().getNom());
|
||||
}
|
||||
}
|
||||
setupNumberGrid(binding.gridNumbers);
|
||||
@@ -342,28 +360,28 @@ public class BetValidation extends Fragment {
|
||||
@Override
|
||||
public void onChanged(List<String> horses) {
|
||||
calculateMise(horses);
|
||||
if(shared.typeOfBet.getValue().getNumberOfHorse() > 2 && horses.size() >= shared.typeOfBet.getValue().getNumberOfHorse()){
|
||||
if (shared.typeOfBet.getValue().getNumberOfHorse() > 2 && horses.size() >= shared.typeOfBet.getValue().getNumberOfHorse()) {
|
||||
binding.order.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
} else {
|
||||
binding.order.setVisibility(View.GONE);
|
||||
}
|
||||
if(horses.contains("X")){
|
||||
if(selectedHorses.getValue().size() == shared.typeOfBet.getValue().getNumberOfHorse()){
|
||||
if (horses.contains("X")) {
|
||||
if (selectedHorses.getValue().size() == shared.typeOfBet.getValue().getNumberOfHorse()) {
|
||||
binding.elargie.setVisibility(View.VISIBLE);
|
||||
binding.elargie.setText("Champ total");
|
||||
return;
|
||||
}else{
|
||||
if(selectedHorses.getValue().size() > shared.typeOfBet.getValue().getNumberOfHorse()){
|
||||
} else {
|
||||
if (selectedHorses.getValue().size() > shared.typeOfBet.getValue().getNumberOfHorse()) {
|
||||
binding.elargie.setVisibility(View.VISIBLE);
|
||||
binding.elargie.setText("Champ partiel");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(horses.size() > shared.typeOfBet.getValue().getNumberOfHorse()){
|
||||
if (horses.size() > shared.typeOfBet.getValue().getNumberOfHorse()) {
|
||||
binding.elargie.setVisibility(View.VISIBLE);
|
||||
binding.elargie.setText("Elargi");
|
||||
}else{
|
||||
} else {
|
||||
binding.elargie.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
@@ -373,48 +391,24 @@ public class BetValidation extends Fragment {
|
||||
calculateMise(selectedHorses.getValue());
|
||||
});
|
||||
|
||||
binding.betValidateBtn.setOnClickListener(v->{
|
||||
// Log.d("PAPER_STATUS", String.valueOf(ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT))+" "+String.valueOf(PackageManager.PERMISSION_GRANTED));
|
||||
// if (ActivityCompat.checkSelfPermission(getContext(), android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
// != PackageManager.PERMISSION_GRANTED) {
|
||||
// // TODO: Consider calling
|
||||
// // ActivityCompat#requestPermissions
|
||||
// // here to request the missing permissions, and then overriding
|
||||
// // public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
||||
// // int[] grantResults)
|
||||
// // to handle the case where the user grants the permission. See the documentation
|
||||
// // for ActivityCompat#requestPermissions for more details.
|
||||
// return;
|
||||
// }
|
||||
// int paperStatus = Printama.with(getContext()).checkPaperStatus();
|
||||
// switch (paperStatus){
|
||||
// case 2 :{
|
||||
// MessageDialog.showError(getContext(), "Le papier d'impression est vide");
|
||||
// return;
|
||||
// }
|
||||
// case 1:{
|
||||
// MessageDialog.showError(getContext(), "Le papier d'impression est presque vide");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
if(binding.paymentType.getSelectedItem().toString().equals("Orange Money") && binding.phoneNumber.getText().toString().isEmpty()){
|
||||
binding.betValidateBtn.setOnClickListener(v -> {
|
||||
if (binding.paymentType.getSelectedItem().toString().equals("Orange Money") && binding.phoneNumber.getText().toString().isEmpty()) {
|
||||
MessageDialog.showError(getContext(), "Veuillez saisir le numéro de téléphone");
|
||||
return;
|
||||
}
|
||||
|
||||
if(shared.typeOfBet == null || shared.selectedCourse == null){
|
||||
if (shared.typeOfBet == null || shared.selectedCourse == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
int required = typeOfBet.getNumberOfHorse();
|
||||
if(selectedHorses.getValue().size() < required){
|
||||
Toast.makeText(getContext(), "Veuillez sélectionner au moins"+required+" chevaux", Toast.LENGTH_SHORT).show();
|
||||
if (Objects.requireNonNull(selectedHorses.getValue()).size() < required) {
|
||||
Toast.makeText(getContext(), "Veuillez sélectionner au moins" + required + " chevaux", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.mise == 0){
|
||||
if (this.mise == 0) {
|
||||
Toast.makeText(getContext(), "Pari non valide", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
@@ -432,61 +426,63 @@ public class BetValidation extends Fragment {
|
||||
"XOF",
|
||||
"Pari"
|
||||
);
|
||||
if (dialog != null && dialog.isShowing()) {
|
||||
return;
|
||||
}
|
||||
_showPariDialog(pari);
|
||||
});
|
||||
|
||||
binding.backBtn.setOnClickListener(v->{
|
||||
binding.backBtn.setOnClickListener(v -> {
|
||||
getActivity().onBackPressed();
|
||||
});
|
||||
|
||||
binding.deleteBtn.setOnClickListener(v->{
|
||||
binding.deleteBtn.setOnClickListener(v -> {
|
||||
_initializeToZero();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
String _getCombinaison(){
|
||||
if(selectedHorses.getValue().contains("X") && shared.typeOfBet.getValue().getNumberOfHorse() < selectedHorses.getValue().size()){
|
||||
String _getCombinaison() {
|
||||
if (selectedHorses.getValue().contains("X") && shared.typeOfBet.getValue().getNumberOfHorse() < selectedHorses.getValue().size()) {
|
||||
String first = selectedHorses.getValue().subList(0, shared.typeOfBet.getValue().getNumberOfHorse()).stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(","));
|
||||
String last = selectedHorses.getValue().subList(shared.typeOfBet.getValue().getNumberOfHorse(), selectedHorses.getValue().size()).stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(","));
|
||||
return first+",R,"+last;
|
||||
}else{
|
||||
return first + ",R," + last;
|
||||
} else {
|
||||
return selectedHorses.getValue().stream().map(String::valueOf).collect(Collectors.joining(","));
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _getFormule(){
|
||||
List<String> _getFormule() {
|
||||
List<String> combinaison = selectedHorses.getValue();
|
||||
if(!combinaison.contains("X")){
|
||||
if(!order){
|
||||
return List.of("UNITAIRE");
|
||||
}else{
|
||||
if (!combinaison.contains("X")) {
|
||||
if (!order) {
|
||||
return List.of("UNITAIRE");
|
||||
} else {
|
||||
return List.of("FORMULE_COMPLETE");
|
||||
}
|
||||
}else{
|
||||
if(shared.typeOfBet.getValue().getNumberOfHorse() < selectedHorses.getValue().size()){
|
||||
if(order){
|
||||
} else {
|
||||
if (shared.typeOfBet.getValue().getNumberOfHorse() < selectedHorses.getValue().size()) {
|
||||
if (order) {
|
||||
return List.of("CHAMP_X", "FORMULE_COMPLETE");
|
||||
}else{
|
||||
} else {
|
||||
return List.of("CHAMP_X");
|
||||
}
|
||||
}else{
|
||||
if(order){
|
||||
} else {
|
||||
if (order) {
|
||||
return List.of("CHAMP_TOTAL", "FORMULE_COMPLETE");
|
||||
}else{
|
||||
} else {
|
||||
return List.of("CHAMP_TOTAL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setSelectedTypeOfBetImage(){
|
||||
switch (shared.typeOfBet.getValue().getName()){
|
||||
private void setSelectedTypeOfBetImage() {
|
||||
switch (shared.typeOfBet.getValue().getName()) {
|
||||
case COUPLE_PLACE:
|
||||
binding.icTypeOfBet.setImageResource(R.drawable.ic_couple_place);
|
||||
break;
|
||||
@@ -506,34 +502,41 @@ public class BetValidation extends Fragment {
|
||||
}
|
||||
|
||||
public void printPari(ParisResponse pari) throws WriterException {
|
||||
try {
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pmu_logo);
|
||||
Bitmap barcode = generateBarcodeBitmap(pari.getNumeroTicket(), 384, 100);
|
||||
StringBuilder tspl = new StringBuilder();
|
||||
Printama printama = Printama.with(getContext());
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.pmu_logo);
|
||||
Bitmap barcode = generateBarcodeBitmap(pari.getNumeroTicket(), 384, 100);
|
||||
StringBuilder tspl = new StringBuilder();
|
||||
tspl.append("Bamako").append("\n");
|
||||
tspl.append(shared.selectedCourse.getValue().getNom()).append("\n");
|
||||
OffsetDateTime dateTime = OffsetDateTime.parse(shared.selectedCourse.getValue().getHeureDepartPrevue());
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
|
||||
String formattedDate = dateTime.format(formatter);
|
||||
tspl.append(formattedDate).append("\n");
|
||||
tspl.append("Course ").append(String.valueOf(shared.selectedCourse.getValue().getId())).append("\n");
|
||||
tspl.append(sunmiPrinterManager.separationText()+ "\n");
|
||||
boolean isElargie = selectedHorses.getValue().size() > shared.typeOfBet.getValue().getNumberOfHorse();
|
||||
String typePari = pari.getTypesParisMises().get(0).getTypePari();
|
||||
tspl.append(isElargie ? typePari + "/Elargie" : typePari).append("\n");
|
||||
tspl.append(order ? "COMBINAISON COMPLETE" + "\n" : "");
|
||||
String combinationText = formatLineWithNumbers(selectedHorses.getValue().stream().map(String::valueOf).toArray(String[]::new), "-") ;
|
||||
tspl.append(combinationText).append("\n");
|
||||
tspl.append("COEF: ").append(String.valueOf(coeff)).append(".0");
|
||||
tspl.append("\n").append(sunmiPrinterManager.separationText()).append("\n");
|
||||
tspl.append("MONTANT: ").append(pari.getTypesParisMises().get(0).getMiseTotale()).append(" XOF");
|
||||
tspl.append("\n").append(sunmiPrinterManager.separationText()).append("\n");
|
||||
tspl.append("AGENT: ").append(prefsHelper.get("code")).append("\n");
|
||||
tspl.append("DATE: ").append(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss").format(LocalDateTime.now())).append("\n");
|
||||
if(mobileName.toLowerCase().contains("sunmi")){
|
||||
String pariText = tspl.toString();
|
||||
if(!sunmiPrinterManager.printPari(resizeToPrinterWidth(bitmap, 384), barcode, pari.getNumeroTicket(), pariText)){
|
||||
MessageDialog.showError(getContext(), "Erreur d'impression");
|
||||
prefsHelper.save("noPrintId", String.valueOf(pari.getId()));
|
||||
return;
|
||||
};
|
||||
_initializeToZero();
|
||||
return;
|
||||
}
|
||||
|
||||
tspl.append("Bamako").append("\n");
|
||||
tspl.append(shared.selectedCourse.getValue().getNom()).append("\n");
|
||||
OffsetDateTime dateTime = OffsetDateTime.parse(shared.selectedCourse.getValue().getHeureDepartPrevue());
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
|
||||
String formattedDate = dateTime.format(formatter);
|
||||
tspl.append(formattedDate).append("\n");
|
||||
tspl.append("Course ").append(String.valueOf(shared.selectedCourse.getValue().getId())).append("\n");
|
||||
tspl.append(printama.lineSeparator()+"\n");
|
||||
boolean isElargie = selectedHorses.getValue().size()>shared.typeOfBet.getValue().getNumberOfHorse();
|
||||
String typePari = pari.getTypesParisMises().get(0).getTypePari();
|
||||
tspl.append(isElargie?typePari+"/Elargie":typePari).append("\n");
|
||||
tspl.append(order?"COMBINAISON COMPLETE"+"\n":"");
|
||||
String combinationText = selectedHorses.getValue().stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining("-"));
|
||||
tspl.append(combinationText).append("\n");
|
||||
tspl.append("COEF: ").append(String.valueOf(coeff)).append(".0");
|
||||
tspl.append("\n").append(printama.lineSeparator()).append("\n");
|
||||
tspl.append("MONTANT: ").append(pari.getTypesParisMises().get(0).getMiseTotale()).append(" XOF");
|
||||
tspl.append("\n").append(printama.lineSeparator()).append("\n");
|
||||
tspl.append("AGENT: ").append(prefsHelper.get("code")).append("\n");
|
||||
tspl.append("DATE: ").append(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss").format(LocalDateTime.now())).append("\n");
|
||||
try {
|
||||
if (BluetoothUtils.needsBluetoothPermissions()) {
|
||||
if (!BluetoothUtils.hasBluetoothPermission(requireContext())) {
|
||||
// Demande la permission si non accordée
|
||||
@@ -544,8 +547,28 @@ public class BetValidation extends Fragment {
|
||||
|
||||
// 2️⃣ Permission OK, on peut afficher la liste
|
||||
|
||||
Printama.with(getContext()).printTextBuilder(tspl, bitmap, pari.getNumeroTicket(), barcode);
|
||||
_initializeToZero();
|
||||
Printama.with(getContext()).printTextBuilder(tspl, bitmap, pari.getNumeroTicket(), barcode, new Printama.PrintCallback() {
|
||||
@Override
|
||||
public void onResult(boolean success, String errorMessage) {
|
||||
if (!success) {
|
||||
new android.app.AlertDialog.Builder(getContext())
|
||||
.setTitle("Impréssion pari")
|
||||
.setMessage("Voulez-vous rééimprimer ce ticket?")
|
||||
.setPositiveButton("Oui", (dialog, which) -> {
|
||||
try {
|
||||
printPari(pari);
|
||||
} catch (WriterException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Non", (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
});
|
||||
} else {
|
||||
_initializeToZero();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (SecurityException e) {
|
||||
Toast.makeText(requireContext(),
|
||||
"Permission Bluetooth non accordée", Toast.LENGTH_SHORT).show();
|
||||
@@ -553,36 +576,103 @@ public class BetValidation extends Fragment {
|
||||
}
|
||||
|
||||
|
||||
public String formatLineWithNumbers(String[] numbers, String separator) {
|
||||
StringBuilder currentLine = new StringBuilder();
|
||||
StringBuilder finalOutput = new StringBuilder();
|
||||
List<String> formatted = new ArrayList<>();
|
||||
int requiredHorse = shared.typeOfBet.getValue().getNumberOfHorse();
|
||||
if(Arrays.stream(numbers).map(String::valueOf).collect(Collectors.toList()).contains("X")){
|
||||
if(numbers.length <= requiredHorse){
|
||||
return Arrays.stream(numbers).map(String::valueOf).collect(Collectors.joining("-")).toString();
|
||||
}
|
||||
List<String> firstPart = Arrays.stream(numbers).limit(requiredHorse).map(String::valueOf).collect(Collectors.toList());
|
||||
List<String> secondPart = Arrays.stream(numbers).skip(requiredHorse).map(String::valueOf).collect(Collectors.toList());
|
||||
formatted.addAll(firstPart);
|
||||
formatted.add("R");
|
||||
formatted.addAll(secondPart);
|
||||
}else{
|
||||
formatted = Arrays.stream(numbers).map(String::valueOf).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
for (String number : formatted) {
|
||||
String element = (currentLine.length() == 0 ? "":separator) + number;
|
||||
|
||||
// Si l'ajout dépasse la largeur max, on termine la ligne et on recommence
|
||||
if (currentLine.length() + element.length() > sunmiPrinterManager.getMaxChar()) {
|
||||
finalOutput.append(currentLine.toString()).append("\n");
|
||||
currentLine = new StringBuilder(number);
|
||||
} else {
|
||||
currentLine.append(element);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLine.length() > 0) {
|
||||
finalOutput.append(currentLine.toString());
|
||||
}
|
||||
|
||||
return finalOutput.toString();
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint({"MissingInflatedId", "SetTextI18n"})
|
||||
void _showPariDialog(Pari pari){
|
||||
void _showPariDialog(Pari pari) {
|
||||
LayoutInflater inflater = getLayoutInflater();
|
||||
View view = inflater.inflate(R.layout.pari_confirmation, null);
|
||||
TextView numero_course = (TextView) view.findViewById(R.id.alert_course_numero);
|
||||
numero_course.setText("Numero Course: "+String.valueOf(shared.selectedCourse.getValue().getId()));
|
||||
numero_course.setText("Numero Course: " + String.valueOf(shared.selectedCourse.getValue().getId()));
|
||||
TextView alert_pari_type = (TextView) view.findViewById(R.id.alert_pari_type);
|
||||
alert_pari_type.setText(String.valueOf(shared.typeOfBet.getValue().getName()));
|
||||
TextView is_elargie = (TextView) view.findViewById(R.id.alert_is_elargie);
|
||||
is_elargie.setText(selectedHorses.getValue().size() > shared.typeOfBet.getValue().getNumberOfHorse()?"CE":"SI");
|
||||
is_elargie.setText(selectedHorses.getValue().size() > shared.typeOfBet.getValue().getNumberOfHorse() ? "CE" : "SI");
|
||||
TextView alert_combinaison = (TextView) view.findViewById(R.id.alert_combinaison);
|
||||
alert_combinaison.setText(selectedHorses.getValue().stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining("-")));
|
||||
TextView aler_coeff = (TextView) view.findViewById(R.id.alert_coeff);
|
||||
aler_coeff.setText("Coef:"+String.valueOf(coeff));
|
||||
aler_coeff.setText("Coef:" + String.valueOf(coeff));
|
||||
TextView alert_montant = (TextView) view.findViewById(R.id.alert_montant);
|
||||
alert_montant.setText("Montant: "+String.valueOf(pari.getTypesParisMises().get(0).getMiseTotale())+" CFA");
|
||||
alert_montant.setText("Montant: " + String.valueOf(pari.getTypesParisMises().get(0).getMiseTotale()) + " CFA");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
builder.setView(view);
|
||||
builder.setCancelable(false);
|
||||
|
||||
dialog = builder.create();
|
||||
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
|
||||
view.findViewById(R.id.alert_validate).setOnClickListener(v->{
|
||||
pariViewModel.createPari(pari).observe(getViewLifecycleOwner(),new Observer<Result<ParisResponse>>() {
|
||||
view.findViewById(R.id.alert_validate).setOnClickListener(v -> {
|
||||
if(mobileName.toLowerCase().contains("sunmi")){
|
||||
if(isPrinterReady.getValue() != null && !isPrinterReady.getValue()){
|
||||
sunmiPrinterManager.connectPrinter(status ->{
|
||||
isPrinterReady.setValue(status);
|
||||
});
|
||||
}
|
||||
switch (sunmiPrinterManager.printerStatus()){
|
||||
case 2:{
|
||||
MessageDialog.showError(getContext(), "L'imprimante n'est pas connectée!");
|
||||
return;
|
||||
}
|
||||
case 3:{
|
||||
MessageDialog.showError(getContext(), "L'imprimante n'est pas disponible!");
|
||||
return;
|
||||
}
|
||||
case 4:{
|
||||
MessageDialog.showError(getContext(), "Veuillez insérer du papier dans l'imprimante, SVP!");
|
||||
return;
|
||||
}
|
||||
case 5: {
|
||||
MessageDialog.showError(getContext(), "Suchauffe iminante de l'imprimante, Veuillez laisser reposer!");
|
||||
return;
|
||||
}
|
||||
case 6: {
|
||||
MessageDialog.showError(getContext(), "Le capot est ouvert, veuillez fermer SVP!");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
pariViewModel.createPari(pari).observe(getViewLifecycleOwner(), new Observer<Result<ParisResponse>>() {
|
||||
@Override
|
||||
public void onChanged(Result<ParisResponse> pariResult) {
|
||||
switch (pariResult.status){
|
||||
switch (pariResult.status) {
|
||||
case ERROR:
|
||||
loader.dismiss();
|
||||
dialog.dismiss();
|
||||
@@ -594,7 +684,7 @@ public class BetValidation extends Fragment {
|
||||
case SUCCESS:
|
||||
try {
|
||||
loader.dismiss();
|
||||
logsViewModel.insertLog(prefsHelper.get("id"), "BET", "Création du pari "+pariResult.data.getNumeroTicket()+", type de paris: "+pariResult.data.getTypesParisMises().get(0).getTypePari()+", combinaison:"+selectedHorses.getValue().stream().map(String::valueOf).collect(Collectors.joining("-")), System.currentTimeMillis());
|
||||
logsViewModel.insertLog(prefsHelper.get("id"), "BET", "Création du pari " + pariResult.data.getNumeroTicket() + ", type de paris: " + pariResult.data.getTypesParisMises().get(0).getTypePari() + ", combinaison:" + selectedHorses.getValue().stream().map(String::valueOf).collect(Collectors.joining("-")), System.currentTimeMillis());
|
||||
printPari(pariResult.data);
|
||||
dialog.dismiss();
|
||||
MessageDialog.showSuccess(getContext(), "Pari créé avec succès");
|
||||
@@ -614,7 +704,7 @@ public class BetValidation extends Fragment {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
void _initializeToZero(){
|
||||
void _initializeToZero() {
|
||||
selectedHorses.setValue(List.of());
|
||||
binding.combination.setText("");
|
||||
binding.order.setChecked(false);
|
||||
@@ -625,29 +715,29 @@ public class BetValidation extends Fragment {
|
||||
setupNumberGrid(binding.gridNumbers);
|
||||
}
|
||||
|
||||
public void calculateMise(List<String> selectedHorses){
|
||||
public void calculateMise(List<String> selectedHorses) {
|
||||
_notClickable(selectedHorses);
|
||||
int nombreChevauxSelectionnes = selectedHorses.size();
|
||||
long mise = 0;
|
||||
int nonPartants = 0;
|
||||
if(shared.typeOfBet.getValue() == null || shared.selectedCourse.getValue() == null){
|
||||
if (shared.typeOfBet.getValue() == null || shared.selectedCourse.getValue() == null) {
|
||||
return;
|
||||
}
|
||||
if(shared.selectedCourse.getValue().getNonPartants() != null){
|
||||
if (shared.selectedCourse.getValue().getNonPartants() != null) {
|
||||
nonPartants = shared.selectedCourse.getValue().getNonPartants().size();
|
||||
}
|
||||
int typeOfBetHorses = shared.typeOfBet.getValue().getNumberOfHorse();
|
||||
int partants = shared.selectedCourse.getValue().getNombrePartants();
|
||||
Course.TypeParis courseName = shared.typeOfBet.getValue().getName();
|
||||
if(shared.typeOfBet.getValue().getNumberOfHorse() > nombreChevauxSelectionnes){
|
||||
binding.mise.setText(mise+" CFA");
|
||||
if (shared.typeOfBet.getValue().getNumberOfHorse() > nombreChevauxSelectionnes) {
|
||||
binding.mise.setText(mise + " CFA");
|
||||
return;
|
||||
}
|
||||
MiseInitiale miseModel = misesInitiales.stream()
|
||||
.filter(miseInitiale-> miseInitiale.getTypePari().equals(courseName))
|
||||
.filter(miseInitiale -> miseInitiale.getTypePari().equals(courseName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(miseModel == null){
|
||||
if (miseModel == null) {
|
||||
MessageDialog.showError(getContext(), "Erreur lors de l'initialisation de la mise");
|
||||
_initializeToZero();
|
||||
return;
|
||||
@@ -662,7 +752,7 @@ public class BetValidation extends Fragment {
|
||||
// Pour COUPLE_GAGNANT ou COUPLE_PLACE: coefficient < 200
|
||||
if (coeff > 200) {
|
||||
// Erreur: coefficient trop élevé
|
||||
MessageDialog.showError(getContext(),"Le coefficient doit être au plus 200 pour "+typePari.toString());
|
||||
MessageDialog.showError(getContext(), "Le coefficient doit être au plus 200 pour " + typePari.toString());
|
||||
_initializeToZero();
|
||||
return;
|
||||
}
|
||||
@@ -670,58 +760,103 @@ public class BetValidation extends Fragment {
|
||||
// Pour les autres types: coefficient <= 20
|
||||
if (coeff > 20) {
|
||||
// Erreur: coefficient trop élevé
|
||||
MessageDialog.showError(getContext(), "Le coefficient doit être inférieur ou égal à 20 pour "+typePari.toString());
|
||||
MessageDialog.showError(getContext(), "Le coefficient doit être inférieur ou égal à 20 pour " + typePari.toString());
|
||||
_initializeToZero();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mise = mise * coeff;
|
||||
if(nombreX>0){
|
||||
if(nombreChevauxSelectionnes == typeOfBetHorses){
|
||||
mise = mise * _calculateArrangement((partants - (typeOfBetHorses-nombreX) - nonPartants), nombreX);
|
||||
}else{
|
||||
if(nombreChevauxSelectionnes - typeOfBetHorses < nombreX || nombreChevauxSelectionnes - typeOfBetHorses==1){
|
||||
if (nombreX > 0) {
|
||||
if (nombreChevauxSelectionnes == typeOfBetHorses) {
|
||||
mise = mise * _calculateArrangement((partants - (typeOfBetHorses - nombreX) - nonPartants), nombreX);
|
||||
} else {
|
||||
if (nombreChevauxSelectionnes - typeOfBetHorses < nombreX || nombreChevauxSelectionnes - typeOfBetHorses == 1) {
|
||||
mise = 0;
|
||||
this.mise = mise;
|
||||
binding.mise.setText(mise+" CFA");
|
||||
binding.mise.setText(mise + " CFA");
|
||||
return;
|
||||
}
|
||||
mise = mise * _calculateArrangement(nombreChevauxSelectionnes - typeOfBetHorses , nombreX);
|
||||
mise = mise * _calculateArrangement(nombreChevauxSelectionnes - typeOfBetHorses, nombreX);
|
||||
}
|
||||
if(order){
|
||||
mise = mise * _calculateArrangement(typeOfBetHorses, (typeOfBetHorses-nombreX));
|
||||
if (order) {
|
||||
mise = mise * _calculateArrangement(typeOfBetHorses, (typeOfBetHorses - nombreX));
|
||||
}
|
||||
this.mise = mise;
|
||||
binding.mise.setText(mise+" CFA");
|
||||
binding.mise.setText(mise + " CFA");
|
||||
return;
|
||||
}
|
||||
if(!order){
|
||||
if (!order) {
|
||||
mise = mise * _calculateCombinaison(nombreChevauxSelectionnes, typeOfBetHorses);
|
||||
}else{
|
||||
} else {
|
||||
mise = mise * _calculateArrangement(nombreChevauxSelectionnes, typeOfBetHorses);
|
||||
}
|
||||
this.mise = mise;
|
||||
binding.mise.setText(mise+" CFA");
|
||||
binding.mise.setText(mise + " CFA");
|
||||
}
|
||||
|
||||
Long _calculateArrangement(int n, int k){
|
||||
return _calculateFactorial(n) / _calculateFactorial(n-k);
|
||||
Long _calculateArrangement(int n, int k) {
|
||||
return _calculateFactorial(n) / _calculateFactorial(n - k);
|
||||
}
|
||||
|
||||
Long _calculateCombinaison(int n, int k){
|
||||
Long _calculateCombinaison(int n, int k) {
|
||||
return _calculateFactorial(n) / (_calculateFactorial(k) * _calculateFactorial(n - k));
|
||||
}
|
||||
|
||||
Long _calculateFactorial(int n){
|
||||
Long _calculateFactorial(int n) {
|
||||
long f = 1;
|
||||
if(n == 0){
|
||||
return f;
|
||||
if (n == 0) {
|
||||
return f;
|
||||
}
|
||||
for(int i = 1; i <=n; i++){
|
||||
for (int i = 1; i <= n; i++) {
|
||||
f *= i;
|
||||
}
|
||||
return f;
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
private Bitmap resizeToPrinterWidth(Bitmap originalBitmap, int printerWidthPx) {
|
||||
int originalWidth = originalBitmap.getWidth();
|
||||
int originalHeight = originalBitmap.getHeight();
|
||||
int newHeight = (originalHeight * printerWidthPx) / originalWidth;
|
||||
|
||||
// 1. Redimensionner sans filtre (conserve les contours nets)
|
||||
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap,
|
||||
printerWidthPx,
|
||||
newHeight,
|
||||
false);
|
||||
|
||||
// 2. Créer un bitmap ARGB_8888 (meilleure qualité que RGB_565)
|
||||
Bitmap result = Bitmap.createBitmap(printerWidthPx, newHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(result);
|
||||
canvas.drawColor(Color.WHITE);
|
||||
|
||||
// 3. Dessiner avec un Paint qui préserve les couleurs
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(false); // Pas d'anti-aliasing (évite le flou)
|
||||
canvas.drawBitmap(scaledBitmap, 0, 0, paint);
|
||||
|
||||
// 4. Seuillage intelligent pour garder les détails
|
||||
for (int x = 0; x < result.getWidth(); x++) {
|
||||
for (int y = 0; y < result.getHeight(); y++) {
|
||||
int pixel = result.getPixel(x, y);
|
||||
int r = Color.red(pixel);
|
||||
int g = Color.green(pixel);
|
||||
int b = Color.blue(pixel);
|
||||
|
||||
// Calculer la luminosité
|
||||
int gray = (r + g + b) / 3;
|
||||
|
||||
// Seuil adaptatif : si c'est sombre, deviens noir
|
||||
if (gray < 130) { // Seuil à 200 pour garder les gris clairs
|
||||
result.setPixel(x, y, Color.BLACK);
|
||||
} else {
|
||||
result.setPixel(x, y, Color.WHITE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -735,6 +870,7 @@ public class BetValidation extends Fragment {
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
public static String generate12Digits() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
|
||||
Reference in New Issue
Block a user