Version avec integration slave master

This commit is contained in:
OnlyPapy98
2026-04-01 19:51:19 +02:00
parent acc5ec1b70
commit 4eaca7e1d8
66 changed files with 3229 additions and 218 deletions

View File

@@ -0,0 +1,94 @@
package com.example.quiz.data.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.quiz.R;
import com.example.quiz.data.model.dtos.auth.User;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class AgentItemAdapter extends RecyclerView.Adapter<AgentItemAdapter.AgentItemViewHolder> {
private List<User> agents = new ArrayList<>();
public AgentItemAdapter(){
}
public AgentItemAdapter(List<User> agents) {
this.agents = agents;
}
public interface onItemClickListener{
void onItemClick(User agent);
}
private onItemClickListener listener;
public void setOnItemClickListener(onItemClickListener listener){
this.listener = listener;
}
static class AgentItemViewHolder extends RecyclerView.ViewHolder{
TextView txtCode, txtDate, txtAdresse;
ImageButton details;
public AgentItemViewHolder(@NonNull View itemView) {
super(itemView);
txtCode = itemView.findViewById(R.id.txtCode);
txtDate = itemView.findViewById(R.id.txtDate);
txtAdresse = itemView.findViewById(R.id.txtAdresse);
details = itemView.findViewById(R.id.details);
}
}
@Override
public int getItemCount() {
return agents.size();
}
@NonNull
@Override
public AgentItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.agent_item, parent, false);
return new AgentItemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull AgentItemViewHolder holder, int position) {
User agent = agents.get(position);
holder.txtCode.setText(agent.getCode());
if(agent.getDateEmbauche() == null){
holder.txtDate.setText("Pas de date");
}else{
LocalDate date = LocalDate.parse(agent.getDateEmbauche());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
holder.txtDate.setText(dateTimeFormatter.format(date));
}
if(agent.getVille() == null || agent.getAdresse() == null){
holder.txtAdresse.setText("Pas d'adresse");
}else{
String address = agent.getVille()+"; "+agent.getAdresse();
holder.txtAdresse.setText(address);
}
holder.details.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(listener != null){
listener.onItemClick(agent);
}
}
});
}
}

View File

@@ -0,0 +1,149 @@
package com.example.quiz.data.adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.quiz.R;
import com.example.quiz.data.model.Course;
import com.example.quiz.data.model.TypeOfBet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MultiTypeOfBetsAdapter extends RecyclerView.Adapter<MultiTypeOfBetsAdapter.TypeOfBetViewHolder> {
private List<TypeOfBet> types;
private Set<Integer> selectedPositions = new HashSet<>();
private onItemClickListener listener;
public interface onItemClickListener {
void onItemClick(TypeOfBet type, boolean isChecked);
void onItemsSelected(List<TypeOfBet> selectedItems);
}
public void setOnItemClickListener(onItemClickListener listener) {
this.listener = listener;
}
public MultiTypeOfBetsAdapter(List<TypeOfBet> types) {
this.types = types;
}
// Méthode pour pré-sélectionner les paris (rendue publique)
public void preSelectAvailableBets(List<Course.TypeParis> preselectedTypes) {
selectedPositions.clear();
for (int i = 0; i < types.size(); i++) {
if (preselectedTypes.contains(types.get(i).getName())) {
selectedPositions.add(i);
}
}
notifyDataSetChanged();
}
public void setTypes(List<TypeOfBet> types) {
this.types = types;
selectedPositions.clear();
notifyDataSetChanged();
}
@NonNull
@Override
public TypeOfBetViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.multi_type_of_bet_item, parent, false);
return new TypeOfBetViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TypeOfBetViewHolder holder, int position) {
TypeOfBet type = types.get(position);
holder.type_of_bet_name.setText(type.getLabel());
boolean isSelected = selectedPositions.contains(position);
holder.checkBox.setChecked(isSelected);
if (isSelected) {
holder.itemView.setBackgroundResource(R.drawable.item_gradient_bg_selected);
} else {
holder.itemView.setBackgroundResource(R.drawable.item_gradient_bg);
}
holder.itemView.setOnClickListener(v -> {
toggleSelection(position, type);
});
holder.checkBox.setOnClickListener(v -> {
toggleSelection(position, type);
});
}
private void toggleSelection(int position, TypeOfBet type) {
boolean isCurrentlySelected = selectedPositions.contains(position);
if (isCurrentlySelected) {
selectedPositions.remove(position);
} else {
selectedPositions.add(position);
}
if (listener != null) {
listener.onItemClick(type, !isCurrentlySelected);
}
notifyItemChanged(position);
}
@Override
public int getItemCount() {
return types.size();
}
public List<TypeOfBet> getSelectedItems() {
List<TypeOfBet> selectedItems = new ArrayList<>();
for (Integer position : selectedPositions) {
if (position >= 0 && position < types.size()) {
selectedItems.add(types.get(position));
}
}
return selectedItems;
}
public boolean isSelected(int position) {
return selectedPositions.contains(position);
}
public void selectAll() {
selectedPositions.clear();
for (int i = 0; i < types.size(); i++) {
selectedPositions.add(i);
}
notifyDataSetChanged();
}
public void deselectAll() {
selectedPositions.clear();
notifyDataSetChanged();
}
public int getSelectedCount() {
return selectedPositions.size();
}
public class TypeOfBetViewHolder extends RecyclerView.ViewHolder {
TextView type_of_bet_name;
CheckBox checkBox;
public TypeOfBetViewHolder(@NonNull View itemView) {
super(itemView);
type_of_bet_name = itemView.findViewById(R.id.tv_bet_name);
checkBox = itemView.findViewById(R.id.checkbox_bet);
}
}
}

View File

@@ -1,6 +1,5 @@
package com.example.quiz.data.model;
import com.example.quiz.data.model.enums.CourseType;
import java.time.LocalDate;
import java.time.LocalDateTime;

View File

@@ -0,0 +1,49 @@
package com.example.quiz.data.model;
public class MiseInitiale {
private Long id;
private Course.TypeParis typePari;
private Long miseInitialeMin;
private Long miseInitialeMax;
private boolean actif;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Course.TypeParis getTypePari() {
return typePari;
}
public void setTypePari(Course.TypeParis typePari) {
this.typePari = typePari;
}
public Long getMiseInitialeMin() {
return miseInitialeMin;
}
public void setMiseInitialeMin(Long miseInitialeMin) {
this.miseInitialeMin = miseInitialeMin;
}
public Long getMiseInitialeMax() {
return miseInitialeMax;
}
public void setMiseInitialeMax(Long miseInitialeMax) {
this.miseInitialeMax = miseInitialeMax;
}
public boolean isActif() {
return actif;
}
public void setActif(boolean actif) {
this.actif = actif;
}
}

View File

@@ -0,0 +1,17 @@
package com.example.quiz.data.model;
import java.util.List;
public class Restriction {
private List<Course.TypeParis> allowedBetTypes;
public Restriction(){}
public List<Course.TypeParis> getAllowedBetTypes() {
return allowedBetTypes;
}
public void setAllowedBetTypes(List<Course.TypeParis> allowedBetTypes) {
this.allowedBetTypes = allowedBetTypes;
}
}

View File

@@ -0,0 +1,52 @@
package com.example.quiz.data.model.dtos;
public class NotifPayload<T> {
private NotifType type;
private String entity;
private Long entityId;
private T payload;
public NotifPayload() {
}
public NotifType getType() {
return type;
}
public void setType(NotifType type) {
this.type = type;
}
public String getEntity() {
return entity;
}
public void setEntity(String entity) {
this.entity = entity;
}
public Long getEntityId() {
return entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
public T getPayload() {
return payload;
}
public void setPayload(T payload) {
this.payload = payload;
}
public enum NotifType {
COURSE_CREATED,
COURSE_UPDATED,
COURSE_CANCELLED,
COURSE_REPORTED,
COURSE_CLOSED_FOR_BETTING,
RUNNER_DECLARED_NON_PARTANT
}
}

View File

@@ -8,6 +8,8 @@ public class Agent {
private String phone;
private String zone;
private String fonction;
private boolean subAgent;
public Agent(){}
public Long getId() {
@@ -65,4 +67,12 @@ public class Agent {
public void setFonction(String fonction) {
this.fonction = fonction;
}
public boolean isSubAgent() {
return subAgent;
}
public void setSubAgent(boolean subAgent) {
this.subAgent = subAgent;
}
}

View File

@@ -0,0 +1,25 @@
package com.example.quiz.data.model.dtos.auth;
public class ChangePin {
private String oldPin;
private String newPin;
public ChangePin() {
}
public String getOldPin() {
return oldPin;
}
public void setOldPin(String oldPin) {
this.oldPin = oldPin;
}
public String getNewPin() {
return newPin;
}
public void setNewPin(String newPin) {
this.newPin = newPin;
}
}

View File

@@ -0,0 +1,17 @@
package com.example.quiz.data.model.dtos.paris;
public class CancelParisPaylaod {
private String statutPari;
public CancelParisPaylaod(String statutPari) {
this.statutPari = statutPari;
}
public String getStatutPari() {
return statutPari;
}
public void setStatutPari(String statutPari) {
this.statutPari = statutPari;
}
}

View File

@@ -0,0 +1,51 @@
package com.example.quiz.data.model.dtos.paris;
public class SoldeResponse {
private int montantParis;
private int nombrePaiements;
private int montantPaiements;
private int nombreAnnulations;
private int montantAnnulations;
// Getters et setters
public int getMontantParis() {
return montantParis;
}
public void setMontantParis(int montantParis) {
this.montantParis = montantParis;
}
public int getNombrePaiements() {
return nombrePaiements;
}
public void setNombrePaiements(int nombrePaiements) {
this.nombrePaiements = nombrePaiements;
}
public int getMontantPaiements() {
return montantPaiements;
}
public void setMontantPaiements(int montantPaiements) {
this.montantPaiements = montantPaiements;
}
public int getNombreAnnulations() {
return nombreAnnulations;
}
public void setNombreAnnulations(int nombreAnnulations) {
this.nombreAnnulations = nombreAnnulations;
}
public int getMontantAnnulations() {
return montantAnnulations;
}
public void setMontantAnnulations(int montantAnnulations) {
this.montantAnnulations = montantAnnulations;
}
}

View File

@@ -1,7 +0,0 @@
package com.example.quiz.data.model.enums;
public enum CourseType {
TIERCE,
QUARTE,
QUINTE
}

View File

@@ -1,21 +1,29 @@
package com.example.quiz.data.remote;
import com.example.quiz.data.model.Course;
import com.example.quiz.data.model.MiseInitiale;
import com.example.quiz.data.model.PagedModel;
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.PointDeVente;
import com.example.quiz.data.model.Restriction;
import com.example.quiz.data.model.Reunion;
import com.example.quiz.data.model.Tpe;
import com.example.quiz.data.model.dtos.auth.ChangePin;
import com.example.quiz.data.model.dtos.auth.LoginPayload;
import com.example.quiz.data.model.dtos.auth.LoginResponse;
import com.example.quiz.data.model.TpeResponse;
import com.example.quiz.data.model.dtos.auth.User;
import com.example.quiz.data.model.dtos.paris.CancelParisPaylaod;
import com.example.quiz.data.model.dtos.paris.SoldeResponse;
import java.util.List;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -26,7 +34,7 @@ public interface ApiService {
Call<List<Reunion>> getReunions(@Path("date") String date);
@GET("courses")
Call<PagedModel<Course>> getCourses(@Query("reunionDate") String reunionDate);
Call<PagedModel<Course>> getCourses(@Query("reunionDate") String reunionDate, @Query("statut") String statut);
@POST("paris")
Call<ParisResponse> createPari(@Body Pari pari);
@@ -37,18 +45,39 @@ public interface ApiService {
@GET("paris/agent/{agentId}/today")
Call<List<ParisResponse>> derniersParis(@Path("agentId") String agentId);
@PUT("pari/annuler/{numeroTicket}")
Call<ParisResponse> annulerPari(@Path("numeroTicket") String numeroTicket);
@PATCH("paris/numero/{numeroTicket}/statut")
Call<ParisResponse> annulerPari(@Path("numeroTicket") String numeroTicket,
@Body() CancelParisPaylaod cancelParisPaylaod);
@GET("paris/agent/{agentId}/solde/course/{courseId}")
Call<Double> getSoldeByCourse(@Path("agentId") String createdBy, @Path("courseId") String courseId);
Call<SoldeResponse> getSoldeByCourse(@Path("agentId") String createdBy, @Path("courseId") String courseId);
@GET("paris/agent/{agentId}/solde")
Call<Double> getSoldeByDay(@Path("agentId") String agentId, @Query("date") String day);
Call<SoldeResponse> getSoldeByDay(@Path("agentId") String agentId, @Query("date") String day);
@POST("terminaux")
Call<TpeResponse> createTpe(@Body Tpe tpe);
@GET("points-de-vente")
Call<PagedModel<PointDeVente>> getPointsDeVente(@Query("nom") String nom);
@PATCH("agents/me/pin")
Call<User> changePin(@Body ChangePin pin);
@GET("config/mise-initiale")
Call<List<MiseInitiale>> getBetInitMise();
@GET("agents/byMaster/{masterId}")
Call<List<User>> getAgentsByMaster(@Path("masterId") String masterId);
@GET("agents/{agentId}")
Call<User> getAgent(@Path("agentId") String agentId);
@GET("agents/{agentId}/available-bets")
Call<List<Course.TypeParis>> getAvailableBets(@Path("agentId") String agentId);
@PATCH("agents/{masterId}/slaves/{slaveId}/access")
Call<Void> setAccess(@Path("masterId") String masterId, @Path("slaveId") String slaveId, @Query("block") boolean access);
@POST("agents/{masterId}/slaves/{slaveId}/available-bets")
Call<Void> setRestrictions(@Path("masterId") String masterId, @Path("slaveId") String slaveId, @Body Restriction restrictions);
}

View File

@@ -0,0 +1,57 @@
package com.example.quiz.data.remote;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.example.quiz.R;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.hilt.android.qualifiers.ApplicationContext;
@Singleton
public class NotificationHelper {
private final Context context;
private static final String CHANNEL_ID = "socket_channel";
@Inject
public NotificationHelper(@ApplicationContext Context context) {
this.context = context;
createChannel();
}
private void createChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Socket Notifications",
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription("Notifications des messages du socket");
NotificationManager manager =
context.getSystemService(NotificationManager.class);
if (manager != null) manager.createNotificationChannel(channel);
}
}
@SuppressLint("MissingPermission")
public void showNotification(String title, String message) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
manager.notify((int) System.currentTimeMillis(), builder.build());
}
}

View File

@@ -1,85 +0,0 @@
package com.example.quiz.data.remote;
import android.util.Log;
import javax.inject.Inject;
import javax.inject.Singleton;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
@Singleton
public class SocketManager {
private final OkHttpClient client;
private final TokenManager tokenManager;
private WebSocket webSocket;
private boolean isConnected = false;
@Inject
public SocketManager(OkHttpClient client, TokenManager tokenManager) {
this.client = client;
this.tokenManager = tokenManager;
}
public synchronized void connect() {
String token = tokenManager.getToken();
if (token == null) {
Log.d("SOCKET", "Token null → no connection");
return;
}
if (isConnected) return;
Request request = new Request.Builder()
.url("ws://boxer-adapting-bluegill.ngrok-free.app/ws")
.addHeader("Authorization", "Bearer " + token)
.build();
webSocket = client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
isConnected = true;
Log.d("SOCKET", "Connected");
}
@Override
public void onMessage(WebSocket webSocket, String text) {
Log.d("SOCKET", "Message: " + text);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
isConnected = false;
Log.e("SOCKET", "Error: " + t.getMessage());
// tentative de reconnexion
reconnect();
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
isConnected = false;
}
});
}
public synchronized void reconnect() {
disconnect();
connect();
}
public synchronized void disconnect() {
if (webSocket != null) {
webSocket.close(1000, "Closing");
webSocket = null;
}
isConnected = false;
}
}

View File

@@ -0,0 +1,285 @@
package com.example.quiz.data.remote;
import android.util.Log;
import com.example.quiz.data.model.Course;
import com.example.quiz.data.model.dtos.NotifPayload;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import ua.naiksoftware.stomp.Stomp;
import ua.naiksoftware.stomp.StompClient;
import ua.naiksoftware.stomp.dto.StompHeader;
@Singleton
public class StompManager {
private final TokenManager tokenManager;
private final NotificationHelper notificationHelper;
private StompClient stompClient;
private boolean isConnected = false;
// Map topic -> liste de listeners
private final Map<String, List<Consumer<String>>> topicListeners = new HashMap<>();
// Gestion des Disposables RxJava
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
// Map pour stocker les subscriptions par topic
private final Map<String, Disposable> topicSubscriptions = new HashMap<>();
@Inject
public StompManager(TokenManager tokenManager,
NotificationHelper notificationHelper) {
this.tokenManager = tokenManager;
this.notificationHelper = notificationHelper;
}
// -------------------- Connexion --------------------
public synchronized void connect() {
if (isConnected) return;
String token = tokenManager.getToken();
if (token == null) {
Log.e("STOMP", "No token available");
return;
}
// URL de connexion WebSocket
String url = "wss://boxer-adapting-bluegill.ngrok-free.app/ws";
try {
// Créer le client STOMP
stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, url);
// Ajouter les headers d'authentification
List<StompHeader> headers = new ArrayList<>();
headers.add(new StompHeader("Authorization", "Bearer " + token));
// Observer la connexion et stocker le Disposable
Disposable lifecycleDisposable = stompClient.lifecycle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(lifecycleEvent -> {
switch (lifecycleEvent.getType()) {
case OPENED:
isConnected = true;
Log.d("STOMP", "Connected to WebSocket");
resubscribeAll();
break;
case CLOSED:
isConnected = false;
Log.d("STOMP", "Disconnected from WebSocket");
break;
case ERROR:
isConnected = false;
Log.e("STOMP", "Error: " + lifecycleEvent.getException());
reconnect();
break;
}
}, throwable -> {
Log.e("STOMP", "Lifecycle error", throwable);
isConnected = false;
reconnect();
});
compositeDisposable.add(lifecycleDisposable);
// Se connecter
stompClient.connect(headers);
} catch (Exception e) {
Log.e("STOMP", "Connection error", e);
reconnect();
}
}
// -------------------- Souscription aux topics --------------------
public synchronized void subscribe(String topic, Consumer<String> listener) {
// Ajouter le listener
topicListeners.computeIfAbsent(topic, k -> new ArrayList<>()).add(listener);
// Si déjà connecté, souscrire immédiatement
if (isConnected && stompClient != null) {
subscribeToTopic(topic);
} else {
connect();
}
}
private void subscribeToTopic(String topic) {
if (stompClient == null) return;
// Si déjà abonné à ce topic, ne pas souscrire à nouveau
if (topicSubscriptions.containsKey(topic)) {
Log.d("STOMP", "Already subscribed to " + topic);
return;
}
// Formater le topic correctement
String destination = topic.startsWith("/topic/") ? topic : "/topic/" + topic;
Log.d("STOMP", "Subscribing to " + destination);
Disposable subscription = stompClient.topic(destination)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
stompMessage -> {
String payload = stompMessage.getPayload();
Log.d("STOMP", "Message on " + destination + ": " + payload);
// Notifier les listeners
List<Consumer<String>> listeners = topicListeners.get(topic);
if (listeners != null) {
for (Consumer<String> listener : listeners) {
try {
listener.accept(payload);
} catch (Exception e) {
Log.e("STOMP", "Listener error", e);
}
}
}
String notif = "";
Type type = new TypeToken<NotifPayload<Course>>(){}.getType();
NotifPayload<Course> notifCourse = new Gson().fromJson(payload, type);
Course updatedCourse = notifCourse.getPayload();
switch (notifCourse.getType()){
case COURSE_CREATED:
notif = "Nouvelle course "+updatedCourse.getNom()+" créée";
break;
case COURSE_UPDATED:
notif = "Course "+updatedCourse.getNom()+" modifiée";
break;
case COURSE_CANCELLED:
notif = "Course "+updatedCourse.getNom()+" annulée";
break;
case COURSE_REPORTED:
notif = "Course "+updatedCourse.getNom()+" reportée";
break;
case COURSE_CLOSED_FOR_BETTING:
notif = "Course "+updatedCourse.getNom()+" fermée aux paris!";
break;
case RUNNER_DECLARED_NON_PARTANT:
notif = "Non partants déclarés pour la course "+updatedCourse.getNom();
break;
}
// Notification
notificationHelper.showNotification(topic, notif);
},
throwable -> {
Log.e("STOMP", "Error on " + destination, throwable);
// Nettoyer en cas d'erreur
topicSubscriptions.remove(topic);
}
);
topicSubscriptions.put(topic, subscription);
compositeDisposable.add(subscription);
}
private void resubscribeAll() {
if (stompClient == null) return;
// Nettoyer les anciennes subscriptions
for (Disposable disposable : topicSubscriptions.values()) {
if (disposable != null && !disposable.isDisposed()) {
disposable.dispose();
}
}
topicSubscriptions.clear();
// Resouscrire à tous les topics
for (String topic : topicListeners.keySet()) {
subscribeToTopic(topic);
}
}
// -------------------- Envoi de messages --------------------
public void sendMessage(String destination, Object payload) {
if (!isConnected || stompClient == null) {
Log.e("STOMP", "Not connected");
return;
}
String dest = destination.startsWith("/app/") ? destination : "/app/" + destination;
Disposable sendDisposable = stompClient.send(dest, payload.toString())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
() -> Log.d("STOMP", "Message sent to " + dest),
throwable -> Log.e("STOMP", "Error sending to " + dest, throwable)
);
compositeDisposable.add(sendDisposable);
}
public void sendToCourses(Object payload) {
sendMessage("/app/courses", payload);
}
// -------------------- Désabonnement --------------------
public synchronized void unsubscribe(String topic, Consumer<String> listener) {
if (topicListeners.containsKey(topic)) {
topicListeners.get(topic).remove(listener);
if (topicListeners.get(topic).isEmpty()) {
topicListeners.remove(topic);
// Si plus de listeners, annuler la souscription STOMP
Disposable subscription = topicSubscriptions.remove(topic);
if (subscription != null && !subscription.isDisposed()) {
subscription.dispose();
compositeDisposable.remove(subscription);
}
}
}
}
// -------------------- Déconnexion --------------------
public synchronized void disconnect() {
// Nettoyer toutes les subscriptions RxJava
compositeDisposable.clear();
topicSubscriptions.clear();
if (stompClient != null) {
stompClient.disconnect();
stompClient = null;
}
isConnected = false;
topicListeners.clear();
}
private void reconnect() {
disconnect();
// Réessayer après délai
new android.os.Handler().postDelayed(() -> {
Log.d("STOMP", "Attempting to reconnect...");
connect();
}, 5000);
}
// À appeler dans le cycle de vie du fragment/activity
public void onCleanup() {
disconnect();
compositeDisposable.dispose();
}
}

View File

@@ -11,16 +11,13 @@ public class TokenManager {
private static final String PREF_NAME = "auth_pref";
private static final String KEY_TOKEN = "auth_token";
SocketManager socketManager;
private SharedPreferences sharedPreferences;
@Inject
public TokenManager(@ApplicationContext Context context, SocketManager socketManager) {
public TokenManager(@ApplicationContext Context context) {
this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
this.socketManager = socketManager;
}
public void saveToken(String token){
sharedPreferences.edit().putString(KEY_TOKEN, token).apply();
socketManager.reconnect();
}
public String getToken(){

View File

@@ -0,0 +1,128 @@
package com.example.quiz.data.repository;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.example.quiz.data.model.Course;
import com.example.quiz.data.model.Restriction;
import com.example.quiz.data.model.dtos.auth.User;
import com.example.quiz.data.remote.ApiService;
import com.example.quiz.utils.Result;
import java.util.List;
import javax.inject.Inject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AgentRepository {
private ApiService apiService;
@Inject
public AgentRepository(ApiService apiService) {
this.apiService = apiService;
}
public LiveData<Result<List<User>>> getAgents(String agentId) {
MutableLiveData<Result<List<User>>> liveAgents = new MutableLiveData<>();
liveAgents.setValue(Result.loading());
apiService.getAgentsByMaster(agentId).enqueue(new Callback<List<User>>() {
@Override
public void onResponse(@NonNull Call<List<User>> call, @NonNull Response<List<User>> response) {
if (response.isSuccessful()) {
liveAgents.postValue(Result.success(response.body()));
} else {
liveAgents.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(@NonNull Call<List<User>> call, @NonNull Throwable throwable) {
liveAgents.postValue(Result.error(throwable.getMessage()));
}
});
return liveAgents;
}
public LiveData<Result<User>> getAgentById(String agentId) {
MutableLiveData<Result<User>> liveAgent = new MutableLiveData<>();
liveAgent.setValue(Result.loading());
apiService.getAgent(agentId).enqueue(new Callback<User>() {
@Override
public void onResponse(@NonNull Call<User> call, @NonNull Response<User> response) {
if (response.isSuccessful()) {
liveAgent.postValue(Result.success(response.body()));
} else {
liveAgent.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(@NonNull Call<User> call, @NonNull Throwable throwable) {
liveAgent.postValue(Result.error(throwable.getMessage()));
}
});
return liveAgent;
}
public LiveData<Result<List<Course.TypeParis>>> getAvailableBets(String agentId) {
MutableLiveData<Result<List<Course.TypeParis>>> liveAvailableBets = new MutableLiveData<>();
liveAvailableBets.setValue(Result.loading());
apiService.getAvailableBets(agentId).enqueue(new Callback<List<Course.TypeParis>>() {
@Override
public void onResponse(@NonNull Call<List<Course.TypeParis>> call, @NonNull Response<List<Course.TypeParis>> response) {
if (response.isSuccessful()) {
liveAvailableBets.postValue(Result.success(response.body()));
} else {
liveAvailableBets.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(@NonNull Call<List<Course.TypeParis>> call, @NonNull Throwable throwable) {
liveAvailableBets.postValue(Result.error(throwable.getMessage()));
}
});
return liveAvailableBets;
}
public LiveData<Result<Void>> setAccess(String masterId, String slaveId, boolean access) {
MutableLiveData<Result<Void>> liveSetAccess = new MutableLiveData<>();
liveSetAccess.setValue(Result.loading());
apiService.setAccess(masterId, slaveId, access).enqueue(new Callback<Void>() {
@Override
public void onResponse(@NonNull Call<Void> call, @NonNull Response<Void> response) {
if (response.isSuccessful()) {
liveSetAccess.postValue(Result.success(null));
} else {
liveSetAccess.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable throwable) {
liveSetAccess.postValue(Result.error(throwable.getMessage()));
}
});
return liveSetAccess;
};
public LiveData<Result<Void>> setRestrictions(String masterId, String slaveId, Restriction restrictions) {
MutableLiveData<Result<Void>> liveSetRestrictions = new MutableLiveData<>();
liveSetRestrictions.setValue(Result.loading());
apiService.setRestrictions(masterId, slaveId, restrictions).enqueue(new Callback<Void>() {
@Override
public void onResponse(@NonNull Call<Void> call, @NonNull Response<Void> response) {
if (response.isSuccessful()) {
liveSetRestrictions.postValue(Result.success(null));
} else {
liveSetRestrictions.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(@NonNull Call<Void> call, @NonNull Throwable throwable) {
liveSetRestrictions.postValue(Result.error(throwable.getMessage()));
}
});
return liveSetRestrictions;
}
}

View File

@@ -10,8 +10,6 @@ import com.example.quiz.data.model.PagedModel;
import com.example.quiz.data.remote.ApiService;
import com.example.quiz.utils.Result;
import java.util.List;
import javax.inject.Inject;
import retrofit2.Call;
@@ -20,6 +18,7 @@ import retrofit2.Response;
public class CourseRepository {
private ApiService apiService;
private final Course.Statut OPENED_STATUT = Course.Statut.OUVERT;
@Inject
public CourseRepository(ApiService apiService) {
@@ -29,11 +28,25 @@ public class CourseRepository {
public LiveData<Result<PagedModel<Course>>> getCourses(String reunionDate) {
MutableLiveData<Result<PagedModel<Course>>> liveCourses = new MutableLiveData<Result<PagedModel<Course>>>();
liveCourses.setValue(Result.loading());
apiService.getCourses(reunionDate).enqueue(new Callback<PagedModel<Course>>() {
apiService.getCourses(reunionDate,String.valueOf(OPENED_STATUT)).enqueue(new Callback<PagedModel<Course>>() {
@Override
public void onResponse(Call<PagedModel<Course>> call, Response<PagedModel<Course>> response) {
if(response.isSuccessful()){
liveCourses.postValue(Result.success(response.body()));
// PagedModel<Course> openedPagesCourses = new PagedModel<>();
// List<Course> listOfCourses = new ArrayList<>();
// for(Course course: response.body().getContent()){
// if(course.getStatut().equals(OPENED_STATUT)){
// listOfCourses.add(course);
// }
// }
//// openedPagesCourses.setTotalPages(response.body().getTotalPages());
//// openedPagesCourses.setTotalElements(response.body().getTotalElements());
//// openedPagesCourses.setPageable(response.body().getPageable());
//// openedPagesCourses.setNumberOfElements(response.body().getNumberOfElements());
//// openedPagesCourses.setSize(response.body().getSize());
//// openedPagesCourses.setContent(listOfCourses);
// liveCourses.postValue(Result.success(response.body()));
}else{
liveCourses.postValue(Result.error(response.message()));
}

View File

@@ -5,8 +5,10 @@ import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.example.quiz.data.model.dtos.auth.ChangePin;
import com.example.quiz.data.model.dtos.auth.LoginPayload;
import com.example.quiz.data.model.dtos.auth.LoginResponse;
import com.example.quiz.data.model.dtos.auth.User;
import com.example.quiz.data.remote.ApiService;
import com.example.quiz.data.remote.TokenManager;
import com.example.quiz.utils.Result;
@@ -20,6 +22,7 @@ import retrofit2.Response;
public class LoginRepository {
private ApiService apiService;
private TokenManager tokenManager;
@Inject
public LoginRepository(ApiService apiService, TokenManager tokenManager) {
this.tokenManager = tokenManager;
@@ -37,15 +40,38 @@ public class LoginRepository {
Log.d("TOKEN", response.body().getToken());
tokenManager.saveToken(response.body().getToken());
}else{
liveLogin.postValue(Result.error(response.message()));
liveLogin.postValue(Result.error(response.toString()));
}
}
@Override
public void onFailure(Call<LoginResponse> call, Throwable throwable) {
liveLogin.postValue(Result.error(throwable.getMessage()));
liveLogin.postValue(Result.error(throwable.toString()));
}
});
return liveLogin;
}
public LiveData<Result<User>> changePin(String oldPin, String newPin){
MutableLiveData<Result<User>> liveUser = new MutableLiveData<Result<User>>();
liveUser.setValue(Result.loading());
ChangePin changePin = new ChangePin();
changePin.setOldPin(oldPin);
changePin.setNewPin(newPin);
apiService.changePin(changePin).enqueue(new Callback<User>(){
@Override
public void onResponse(Call<User> call, Response<User> response) {
if(response.isSuccessful()) {
liveUser.postValue(Result.success(response.body()));
}else {
liveUser.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(Call<User> call, Throwable throwable) {
liveUser.postValue(Result.error(throwable.getMessage()));
}
});
return liveUser;
}
}

View File

@@ -0,0 +1,47 @@
package com.example.quiz.data.repository;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.example.quiz.data.model.MiseInitiale;
import com.example.quiz.data.model.PariMise;
import com.example.quiz.data.remote.ApiService;
import com.example.quiz.data.remote.TokenManager;
import com.example.quiz.utils.Result;
import java.util.List;
import javax.inject.Inject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class PariMiseRepository {
private ApiService apiService;
@Inject
public PariMiseRepository(ApiService apiService, TokenManager tokenManager) {
this.apiService = apiService;
}
public LiveData<Result<List<MiseInitiale>>> getBetInitMise() {
MutableLiveData<Result<List<MiseInitiale>>> livePariMise = new MutableLiveData<Result<List<MiseInitiale>>>();
livePariMise.setValue(Result.loading());
apiService.getBetInitMise().enqueue(new Callback<List<MiseInitiale>>() {
@Override
public void onResponse(Call<List<MiseInitiale>> call, Response<List<MiseInitiale>> response) {
if (response.isSuccessful()) {
livePariMise.postValue(Result.success(response.body()));
}else{
livePariMise.postValue(Result.error(response.message()));
}
}
@Override
public void onFailure(Call<List<MiseInitiale>> call, Throwable throwable) {
livePariMise.postValue(Result.error(throwable.getMessage()));
}
});
return livePariMise;
}
}

View File

@@ -8,6 +8,8 @@ import androidx.lifecycle.MutableLiveData;
import com.example.quiz.data.model.Pari;
import com.example.quiz.data.model.ParisResponse;
import com.example.quiz.data.model.dtos.paris.CancelParisPaylaod;
import com.example.quiz.data.model.dtos.paris.SoldeResponse;
import com.example.quiz.data.remote.ApiService;
import com.example.quiz.utils.Result;
@@ -96,7 +98,8 @@ public class PariRepository {
public LiveData<Result<ParisResponse>> annulerPari(String numeroTicket){
MutableLiveData<Result<ParisResponse>> pariResponse = new MutableLiveData<>();
pariResponse.setValue(Result.loading());
apiService.annulerPari(numeroTicket).enqueue(new Callback<ParisResponse>(){
CancelParisPaylaod cancelParisPaylaod = new CancelParisPaylaod("ANNULE");
apiService.annulerPari(numeroTicket, cancelParisPaylaod).enqueue(new Callback<ParisResponse>(){
@Override
public void onResponse(Call<ParisResponse> call, Response<ParisResponse> response) {
if(response.isSuccessful()){
@@ -121,12 +124,12 @@ public class PariRepository {
return pariResponse;
}
public LiveData<Result<Double>> getSoldeByCourse(String agentId, String courseId){
MutableLiveData<Result<Double>> solde = new MutableLiveData<>();
public LiveData<Result<SoldeResponse>> getSoldeByCourse(String agentId, String courseId){
MutableLiveData<Result<SoldeResponse>> solde = new MutableLiveData<>();
solde.setValue(Result.loading());
apiService.getSoldeByCourse(agentId, courseId).enqueue(new Callback<Double>() {
apiService.getSoldeByCourse(agentId, courseId).enqueue(new Callback<SoldeResponse>() {
@Override
public void onResponse(Call<Double> call, Response<Double> response) {
public void onResponse(Call<SoldeResponse> call, Response<SoldeResponse> response) {
if(response.isSuccessful()){
solde.postValue(Result.success(response.body()));
}else{
@@ -139,19 +142,19 @@ public class PariRepository {
}
@Override
public void onFailure(Call<Double> call, Throwable throwable) {
public void onFailure(Call<SoldeResponse> call, Throwable throwable) {
solde.postValue(Result.error(throwable.getMessage()));
}
});
return solde;
}
public LiveData<Result<Double>> getSoldeByDay(String agentId, String day){
MutableLiveData<Result<Double>> solde = new MutableLiveData<>();
public LiveData<Result<SoldeResponse>> getSoldeByDay(String agentId, String day){
MutableLiveData<Result<SoldeResponse>> solde = new MutableLiveData<>();
solde.setValue(Result.loading());
apiService.getSoldeByDay(agentId, day).enqueue(new Callback<Double>() {
apiService.getSoldeByDay(agentId, day).enqueue(new Callback<SoldeResponse>() {
@Override
public void onResponse(Call<Double> call, Response<Double> response) {
public void onResponse(Call<SoldeResponse> call, Response<SoldeResponse> response) {
if(response.isSuccessful()){
solde.postValue(Result.success(response.body()));
}else{
@@ -164,7 +167,7 @@ public class PariRepository {
}
@Override
public void onFailure(Call<Double> call, Throwable throwable) {
public void onFailure(Call<SoldeResponse> call, Throwable throwable) {
solde.postValue(Result.error(throwable.getMessage()));
}
});