Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 608339b7ff | |||
| f89b7df8d9 |
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(tree:*)",
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(dir:*)",
|
||||
"Bash(timeout:*)",
|
||||
"WebSearch",
|
||||
"Bash(git rm:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(gradlew.bat build:*)",
|
||||
"Bash(gradlew.bat assembleDebug:*)",
|
||||
"Bash(cmd.exe /c \"gradlew.bat assembleDebug 2>&1\")",
|
||||
"Bash(cmd.exe:*)",
|
||||
"Bash(./gradlew.bat assembleDebug:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git push:*)",
|
||||
"mcp__plugin_serena_serena__initial_instructions",
|
||||
"mcp__plugin_serena_serena__list_dir",
|
||||
"mcp__plugin_serena_serena__activate_project",
|
||||
"mcp__plugin_serena_serena__get_symbols_overview",
|
||||
"mcp__plugin_serena_serena__find_symbol",
|
||||
"mcp__plugin_serena_serena__read_file",
|
||||
"mcp__plugin_serena_serena__create_text_file",
|
||||
"mcp__plugin_serena_serena__replace_content",
|
||||
"Bash(adb:*)",
|
||||
"mcp__plugin_serena_serena__search_for_pattern",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(.\\\\gradlew.bat:*)",
|
||||
"Bash(Select-String -Pattern \"error|Error|BUILD|FAILED|SUCCESS\")",
|
||||
"Bash(Select-Object:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(findstr /i \"\\\\.java$\")",
|
||||
"Bash(findstr:*)",
|
||||
"Bash(.\\\\gradlew.bat test:*)",
|
||||
"Bash(./gradlew.bat test)",
|
||||
"Bash(./gradlew.bat test --tests \"*QuestionCategoryTest*\")",
|
||||
"Bash(./gradlew.bat test:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(./gradlew.bat:*)",
|
||||
"mcp__plugin_serena_serena__replace_symbol_body",
|
||||
"mcp__plugin_serena_serena__think_about_collected_information",
|
||||
"mcp__plugin_serena_serena__find_file",
|
||||
"Bash(gradlew test:*)",
|
||||
"Bash(cat app/build/reports/tests/testDebugUnitTest/index.html)",
|
||||
"Bash(Select-String:*)",
|
||||
"Bash(gh pr view:*)",
|
||||
"Bash(cmd:*)",
|
||||
"Bash(git restore:*)",
|
||||
"Bash(git config:*)",
|
||||
"mcp__plugin_serena_serena__check_onboarding_performed",
|
||||
"Bash(powershell:*)",
|
||||
"mcp__plugin_serena_serena__execute_shell_command",
|
||||
"mcp__plugin_serena_serena__list_memories",
|
||||
"mcp__plugin_serena_serena__onboarding",
|
||||
"mcp__plugin_serena_serena__write_memory",
|
||||
"mcp__plugin_serena_serena__think_about_whether_you_are_done",
|
||||
"Bash(.gradlew.bat assembleDebug)"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
BoideloV3
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="Unnamed">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-01-17T14:48:23.759734500Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\polar\.android\avd\Pixel_3a_API_34_extension_level_7_x86_64.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+2
-1
@@ -5,13 +5,14 @@
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#JAVA_HOME" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
Generated
+1
-4
@@ -1,9 +1,6 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
Generated
-17
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
-457
@@ -1,457 +0,0 @@
|
||||
# Analyse Complète du Code - BoideloV3
|
||||
|
||||
## 📋 Table des Matières
|
||||
|
||||
1. [Présentation du Projet](#présentation-du-projet)
|
||||
2. [Structure du Projet](#structure-du-projet)
|
||||
3. [Fichiers Principaux](#fichiers-principaux)
|
||||
4. [Fonctionnalités](#fonctionnalités)
|
||||
5. [Technologies et Dépendances](#technologies-et-dépendances)
|
||||
6. [Architecture](#architecture)
|
||||
7. [Composants et Leurs Relations](#composants-et-leurs-relations)
|
||||
8. [Configuration](#configuration)
|
||||
9. [Changelog et Améliorations Récentes](#changelog-et-améliorations-récentes)
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Présentation du Projet
|
||||
|
||||
**BoideloV3** est une application Android de jeu d'alcool (similaire à "King's Cup" ou "Never Have I Ever"). L'application permet à un groupe de joueurs de répondre à des questions aléatoires avec des consommations de boissons associées.
|
||||
|
||||
### Caractéristiques Principales
|
||||
|
||||
- **Jeu multijoueur** (minimum 3 joueurs, sans maximum)
|
||||
- **150 questions préchargées** depuis un fichier JSON
|
||||
- **Interface en français** avec Material Design 3
|
||||
- **Défis multi-manches** avec système de gestion
|
||||
- **Paramètres personnalisables** (nombre de questions, ajout de gorgées, durée des défis)
|
||||
- **Générateur aléatoire optimisé** pour une vraie randomisation
|
||||
- **Intégration OpenAI** (en développement)
|
||||
- **Connexion PostgreSQL** (prête)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Structure du Projet
|
||||
|
||||
```
|
||||
D:\boidelo\
|
||||
├── .gradle/ # Cache de construction Gradle
|
||||
├── .idea/ # Configuration IntelliJ IDEA
|
||||
├── app/
|
||||
│ ├── build.gradle # Configuration de build app-level
|
||||
│ ├── libs/ # Librairies JAR externes
|
||||
│ │ ├── postgresql-42.2.27.jre7.jar
|
||||
│ │ └── postgresql-42.6.0.jar
|
||||
│ └── src/
|
||||
│ └── main/
|
||||
│ ├── AndroidManifest.xml
|
||||
│ ├── assets/
|
||||
│ │ └── question.json # Base de données questions (150 questions)
|
||||
│ ├── java/com/example/boidelov3/
|
||||
│ │ ├── MainActivity.java # Écran principal avec ajout dynamique de joueurs
|
||||
│ │ ├── Jeux.java # Activité principale du jeu avec gestion des défis
|
||||
│ │ ├── Jeuxold.java # Implémentation héritée
|
||||
│ │ ├── JeuxParametres.java # Paramètres du jeu avec sliders
|
||||
│ │ ├── EndGameActivity.java # Écran de fin de partie
|
||||
│ │ ├── ChatGPTTask.java # Intégration OpenAI (commenté)
|
||||
│ │ ├── DatabaseConnection.java # Connexion PostgreSQL
|
||||
│ │ ├── data/
|
||||
│ │ │ ├── Question.java # Modèle de données Question
|
||||
│ │ │ └── QuestionRepository.java # Repository des questions
|
||||
│ │ ├── game/
|
||||
│ │ │ └── GameEngine.java # Moteur de jeu
|
||||
│ │ └── utils/
|
||||
│ │ ├── BoideloAnimationUtils.java # Animations UI
|
||||
│ │ └── OnBoardingActivity.java # Page d'accueil
|
||||
│ └── res/
|
||||
│ ├── anim/ # Animations
|
||||
│ ├── drawable/ # Icônes et graphiques
|
||||
│ ├── layout/ # Layouts UI
|
||||
│ │ ├── activity_main.xml # Écran principal
|
||||
│ │ ├── activity_jeux.xml # Jeu en cours
|
||||
│ │ ├── activity_jeux_parametres.xml # Paramètres
|
||||
│ │ └── activity_end_game.xml # Fin de partie
|
||||
│ ├── mipmap-*/ # Icônes pour différentes densités
|
||||
│ ├── values/ # Chaînes, couleurs, thèmes
|
||||
│ │ └── strings.xml # Textes localisés
|
||||
│ ├── values-night/ # Ressources mode nuit
|
||||
│ └── xml/ # Fichiers de configuration XML
|
||||
├── build.gradle # Configuration de build projet-level
|
||||
├── gradle.properties # Propriétés Gradle
|
||||
├── settings.gradle # Paramètres du projet
|
||||
├── gradlew, gradlew.bat # Exécutables Gradle wrapper
|
||||
└── .gitignore # Fichier Git ignore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 Fichiers Principaux
|
||||
|
||||
### Activités Principales
|
||||
|
||||
#### `MainActivity.java`
|
||||
**Point d'entrée principal** de l'application avec ajout dynamique de joueurs.
|
||||
|
||||
**Responsabilités :**
|
||||
- Gestion de la saisie des noms de joueurs (3 champs fixes + ajouts dynamiques)
|
||||
- Validation du minimum de 3 joueurs
|
||||
- Génération dynamique de l'interface utilisateur avec TextInputLayout cohérents
|
||||
- Persistence des noms avec SharedPreferences
|
||||
- Compteur de joueurs en temps réel
|
||||
|
||||
**Améliorations récentes :**
|
||||
- ✅ Correction de l'UI pour les champs dynamiques (bordure visible, espacement cohérent)
|
||||
- ✅ Bouton suppression plus petit et mieux positionné
|
||||
- ✅ Icône de suppression (poubelle) optimisée
|
||||
|
||||
#### `Jeux.java`
|
||||
**Cœur du jeu**, gère l'affichage des questions, les mécaniques de jeu et les défis.
|
||||
|
||||
**Responsabilités :**
|
||||
- Chargement des questions depuis le JSON
|
||||
- Sélection aléatoire sans répétition
|
||||
- Gestion des tours de joueurs
|
||||
- Gestion des défis multi-manches
|
||||
- Feedback visuel avec arrière-plans colorés
|
||||
- Remplacement dynamique des variantes et joueurs
|
||||
|
||||
**Améliorations récentes :**
|
||||
- ✅ **Générateur aléatoire optimisé** : une seule instance `Random partagée` pour toute l'activité
|
||||
- ✅ **Empêcher nouveaux défis quand un défi est en cours** : filtrage des questions avec `<manches>`
|
||||
- ✅ **Bug fix fin de jeu** : flag `isFinishingGame` pour éviter les clics multiples
|
||||
- ✅ **Offset durée des défis** : applique le paramètre `durationDefis` au nombre de manches (5-14 + offset)
|
||||
|
||||
#### `JeuxParametres.java`
|
||||
**Écran de configuration** des paramètres du jeu avec sliders interactifs.
|
||||
|
||||
**Responsabilités :**
|
||||
- Configuration du nombre de questions (20-150, par pas de 10)
|
||||
- Gestion des ajouts de gorgées (0-20) avec paliers de difficulté
|
||||
- Configuration du ratio BDD/OPENAI (1/1 à 1/15)
|
||||
- **Configuration de la durée des défis** (-5 à +15, par défaut 0)
|
||||
- Gestion de la clé API OpenAI
|
||||
- Test de connexion API
|
||||
|
||||
**Améliorations récentes :**
|
||||
- ✅ Correction des sliders : affichage en temps réel des valeurs
|
||||
- ✅ Slider durée des défis avec valeurs négatives autorisées (offset de -5 à +15)
|
||||
|
||||
#### `EndGameActivity.java`
|
||||
**Écran de fin de partie** avec statistiques.
|
||||
|
||||
**Responsabilités :**
|
||||
- Affichage des statistiques de partie
|
||||
- Boutons pour rejouer ou retourner au menu
|
||||
|
||||
### Modèles de Données
|
||||
|
||||
#### `Question.java`
|
||||
**Modèle de données** pour une question individuelle.
|
||||
|
||||
```java
|
||||
public class Question {
|
||||
private int id; // Identifiant unique
|
||||
private String question; // Texte de la question
|
||||
private int gorger; // Nombre de gorgées
|
||||
private boolean distribution; // Flag de distribution
|
||||
private boolean recois; // Flag de réception
|
||||
private List<String> variante; // Variantes de la question
|
||||
private int manchesRestantes; // Nombre de manches restantes
|
||||
private String arretMessageManche; // Message de fin de défi
|
||||
}
|
||||
```
|
||||
|
||||
#### `QuestionRepository.java`
|
||||
**Repository** pour la gestion des questions avec filtrage.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Fonctionnalités
|
||||
|
||||
### Gestion des Joueurs
|
||||
|
||||
- **3 champs fixes** pour les premiers joueurs
|
||||
- **Ajout dynamique** de joueurs avec bouton de suppression
|
||||
- **Minimum 3 joueurs** requis pour commencer
|
||||
- **Support illimité** de nombre de joueurs
|
||||
- **Persistance** avec SharedPreferences
|
||||
- **Compteur en temps réel** : "Joueurs: X / min. 3"
|
||||
|
||||
### Système de Questions
|
||||
|
||||
- **150 questions préchargées** depuis JSON
|
||||
- **Sélection aléatoire optimisée** sans répétition
|
||||
- **Variantes** avec remplacement dynamique de contenu
|
||||
- **Questions à manches** avec mécanique de compte à rebours
|
||||
- **Filtrage intelligent** : pas de nouveau défi si un défi est en cours
|
||||
|
||||
### Mécaniques de Jeu
|
||||
|
||||
- **Ajustement dynamique** du nombre de gorgées via slider (0-20)
|
||||
- **Randomisation optimisée** des joueurs pour les questions
|
||||
- **Feedback visuel** avec arrière-plans colorés
|
||||
- **Défis multi-manches** avec affichage du compteur
|
||||
- **Système de paliers** : de "Grosse merde" à "L'ENDER DRAGON"
|
||||
|
||||
### Paramètres de Partie
|
||||
|
||||
| Paramètre | Plage | Défaut | Description |
|
||||
|-----------|-------|--------|-------------|
|
||||
| **Nombre de questions** | 20-150 (par 10) | 50 | Nombre de questions avant fin de partie |
|
||||
| **Ajout de gorgées** | 0-20 | 0 | Gorgées supplémentaires à chaque consommation |
|
||||
| **Ratio BDD/OPENAI** | 1/1 à 1/15 | 1/8 | Ratio entre questions locales et IA |
|
||||
| **Durée des défis (offset)** | -5 à +15 | 0 | Offset appliqué au nombre de manches |
|
||||
|
||||
### Interface Utilisateur
|
||||
|
||||
- **Material Design 3** avec composants modernes
|
||||
- **Animations fluides** (fade in, slide out, pop in)
|
||||
- **Feedback haptique** sur les interactions
|
||||
- **Localisation en français** entièrement corrigée
|
||||
- **Mode sombre** avec thèmes adaptés
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Technologies et Dépendances
|
||||
|
||||
### Technologies Principales
|
||||
|
||||
| Technologie | Version | Usage |
|
||||
|-------------|---------|-------|
|
||||
| **Android SDK** | 33 (compile) | Développement Android natif |
|
||||
| **Java** | 17 | Langage principal |
|
||||
| **Gradle** | 8.2 | Automatisation de build |
|
||||
|
||||
### Bibliothèques Principales
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
// Material Design
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
|
||||
// Cœur AndroidX
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
|
||||
// Réseau
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
|
||||
// Base de données
|
||||
implementation 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.8.3'
|
||||
|
||||
// Traitement JSON
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
// Tests
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗 Architecture
|
||||
|
||||
### Pattern MVC (Model-View-Controller)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Model │
|
||||
│ • Question.java │
|
||||
│ • QuestionRepository.java │
|
||||
└─────────────────────────────────────────────────┘
|
||||
↑
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Controller │
|
||||
│ • MainActivity.java │
|
||||
│ • Jeux.java │
|
||||
│ • JeuxParametres.java │
|
||||
│ • EndGameActivity.java │
|
||||
└─────────────────────────────────────────────────┘
|
||||
↑
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ View │
|
||||
│ • XML Layouts │
|
||||
│ • Resources (drawable, strings, colors) │
|
||||
│ • Animations │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Flux des Activités
|
||||
|
||||
```
|
||||
MainActivity (Saisie joueurs)
|
||||
↓ validation (min. 3 joueurs)
|
||||
JeuxParametres (Configuration)
|
||||
↓ paramètres configurés
|
||||
Jeux (Partie en cours)
|
||||
↓ toutes les questions posées OU fin manches
|
||||
EndGameActivity (Stats et fin)
|
||||
↓
|
||||
MainActivity (Retour menu)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Composants et Leurs Relations
|
||||
|
||||
### Flux de Données
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ MainActivity │
|
||||
│ - 3 champs fixes + ajouts dynamiques │
|
||||
│ - Compteur temps réel : "Joueurs: X / min. 3" │
|
||||
│ - UI cohérente pour tous les champs │
|
||||
└────────────────────┬───────────────────────────────────────┘
|
||||
│ Intent avec liste joueurs
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ JeuxParametres │
|
||||
│ - Slider questions : 20-150 (pas de 10) │
|
||||
│ - Slider gorgées : 0-20 avec paliers │
|
||||
│ - Slider ratio BDD/OPENAI : 1-15 │
|
||||
│ - Slider durée défis : -5 à +15 (offset) │
|
||||
│ - Toggle OpenAI + champ clé API │
|
||||
└────────────────────┬───────────────────────────────────────┘
|
||||
│ Intent avec tous les paramètres
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ Jeux │
|
||||
│ - Charge questions depuis JSON (150 questions) │
|
||||
│ - Générateur Random optimisé (instance unique) │
|
||||
│ - Filtre questions <manches> si défi en cours │
|
||||
│ - Applique offset durée défis au nombre de manches │
|
||||
│ - Gère défis multi-manches │
|
||||
│ - Flag isFinishingGame pour éviter bugs fin de partie │
|
||||
└────────────────────┬───────────────────────────────────────┘
|
||||
│ Fin de partie
|
||||
▼
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ EndGameActivity │
|
||||
│ - Affiche statistiques │
|
||||
│ - Boutons Rejouer / Retour │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Configuration de Build
|
||||
|
||||
| Propriété | Valeur |
|
||||
|-----------|--------|
|
||||
| **Compile SDK** | 33 |
|
||||
| **Min SDK** | 24 |
|
||||
| **Target SDK** | 33 |
|
||||
| **Version Code** | 1 |
|
||||
| **Version Name** | "1.0" |
|
||||
| **Package** | com.example.boidelov3 |
|
||||
|
||||
### Configuration Base de Données
|
||||
|
||||
```
|
||||
Hôte : 82.65.214.214
|
||||
Port : 5432
|
||||
Base de données : boidelo
|
||||
Utilisateur : android
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Changelog et Améliorations Récentes
|
||||
|
||||
### Version Actuelle - 2025
|
||||
|
||||
#### ✅ Corrections Orthographe (2025)
|
||||
- **MainActivity.java** : "ne peux pas" → "ne peut pas"
|
||||
- **question.json** : "diferentes" → "différentes", "veritable" → "véritable", "tu a" → "tu as", etc.
|
||||
- **strings.xml** : "c ki" → "C'est qui", "komment" → "comment", "Prenom" → "Prénom"
|
||||
- **Jeuxold.java** : nombreuses corrections (tour de rôle, choisissant, défie, etc.)
|
||||
|
||||
#### ✅ Optimisation Aléatoire (2025)
|
||||
- **Problème** : `new Random()` créé à chaque appel causait des répétitions
|
||||
- **Solution** : Instance unique `private final Random random = new Random()` partagée dans toute l'activité
|
||||
- **Fichiers modifiés** : `Jeux.java`, `Jeuxold.java`
|
||||
- **Résultat** : Vraie randomisation des joueurs et questions
|
||||
|
||||
#### ✅ Interface Utilisateur Cohérente (2025)
|
||||
- **Problème** : Champs dynamiques avaient un style différent des 3 premiers
|
||||
- **Solution** : Application complète du style Material3
|
||||
- Bordure visible avec `setBoxStrokeColorStateList()`
|
||||
- Couleur de fond `@color/surface`
|
||||
- Largeur bordure 2dp
|
||||
- Icône de suppression (X) pour effacer
|
||||
- **Bouton suppression** : Taille réduite (36dp), espacement optimisé (68dp marge)
|
||||
- **Résultat** : UI uniforme entre tous les champs de saisie
|
||||
|
||||
#### ✅ Slider Durée des Défis (2025)
|
||||
- **Nouveau paramètre** : Offset pour la durée des défis
|
||||
- **Plage** : -5 à +15 manches
|
||||
- **Défaut** : 0 (pas d'offset)
|
||||
- **Affichage** : Signe +/- pour les valeurs non nulles (-3, +5, etc.)
|
||||
- **Application** : `nbaleatoiremanches = (5-14) + durationDefis`
|
||||
- **Minimum garanti** : 1 manche (même avec offset négatif)
|
||||
|
||||
#### ✅ Gestion Intelligente des Défis (2025)
|
||||
- **Problème** : Nouveaux défis pouvaient s'accumuler même quand un défi était en cours
|
||||
- **Solution** : Filtrage des questions avec `<manches>` dans `getRandomQuestion()`
|
||||
- **Résultat** : Plus d'accumulation de défis, un seul défi à la fois
|
||||
|
||||
#### ✅ Bug Fix Fin de Jeu (2025)
|
||||
- **Problème** : Quand un défi était en cours à la fin des questions, il fallait cliquer plusieurs fois
|
||||
- **Solution** : Flag `isFinishingGame` + désactivation bouton "Suivant"
|
||||
- **Résultat** : Fin de jeu fluide sans clics multiples
|
||||
|
||||
#### ✅ Expansion Contenu (2025)
|
||||
- **Ajout de 50 nouvelles questions** (passage de 100 à 150 questions)
|
||||
- **Nouvelles catégories** :
|
||||
- Questions de groupe (ski, tatouage, iPhone, etc.)
|
||||
- Questions de classement (le plus drôle, le plus susceptible, etc.)
|
||||
- Questions couples/amour (ghosting, premier baiser, etc.)
|
||||
- Défis avec manches (accents, interdictions, mini-jeux)
|
||||
- Questions interactives J1/J2/J3 (bras de fer, devinettes, mimes)
|
||||
- Questions variante (activités, fandoms, musique)
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Statut des Fonctionnalités
|
||||
|
||||
| Fonctionnalité | Statut | Détails |
|
||||
|----------------|--------|---------|
|
||||
| Jeu multijoueur | ✅ Terminé | Support illimité, UI cohérente |
|
||||
| Questions JSON | ✅ Terminé | 150 questions, orthographe corrigée |
|
||||
| Interface UI | ✅ Terminé | Material Design 3, animations |
|
||||
| Sliders paramètres | ✅ Terminé | Affichage temps réel, 4 sliders actifs |
|
||||
| Défis multi-manches | ✅ Terminé | Gestion optimisée, pas d'accumulation |
|
||||
| Générateur aléatoire | ✅ Optimisé | Instance unique Random partagée |
|
||||
| Durée défis (offset) | ✅ Terminé | -5 à +15 manches |
|
||||
| Orthographe | ✅ Corrigée | Tous fichiers vérifiés |
|
||||
| PostgreSQL | 🔧 Prêt | Connexion configurée |
|
||||
| OpenAI ChatGPT | 🚧 En développement | Interface prête |
|
||||
|
||||
---
|
||||
|
||||
## 📝 Conclusion
|
||||
|
||||
Le codebase représente une **application Android de jeu d'alcool mature et bien structurée** avec des patterns d'architecture modernes. Les améliorations récentes ont considérablement amélioré l'expérience utilisateur :
|
||||
|
||||
- **Randomisation fiable** grâce au générateur optimisé
|
||||
- **Interface cohérente** pour tous les champs de saisie
|
||||
- **Défis gérés intelligemment** sans accumulation
|
||||
- **Paramètres flexibles** pour personnaliser les parties
|
||||
- **Contenu enrichi** avec 150 questions variées
|
||||
- **Orthographe parfaite** dans tout le projet
|
||||
|
||||
L'architecture est propre, modulaire et facilement extensible pour de futures améliorations.
|
||||
|
||||
---
|
||||
|
||||
*Document mis à jour - Janvier 2025* 🍺
|
||||
@@ -1,230 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Build the Project
|
||||
```bash
|
||||
# On Windows (using gradlew.bat)
|
||||
.\gradlew.bat build
|
||||
|
||||
# On Linux/Mac (using gradlew)
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
### Build and Install Debug APK
|
||||
```bash
|
||||
# On Windows
|
||||
.\gradlew.bat assembleDebug
|
||||
|
||||
# Install to connected device
|
||||
adb install -r app\build\outputs\apk\debug\app-debug.apk
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
```bash
|
||||
# Unit tests only
|
||||
.\gradlew.bat test
|
||||
|
||||
# Android instrumented tests (requires connected device/emulator)
|
||||
.\gradlew.bat connectedAndroidTest
|
||||
|
||||
# Run specific test class
|
||||
.\gradlew.bat test --tests com.example.boidelov3.game.GameEngineTest
|
||||
```
|
||||
|
||||
### Clean Build
|
||||
```bash
|
||||
.\gradlew.bat clean
|
||||
```
|
||||
|
||||
## Project Architecture
|
||||
|
||||
### High-Level Structure
|
||||
Boidelo is an Android drinking game app built with Java and Gradle. The architecture has evolved from a single-game app to a multi-game platform with a central hub.
|
||||
|
||||
**Package Structure:**
|
||||
- `com.example.boidelov3` - Root package
|
||||
- `com.example.boidelov3.hub` - Game selection hub (main entry point)
|
||||
- `com.example.boidelov3.games.boideloclassic` - Original Boidelo game
|
||||
- `com.example.boidelov3.games.game89` - 89++ card game
|
||||
- `com.example.boidelov3.games.papelito` - Undercover party game
|
||||
- `com.example.boidelov3.rules` - Rules viewer for popular party games
|
||||
- `com.example.boidelov3.data` - Data models and repositories
|
||||
- `com.example.boidelov3.game` - GameEngine (pure Java game logic)
|
||||
- `com.example.boidelov3.utils` - Utilities (sound, error handling, security)
|
||||
|
||||
### Entry Points
|
||||
- **GameSelectionActivity** (`hub/GameSelectionActivity.java`) - Main hub, displays available games via RecyclerView. This is the LAUNCHER activity in AndroidManifest.xml.
|
||||
- **Legacy Activities** (`Jeux.java`, `JeuxParametres.java`, `EndGameActivity.java`) - Being phased out, do not use for new development
|
||||
|
||||
### Game Flow Pattern
|
||||
Each game follows this flow:
|
||||
1. **Setup Activity** - Player configuration (e.g., `BoideloClassicSetupActivity`, `Game89SetupActivity`)
|
||||
2. **Parameters Activity** - Game settings (e.g., `BoideloClassicParamsActivity`)
|
||||
3. **Game Activity** - Main gameplay (e.g., `BoideloClassicGameActivity`, `Game89GameActivity`)
|
||||
4. **End Game Activity** - Results and statistics
|
||||
|
||||
### Core Architecture Components
|
||||
|
||||
**Data Layer:**
|
||||
- `QuestionRepository` - Loads questions from JSON assets, manages SharedPreferences
|
||||
- `Result<T, E>` - Type-safe error handling wrapper (pattern: `Result.success(value)` or `Result.error(error)`)
|
||||
- `PlayerStats` - Tracks drinks consumed/distributed per player
|
||||
- `Question` - Rich model with variants, manches (rounds), distribution flags
|
||||
- `QuestionCategory` - Categorization with styling (colors, icons)
|
||||
|
||||
**Business Logic:**
|
||||
- `GameEngine` - Pure Java game logic, isolated from Android framework for testability
|
||||
- `OpenAIService` - AI-powered question generation via ChatGPT API
|
||||
- `ChatGPTTask` - Async task for AI question generation
|
||||
|
||||
**UI Patterns:**
|
||||
- Material Design 3 components
|
||||
- RecyclerView for game selection
|
||||
- Dynamic player input (add/remove fields)
|
||||
- Haptic feedback and sound effects
|
||||
|
||||
### Question Processing Pipeline
|
||||
Questions go through several transformations before display:
|
||||
1. Load from `assets/questions.json` (150+ questions)
|
||||
2. Replace player placeholders (`<J1>`, `<J2>`, `<J3>`)
|
||||
3. Process variants (`<variante>`)
|
||||
4. Handle manches (round challenges with countdown)
|
||||
5. Add drink count and verb conjugation
|
||||
6. Apply category-based styling
|
||||
|
||||
### Persistence
|
||||
- **SharedPreferences** - Player names, settings, asked questions tracking, statistics
|
||||
- **JSON Assets** - Pre-loaded question database
|
||||
- **BuildConfig** - API keys (stored in `local.properties`, not version-controlled)
|
||||
|
||||
### Security Configuration
|
||||
- API keys stored in `local.properties` (excluded from git)
|
||||
- `SecureConfig` utility for secure access
|
||||
- Database credentials in BuildConfig are intentionally empty (use backend API)
|
||||
|
||||
### Testing Infrastructure
|
||||
- Unit tests in `app/src/test/java/com/example/boidelov3/`
|
||||
- `GameEngineTest` - 15 tests for game logic
|
||||
- `ResultTest` - Error handling wrapper tests
|
||||
- `QuestionCategoryTest` - Category validation tests
|
||||
- `PlayerStatsTest` - Player statistics tracking tests
|
||||
- Game-specific tests in subpackages (e.g., `games/papelito/`, `games/game89/`)
|
||||
- JUnit 4 framework
|
||||
- Mockito 5.7.0 for mocking
|
||||
- Robolectric 4.11.1 for Android framework testing
|
||||
|
||||
**Test Patterns:**
|
||||
- Each game has comprehensive test coverage (15+ tests per game)
|
||||
- Tests cover: setup/teardown, edge cases, state transitions, player management, win conditions
|
||||
- Defensive copy testing for collections
|
||||
|
||||
**Known Issue:** Gradle 8.13 has a bug with unit tests from command line. If tests fail, run from Android Studio or use JDK 17-21.
|
||||
|
||||
### Important Files
|
||||
- `app/build.gradle` - Dependencies, build config, BuildConfig fields
|
||||
- `local.properties` - Local SDK path and API keys (not in git)
|
||||
- `assets/questions.json` - Question database
|
||||
|
||||
### Resource Management Conventions
|
||||
**Layout Naming:** `activity_<game>_<screen>.xml`
|
||||
- `activity_<game>_setup.xml` - Player configuration
|
||||
- `activity_<game>_game.xml` - Main game screen
|
||||
- `activity_<game>_result.xml` - Results screen
|
||||
- `dialog_<game>_<purpose>.xml` - Custom dialogs
|
||||
|
||||
**String Resources:** Internationalized strings in `res/values/strings.xml`, organized by game with clear prefixes (e.g., `papelito_`, `boidelo_`, `game89_`)
|
||||
|
||||
**Drawables:** Game icons follow `ic_<gamename>.xml` pattern in `res/drawable/`
|
||||
|
||||
### Game Registration (GameInfo System)
|
||||
Games are registered in `GameSelectionActivity.setupGamesList()` via the `GameInfo` class. Each entry includes:
|
||||
- Game name (hardcoded string)
|
||||
- Description (hardcoded string)
|
||||
- Icon resource (drawable resource ID)
|
||||
- GameType enum value (BOIDELO_CLASSIC, GAME_89, UNDERCOVER, RULES)
|
||||
- Availability flag (boolean)
|
||||
|
||||
Games are launched via Intent in `GameSelectionActivity.onItemClick()` using switch statement on `GameType` enum values.
|
||||
|
||||
### Adding a New Game
|
||||
1. Create package under `com.example.boidelov3.games.<gamename>`
|
||||
2. Implement setup, parameters, and game activities (follow Game Flow Pattern)
|
||||
3. Add GameType enum value to `GameInfo.GameType`
|
||||
4. Create `GameInfo` entry in `GameSelectionActivity.setupGamesList()`
|
||||
5. Add case in `GameSelectionActivity.onItemClick()` switch statement
|
||||
6. Add activities to AndroidManifest.xml with `android:screenOrientation="portrait"`
|
||||
7. Add game icon/drawable resource following `ic_<gamename>.xml` pattern
|
||||
|
||||
### Dependencies
|
||||
- AndroidX AppCompat 1.7.0
|
||||
- Material Design 1.12.0
|
||||
- ConstraintLayout 2.2.0
|
||||
- OkHttp 4.12.0
|
||||
- Gson 2.11.0
|
||||
- pgjdbc-ng 0.8.3 (PostgreSQL - currently unused, backend recommended)
|
||||
- JUnit 4.13.2
|
||||
- Mockito 5.7.0
|
||||
- Robolectric 4.11.1
|
||||
- AndroidX Test JUnit 1.2.1
|
||||
- Espresso 3.6.1
|
||||
|
||||
### Build Configuration
|
||||
- `minSdk`: 24 (Android 7.0+)
|
||||
- `targetSdk`: 35
|
||||
- `compileSdk`: 35
|
||||
- Java 8 compatibility
|
||||
- Namespace: `com.example.boidelov3`
|
||||
- All game activities use `android:screenOrientation="portrait"` in AndroidManifest.xml
|
||||
- Game activities handle config changes with `android:configChanges="orientation|screenSize"`
|
||||
|
||||
---
|
||||
|
||||
## Code Style and Conventions
|
||||
|
||||
### Language Usage
|
||||
- **Business logic terms in French:** `gorgees`, `manches`, `caliente` (domain-specific)
|
||||
- **Technical terms in English:** `allPlayers`, `questionsWithManches`
|
||||
- **UI text in French** (user-facing)
|
||||
|
||||
### Constants Management
|
||||
All SharedPreferences keys are centralized in `PreferencesKeys.java`:
|
||||
```java
|
||||
// File names: PREFS_NAME_*
|
||||
// Keys: KEY_*
|
||||
// Dynamic keys: use getPlayerKey(int playerNumber)
|
||||
```
|
||||
|
||||
Replace hardcoded strings throughout the codebase with these constants.
|
||||
|
||||
### Result<T, E> Pattern
|
||||
Type-safe error handling wrapper used throughout:
|
||||
```java
|
||||
Result<String, Exception> result = repository.loadQuestions();
|
||||
if (result.isSuccess()) {
|
||||
String data = result.getData();
|
||||
} else {
|
||||
Exception error = result.getError();
|
||||
}
|
||||
```
|
||||
|
||||
### Resource Naming
|
||||
- **Layouts:** `activity_<game>_<screen>.xml`, `dialog_<game>_<purpose>.xml`
|
||||
- **Drawables:** `ic_<gamename>.xml` pattern
|
||||
- **Strings:** Prefix by game (e.g., `papelito_`, `boidelo_`, `game89_`)
|
||||
|
||||
### Material Design
|
||||
- Use `@color/primary` (#9395D3), `@color/accent` (#B3B7EE)
|
||||
- Custom styles: `BoideloButton`, `BoideloTitle`, `BoideloCard`
|
||||
- MaterialCardView with rounded corners (16-24dp) and elevation (4-8dp)
|
||||
|
||||
---
|
||||
|
||||
## Additional Documentation
|
||||
|
||||
- `README_ARCHITECTURE.md` - Detailed architecture documentation (French)
|
||||
- `STANDARDISATION_ET_TESTS.md` - Standardization work and test coverage (French)
|
||||
- `SECURITY_RECOMMENDATIONS.md` - Security guidelines and best practices
|
||||
- `PAPELITO_LAYOUTS.md` - Papelito game layout documentation
|
||||
@@ -1,372 +0,0 @@
|
||||
<div align="center">
|
||||
|
||||
# 🎲 Boidelo
|
||||
|
||||
**L'application de jeux de soirée — questions, défis et cartes entre amis**
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 📖 Description
|
||||
|
||||
**Boidelo** est une application Android de jeux de soirée regroupant plusieurs mini-jeux autour d'un hub central. Entrez les noms des joueurs, choisissez un jeu, et laissez l'ambiance opérer !
|
||||
|
||||
L'application propose trois jeux distincts ainsi qu'un consulteur de règles de jeux d'ambiance populaires.
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Jeux disponibles
|
||||
|
||||
### 1. Boidelo Classic
|
||||
|
||||
Le jeu original de questions et défis. Inspiré de jeux comme *King's Cup* ou *Never Have I Ever*.
|
||||
|
||||
- **150+ questions** préchargées depuis un fichier JSON, avec 10 catégories distinctes
|
||||
- Questions dynamiques avec remplacement de variables (`<J1>`, `<J2>`, `<J3>`, `<variante>`)
|
||||
- **Défis multi-manches** avec compte à rebours et message de fin
|
||||
- Gestion intelligente : pas d'accumulation de défis simultanés
|
||||
- Système de **gorgées** (boire / distribuer) avec accord grammatical automatique
|
||||
- Catégorisation automatique des questions avec couleur de fond dynamique :
|
||||
- Ciblage, Classement, Jugement, Duel, Interactif, Défi, Variante, Caliente, Vote, Classique
|
||||
- Paramètres personnalisables : nombre de questions, gorgées supplémentaires, durée des défis
|
||||
- **Intégration OpenAI** (optionnelle) pour générer des questions via ChatGPT
|
||||
|
||||
### 2. 89++
|
||||
|
||||
Un jeu de cartes avec timer et défis spéciaux.
|
||||
|
||||
- Objectif : ne pas être celui qui atteint ou dépasse **89**
|
||||
- Règles spéciales : Valet (−10), Dame (changement de sens), Roi (choix de valeur)
|
||||
- Gorgées distribuées à chaque dizaine atteinte (70 = double !)
|
||||
- **9 types de défis** aléatoires :
|
||||
- Changement de sens, Immunité, Gorgée surprise, Joker (passer son tour), Échange de mains, Double prochaine valeur, Compte mystère, Tour générale, Victime au choix
|
||||
- Timer configurable pour les défis
|
||||
- Suivi des gorgées par joueur
|
||||
|
||||
### 3. Papelito (Undercover)
|
||||
|
||||
Un jeu de déduction sociale. Trouvez l'Undercover avant qu'il ne soit trop tard !
|
||||
|
||||
- **20 paires de mots** préchargées (Pizza/Burger, Chat/Chien, Vin/Bière, etc.)
|
||||
- Attribution aléatoire des rôles : **Civil**, **Undercover**, **Mr. White**
|
||||
- Mécanique de **discussion → vote → élimination**
|
||||
- Conditions de victoire :
|
||||
- Civils gagnent si tous les Undercovers sont éliminés
|
||||
- Undercovers gagnent s'ils sont en nombre ≥ aux Civils
|
||||
- Gestion des égalités de votes (personne n'est éliminé)
|
||||
- Machine à états avec validation des transitions légales
|
||||
|
||||
### 4. Règles de jeux
|
||||
|
||||
Consultez les règles des jeux d'ambiance populaires directement dans l'application.
|
||||
|
||||
---
|
||||
|
||||
## 🏗 Architecture
|
||||
|
||||
### Vue d'ensemble
|
||||
|
||||
Boidelo suit un pattern **Repository + Séparation des responsabilités**. Chaque jeu est isolé dans son propre package avec sa propre logique métier.
|
||||
|
||||
```
|
||||
app/src/main/java/com/example/boidelov3/
|
||||
├── hub/ # Hub central
|
||||
│ ├── GameSelectionActivity.java # Activité LAUNCHER (point d'entrée)
|
||||
│ ├── adapter/
|
||||
│ │ └── GameAdapter.java # RecyclerView adapter
|
||||
│ └── model/
|
||||
│ └── GameInfo.java # Modèle (nom, description, icône, type)
|
||||
│
|
||||
├── games/ # Jeux isolés par package
|
||||
│ ├── boideloclassic/ # Boidelo Classic
|
||||
│ │ ├── BoideloClassicSetupActivity.java
|
||||
│ │ ├── BoideloClassicParamsActivity.java
|
||||
│ │ ├── BoideloClassicGameActivity.java
|
||||
│ │ └── manager/
|
||||
│ │ ├── BoideloPlayerManager.java
|
||||
│ │ └── BoideloPlayerRowManager.java
|
||||
│ ├── game89/ # 89++
|
||||
│ │ ├── Game89SetupActivity.java
|
||||
│ │ ├── Game89GameActivity.java
|
||||
│ │ ├── Game89Player.java # Modèle joueur
|
||||
│ │ └── Game89ChallengeManager.java # Gestionnaire de défis
|
||||
│ └── papelito/ # Papelito (Undercover)
|
||||
│ ├── PapelitoSetupActivity.java
|
||||
│ ├── PapelitoGameActivity.java
|
||||
│ ├── PapelitoResultActivity.java
|
||||
│ ├── PapelitoResultAdapter.java
|
||||
│ ├── PapelitoGame.java # Logique complète du jeu
|
||||
│ └── PapelitoPlayer.java # Modèle joueur (Serializable)
|
||||
│
|
||||
├── data/ # Couche d'accès aux données
|
||||
│ ├── QuestionRepository.java # Chargement JSON + SharedPreferences
|
||||
│ ├── Result.java # Wrapper type-safe succès/erreur
|
||||
│ ├── PlayerStats.java # Stats joueur (Parcelable)
|
||||
│ └── QuestionCategory.java # Catégorisation + couleurs
|
||||
│
|
||||
├── game/ # Logique métier commune
|
||||
│ └── GameEngine.java # Moteur de jeu (pur Java)
|
||||
│
|
||||
├── utils/ # Utilitaires
|
||||
│ ├── ErrorHandler.java
|
||||
│ ├── SecureConfig.java
|
||||
│ ├── SoundManager.java
|
||||
│ └── SoundGenerator.java
|
||||
│
|
||||
├── MainActivity.java # Legacy (à supprimer)
|
||||
├── Jeux.java # Legacy (à supprimer)
|
||||
├── JeuxParametres.java # Legacy (à supprimer)
|
||||
├── EndGameActivity.java # Legacy (à supprimer)
|
||||
├── OpenAIService.java # Service OpenAI
|
||||
├── ChatGPTTask.java # Task async OpenAI
|
||||
├── DatabaseConnection.java # Connexion PostgreSQL
|
||||
├── Question.java # Modèle de question
|
||||
├── Questions.java # Conteneur de questions (Gson)
|
||||
├── PreferencesKeys.java # Constantes SharedPreferences
|
||||
└── BoideloAnimationUtils.java # Animations UI
|
||||
```
|
||||
|
||||
### Flux d'une partie
|
||||
|
||||
Chaque jeu suit le même schéma d'activité :
|
||||
|
||||
```
|
||||
Hub (GameSelectionActivity)
|
||||
└─► Setup (saisie des joueurs)
|
||||
└─► Parameters (configuration, optionnel)
|
||||
└─► Game (partie en cours)
|
||||
└─► End Game (statistiques & résultats)
|
||||
```
|
||||
|
||||
### Pattern Result<T, E>
|
||||
|
||||
Le projet utilise un wrapper type-safe pour la gestion des erreurs, évitant les exceptions non contrôlées :
|
||||
|
||||
```java
|
||||
Result<Questions, QuestionLoadException> result = repository.loadQuestions();
|
||||
if (result.isSuccess()) {
|
||||
Questions questions = result.getData();
|
||||
} else {
|
||||
Exception error = result.getError();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Stack technique
|
||||
|
||||
| Élément | Version / Détail |
|
||||
|---|---|
|
||||
| **Langage** | Java 8 |
|
||||
| **Min SDK** | 24 (Android 7.0) |
|
||||
| **Target / Compile SDK** | 35 (Android 15) |
|
||||
| **Build system** | Gradle 8.12.0 + AGP 8.12.0 |
|
||||
| **UI** | Material Design 3, AppCompat 1.7.0 |
|
||||
| **Layout** | ConstraintLayout 2.2.0 |
|
||||
| **Réseau** | OkHttp 4.12.0 |
|
||||
| **JSON** | Gson 2.11.0 |
|
||||
| **Base de données** | pgjdbc-ng 0.8.3 (PostgreSQL, actuellement inutilisé) |
|
||||
| **Tests unitaires** | JUnit 4.13.2, Mockito 5.7.0, Robolectric 4.11.1 |
|
||||
| **Tests UI** | AndroidX Test JUnit 1.2.1, Espresso 3.6.1 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Tests
|
||||
|
||||
Le projet dispose de **11 classes de test** couvrant l'ensemble des jeux et des composants de données :
|
||||
|
||||
| Classe de test | Fichier |
|
||||
|---|---|
|
||||
| `GameEngineTest` | `game/GameEngineTest.java` (15 tests) |
|
||||
| `ResultTest` | `data/ResultTest.java` (8 tests) |
|
||||
| `PlayerStatsTest` | `data/PlayerStatsTest.java` |
|
||||
| `QuestionCategoryTest` | `data/QuestionCategoryTest.java` |
|
||||
| `QuestionTest` | `QuestionTest.java` |
|
||||
| `QuestionsClassTest` | `QuestionsClassTest.java` |
|
||||
| `BoideloPlayerManagerTest` | `games/boideloclassic/manager/BoideloPlayerManagerTest.java` |
|
||||
| `Game89PlayerTest` | `games/game89/Game89PlayerTest.java` |
|
||||
| `Game89ChallengeManagerTest` | `games/game89/Game89ChallengeManagerTest.java` |
|
||||
| `PapelitoPlayerTest` | `games/papelito/PapelitoPlayerTest.java` |
|
||||
| `PapelitoGameTest` | `games/papelito/PapelitoGameTest.java` |
|
||||
|
||||
---
|
||||
|
||||
## 🔨 Compilation et installation
|
||||
|
||||
### Prérequis
|
||||
|
||||
- **Android Studio** (recommandé) ou **JDK 17-21** + SDK Android
|
||||
- Android SDK avec compileSdk 35
|
||||
|
||||
### Build depuis la ligne de commande
|
||||
|
||||
```bash
|
||||
# Cloner le projet
|
||||
git clone <url-du-repo>
|
||||
cd boidelo
|
||||
|
||||
# Build
|
||||
./gradlew build
|
||||
|
||||
# APK debug
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Installer sur un appareil connecté
|
||||
adb install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### Exécuter les tests
|
||||
|
||||
```bash
|
||||
# Tous les tests unitaires
|
||||
./gradlew test
|
||||
|
||||
# Tests instrumentés (appareil/émulateur requis)
|
||||
./gradlew connectedAndroidTest
|
||||
|
||||
# Test spécifique
|
||||
./gradlew test --tests com.example.boidelov3.game.GameEngineTest
|
||||
```
|
||||
|
||||
> ⚠️ **Note :** Un bug connu avec Gradle 8.13 peut causer l'échec des tests unitaires en CLI. Utilisez Android Studio ou un JDK 17-21 pour contourner le problème.
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
./gradlew clean
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Interface
|
||||
|
||||
- **Material Design 3** avec composants modernes (MaterialCardView, TextInputLayout, etc.)
|
||||
- **Mode sombre** avec thèmes dédiés (`values-night/`)
|
||||
- **Animations fluides** : fade in/out, slide, color transition, button press/release
|
||||
- **Feedback haptique** et effets sonores via `SoundManager` / `SoundGenerator`
|
||||
- **Palette principale** : Violet (`#9395D3`) / Lavande (`#B3B7EE`) sur fond blanc cassé (`#FBF9FF`)
|
||||
- Couleurs dynamiques par catégorie de question (10 couleurs distinctes)
|
||||
- Toutes les activités en mode **portrait**
|
||||
|
||||
---
|
||||
|
||||
## 📁 Structure des ressources
|
||||
|
||||
```
|
||||
app/src/main/res/
|
||||
├── anim/ # 10 animations (slide, fade, button, color)
|
||||
├── drawable/ # Icônes de jeux, boutons, fonds
|
||||
├── layout/ # 19 layouts XML
|
||||
│ ├── activity_game_selection.xml # Hub principal
|
||||
│ ├── activity_boidelo_classic_setup.xml
|
||||
│ ├── activity_boidelo_classic_params.xml
|
||||
│ ├── activity_boidelo_classic_game.xml
|
||||
│ ├── activity_game89_setup.xml
|
||||
│ ├── activity_game89_game.xml
|
||||
│ ├── activity_papelito_setup.xml
|
||||
│ ├── activity_papelito_game.xml
|
||||
│ ├── activity_papelito_result.xml
|
||||
│ ├── dialog_papelito_vote.xml
|
||||
│ └── dialog_papelito_word_reveal.xml
|
||||
├── values/ # Strings, couleurs, thèmes, styles
|
||||
├── values-night/ # Variantes mode sombre
|
||||
├── mipmap-*/ # Icônes launcher (mdpi → xxxhdpi)
|
||||
└── xml/ # Backup rules, data extraction rules
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Données
|
||||
|
||||
Les questions de Boidelo Classic sont stockées dans `app/src/main/assets/question.json`. Chaque question contient :
|
||||
|
||||
- `id` — identifiant unique
|
||||
- `question` — texte avec placeholders (`<J1>`, `<variante>`, `<manches>`)
|
||||
- `gorger` — nombre de gorgées
|
||||
- `distribution` / `recois` — flags pour boire ou distribuer
|
||||
- `variante` — liste de variantes alternatives
|
||||
- `manches` — nombre de manches pour les défis
|
||||
- `arret` — message de fin de défi
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Configuration
|
||||
|
||||
### Clé API OpenAI (optionnel)
|
||||
|
||||
Les clés sensibles sont stockées dans `local.properties` (non versionné) :
|
||||
|
||||
```properties
|
||||
OPENAI_API_KEY=sk-votre-cle-ici
|
||||
```
|
||||
|
||||
La classe `SecureConfig` fournit un accès sécurisé à ces valeurs.
|
||||
|
||||
### Base de données PostgreSQL
|
||||
|
||||
La connexion PostgreSQL est préparée mais le backend n'est pas encore en production. Les identifiants BuildConfig sont intentionnellement vides — l'usage d'une API backend est recommandé.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Ajouter un nouveau jeu
|
||||
|
||||
1. Créer un package sous `com.example.boidelov3.games.<nom_du_jeu>`
|
||||
2. Implémenter les activités **Setup**, **Game** et **Result** (suivre le flux standard)
|
||||
3. Ajouter une valeur à l'enum `GameInfo.GameType`
|
||||
4. Enregistrer le jeu dans `GameSelectionActivity.setupGamesList()`
|
||||
5. Ajouter le cas dans le `switch` de `GameSelectionActivity.onItemClick()`
|
||||
6. Déclarer les activités dans `AndroidManifest.xml` avec `screenOrientation="portrait"`
|
||||
7. Ajouter l'icône drawable `ic_<nom_du_jeu>.xml`
|
||||
8. Écrire les tests unitaires correspondants
|
||||
|
||||
---
|
||||
|
||||
## 📝 Conventions
|
||||
|
||||
- **Langue métier** : français (`gorgees`, `manches`, `caliente`)
|
||||
- **Code technique** : anglais (`allPlayers`, `activeManches`)
|
||||
- **Interface utilisateur** : français
|
||||
- **Clés SharedPreferences** : centralisées dans `PreferencesKeys.java`
|
||||
- **Nommage layouts** : `activity_<jeu>_<écran>.xml`, `dialog_<jeu>_<usage>.xml`
|
||||
- **Nommage strings** : préfixe par jeu (`papelito_`, `boidelo_`, `game89_`)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Statut des fonctionnalités
|
||||
|
||||
| Fonctionnalité | Statut |
|
||||
|---|---|
|
||||
| Hub de sélection de jeux | ✅ Opérationnel |
|
||||
| Boidelo Classic (150+ questions) | ✅ Opérationnel |
|
||||
| 89++ (cartes + défis) | ✅ Opérationnel |
|
||||
| Papelito / Undercover | ✅ Opérationnel |
|
||||
| Règles de jeux | 🚧 En cours |
|
||||
| Intégration OpenAI (questions IA) | 🚧 En développement |
|
||||
| Connexion PostgreSQL | 🔧 Prête (backend requis) |
|
||||
| Mode sombre | ✅ Opérationnel |
|
||||
| Tests unitaires (11 classes) | ✅ Opérationnel |
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation supplémentaire
|
||||
|
||||
- [`ANALYSE_CODE.md`](ANALYSE_CODE.md) — Analyse détaillée du code (ancienne architecture)
|
||||
- [`README_ARCHITECTURE.md`](README_ARCHITECTURE.md) — Documentation de l'architecture Repository
|
||||
- [`CLAUDE.md`](CLAUDE.md) — Guide de développement pour l'IA
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licence
|
||||
|
||||
Projet privé — tous droits réservés.
|
||||
|
||||
---
|
||||
|
||||
*Boidelo — buvez responsable* 🍻
|
||||
@@ -1,509 +0,0 @@
|
||||
# BoideloV3 - Documentation Architecture
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Ce document décrit l'architecture mise en place pour l'application BoideloV3, un jeu de défi à boire pour Android.
|
||||
|
||||
L'architecture suit le pattern **Repository + Separation of Concerns**, permettant :
|
||||
- Une meilleure séparation des responsabilités
|
||||
- Un code testable unitairement
|
||||
- Une gestion d'erreurs robuste
|
||||
- Une maintenance facilitée
|
||||
|
||||
---
|
||||
|
||||
## Structure du projet
|
||||
|
||||
```
|
||||
app/src/main/java/com/example/boidelov3/
|
||||
├── data/ # Couche d'accès aux données
|
||||
│ ├── Result.java # Wrapper de résultat (succès/erreur)
|
||||
│ └── QuestionRepository.java # Repository pour les questions
|
||||
├── game/ # Logique métier du jeu
|
||||
│ └── GameEngine.java # Moteur de jeu
|
||||
├── ui/ # Activités et Fragments
|
||||
│ ├── Jeux.java # Activité principale du jeu
|
||||
│ ├── JeuxParametres.java # Écran de paramètres
|
||||
│ ├── EndGameActivity.java # Écran de fin de partie
|
||||
│ └── MainActivity.java # Écran d'accueil
|
||||
└── utils/ # Utilitaires
|
||||
├── DatabaseConnection.java # Connexion BDD PostgreSQL
|
||||
├── SoundManager.java # Gestion des sons
|
||||
└── BoideloAnimationUtils.java # Animations UI
|
||||
|
||||
app/src/test/java/com/example/boidelov3/
|
||||
├── data/
|
||||
│ └── ResultTest.java # Tests de Result
|
||||
└── game/
|
||||
└── GameEngineTest.java # Tests de GameEngine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Couche Data (données)
|
||||
|
||||
### 1.1 Result<T, E>
|
||||
|
||||
Wrapper type-safe pour représenter le résultat d'une opération qui peut échouer.
|
||||
|
||||
**Avantages :**
|
||||
- Remplace les exceptions par des valeurs de retour
|
||||
- Force le traitement des erreurs
|
||||
- Rend le code plus prévisible
|
||||
|
||||
**Utilisation :**
|
||||
|
||||
```java
|
||||
// Créer un résultat succès
|
||||
Result<String, Exception> success = Result.success("Données chargées");
|
||||
|
||||
// Créer un résultat erreur
|
||||
Result<String, Exception> failure = Result.failure(new Exception("Erreur de chargement"));
|
||||
|
||||
// Utiliser le résultat
|
||||
if (result.isSuccess()) {
|
||||
String data = result.getData();
|
||||
// Traiter les données
|
||||
} else {
|
||||
Exception error = result.getError();
|
||||
// Gérer l'erreur
|
||||
}
|
||||
|
||||
// Ou avec getOrElse
|
||||
String value = result.getOrElse("valeur par défaut");
|
||||
```
|
||||
|
||||
**Méthodes disponibles :**
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `success(T data)` | Crée un résultat réussi |
|
||||
| `failure(E error)` | Crée un résultat d'erreur |
|
||||
| `isSuccess()` | Vrai si réussi |
|
||||
| `isFailure()` | Vrai si échoué |
|
||||
| `getData()` | Retourne les données (null si erreur) |
|
||||
| `getError()` | Retourne l'erreur (null si succès) |
|
||||
| `getOrNull()` | Retourne les données ou null |
|
||||
| `getOrElse(T default)` | Retourne les données ou une valeur par défaut |
|
||||
|
||||
---
|
||||
|
||||
### 1.2 QuestionRepository
|
||||
|
||||
Gère l'accès aux données des questions (JSON + SharedPreferences).
|
||||
|
||||
**Responsabilités :**
|
||||
- Charger les questions depuis `assets/question.json`
|
||||
- Suivre les questions déjà posées
|
||||
- Sauvegarder les statistiques de jeu
|
||||
|
||||
**Utilisation :**
|
||||
|
||||
```java
|
||||
// Créer le repository
|
||||
QuestionRepository repository = new QuestionRepository(context);
|
||||
|
||||
// Charger les questions
|
||||
Result<Questions, QuestionLoadException> result = repository.loadQuestions();
|
||||
|
||||
if (result.isSuccess()) {
|
||||
Questions questions = result.getData();
|
||||
List<Question> questionList = questions.getQuestions();
|
||||
// Utiliser les questions
|
||||
} else {
|
||||
QuestionLoadException error = result.getError();
|
||||
Log.e("TAG", "Erreur: " + error.getMessage());
|
||||
}
|
||||
|
||||
// Obtenir une question aléatoire non posée
|
||||
Result<Question, QuestionLoadException> randomResult =
|
||||
repository.getRandomUnaskedQuestion(questionList);
|
||||
|
||||
// Réinitialiser les questions posées
|
||||
repository.resetAskedQuestions();
|
||||
|
||||
// Sauvegarder les stats
|
||||
repository.saveGameStats(questionsPlayed, playersCount);
|
||||
```
|
||||
|
||||
**Méthodes principales :**
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `loadQuestions()` | Charge toutes les questions depuis le JSON |
|
||||
| `getRandomUnaskedQuestion(List)` | Retourne une question non posée |
|
||||
| `markQuestionAsAsked(int)` | Marque une question comme posée |
|
||||
| `resetAskedQuestions()` | Réinitialise le suivi des questions |
|
||||
| `saveGameStats(int, int)` | Sauvegarde les statistiques |
|
||||
|
||||
---
|
||||
|
||||
## 2. Couche Game (logique métier)
|
||||
|
||||
### 2.1 GameEngine
|
||||
|
||||
Contient toute la logique du jeu, indépendante de l'UI.
|
||||
|
||||
**Responsabilités :**
|
||||
- Remplacement des variables dans les questions (`<J1>`, `<variante>`, etc.)
|
||||
- Gestion des manches actives
|
||||
- Sélection aléatoire de joueurs
|
||||
- Calcul des gorgées
|
||||
|
||||
**Utilisation :**
|
||||
|
||||
```java
|
||||
// Créer le moteur de jeu
|
||||
GameEngine engine = new GameEngine();
|
||||
|
||||
// Préparer les joueurs
|
||||
List<String> players = Arrays.asList("Alice", "Bob", "Charlie", "David");
|
||||
int addedGorgees = 2; // Paramètre de jeu
|
||||
|
||||
// Traiter une question
|
||||
Question rawQuestion = ...; // Question depuis le JSON
|
||||
ProcessedQuestion processed = engine.processQuestion(rawQuestion, players, addedGorgees);
|
||||
|
||||
// Afficher la question traitée
|
||||
textView.setText(Html.fromHtml(processed.question.getQuestion()));
|
||||
|
||||
// Vérifier si c'est une manche
|
||||
if (processed.isManche) {
|
||||
// C'est un défi à manches
|
||||
Log.d("Game", "Manche active: " + processed.question.getManchesRestantes());
|
||||
}
|
||||
|
||||
// Mettre à jour les manches (à chaque tour)
|
||||
MancheState mancheState = engine.updateManches();
|
||||
|
||||
if (mancheState.endMessage != null) {
|
||||
// Une manche s'est terminée
|
||||
Toast.makeText(context, mancheState.endMessage, Toast.LENGTH_LONG).show();
|
||||
} else if (mancheState.activeManche != null) {
|
||||
// Une manche est toujours active
|
||||
Question activeManche = mancheState.activeManche;
|
||||
int remaining = activeManche.getManchesRestantes();
|
||||
// Afficher le compteur de manches
|
||||
}
|
||||
|
||||
// Sélectionner des joueurs aléatoires
|
||||
List<String> selectedPlayers = engine.selectRandomPlayers(players, 3);
|
||||
// Retourne 3 joueurs uniques
|
||||
|
||||
// Vider toutes les manches (fin de partie)
|
||||
engine.clearManches();
|
||||
```
|
||||
|
||||
**Classes internes :**
|
||||
|
||||
| Classe | Description |
|
||||
|--------|-------------|
|
||||
| `ProcessedQuestion` | Conteneur pour une question traitée |
|
||||
| `MancheState` | État des manches après mise à jour |
|
||||
|
||||
**Méthodes principales :**
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `processQuestion(Question, List, int)` | Traite une question (remplace variables) |
|
||||
| `selectRandomPlayers(List, int)` | Sélectionne n joueurs uniques |
|
||||
| `updateManches()` | Met à jour et retourne l'état des manches |
|
||||
| `hasActiveManche()` | Vrai si une manche est active |
|
||||
| `clearManches()` | Vide toutes les manches actives |
|
||||
|
||||
---
|
||||
|
||||
## 3. Utilisation recommandée dans une Activity
|
||||
|
||||
Voici comment intégrer les nouvelles classes dans `Jeux.java` :
|
||||
|
||||
```java
|
||||
public class Jeux extends AppCompatActivity {
|
||||
private QuestionRepository repository;
|
||||
private GameEngine gameEngine;
|
||||
private List<String> players;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_jeux);
|
||||
|
||||
// Initialiser les composants
|
||||
repository = new QuestionRepository(this);
|
||||
gameEngine = new GameEngine();
|
||||
players = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
|
||||
// Charger les questions
|
||||
loadAndStartGame();
|
||||
}
|
||||
|
||||
private void loadAndStartGame() {
|
||||
Result<Questions, QuestionRepository.QuestionLoadException> result =
|
||||
repository.loadQuestions();
|
||||
|
||||
if (result.isSuccess()) {
|
||||
Questions questions = result.getData();
|
||||
displayNextQuestion(questions);
|
||||
} else {
|
||||
Toast.makeText(this, "Erreur de chargement", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayNextQuestion(Questions allQuestions) {
|
||||
// Obtenir une question aléatoire
|
||||
Result<Question, QuestionRepository.QuestionLoadException> result =
|
||||
repository.getRandomUnaskedQuestion(allQuestions.getQuestions());
|
||||
|
||||
if (result.isFailure()) {
|
||||
endGame();
|
||||
return;
|
||||
}
|
||||
|
||||
Question question = result.getData();
|
||||
|
||||
// Traiter la question avec le moteur de jeu
|
||||
int addedGorgees = getIntent().getIntExtra("EXTRA_AJOUT_GORGEE", 0);
|
||||
GameEngine.ProcessedQuestion processed =
|
||||
gameEngine.processQuestion(question, players, addedGorgees);
|
||||
|
||||
// Afficher
|
||||
questionTextView.setText(Html.fromHtml(processed.question.getQuestion()));
|
||||
|
||||
// Gérer les manches
|
||||
GameEngine.MancheState mancheState = gameEngine.updateManches();
|
||||
if (mancheState.endMessage != null) {
|
||||
Toast.makeText(this, mancheState.endMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void endGame() {
|
||||
repository.saveGameStats(totalQuestions, players.size());
|
||||
// Naviguer vers EndGameActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
gameEngine.clearManches();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Tests unitaires
|
||||
|
||||
Les tests sont situés dans `app/src/test/java/`.
|
||||
|
||||
### 4.1 Exécuter les tests
|
||||
|
||||
**Depuis Android Studio :**
|
||||
1. Clic droit sur la classe de test
|
||||
2. Select "Run 'ResultTest'"
|
||||
|
||||
**Depuis la ligne de commande :**
|
||||
```bash
|
||||
./gradlew testDebugUnitTest
|
||||
```
|
||||
|
||||
*Note : Avec Gradle 8.13, il y a un bug connu. Utilisez Android Studio ou un JDK 17-21.*
|
||||
|
||||
### 4.2 Résultat des tests
|
||||
|
||||
| Classe | Tests | Couverture |
|
||||
|--------|-------|------------|
|
||||
| `ResultTest` | 8 tests | 100% Result |
|
||||
| `GameEngineTest` | 15 tests | Logique jeu complète |
|
||||
|
||||
---
|
||||
|
||||
## 5. Connexion Base de Données
|
||||
|
||||
### 5.1 DatabaseConnection
|
||||
|
||||
Classe moderne pour la connexion PostgreSQL (remplace l'obsolète AsyncTask).
|
||||
|
||||
**Utilisation :**
|
||||
|
||||
```java
|
||||
DatabaseConnection dbConnection = new DatabaseConnection();
|
||||
|
||||
dbConnection.connectAsync(new DatabaseConnection.ConnectionCallback() {
|
||||
@Override
|
||||
public void onSuccess(PGConnection connection) {
|
||||
// Connexion réussie - thread principal (UI)
|
||||
Log.d("Database", "Connecté !");
|
||||
// Utiliser la connexion...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception error) {
|
||||
// Erreur - thread principal (UI)
|
||||
Log.e("Database", "Erreur de connexion", error);
|
||||
Toast.makeText(context, "Erreur: " + error.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
// Dans onDestroy()
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
dbConnection.shutdown(); // Libérer les ressources
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Configuration
|
||||
|
||||
Les identifiants de connexion sont dans `local.properties` (non versionné) :
|
||||
|
||||
```properties
|
||||
# Database credentials (DO NOT COMMIT)
|
||||
db.url=jdbc:postgresql://your-host:5432/database
|
||||
db.user=your-username
|
||||
db.password=your-password
|
||||
```
|
||||
|
||||
Ils sont injectés automatiquement dans `BuildConfig` par `build.gradle`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Bonnes pratiques
|
||||
|
||||
### 6.1 Gestion des erreurs
|
||||
|
||||
```java
|
||||
// ✅ BON - Utiliser Result
|
||||
Result<Data, Exception> result = repository.loadQuestions();
|
||||
if (result.isSuccess()) {
|
||||
// Traiter les données
|
||||
} else {
|
||||
// Gérer l'erreur
|
||||
}
|
||||
|
||||
// ❌ Mauvais - Exceptions non gérées
|
||||
try {
|
||||
Data data = repository.loadQuestions();
|
||||
} catch (Exception e) {
|
||||
// Peut être oublié...
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Séparation des responsabilités
|
||||
|
||||
```java
|
||||
// ✅ BON - Logique dans GameEngine
|
||||
ProcessedQuestion processed = gameEngine.processQuestion(question, players, addedGorgees);
|
||||
|
||||
// ❌ Mauvais - Logique dans l'Activity
|
||||
String text = question.getQuestion().replace("<J1>", players.get(0));
|
||||
// ... logique compliquée dans l'UI
|
||||
```
|
||||
|
||||
### 6.3 Cycle de vie
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// Toujours nettoyer les ressources
|
||||
if (dbConnection != null) {
|
||||
dbConnection.shutdown();
|
||||
}
|
||||
if (gameEngine != null) {
|
||||
gameEngine.clearManches();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Diagramme de flux
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Jeux Activity │ (UI)
|
||||
└────────┬────────┘
|
||||
│
|
||||
├── loadQuestions() ──────► QuestionRepository
|
||||
│ │
|
||||
│ ├── Lire JSON
|
||||
│ └── Retourner Result<Questions>
|
||||
│
|
||||
├── processQuestion() ──────► GameEngine
|
||||
│ │
|
||||
│ ├── Remplacer <J1>, <J2>, etc.
|
||||
│ ├── Remplacer <variante>
|
||||
│ ├── Gérer <manches>
|
||||
│ └── Retourner ProcessedQuestion
|
||||
│
|
||||
└── Afficher dans TextView
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Migration depuis l'ancien code
|
||||
|
||||
### Ancienne méthode (AsyncTask)
|
||||
|
||||
```java
|
||||
// Ancien code - À NE PAS UTILISER
|
||||
new DatabaseConnection().execute();
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Connection connection) {
|
||||
if (connection != null) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Nouvelle méthode (ExecutorService + Callback)
|
||||
|
||||
```java
|
||||
// Nouveau code - Recommandé
|
||||
DatabaseConnection db = new DatabaseConnection();
|
||||
db.connectAsync(new ConnectionCallback() {
|
||||
@Override
|
||||
public void onSuccess(PGConnection connection) {
|
||||
// Succès
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception error) {
|
||||
// Erreur
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Dépannage
|
||||
|
||||
### Problème : Les tests unitaires échouent avec Gradle 8.13
|
||||
|
||||
**Solution :** Exécutez les tests depuis Android Studio, ou utilisez un JDK 17-21 au lieu de Java 24.
|
||||
|
||||
### Problème : "Cannot resolve BuildConfig"
|
||||
|
||||
**Solution :** Faites un Build > Rebuild Project pour régénérer BuildConfig.
|
||||
|
||||
### Problème : "QuestionRepository.QuestionLoadException"
|
||||
|
||||
**Solution :** Importez la classe interne :
|
||||
```java
|
||||
import com.example.boidelov3.data.QuestionRepository.QuestionLoadException;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Ressources supplémentaires
|
||||
|
||||
- [Android Guide to App Architecture](https://developer.android.com/topic/architecture)
|
||||
- [Repository Pattern](https://developer.android.com/topic/architecture/data-layer)
|
||||
- [Result Type in Java](https://www.baeldung.com/java-result-type)
|
||||
|
||||
---
|
||||
|
||||
**Dernière mise à jour :** 31 décembre 2025
|
||||
**Version :** 3.0
|
||||
+9
-28
@@ -2,35 +2,18 @@ plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
// Load local.properties
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file('local.properties')
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withInputStream { localProperties.load(it) }
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.example.boidelov3'
|
||||
compileSdk 35
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
compileSdk 33
|
||||
packagingOptions { resources.excludes.add("META-INF/*") } // This line is added to avoid the error: Duplicate files copied in APK META-INF/LICENSE.txt
|
||||
defaultConfig {
|
||||
applicationId "com.example.boidelov3"
|
||||
minSdk 24
|
||||
targetSdk 35
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// IMPORTANT: Database credentials should NEVER be stored in BuildConfig
|
||||
// Use a secure backend API instead, or Android Keystore for local storage
|
||||
// These fields are kept empty for backward compatibility but will be removed
|
||||
buildConfigField "String", "DB_URL", "\"\""
|
||||
buildConfigField "String", "DB_USER", "\"\""
|
||||
buildConfigField "String", "DB_PASSWORD", "\"\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -47,16 +30,14 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:5.7.0'
|
||||
testImplementation 'org.robolectric:robolectric:4.11.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
|
||||
implementation 'com.impossibl.pgjdbc-ng:pgjdbc-ng:0.8.3'
|
||||
implementation 'com.google.code.gson:gson:2.11.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.8'
|
||||
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
@@ -16,91 +13,29 @@
|
||||
tools:targetApi="31">
|
||||
|
||||
<activity
|
||||
android:name=".hub.GameSelectionActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.BoideloV3">
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Boidelo Classic Activities -->
|
||||
<activity
|
||||
android:name=".games.boideloclassic.BoideloClassicSetupActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name=".games.boideloclassic.BoideloClassicGameActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name=".games.boideloclassic.BoideloClassicParamsActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<!-- Game 89++ Activities -->
|
||||
<activity
|
||||
android:name=".games.game89.Game89SetupActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name=".games.game89.Game89GameActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<!-- Papelito (Undercover) Activities -->
|
||||
<activity
|
||||
android:name=".games.papelito.PapelitoSetupActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name=".games.papelito.PapelitoGameActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name=".games.papelito.PapelitoResultActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<!-- Rules Activities -->
|
||||
<activity
|
||||
android:name=".rules.RulesListActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name=".rules.RuleDetailActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<!-- Legacy Activities (to be removed) -->
|
||||
<!-- Legacy activities placeholder -->
|
||||
|
||||
<activity
|
||||
android:name="com.example.boidelov3.Jeux"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
android:exported="true">
|
||||
<!-- Ajoutez ici les filtres d'intention appropriés si nécessaire -->
|
||||
</activity>
|
||||
<activity android:name="com.example.boidelov3.JeuxParametres"
|
||||
android:exported="true">
|
||||
|
||||
<activity
|
||||
android:name="com.example.boidelov3.JeuxParametres"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name="com.example.boidelov3.EndGameActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait" />
|
||||
</activity>
|
||||
<activity android:name="com.example.boidelov3.ChatGPTTask"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
|
||||
<!-- Ajoutez ici les filtres d'intention appropriés si nécessaire -->
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
+141
-790
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 269 KiB |
@@ -1,415 +0,0 @@
|
||||
package com.example.boidelov3;
|
||||
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* Classe utilitaire pour les animations et effets visuels
|
||||
*/
|
||||
public class BoideloAnimationUtils {
|
||||
|
||||
private static final Interpolator OVERSHOOT = new OvershootInterpolator();
|
||||
|
||||
/**
|
||||
* Anime le changement de couleur de fond d'une vue
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param targetColor La couleur cible
|
||||
* @param duration La durée de l'animation en ms
|
||||
*/
|
||||
public static void animateBackgroundColor(View view, int targetColor, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
int currentColor = getBackgroundColor(view);
|
||||
if (currentColor == targetColor) return;
|
||||
|
||||
ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), currentColor, targetColor);
|
||||
anim.setDuration(duration);
|
||||
anim.addUpdateListener(animation -> {
|
||||
int color = (int) animation.getAnimatedValue();
|
||||
view.setBackgroundColor(color);
|
||||
});
|
||||
anim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime le changement de couleur de fond avec la durée par défaut (500ms)
|
||||
*/
|
||||
public static void animateBackgroundColor(View view, int targetColor) {
|
||||
animateBackgroundColor(view, targetColor, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient la couleur de fond actuelle d'une vue
|
||||
*/
|
||||
private static int getBackgroundColor(View view) {
|
||||
if (view.getBackground() instanceof ColorDrawable) {
|
||||
return ((ColorDrawable) view.getBackground()).getColor();
|
||||
}
|
||||
return 0xFFFFFFFF; // Blanc par défaut
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclenche une vibration haptique
|
||||
*
|
||||
* @param context Le contexte
|
||||
* @param duration Durée de la vibration en ms
|
||||
*/
|
||||
public static void triggerHapticFeedback(Context context, int duration) {
|
||||
if (context == null) return;
|
||||
|
||||
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vibrator != null && vibrator.hasVibrator()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
VibrationEffect effect = VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
|
||||
vibrator.vibrate(effect);
|
||||
} else {
|
||||
vibrator.vibrate(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclenche une vibration haptique courte (100ms)
|
||||
*/
|
||||
public static void triggerHapticFeedback(Context context) {
|
||||
triggerHapticFeedback(context, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclenche une vibration haptique de succès
|
||||
*/
|
||||
public static void triggerSuccessHaptic(Context context) {
|
||||
if (context == null) return;
|
||||
|
||||
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vibrator != null && vibrator.hasVibrator() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
long[] pattern = {0, 50, 50, 50};
|
||||
VibrationEffect effect = VibrationEffect.createWaveform(pattern, -1);
|
||||
vibrator.vibrate(effect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Déclenche une vibration haptique d'erreur
|
||||
*/
|
||||
public static void triggerErrorHaptic(Context context) {
|
||||
if (context == null) return;
|
||||
|
||||
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||
if (vibrator != null && vibrator.hasVibrator() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
long[] pattern = {0, 100, 50, 100};
|
||||
VibrationEffect effect = VibrationEffect.createWaveform(pattern, -1);
|
||||
vibrator.vibrate(effect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime l'apparition d'une vue avec un effet de fade-in
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void fadeIn(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setAlpha(0f);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.animate()
|
||||
.alpha(1f)
|
||||
.setDuration(duration)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime la disparition d'une vue avec un effet de fade-out
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void fadeOut(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.alpha(0f)
|
||||
.setDuration(duration)
|
||||
.withEndAction(() -> view.setVisibility(View.GONE))
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de scale
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param scale Échelle cible (1.0 = normal, 0.5 = moitié)
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void scale(View view, float scale, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(OVERSHOOT)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de slide depuis le bas
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void slideUp(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setTranslationY(view.getHeight());
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.animate()
|
||||
.translationY(0f)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new android.view.animation.DecelerateInterpolator())
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique une animation de pression à un bouton
|
||||
*
|
||||
* @param view La vue (bouton) à animer
|
||||
*/
|
||||
public static void applyButtonPressAnimation(View view) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setOnTouchListener((v, event) -> {
|
||||
switch (event.getAction()) {
|
||||
case android.view.MotionEvent.ACTION_DOWN:
|
||||
v.animate()
|
||||
.scaleX(0.95f)
|
||||
.scaleY(0.95f)
|
||||
.setDuration(100)
|
||||
.start();
|
||||
return true;
|
||||
case android.view.MotionEvent.ACTION_UP:
|
||||
case android.view.MotionEvent.ACTION_CANCEL:
|
||||
v.animate()
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(100)
|
||||
.start();
|
||||
v.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de pulsation
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée d'un cycle de pulsation
|
||||
*/
|
||||
public static void pulse(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.scaleX(1.05f)
|
||||
.scaleY(1.05f)
|
||||
.setDuration(duration / 2)
|
||||
.withEndAction(() -> {
|
||||
view.animate()
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setDuration(duration / 2)
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de shake (tremblement)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
*/
|
||||
public static void shake(View view) {
|
||||
if (view == null) return;
|
||||
|
||||
Animation shake = AnimationUtils.loadAnimation(view.getContext(), R.anim.button_press);
|
||||
view.startAnimation(shake);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient la couleur depuis une ressource de couleur
|
||||
*/
|
||||
public static int getColorFromResource(Context context, int colorResId) {
|
||||
return ContextCompat.getColor(context, colorResId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de bounce (rebond)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void bounce(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.scaleY(0.8f)
|
||||
.scaleX(0.8f)
|
||||
.setDuration(duration / 2)
|
||||
.setInterpolator(new android.view.animation.DecelerateInterpolator())
|
||||
.withEndAction(() -> {
|
||||
view.animate()
|
||||
.scaleY(1f)
|
||||
.scaleX(1f)
|
||||
.setDuration(duration / 2)
|
||||
.setInterpolator(OVERSHOOT)
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue pour la supprimer (slide out + fade out)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
* @param endAction Action à exécuter après l'animation
|
||||
*/
|
||||
public static void slideOutToRemove(View view, int duration, Runnable endAction) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.translationX(-view.getWidth())
|
||||
.alpha(0f)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new android.view.animation.AccelerateInterpolator())
|
||||
.withEndAction(endAction)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime l'apparition d'une vue (slide in + fade in)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void slideIn(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setTranslationX(view.getWidth());
|
||||
view.setAlpha(0f);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.animate()
|
||||
.translationX(0f)
|
||||
.alpha(1f)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new android.view.animation.DecelerateInterpolator())
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de rotation
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param degrees Angle de rotation en degrés
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void rotate(View view, float degrees, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.rotation(degrees)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new android.view.animation.DecelerateInterpolator())
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de wiggle (gauche-droite)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
*/
|
||||
public static void wiggle(View view) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.rotation(5f)
|
||||
.setDuration(50)
|
||||
.withEndAction(() -> {
|
||||
view.animate()
|
||||
.rotation(-5f)
|
||||
.setDuration(50)
|
||||
.withEndAction(() -> {
|
||||
view.animate()
|
||||
.rotation(3f)
|
||||
.setDuration(50)
|
||||
.withEndAction(() -> {
|
||||
view.animate()
|
||||
.rotation(0f)
|
||||
.setDuration(50)
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de pop-in (apparition avec scale)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
*/
|
||||
public static void popIn(View view, int duration) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setScaleX(0f);
|
||||
view.setScaleY(0f);
|
||||
view.setAlpha(0f);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.animate()
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.alpha(1f)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(OVERSHOOT)
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime une vue avec un effet de pop-out (disparition avec scale)
|
||||
*
|
||||
* @param view La vue à animer
|
||||
* @param duration Durée de l'animation
|
||||
* @param endAction Action à exécuter après l'animation
|
||||
*/
|
||||
public static void popOut(View view, int duration, Runnable endAction) {
|
||||
if (view == null) return;
|
||||
|
||||
view.animate()
|
||||
.scaleX(0f)
|
||||
.scaleY(0f)
|
||||
.alpha(0f)
|
||||
.setDuration(duration)
|
||||
.setInterpolator(new android.view.animation.AccelerateInterpolator())
|
||||
.withEndAction(endAction)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
@@ -1,88 +1,40 @@
|
||||
package com.example.boidelov3;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.example.boidelov3.BuildConfig;
|
||||
import com.impossibl.postgres.api.jdbc.PGConnection;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Gère la connexion à la base de données PostgreSQL de manière asynchrone.
|
||||
* Remplace l'obsolète AsyncTask par ExecutorService + Handler.
|
||||
*
|
||||
* SECURITY WARNING: This class is currently disabled because database credentials
|
||||
* should NEVER be stored in BuildConfig or in the app code.
|
||||
*
|
||||
* Recommended secure alternatives:
|
||||
* 1. Use a backend API that handles database connections
|
||||
* 2. Use Android Keystore for storing encrypted credentials locally
|
||||
* 3. Use Firebase Authentication or similar secure services
|
||||
*
|
||||
* To re-enable database functionality, implement one of the secure solutions above.
|
||||
*/
|
||||
public class DatabaseConnection {
|
||||
public class DatabaseConnection extends AsyncTask<Void, Void, PGConnection> {
|
||||
private static final String DB_URL = "jdbc:postgresql://82.65.214.214:5432/boidelo";
|
||||
private static final String USER = "Tux2543";
|
||||
private static final String PASSWORD = "6wa*teCnuxsG#grAc5HzC!Rh%#@c&";
|
||||
|
||||
// Database credentials are now disabled for security
|
||||
private static final String DB_URL = "";
|
||||
private static final String USER = "";
|
||||
private static final String PASSWORD = "";
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final Handler mainHandler;
|
||||
|
||||
public DatabaseConnection() {
|
||||
this.executorService = Executors.newSingleThreadExecutor();
|
||||
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface de callback pour le résultat de la connexion.
|
||||
*/
|
||||
public interface ConnectionCallback {
|
||||
void onSuccess(PGConnection connection);
|
||||
void onFailure(Exception error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connexion asynchrone à la base de données.
|
||||
*
|
||||
* @param callback Callback appelé sur le thread principal
|
||||
*/
|
||||
public void connectAsync(ConnectionCallback callback) {
|
||||
executorService.execute(() -> {
|
||||
@Override
|
||||
protected PGConnection doInBackground(Void... params) {
|
||||
PGConnection connection = null;
|
||||
Exception error = null;
|
||||
|
||||
try {
|
||||
connection = (PGConnection) DriverManager.getConnection(DB_URL, USER, PASSWORD);
|
||||
// Code de connexion à la base de données PostgreSQL
|
||||
String url = DB_URL;
|
||||
String username = USER;
|
||||
String password = PASSWORD;
|
||||
connection = (PGConnection) DriverManager.getConnection(url, username, password);
|
||||
} catch (SQLException e) {
|
||||
error = e;
|
||||
e.printStackTrace();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Retour sur le thread principal pour notifier le résultat
|
||||
final PGConnection finalConnection = connection;
|
||||
final Exception finalError = error;
|
||||
|
||||
mainHandler.post(() -> {
|
||||
if (finalError == null && finalConnection != null) {
|
||||
callback.onSuccess(finalConnection);
|
||||
protected void onPostExecute(Connection connection) {
|
||||
// Traitez le résultat de la connexion ici
|
||||
if (connection != null) {
|
||||
// Connexion réussie
|
||||
} else {
|
||||
callback.onFailure(finalError != null ? finalError : new SQLException("Connection failed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ferme l'executorService pour libérer les ressources.
|
||||
* À appeler dans onDestroy() de l'Activity.
|
||||
*/
|
||||
public void shutdown() {
|
||||
executorService.shutdown();
|
||||
// Échec de la connexion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
package com.example.boidelov3;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.example.boidelov3.data.PlayerStats;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Activité de fin de partie
|
||||
* Affiche un résumé de la partie et permet de rejouer ou retourner à l'accueil
|
||||
*/
|
||||
public class EndGameActivity extends AppCompatActivity {
|
||||
|
||||
// Vues
|
||||
private TextView questionsPlayedValue;
|
||||
private TextView playersCountValue;
|
||||
private TextView gorgeesTotalValue;
|
||||
private TextView plusBuValue;
|
||||
private TextView plusDistribueValue;
|
||||
private MaterialButton homeButton;
|
||||
private MaterialButton replayButton;
|
||||
|
||||
// Données
|
||||
private int questionsPlayed;
|
||||
private int playersCount;
|
||||
private ArrayList<String> players;
|
||||
private ArrayList<PlayerStats> playerStatsList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_end_game);
|
||||
|
||||
// Initialiser les vues
|
||||
initViews();
|
||||
|
||||
// Récupérer les données de la partie
|
||||
getGameData();
|
||||
|
||||
// Afficher les statistiques
|
||||
displayStats();
|
||||
|
||||
// Configurer les boutons
|
||||
setupButtons();
|
||||
|
||||
// Animations d'entrée
|
||||
animateEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise toutes les vues
|
||||
*/
|
||||
private void initViews() {
|
||||
questionsPlayedValue = findViewById(R.id.questionsPlayedValue);
|
||||
playersCountValue = findViewById(R.id.playersCountValue);
|
||||
gorgeesTotalValue = findViewById(R.id.gorgeesTotalValue);
|
||||
plusBuValue = findViewById(R.id.plusBuValue);
|
||||
plusDistribueValue = findViewById(R.id.plusDistribueValue);
|
||||
homeButton = findViewById(R.id.homeButton);
|
||||
replayButton = findViewById(R.id.replayButton);
|
||||
|
||||
// Appliquer les animations aux boutons
|
||||
BoideloAnimationUtils.applyButtonPressAnimation(homeButton);
|
||||
BoideloAnimationUtils.applyButtonPressAnimation(replayButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les données de la partie terminée
|
||||
*/
|
||||
private void getGameData() {
|
||||
// Récupérer les données depuis l'intent
|
||||
questionsPlayed = getIntent().getIntExtra("EXTRA_QUESTIONS_PLAYED", 0);
|
||||
playersCount = getIntent().getIntExtra("EXTRA_PLAYERS_COUNT", 0);
|
||||
players = getIntent().getStringArrayListExtra("EXTRA_PLAYERS");
|
||||
|
||||
// Récupérer les statistiques des joueurs
|
||||
playerStatsList = getIntent().getParcelableArrayListExtra("PLAYER_STATS");
|
||||
|
||||
// Si pas de données, utiliser les SharedPreferences
|
||||
if (questionsPlayed == 0) {
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("game_stats", Context.MODE_PRIVATE);
|
||||
questionsPlayed = prefs.getInt("questions_played", 0);
|
||||
playersCount = prefs.getInt("players_count", 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche les statistiques de la partie
|
||||
*/
|
||||
private void displayStats() {
|
||||
// Animer les chiffres
|
||||
animateValue(questionsPlayedValue, 0, questionsPlayed, 1000);
|
||||
animateValue(playersCountValue, 0, playersCount, 1000);
|
||||
|
||||
// Calculer et afficher les statistiques détaillées
|
||||
if (playerStatsList != null && !playerStatsList.isEmpty()) {
|
||||
// Calculer le total des gorgées bues
|
||||
int totalGorgeesBuves = 0;
|
||||
PlayerStats biggestDrinker = null;
|
||||
int maxGorgeesBuves = -1;
|
||||
|
||||
PlayerStats biggestDistributor = null;
|
||||
int maxGorgeesDistribuees = -1;
|
||||
|
||||
for (PlayerStats stats : playerStatsList) {
|
||||
totalGorgeesBuves += stats.getGorgeesBuves();
|
||||
|
||||
// Trouver le plus gros buveur
|
||||
if (stats.getGorgeesBuves() > maxGorgeesBuves) {
|
||||
maxGorgeesBuves = stats.getGorgeesBuves();
|
||||
biggestDrinker = stats;
|
||||
}
|
||||
|
||||
// Trouver le plus gros distributeur
|
||||
if (stats.getGorgeesDistribuees() > maxGorgeesDistribuees) {
|
||||
maxGorgeesDistribuees = stats.getGorgeesDistribuees();
|
||||
biggestDistributor = stats;
|
||||
}
|
||||
}
|
||||
|
||||
// Afficher le total des gorgées bues
|
||||
animateValue(gorgeesTotalValue, 0, totalGorgeesBuves, 1000);
|
||||
|
||||
// Afficher le joueur qui a le plus bu
|
||||
if (biggestDrinker != null && biggestDrinker.getGorgeesBuves() > 0) {
|
||||
plusBuValue.setText(biggestDrinker.getPlayerName() + " (" + biggestDrinker.getGorgeesBuves() + ")");
|
||||
} else {
|
||||
plusBuValue.setText("Personne");
|
||||
}
|
||||
|
||||
// Afficher le joueur qui a le plus distribué
|
||||
if (biggestDistributor != null && biggestDistributor.getGorgeesDistribuees() > 0) {
|
||||
plusDistribueValue.setText(biggestDistributor.getPlayerName() + " (" + biggestDistributor.getGorgeesDistribuees() + ")");
|
||||
} else {
|
||||
plusDistribueValue.setText("Personne");
|
||||
}
|
||||
} else {
|
||||
// Pas de stats disponibles
|
||||
gorgeesTotalValue.setText("0");
|
||||
plusBuValue.setText("Personne");
|
||||
plusDistribueValue.setText("Personne");
|
||||
}
|
||||
|
||||
// Afficher un message de félicitations
|
||||
showCongratulationMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message de félicitations selon le nombre de questions
|
||||
*/
|
||||
private void showCongratulationMessage() {
|
||||
// Le message pourrait être personnalisé selon les performances
|
||||
// Pour l'instant, on utilise le titre par défaut du layout
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure les boutons
|
||||
*/
|
||||
private void setupButtons() {
|
||||
homeButton.setOnClickListener(v -> {
|
||||
BoideloAnimationUtils.triggerHapticFeedback(this);
|
||||
goToHome();
|
||||
});
|
||||
|
||||
replayButton.setOnClickListener(v -> {
|
||||
BoideloAnimationUtils.triggerSuccessHaptic(this);
|
||||
replay();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne à l'écran d'accueil (hub de jeux)
|
||||
*/
|
||||
private void goToHome() {
|
||||
Intent intent = new Intent(this, com.example.boidelov3.hub.GameSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Relance une nouvelle partie (retourne au hub)
|
||||
*/
|
||||
private void replay() {
|
||||
Intent intent = new Intent(this, com.example.boidelov3.hub.GameSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime l'entrée des éléments
|
||||
*/
|
||||
private void animateEntry() {
|
||||
// Animation du trophée
|
||||
View trophyIcon = findViewById(R.id.trophyIcon);
|
||||
BoideloAnimationUtils.scale(trophyIcon, 1.0f, 400);
|
||||
|
||||
// Animation fade-in pour le contenu
|
||||
View titleText = findViewById(R.id.titleText);
|
||||
View subtitleText = findViewById(R.id.subtitleText);
|
||||
|
||||
BoideloAnimationUtils.fadeIn(titleText, 600);
|
||||
BoideloAnimationUtils.fadeIn(subtitleText, 800);
|
||||
}
|
||||
|
||||
/**
|
||||
* Anime un chiffre de 0 à la valeur cible
|
||||
*/
|
||||
private void animateValue(TextView textView, int start, int end, int duration) {
|
||||
if (textView == null) return;
|
||||
|
||||
android.animation.ValueAnimator animator = android.animation.ValueAnimator.ofInt(start, end);
|
||||
animator.setDuration(duration);
|
||||
animator.setInterpolator(new DecelerateInterpolator());
|
||||
|
||||
animator.addUpdateListener(animation -> {
|
||||
int value = (int) animation.getAnimatedValue();
|
||||
textView.setText(String.valueOf(value));
|
||||
});
|
||||
|
||||
animator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Retourner à l'accueil au lieu de revenir au jeu
|
||||
goToHome();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,13 +8,9 @@ import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import android.widget.EditText;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
@@ -23,8 +19,6 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.utils.ErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -37,14 +31,11 @@ import okhttp3.Response;
|
||||
|
||||
public class JeuxParametres extends AppCompatActivity {
|
||||
|
||||
private SeekBar seekBar1, seekBar2, seekBar3, seekBarDuration;
|
||||
private TextView textView1, textView2, textView5, textViewRatioGen, questionCountValue, gorgeesValue, durationValue;
|
||||
private SwitchMaterial checkBoxGPT;
|
||||
private Button buttonTestApi;
|
||||
private SeekBar seekBar1, seekBar2, seekBar3;
|
||||
private TextView textView1, textView2, textView5, textViewRatioGen;
|
||||
private CheckBox checkBox, checkBoxGPT;
|
||||
private EditText editText, editTextKeyGPT;
|
||||
private AutoCompleteTextView autoCompleteProvider;
|
||||
private String keyGPT;
|
||||
private OpenAIService.AIProvider selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
private int nbQuestions;
|
||||
|
||||
private List<String> toutlesjoueurs;
|
||||
@@ -60,71 +51,13 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
seekBar1 = findViewById(R.id.seekBar1);
|
||||
seekBar2 = findViewById(R.id.seekBar2);
|
||||
seekBar3 = findViewById(R.id.seekBar3);
|
||||
seekBarDuration = findViewById(R.id.seekBarDuration);
|
||||
textView1 = findViewById(R.id.textView1);
|
||||
textView2 = findViewById(R.id.textView2);
|
||||
textView5 = findViewById(R.id.textView5);
|
||||
editTextKeyGPT = findViewById(R.id.editTextGPT);
|
||||
autoCompleteProvider = findViewById(R.id.autoCompleteProvider);
|
||||
buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
textViewRatioGen = findViewById(R.id.textViewRatioGen);
|
||||
questionCountValue = findViewById(R.id.questionCountValue);
|
||||
gorgeesValue = findViewById(R.id.gorgeesValue);
|
||||
durationValue = findViewById(R.id.durationValue);
|
||||
|
||||
// Configuration du dropdown pour le provider IA
|
||||
String[] providers = new String[]{
|
||||
OpenAIService.AIProvider.OPENAI.getDisplayName(),
|
||||
OpenAIService.AIProvider.OPENROUTER.getDisplayName(),
|
||||
OpenAIService.AIProvider.ZAI.getDisplayName()
|
||||
};
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, providers);
|
||||
autoCompleteProvider.setAdapter(adapter);
|
||||
|
||||
// Charger le provider sauvegardé
|
||||
SharedPreferences providerPrefs = getSharedPreferences("MyPrefs", MODE_PRIVATE);
|
||||
String savedProvider = providerPrefs.getString("aiProvider", OpenAIService.AIProvider.OPENAI.getDisplayName());
|
||||
autoCompleteProvider.setText(savedProvider, false);
|
||||
|
||||
// Définir le provider sélectionné
|
||||
if (savedProvider.equals(OpenAIService.AIProvider.OPENROUTER.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENROUTER;
|
||||
} else if (savedProvider.equals(OpenAIService.AIProvider.ZAI.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.ZAI;
|
||||
} else {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
}
|
||||
|
||||
// Listener pour le changement de provider
|
||||
autoCompleteProvider.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
String selected = (String) parent.getItemAtPosition(position);
|
||||
SharedPreferences.Editor editor = providerPrefs.edit();
|
||||
editor.putString("aiProvider", selected);
|
||||
editor.apply();
|
||||
|
||||
if (selected.equals(OpenAIService.AIProvider.OPENROUTER.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENROUTER;
|
||||
} else if (selected.equals(OpenAIService.AIProvider.ZAI.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.ZAI;
|
||||
} else {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialiser les TextView avec les valeurs par défaut
|
||||
int initialQuestions = 50;
|
||||
int initialGorgees = 0;
|
||||
int initialRatio = 8;
|
||||
int initialDuration = 0; // 0 pour avoir 4-10 tours par défaut (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
questionCountValue.setText(String.valueOf(initialQuestions));
|
||||
gorgeesValue.setText(String.valueOf(initialGorgees));
|
||||
durationValue.setText("0"); // Afficher 0 par défaut pour avoir 4-10 tours
|
||||
textView5.setText("Palier : Grosse merde");
|
||||
textViewRatioGen.setText("Ratio BDD/OPENAI : 1/" + initialRatio);
|
||||
Button buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
|
||||
// Configuration de la seekBar1
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -145,13 +78,6 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
seekBar3.setMax(15);
|
||||
seekBar3.setProgress(8);
|
||||
|
||||
// Configuration de la seekBarDuration (permet valeurs négatives pour offset)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBarDuration.setMin(-5); // Permet un offset négatif jusqu'à -5
|
||||
}
|
||||
seekBarDuration.setMax(15);
|
||||
seekBarDuration.setProgress(0); // Valeur par défaut à 0 pour avoir 4-10 tours (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
// Configuration des listeners pour les seekBars
|
||||
seekBar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
@@ -159,7 +85,7 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
// Ajustement de la valeur au multiple de 10 le plus proche
|
||||
int adjustedProgress = Math.round(progress / 10) * 10;
|
||||
seekBar.setProgress(adjustedProgress);
|
||||
questionCountValue.setText(String.valueOf(adjustedProgress));
|
||||
textView1.setText("Nombre de questions avant la fin de partie : " + adjustedProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,8 +100,8 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Mise à jour du gorgeesValue en fonction de la valeur de la seekBar2
|
||||
gorgeesValue.setText(String.valueOf(progress));
|
||||
// Mise à jour du textView2 en fonction de la valeur de la seekBar2
|
||||
textView2.setText("Ajout de gorgées : " + progress);
|
||||
// Mise à jour du textView5 en fonction de la valeur de la seekBar2
|
||||
switch (progress) {
|
||||
case 0:
|
||||
@@ -232,26 +158,16 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration du checkBox // Q : IL sert à quoi ?
|
||||
// Configuration du checkBox // Q : IL sert a quoi ?
|
||||
// R : Il sert à activer/désactiver les vues en dessous
|
||||
|
||||
Button buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
|
||||
checkBoxGPT = findViewById(R.id.checkBoxGPT);
|
||||
checkBoxGPT.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
checkBox = findViewById(R.id.checkBoxGPT);
|
||||
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
// Activation/désactivation des vues en fonction de l'état du checkBox
|
||||
autoCompleteProvider.setEnabled(isChecked);
|
||||
// Pour le champ API key : on garde le layout activé pour le toggle password,
|
||||
// mais on désactive l'édition du texte
|
||||
editTextKeyGPT.setFocusable(isChecked);
|
||||
editTextKeyGPT.setFocusableInTouchMode(isChecked);
|
||||
editTextKeyGPT.setClickable(isChecked);
|
||||
editTextKeyGPT.setCursorVisible(isChecked);
|
||||
if (!isChecked) {
|
||||
editTextKeyGPT.clearFocus();
|
||||
}
|
||||
editTextKeyGPT.setEnabled(isChecked);
|
||||
//editText.setEnabled(isChecked);
|
||||
textViewRatioGen.setEnabled(isChecked);
|
||||
seekBar3.setEnabled(isChecked);
|
||||
buttonTestApi.setEnabled(isChecked);
|
||||
@@ -274,30 +190,8 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration de la seekBarDuration
|
||||
seekBarDuration.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Afficher avec signe +/- pour bien voir l'offset, mais sans signe pour 0
|
||||
String displayValue;
|
||||
if (progress > 0) {
|
||||
displayValue = "+" + progress;
|
||||
} else {
|
||||
displayValue = String.valueOf(progress); // Affiche "0" ou "-X"
|
||||
}
|
||||
durationValue.setText(displayValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
// Partie OpenAI : enregistrement de la clé en dur.
|
||||
checkBoxGPT = findViewById(R.id.checkBoxGPT);
|
||||
// Récupérer une instance des SharedPreferences
|
||||
SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE);
|
||||
final SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
@@ -330,99 +224,51 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
}
|
||||
|
||||
public void onClickButtonTestAPI(View view) {
|
||||
|
||||
String apiKey = editTextKeyGPT.getText().toString();
|
||||
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
Toast.makeText(this, "Veuillez entrer une clé API", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Créer un client OkHttpClient pour effectuer la requête
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.build();
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
// Déterminer l'URL, le modèle et le format selon le provider sélectionné
|
||||
String testUrl;
|
||||
String testModel;
|
||||
String jsonBody;
|
||||
boolean isAnthropicFormat = (selectedProvider == OpenAIService.AIProvider.ZAI);
|
||||
|
||||
switch (selectedProvider) {
|
||||
case OPENROUTER:
|
||||
testUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||
testModel = "openai/gpt-3.5-turbo";
|
||||
// Format OpenAI
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
case ZAI:
|
||||
testUrl = "https://api.z.ai/v1/messages";
|
||||
testModel = "claude-3-5-sonnet";
|
||||
// Format Anthropic
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
case OPENAI:
|
||||
default:
|
||||
testUrl = "https://api.openai.com/v1/chat/completions";
|
||||
testModel = "gpt-3.5-turbo";
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
}
|
||||
|
||||
// Construire la requête
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(testUrl)
|
||||
.addHeader("Content-Type", "application/json");
|
||||
|
||||
// Ajouter les headers selon le provider
|
||||
switch (selectedProvider) {
|
||||
case OPENAI:
|
||||
case OPENROUTER:
|
||||
requestBuilder.addHeader("Authorization", "Bearer " + apiKey);
|
||||
break;
|
||||
case ZAI:
|
||||
requestBuilder.addHeader("x-api-key", apiKey);
|
||||
requestBuilder.addHeader("anthropic-version", "2023-06-01");
|
||||
break;
|
||||
}
|
||||
|
||||
// Headers spécifiques pour OpenRouter
|
||||
if (selectedProvider == OpenAIService.AIProvider.OPENROUTER) {
|
||||
requestBuilder.addHeader("HTTP-Referer", "https://boidelo.app");
|
||||
requestBuilder.addHeader("X-Title", "Boidelo");
|
||||
}
|
||||
|
||||
Request request = requestBuilder
|
||||
.post(okhttp3.RequestBody.create(jsonBody, okhttp3.MediaType.get("application/json")))
|
||||
// Construire la requête d'essai vers l'API
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.openai.com/v1/engines/davinci") // Endpoint d'essai, vous pouvez le modifier selon vos besoins
|
||||
.header("Authorization", "Bearer " + apiKey) // Ajouter la clé API dans l'en-tête de la requête
|
||||
.build();
|
||||
|
||||
// Exécuter la requête de test
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, IOException e) {
|
||||
String operation = "Test de connexion API " + selectedProvider.getDisplayName();
|
||||
String details = "Échec de connexion lors du test de l'API";
|
||||
ErrorHandler.logErrorOnly("JeuxParametres", operation + " - " + details, e);
|
||||
runOnUiThread(() -> {
|
||||
String userMessage = "Échec de connexion " + selectedProvider.getDisplayName() + " : " + e.getMessage();
|
||||
Toast.makeText(getApplicationContext(), userMessage, Toast.LENGTH_SHORT).show();
|
||||
// Gérer les erreurs de requête
|
||||
e.printStackTrace();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), "Échec de la communication avec l'API !", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
// Vérifier le code de réponse de la requête
|
||||
if (response.isSuccessful()) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Connexion " + selectedProvider.getDisplayName() + " réussie !",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
// La clé API est valide et l'API a répondu avec succès
|
||||
// Vous pouvez effectuer d'autres opérations ici
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), "Communication avec l'API réussie !", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Erreur " + selectedProvider.getDisplayName() + " (HTTP " + response.code() + ")",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
// La clé API est invalide ou il y a eu une erreur de communication avec l'API
|
||||
System.out.println("Échec de la communication avec l'API !");
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), "Échec de la communication avec l'API !", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
response.close();
|
||||
@@ -435,7 +281,7 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
int nombreQuestions = seekBar1.getProgress();
|
||||
int ajoutGorgees = seekBar2.getProgress();
|
||||
int ratioBddOpenAI = seekBar3.getProgress();
|
||||
int durationDefis = seekBarDuration.getProgress();
|
||||
checkBoxGPT = findViewById(R.id.checkBoxGPT);
|
||||
boolean openAI = checkBoxGPT.isChecked();
|
||||
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
@@ -467,11 +313,9 @@ public class JeuxParametres extends AppCompatActivity {
|
||||
intent.putExtra("EXTRA_NOMBRE_QUESTIONS", nombreQuestions);
|
||||
intent.putExtra("EXTRA_AJOUT_GORGEE", ajoutGorgees);
|
||||
intent.putExtra("EXTRA_RATIO_OPENAI", ratioBddOpenAI);
|
||||
intent.putExtra("EXTRA_DURATION_DEFIS", durationDefis);
|
||||
intent.putExtra("EXTRA_OPENAI", openAI);
|
||||
final EditText editText = findViewById(R.id.editTextGPT);
|
||||
intent.putExtra("EXTRA_KEY_OPENAI",editText.getText().toString() );
|
||||
intent.putExtra("EXTRA_AI_PROVIDER", selectedProvider.name());
|
||||
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
intent.putStringArrayListExtra("EXTRA_LIST_JOUEUR", (ArrayList<String>) toutlesjoueurs);
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
package com.example.boidelov3;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class Jeuxold extends AppCompatActivity {
|
||||
private List<String> toutlesjoueurs, phraseGPT;
|
||||
private int nombreQuestions;
|
||||
private int ajoutGorgees;
|
||||
boolean openAI;
|
||||
int ratiOpenai;
|
||||
String keyOpenai, phraseGPTString;
|
||||
|
||||
|
||||
public Jeuxold() {
|
||||
//System.out.println("Je suis dans le constructeur jeux");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_jeux);
|
||||
//Recuperation des valeurs des activités précédentes
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
nombreQuestions = getIntent().getIntExtra("EXTRA_NOMBRE_QUESTIONS", 75);
|
||||
ajoutGorgees = getIntent().getIntExtra("EXTRA_AJOUT_GORGEE", 0);
|
||||
openAI = getIntent().getBooleanExtra("EXTRA_OPENAI", false);
|
||||
ratiOpenai = getIntent().getIntExtra("EXTRA_RATIO_OPENAI", 0);
|
||||
keyOpenai = getIntent().getStringExtra("EXTRA_KEY_OPENAI");
|
||||
|
||||
|
||||
|
||||
System.out.println("ACTJeux all player : " + toutlesjoueurs);
|
||||
System.out.println("ACTJeux nombre de questions : " + nombreQuestions);
|
||||
System.out.println("ACTJeux ajout de gorgées : " + ajoutGorgees);
|
||||
System.out.println("ACTJeux openAI : " + openAI);
|
||||
System.out.println("ACTJeux ratio openAI : " + ratiOpenai);
|
||||
System.out.println("ACTJeux key openAI : " + keyOpenai);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//Parti OpenAI ; keyOpenai ; ratiOpenai, openAI
|
||||
//new DatabaseConnection().execute();
|
||||
|
||||
// if(openAI) {
|
||||
// ChatGPTTask chatGPTTask = new ChatGPTTask( this, keyOpenai);
|
||||
// chatGPTTask.execute();
|
||||
//
|
||||
// }
|
||||
|
||||
//Phrase avec nom ou pas?
|
||||
/* if(JoueurOuPas()){
|
||||
PhraseAvecNom(toutlesjoueurs);
|
||||
}else{
|
||||
PhraseSansNom();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
public void handleExtractedMessage(String phraseGPTString) {
|
||||
// Traitez la réponse extraite ici
|
||||
System.out.println(phraseGPTString);
|
||||
// Par exemple, affichez-la dans une TextView ou effectuez une action en fonction de la réponse
|
||||
}
|
||||
|
||||
public void navigateToJeuxParametres() {
|
||||
Intent intent = new Intent(Jeuxold.this, JeuxParametres.class);
|
||||
Toast.makeText(getApplicationContext(), "Échec de la communication avec l'API !", Toast.LENGTH_SHORT).show();
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
/*public void PhraseAvecNom(List toutlesjoueurs){
|
||||
//System.out.println("Je suis dans phrase avec pseudo");
|
||||
List<String> phraseAvecNom = new ArrayList<String>();
|
||||
List aleatoirejoueurs = TroisJoueurAleatoire(toutlesjoueurs);
|
||||
phraseAvecNom.add(ChoixJoueurAleatoire(toutlesjoueurs) + " dois boire " + Gorgeesaleatoire(2, 4)+ " Gorgées");
|
||||
phraseAvecNom.add(ChoixJoueurAleatoire((toutlesjoueurs))+ " est le vieux briscard ! Interdiction de montrer tes dents pendant 5 manches");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0) + " et "+ aleatoirejoueurs.get(1) +" liser le premier SMS qui s'affiche quand on tape désolé dans la barre de recherche. Refusez pour 5 gorgées");
|
||||
phraseAvecNom.add( "A tour de role, vous avez exactement 3 secondes pour donner un mot en rapport avec le mots dit precedemment. Le joueur qui perd boit "+ Gorgeesaleatoire(2, 4) + " Gorgées! "+ aleatoirejoueurs.get(2)+" tu commences en choissisant un mot.");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+ " defie "+ aleatoirejoueurs.get(1) + " au chifoumi ! Le joueur qui gagne distribue 5 Gorgées");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+ " a toi de juger : entre "+aleatoirejoueurs.get(1)+ " et "+ aleatoirejoueurs.get(2) + " qui stresse le plus pour un rien selon toi? Cette personne se detendra en buvant " + Gorgeesaleatoire(3, 5 ) + " Gorgées");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" est dans le futur ! Tu dois parler au futur pendant 4 tours Une gorgées a chaque manque.");
|
||||
phraseAvecNom.add("Les joueurs de Counter Strike peuvent distribuer" + GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" tu bois autant de gorgées que tu as d'années d'études après le BAC");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+aleatoirejoueurs.get(1)+" ferment leurs yeux ! Ils/Elles doivent deviner la couleur des yeux de l'autre. Si ils/elles se trompent, c'est "+GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" est manchot ! Il/Elle ne peut plus utiliser ses doigts durant 3 tours . Si il/elle s'en sert, il/elle devra boire autant de gorgées qu'il/elle a utilisé de doigts");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+ aleatoirejoueurs.get(1)+" , si vous êtes ensemble dans la vraie vie, vous pouvez distribuer 2 gorgées , autrement buvez-les");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", donne le nombre d'habitant du Tadjikistant ( à 1 000 000 près) ou boit "+GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" a la tourette ! A chaque fois que tu bois une gorgée, tu dois CRIER une insulte. C'est un stade avancé, ça dure 3 tours");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", donne la couleur préférée de "+aleatoirejoueurs.get(1)+" si tu te trompes, c'est 2 gorgées");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" à l'oeil de serpent pendant 5 tours ! Dès qu'un joueur te regarde dans les yeux, il/elle boit. Si personne ne t'as regardé tu bois"+GorgeesaleatoireAmeliorer(5, 9));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+ aleatoirejoueurs.get(1)+"se mesurent ! Le plus petit peut bois"+GorgeesaleatoireAmeliorer(3, 5));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" doit terminer toutes ses phrases par - C'est clair pendant 7 tours");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" distribue"+GorgeesaleatoireAmeliorer(2,5)+" à la personne que tu trouves la mieux foutue");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" distribue"+GorgeesaleatoireAmeliorer(2,5)+" à qui tu veux.");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+aleatoirejoueurs.get(1)+" se défient au 'je te tiens, tu me tiens', le premier qui rit sera une tapette, et devra boire"+GorgeesaleatoireAmeliorer(4,6));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+aleatoirejoueurs.get(1)+"n'ont plus le droit d'utiliser leur téléphone jusqu'à la fin du jeu ! A chaque manque c'est"+GorgeesaleatoireAmeliorer(1,3));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+aleatoirejoueurs.get(1)+ "racontent une anecdote, celui/celle qui sort la plus banale boit "+GorgeesaleatoireAmeliorer(3, 6));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", pour"+GorgeesaleatoireAmeliorer(2,4)+", a qui est ce slogan? Y a pas plus fort. (Vigor)");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", Vrai ou faux? L'eau est bleu car elle reflète le ciel? (Non) Si tu as repondu faux tu devras boire : "+GorgeesaleatoireAmeliorer(2,4));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", Si on te dit Marco? ... Si tu as dis Polo tu bois "+GorgeesaleatoireAmeliorer(1,3));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", Boire un café fait baisser le taux d'alcool? "+GorgeesaleatoireAmeliorer(5, 8)+"en jeu (FAUX)");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" est l'aigris pendant 5tours ! Dès que tu souris ou rigoles, tu bois "+GorgeesaleatoireAmeliorer(2,3));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" fait un geste, le suivant répète et en ajoute un. Le perdant boit"+GorgeesaleatoireAmeliorer(3,5));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", "+aleatoirejoueurs.get(2)+" et "+aleatoirejoueurs.get(1)+" vont désigner quelqu'un qui doit terminer son verre ");
|
||||
phraseAvecNom.add("Récitez l'alphabet en énonçant une lettre à tour de rôle. Si "+aleatoirejoueurs.get(0)+" finit son verre avant, cul sec pour tout le monde !");
|
||||
phraseAvecNom.add("Si"+aleatoirejoueurs.get(0)+" arrive a finir son verre en moins de 5 secondes, il/elle peut distribuer"+ GorgeesaleatoireAmeliorer(5, 8));
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" et "+ aleatoirejoueurs.get(1)+"sont lies, si l'un boit alors l'autre aussi, et ce pendant 5 tours");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+", "+aleatoirejoueurs.get(2)+" et "+ aleatoirejoueurs.get(1)+"sont lies, si l'un boit alors les autres aussi, et ce pendant 5 tours");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" dit un mot, la personne suivante le répète et en ajoute un nouveau, ainsi de suite jusqu'a ce que quelqu'un se trompe. Le perdant boit autant de gorgées qu'il y a eu de personne avant lui");
|
||||
phraseAvecNom.add(aleatoirejoueurs.get(0)+" doit choisir un mot que tout le monde devra dire à chaque fois qu'une personne boit.");
|
||||
//phraseAvecNom.add(aleatoirejoueurs.get(0)+"");
|
||||
//phraseAvecNom.add(aleatoirejoueurs.get(0)+"");
|
||||
//phraseAvecNom.add(aleatoirejoueurs.get(0)+"");
|
||||
//Affichage :
|
||||
TextView textView1 = (TextView) findViewById(R.id.textView1);
|
||||
textView1.setText(Nbaleatoirelist(phraseAvecNom));
|
||||
}
|
||||
public void PhraseSansNom(){
|
||||
//System.out.println("Je suis dans phrase sans pseudo");
|
||||
List<String> phraseSansNom = new ArrayList<String>();
|
||||
//Ajout de defis
|
||||
phraseSansNom.add("Tout le monde boit "+ Gorgeesaleatoire(1, 2)+" gorgée(s)");
|
||||
phraseSansNom.add("Quand l'heure affichera un multiple de 10 (22h, 22h10 ...) le premier a crier \"merde j'ai oublié mon chat\" distribura " + Gorgeesaleatoire(10, 12)+ " Gorgées");
|
||||
phraseSansNom.add("Ceux qui ont dansé aujourd'hui boivent 4 gorgées");
|
||||
phraseSansNom.add("Bois "+ Gorgeesaleatoire(2, 6)+ " Gorgées si tu n'as pas ton veritable nom sur insta");
|
||||
phraseSansNom.add("Bois "+ Gorgeesaleatoire(2, 3)+ " Gorgées si tu a des photos sur insta.");
|
||||
phraseSansNom.add("Plutôt ne plus avoir de mains ou de jambes? les perdants boivent "+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celles/Ceux qui ont habité dans plus de 3 villes boivent "+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Vive la poésie ! Nos phrases doivent rimer sous peine d'une gorgée");
|
||||
phraseSansNom.add("Elisez le joueur le moins drôle d'entre vous, ce dernier boit" + GorgeesaleatoireAmeliorer(1,4 ));
|
||||
phraseSansNom.add("Elisez le joueur le plus drôle d'entre vous, ce dernier distribue" + GorgeesaleatoireAmeliorer(1,4 ));
|
||||
phraseSansNom.add("La dernière personne à avoir vomi en soirée distribue" + GorgeesaleatoireAmeliorer(2,4));
|
||||
phraseSansNom.add("Les filles peuvent distribuer"+ GorgeesaleatoireAmeliorer(1, 2));
|
||||
phraseSansNom.add("Les garçons peuvent distribuer"+ GorgeesaleatoireAmeliorer(1, 2));
|
||||
phraseSansNom.add("Toutes celles (ou ceux) qui ont du verni à ongles boivent"+GorgeesaleatoireAmeliorer(1,2));
|
||||
phraseSansNom.add("Tous les joueurs célibataires boivent"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Tous ceux qui ont des lunettes boivent"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Le premier joueur qui arrive à mettre son doigt dans le nez d'un autre joueur peut distribuer"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Tous ceux qui ont déjà triché à un examen boivent "+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Plutôt avoir un tapis volant, ou un frigo qui se remplit tout seul ? Votez tous en même temps. La minorité boit "+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Les couples trinquer ensemble "+ GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Le/La plus radin(e) boit"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Le mec qui a le plus gros ventre à bière boit"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Tous ceux qui se sont déjà fait exclure de cours boivent"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Tous ceux qui ont des frères et soeurs boivent"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celles et ceux qui ont un Windows phone peuvent distribuer"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celles/Ceux qui se sont déjà battus boivent"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celui/Celle qui pèse le plus lourd boit "+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Pour se décoincer, le/la plus timide boit"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Le/La plus jeune boit"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Plutôt avoir du temps ou de l'argent ? Votez tous en même temps. La minorité boit"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celles/Ceux qui ont fait des études de L boivent"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Le premier joueur qui en embrasse un autre sur la bouche pourra distribuer"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celles et ceux qui joue de la guitare peuvent distribuer"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Celles et ceux qui joue du piano peuvent distribuer"+GorgeesaleatoireAmeliorer(1,4));
|
||||
phraseSansNom.add("Les gens qui se sont masturbés aujourd'hui peuvent distribuer"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Celui ou celle a la meilleure place boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Celles et ceux qui n'ont jamais trompé leur partenaire (c'est bien) peuvent distribuer"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Celui/Celle avec les vêtements les plus moches boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Celui/Celle qui a les cheveux les plus longs boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("On doit doser son Alcool les yeux fermés"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Plutôt série ou film ? Votez tous en même temps. La minorité boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Elisez le plus débile d'entre vous, ce dernier boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Le premier qui donne un film de - Christopher Nolan - pourra distribuer"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Le premier qui donne un film avec Christian Clavier pourra boire"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Les végans boivent "+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("La fille la plus maquillé boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Celles/Ceux qui ont déjà appelé leur partenaire par le prénom de leurs ex boivent"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("La première personne qui désigne le plus jeune peut distribuer"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Plutôt avoir des connaissances illimitées ou dirigier le monde ? Votez tous en même temps. La minorité boit"+GorgeesaleatoireAmeliorer(1, 4));
|
||||
phraseSansNom.add("Plutôt n'avoir aucun ami ou ne plus pouvoir utiliser d'appareil électronique ? Votez tous en même temps. La minorité boit"+ GorgeesaleatoireAmeliorer(2, 5));
|
||||
phraseSansNom.add("Plutot vaincre le patrikaka ou la polution dans le monde? Votez tous en meme temps. La minorité boit"+GorgeesaleatoireAmeliorer(1, 2));
|
||||
phraseSansNom.add("Jeu du LUTIN : Jusqu'a la fin du jeu. Vous devez enlever le lutin de votre verre pour pouvoir boire et le remettre ensuite sinon vous devait reboire");
|
||||
phraseSansNom.add("Celles et ceux qui boivent de la Vodka peuvent distribuer "+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Les joueurs qui ont un A dans leur prénom boivent "+GorgeesaleatoireAmeliorer(3,5));
|
||||
phraseSansNom.add("Les joueurs qui ont un P dans le prénom distribue"+GorgeesaleatoireAmeliorer(1, 3));
|
||||
phraseSansNom.add("Le premier joueur à ramener un objet rouge (pas de vêtements) peut distribuer"+GorgeesaleatoireAmeliorer(3,5));
|
||||
phraseSansNom.add("Le premier joueur qui dévoile un de ses secrets et que personne autour ne sait peut distribuer"+ GorgeesaleatoireAmeliorer(3, 6));
|
||||
phraseSansNom.add("Chaque joueur doit lire à haute voix le dernier SMS qu'il a reçu. Si il/elle refuse, c'est"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Le joueur avec le plus gros cul boit"+ GorgeesaleatoireAmeliorer(2, 6));
|
||||
phraseSansNom.add("Celles/Ceux qui ont moins de 20ans boivent"+ GorgeesaleatoireAmeliorer(2, 7));
|
||||
phraseSansNom.add("Celui ou celle avec le plus gros appetit sexuel boit"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Ceux/Celles qui fumes boivent "+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Celles et ceux qui ont au moins un BAC +3 peuvent distribuer"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Le premier joueur à se lever peut donner"+ GorgeesaleatoireAmeliorer(6, 7));
|
||||
phraseSansNom.add("Celles et ceux qui n'ont jamais fait de strip tease boivent"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Le premier joueur à enlever un vêtements pourra distribuer"+ GorgeesaleatoireAmeliorer(5, 7));
|
||||
phraseSansNom.add("Jeu des peaux ! Triez vous du joueur le plus bronzé au joueur le moins bronzé. Le plus bronzé prend 1 gorgée, le second 2 gorgées, etc.");
|
||||
phraseSansNom.add("Tous ceux qui ont déjà uriné dans une piscine boivent"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Celui/Celle avec le plus d'amis sur Facebook boit"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Celui/Celle avec le nom de famille le plus compliqué boit"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Les joueurs qui n'ont pas encore distribué de gorgées boivent"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Plutôt avoir du pouvoir ou de la connaissance ? Votez tous en même temps. La minorité boit"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("le plus gros dalleux avec les filles boit"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Le premier joueur à donner l'heure pourra distribuer"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Celles et ceux qui ont déjà dépenser plus de 2000 euros en un achat peuvent distribuer"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Le mec le moins courageux boit "+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
phraseSansNom.add("Celles/Ceux qui rentre chez eux à la fin de la soirée boivent"+ GorgeesaleatoireAmeliorer(8, 12));
|
||||
phraseSansNom.add("Il est désormais interdit de se tutoyer");
|
||||
phraseSansNom.add("Toutes les règles existantes sont annulées");
|
||||
phraseSansNom.add("Celles et ceux dont le jour d'anniversaire est un nombre impair boivent"+ GorgeesaleatoireAmeliorer(2, 4));
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");
|
||||
//phraseSansNom.add("");*/
|
||||
|
||||
|
||||
//
|
||||
|
||||
//Affichage :
|
||||
TextView textView1 = (TextView) findViewById(R.id.textView1);
|
||||
//textView1.setText(Nbaleatoirelist(phraseSansNom));
|
||||
//}
|
||||
|
||||
public int Gorgeesaleatoire(int Min, int Max){
|
||||
int offset = ajoutGorgees;
|
||||
int nbgorgées;
|
||||
Random rand = new Random();
|
||||
if (Min == 1 && Max == 2){
|
||||
nbgorgées = rand.nextInt(Max + Min);
|
||||
}else {
|
||||
nbgorgées = Min+rand.nextInt(Max - Min);
|
||||
}
|
||||
if(nbgorgées == 0){
|
||||
nbgorgées = 1;
|
||||
}
|
||||
nbgorgées = nbgorgées + offset;
|
||||
return nbgorgées;
|
||||
}
|
||||
public String GorgeesaleatoireAmeliorer(int Min, int Max){
|
||||
int offset = ajoutGorgees;
|
||||
int nbgorgées;
|
||||
Random rand = new Random();
|
||||
if (Min == 1 && Max == 2){
|
||||
nbgorgées = rand.nextInt(Max + Min);
|
||||
}else {
|
||||
nbgorgées = Min+rand.nextInt(Max - Min);
|
||||
}
|
||||
if(nbgorgées == 0){
|
||||
nbgorgées = 1;
|
||||
}
|
||||
nbgorgées = nbgorgées + offset;
|
||||
String debut;
|
||||
|
||||
String nbgorgéesstr;
|
||||
String nbgorgéesstr1;
|
||||
debut = " ";
|
||||
nbgorgéesstr1 = " Gorgée(s)";
|
||||
nbgorgéesstr = debut + Integer.toString(nbgorgées) + nbgorgéesstr1;
|
||||
return nbgorgéesstr;
|
||||
}
|
||||
|
||||
public String Nbaleatoirelist(List list){
|
||||
Random rand = new Random();
|
||||
String phrase = (String) list.get(rand.nextInt(list.size()));
|
||||
return phrase;
|
||||
}
|
||||
public int Nbaleatoire(){
|
||||
int Max = 100;
|
||||
int Min = 0;
|
||||
Random rand = new Random();
|
||||
int nbaleatoire = rand.nextInt(Max - Min);
|
||||
return nbaleatoire;
|
||||
}
|
||||
public boolean JoueurOuPas(){
|
||||
boolean TrueFalse;
|
||||
int nbaleatoire = Nbaleatoire();
|
||||
int pourcentage = 40;
|
||||
//System.out.println(nbaleatoire);
|
||||
if(nbaleatoire >= pourcentage){
|
||||
TrueFalse = false;}
|
||||
else{
|
||||
TrueFalse = true;
|
||||
}
|
||||
//System.out.println(TrueFalse);
|
||||
return TrueFalse;
|
||||
}
|
||||
public List TroisJoueurAleatoire(List toutlesjoueurs){
|
||||
List<String> listJoueur = new ArrayList<String>();
|
||||
while (true){
|
||||
Random rand = new Random();
|
||||
String joueur1 = (String) toutlesjoueurs.get(rand.nextInt(toutlesjoueurs.size()));
|
||||
String joueur2 = (String) toutlesjoueurs.get(rand.nextInt(toutlesjoueurs.size()));
|
||||
String joueur3 = (String) toutlesjoueurs.get(rand.nextInt(toutlesjoueurs.size()));
|
||||
if(joueur1 == joueur2 ){
|
||||
}else{
|
||||
if (joueur1 == joueur3){
|
||||
}else {
|
||||
if (joueur2 == joueur3) {
|
||||
}else{
|
||||
listJoueur.add(joueur1);
|
||||
listJoueur.add(joueur2);
|
||||
listJoueur.add(joueur3);
|
||||
return listJoueur;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String ChoixJoueurAleatoire( List toutlesjoueurs){
|
||||
//System.out.println(inttoutlesjoueurs);
|
||||
Random rand = new Random();
|
||||
String joueur = (String) toutlesjoueurs.get(rand.nextInt(toutlesjoueurs.size()));
|
||||
//System.out.println(joueur);
|
||||
//int nbaleatoire = rand.nextInt(max -min + 1 ) + min;
|
||||
//int nbaleatoire2 = nbaleatoire - 1;
|
||||
//if(nbaleatoire2 == -1 ){
|
||||
// nbaleatoire2 = 0;
|
||||
//}
|
||||
//System.out.println( "nb aleatoire " + nbaleatoire) ;
|
||||
//joueur = (String) toutlesjoueurs.get(nbaleatoire2);
|
||||
//System.out.println(joueur);
|
||||
return joueur ;
|
||||
|
||||
}
|
||||
public void OnClickButton1(View v){
|
||||
finish();
|
||||
startActivity(getIntent());
|
||||
|
||||
}
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Votre code pour gérer les modifications d'orientation ici
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,316 +3,113 @@ package com.example.boidelov3;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
//public static final List EXTRA_LIST_JOUEUR = Collections.singletonList("com.example.piccolov2.jeux.EXTRA_LISTJOUEUR");
|
||||
|
||||
String J1S,J2S,J3S;
|
||||
private int offset;
|
||||
private List<String> toutlesjoueurs;
|
||||
private TextInputEditText J1;
|
||||
private TextInputEditText J2;
|
||||
private TextInputEditText J3;
|
||||
private List<TextInputEditText> editTextList = new ArrayList<>();
|
||||
private List<View> playerRowList = new ArrayList<>(); // Liste des lignes de joueurs pour suppression
|
||||
private TextView playerCountText;
|
||||
private List<String> PhraseSansNom;
|
||||
EditText J1;
|
||||
EditText J2;
|
||||
EditText J3;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Récupérer le TextView du compteur
|
||||
playerCountText = findViewById(R.id.playerCountText);
|
||||
|
||||
// Initialiser les 3 champs statiques et ajouter les listeners
|
||||
J1 = findViewById(R.id.J1);
|
||||
J2 = findViewById(R.id.J2);
|
||||
J3 = findViewById(R.id.J3);
|
||||
|
||||
// Ajouter un TextWatcher à chaque champ pour mettre à jour le compteur
|
||||
TextWatcher playerCountWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
updatePlayerCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
};
|
||||
|
||||
if (J1 != null) J1.addTextChangedListener(playerCountWatcher);
|
||||
if (J2 != null) J2.addTextChangedListener(playerCountWatcher);
|
||||
if (J3 != null) J3.addTextChangedListener(playerCountWatcher);
|
||||
|
||||
// Mise à jour initiale
|
||||
updatePlayerCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le compteur de joueurs en temps réel
|
||||
*/
|
||||
private void updatePlayerCount() {
|
||||
int count = 0;
|
||||
if (J1 != null && J1.getText() != null && !J1.getText().toString().isEmpty()) count++;
|
||||
if (J2 != null && J2.getText() != null && !J2.getText().toString().isEmpty()) count++;
|
||||
if (J3 != null && J3.getText() != null && !J3.getText().toString().isEmpty()) count++;
|
||||
|
||||
// Compter les champs dynamiques
|
||||
for (TextInputEditText edit : editTextList) {
|
||||
if (edit != null && edit.getText() != null && !edit.getText().toString().isEmpty()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (playerCountText != null) {
|
||||
playerCountText.setText("Joueurs: " + count + " / min. 3");
|
||||
}
|
||||
}
|
||||
|
||||
private List<EditText> editTextList = new ArrayList<>();
|
||||
public void onClickButton1(View view) {
|
||||
LinearLayout nameEntryLayout = findViewById(R.id.nameEntryLayout);
|
||||
LinearLayout namesContainer = findViewById(R.id.namesContainer); // Récupère le conteneur des noms
|
||||
|
||||
// Créer un conteneur pour la ligne de joueur (EditText + Bouton supprimer)
|
||||
FrameLayout playerRow = new FrameLayout(this);
|
||||
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
rowParams.setMargins(0, 0, 0, 12); // Marge identique aux champs statiques (12dp)
|
||||
playerRow.setLayoutParams(rowParams);
|
||||
|
||||
// Créer un nouveau TextInputLayout
|
||||
com.google.android.material.textfield.TextInputLayout textInputLayout = new com.google.android.material.textfield.TextInputLayout(this);
|
||||
FrameLayout.LayoutParams textInputParams = new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
// Plus d'espace pour le bouton poubelle (68dp au lieu de 52dp)
|
||||
textInputParams.setMargins(0, 0, 68, 0);
|
||||
textInputLayout.setLayoutParams(textInputParams);
|
||||
textInputLayout.setBoxBackgroundMode(com.google.android.material.textfield.TextInputLayout.BOX_BACKGROUND_OUTLINE);
|
||||
textInputLayout.setHint("Nom");
|
||||
|
||||
// Appliquer exactement les mêmes styles que le XML
|
||||
// Créer un ColorStateList pour la bordure : grise par défaut, primaire quand focus
|
||||
int[][] states = new int[][] {
|
||||
new int[] { android.R.attr.state_focused},
|
||||
new int[] {}
|
||||
};
|
||||
int[] colors = new int[] {
|
||||
androidx.core.content.ContextCompat.getColor(this, R.color.primary),
|
||||
androidx.core.content.ContextCompat.getColor(this, android.R.color.darker_gray) // Bordure grise par défaut
|
||||
};
|
||||
android.content.res.ColorStateList strokeColorStateList = new android.content.res.ColorStateList(states, colors);
|
||||
textInputLayout.setBoxStrokeColorStateList(strokeColorStateList);
|
||||
textInputLayout.setBoxStrokeWidth(2); // Largeur de bordure comme dans le XML
|
||||
textInputLayout.setBoxBackgroundColor(androidx.core.content.ContextCompat.getColor(this, R.color.surface));
|
||||
textInputLayout.setDefaultHintTextColor(androidx.core.content.ContextCompat.getColorStateList(this, R.color.text_hint));
|
||||
textInputLayout.setEndIconMode(com.google.android.material.textfield.TextInputLayout.END_ICON_CLEAR_TEXT);
|
||||
|
||||
// Créer un nouveau TextInputEditText
|
||||
TextInputEditText newEditText = new TextInputEditText(this);
|
||||
newEditText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
newEditText.setInputType(InputType.TYPE_TEXT_FLAG_CAP_WORDS);
|
||||
newEditText.setMaxLines(1);
|
||||
newEditText.setTextSize(16);
|
||||
newEditText.setTextColor(androidx.core.content.ContextCompat.getColor(this, R.color.text_primary));
|
||||
newEditText.setHintTextColor(androidx.core.content.ContextCompat.getColor(this, R.color.text_hint));
|
||||
|
||||
// Ajouter un TextWatcher pour mettre à jour le compteur en temps réel
|
||||
newEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
updatePlayerCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
|
||||
// Ajouter l'EditText au TextInputLayout
|
||||
textInputLayout.addView(newEditText);
|
||||
|
||||
// Créer le bouton de suppression - style élégant avec fond circulaire
|
||||
ImageButton deleteButton = new ImageButton(this);
|
||||
int dp40 = (int) (40 * getResources().getDisplayMetrics().density);
|
||||
FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(dp40, dp40);
|
||||
buttonParams.setMargins(0, 0, 8, 0);
|
||||
buttonParams.gravity = android.view.Gravity.END | android.view.Gravity.CENTER_VERTICAL;
|
||||
deleteButton.setLayoutParams(buttonParams);
|
||||
|
||||
// Utiliser l'icône de poubelle Material (ic_menu_delete est une croix)
|
||||
deleteButton.setImageResource(android.R.drawable.ic_menu_delete);
|
||||
deleteButton.setColorFilter(androidx.core.content.ContextCompat.getColor(this, R.color.text_secondary));
|
||||
deleteButton.setScaleType(ImageButton.ScaleType.CENTER_INSIDE);
|
||||
|
||||
// Fond circulaire blanc avec bordure fine
|
||||
deleteButton.setBackgroundColor(androidx.core.content.ContextCompat.getColor(this, R.color.white));
|
||||
deleteButton.setPadding(8, 8, 8, 8);
|
||||
|
||||
// Arrondir le bouton en cercle via un OutlineProvider
|
||||
deleteButton.setOutlineProvider(new android.view.ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(android.view.View view, android.graphics.Outline outline) {
|
||||
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
||||
}
|
||||
});
|
||||
deleteButton.setClipToOutline(true);
|
||||
|
||||
deleteButton.setContentDescription("Supprimer ce joueur");
|
||||
|
||||
// Configuration du bouton de suppression avec animation
|
||||
deleteButton.setOnClickListener(v -> removePlayerRow(playerRow, newEditText));
|
||||
|
||||
// Ajouter les éléments au conteneur
|
||||
playerRow.addView(textInputLayout);
|
||||
playerRow.addView(deleteButton);
|
||||
|
||||
// Ajouter à la liste et au layout avec animation
|
||||
editTextList.add(newEditText);
|
||||
playerRowList.add(playerRow);
|
||||
nameEntryLayout.addView(playerRow);
|
||||
|
||||
// Animation d'apparition
|
||||
BoideloAnimationUtils.popIn(playerRow, 300);
|
||||
BoideloAnimationUtils.triggerHapticFeedback(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une ligne de joueur avec animation
|
||||
*/
|
||||
private void removePlayerRow(View playerRow, TextInputEditText editText) {
|
||||
BoideloAnimationUtils.triggerHapticFeedback(this);
|
||||
|
||||
// Animation de suppression
|
||||
BoideloAnimationUtils.slideOutToRemove(playerRow, 300, () -> {
|
||||
// Retirer de la liste et du layout après l'animation
|
||||
editTextList.remove(editText);
|
||||
playerRowList.remove(playerRow);
|
||||
ViewGroup parent = (ViewGroup) playerRow.getParent();
|
||||
if (parent != null) {
|
||||
parent.removeView(playerRow);
|
||||
}
|
||||
// Mettre à jour le compteur après suppression
|
||||
updatePlayerCount();
|
||||
});
|
||||
EditText newEditText = new EditText(this); // Crée un nouvel EditText
|
||||
newEditText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); // Définit les paramètres de mise en page
|
||||
newEditText.setHint("Nom"); // Définit le texte d'indication
|
||||
newEditText.setInputType(InputType.TYPE_TEXT_FLAG_CAP_WORDS); // Définit le type d'entrée (texte avec majuscule au début des mots)
|
||||
editTextList.add(newEditText); // Ajoute l'EditText à la liste des EditText
|
||||
namesContainer.addView(newEditText); // Ajoute le nouvel EditText au conteneur des noms
|
||||
}
|
||||
|
||||
|
||||
public void onClickButtonStart(View view) {
|
||||
// Récupérer les 3 premiers champs de saisie
|
||||
J1 = findViewById(R.id.J1);
|
||||
if (J1 != null && J1.getText() != null) {
|
||||
J1 = (EditText) findViewById(R.id.J1);
|
||||
J1S = J1.getText().toString();
|
||||
} else {
|
||||
J1S = "";
|
||||
}
|
||||
|
||||
J2 = findViewById(R.id.J2);
|
||||
if (J2 != null && J2.getText() != null) {
|
||||
J2 = (EditText) findViewById(R.id.J2);
|
||||
J2S = J2.getText().toString();
|
||||
} else {
|
||||
J2S = "";
|
||||
}
|
||||
|
||||
J3 = findViewById(R.id.J3);
|
||||
if (J3 != null && J3.getText() != null) {
|
||||
J3 = (EditText) findViewById(R.id.J3);
|
||||
J3S = J3.getText().toString();
|
||||
} else {
|
||||
J3S = "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Creation d'une liste avec tt les j et verif si elle est completé
|
||||
toutlesjoueurs = new ArrayList<>();
|
||||
if (!J1S.isEmpty()) {
|
||||
toutlesjoueurs.add(J1S);
|
||||
}
|
||||
if (!J2S.isEmpty()) {
|
||||
toutlesjoueurs.add(J2S);
|
||||
}
|
||||
if (!J3S.isEmpty()) {
|
||||
toutlesjoueurs.add(J3S);
|
||||
}
|
||||
toutlesjoueurs = new ArrayList<String>();
|
||||
if (J1S.isEmpty()) {
|
||||
//System.out.println("J1S A pas de valeur");
|
||||
}else toutlesjoueurs.add(J1S);
|
||||
if (J2S.isEmpty()) {
|
||||
//System.out.println("J2S a pas de valeur");
|
||||
}else toutlesjoueurs.add(J2S);
|
||||
if (J3S.isEmpty()) {
|
||||
//System.out.println("J3S a pas de valeur");
|
||||
}else toutlesjoueurs.add(J3S);
|
||||
|
||||
// Ajouter les champs dynamiques
|
||||
//
|
||||
int nbnom = editTextList.size();
|
||||
//String test = editTextList.get(0).getText().toString();
|
||||
//System.out.println(test);
|
||||
System.out.println(nbnom);
|
||||
for (int i = 0; i < nbnom; i++) {
|
||||
TextInputEditText editText = editTextList.get(i);
|
||||
if (editText != null && editText.getText() != null) {
|
||||
String nom = editText.getText().toString();
|
||||
if (!nom.isEmpty()) {
|
||||
String nom = editTextList.get(i).getText().toString();
|
||||
System.out.println(nom);
|
||||
if (nom.isEmpty()) {
|
||||
System.out.println("Nom vide");
|
||||
} else {
|
||||
toutlesjoueurs.add(nom);
|
||||
}
|
||||
}
|
||||
}
|
||||
//TestListe
|
||||
//System.out.println(toutlesjoueurs);
|
||||
|
||||
openParametres();
|
||||
}
|
||||
|
||||
public void openParametres(){
|
||||
// Vérifier s'il y a des noms en double (insensible à la casse)
|
||||
List<String> nomsNormauxes = new ArrayList<>();
|
||||
for (String nom : toutlesjoueurs) {
|
||||
String nomNormalise = nom.trim().toLowerCase();
|
||||
if (nomsNormauxes.contains(nomNormalise)) {
|
||||
// Nom en double détecté
|
||||
Context context = getApplicationContext();
|
||||
CharSequence text = "Erreur : Le nom \"" + nom + "\" est utilisé plusieurs fois. Chaque joueur doit avoir un nom unique.";
|
||||
int duration = Toast.LENGTH_LONG;
|
||||
Toast toast = Toast.makeText(context, text, duration);
|
||||
toast.show();
|
||||
return; // Ne pas continuer
|
||||
}
|
||||
nomsNormauxes.add(nomNormalise);
|
||||
}
|
||||
|
||||
//enregistrement des joueurs dans les shared preferences Joueurs
|
||||
SharedPreferences sharedPreferences = getSharedPreferences("Joueurs", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
editor.putString("J1", J1S != null ? J1S : "");
|
||||
editor.putString("J2", J2S != null ? J2S : "");
|
||||
editor.putString("J3", J3S != null ? J3S : "");
|
||||
editor.putString("J1", J1S);
|
||||
editor.putString("J2", J2S);
|
||||
editor.putString("J3", J3S);
|
||||
|
||||
for (int i = 0; i < editTextList.size(); i++) {
|
||||
TextInputEditText editText = editTextList.get(i);
|
||||
if (editText != null && editText.getText() != null) {
|
||||
String nom = editText.getText().toString();
|
||||
String nom = editTextList.get(i).getText().toString();
|
||||
editor.putString("J" + (i + 4), nom);
|
||||
}
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
//Lancement de l'activité (Jeux_parametres)
|
||||
|
||||
Intent intent = new Intent(this, JeuxParametres.class);
|
||||
//Regarde si le pseudo est vide et envoie à l'activité jeux
|
||||
//Regarde si le pseudo est vide et envoie a l'activité jeux
|
||||
if (toutlesjoueurs.isEmpty()){
|
||||
Context context = getApplicationContext();
|
||||
CharSequence text = "Merci de rentrer des joueurs";
|
||||
@@ -324,16 +121,73 @@ public class MainActivity extends AppCompatActivity {
|
||||
else {
|
||||
if (toutlesjoueurs.size() >= 3) {
|
||||
intent.putStringArrayListExtra("EXTRA_LIST_JOUEUR", (ArrayList<String>) toutlesjoueurs);
|
||||
//intent.putStringArrayListExtra("EXTRA_LIST_DEFI_NON_JOUEUR", (ArrayList<String>) PhraseSansNom);
|
||||
intent.putExtra("EXTRA_OFFSET", offset);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Context context = getApplicationContext();
|
||||
CharSequence text = "La partie ne peut pas commencer avec moins de 3 joueurs";
|
||||
CharSequence text = "La partie ne peux pas commencer avec moins de 3 joueurs";
|
||||
int duration = Toast.LENGTH_SHORT;
|
||||
Toast toast = Toast.makeText(context, text, duration);
|
||||
toast.show();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}}
|
||||
//Lancement de la fonction activité (Jeux)
|
||||
// openJeux();
|
||||
// }
|
||||
|
||||
//Lancement de l'activité (Jeux)
|
||||
// public void openJeux(){
|
||||
//
|
||||
// Intent intent = new Intent(this, Jeux.class);
|
||||
// //Regarde si le pseudo est vide et envoie a l'activité jeux
|
||||
// if (toutlesjoueurs.isEmpty()){
|
||||
// Context context = getApplicationContext();
|
||||
// CharSequence text = "Merci de rentrer des joueurs";
|
||||
// int duration = Toast.LENGTH_SHORT;
|
||||
//
|
||||
// Toast toast = Toast.makeText(context, text, duration);
|
||||
// toast.show();
|
||||
// }
|
||||
// else{
|
||||
// if(toutlesjoueurs.size() >= 3){
|
||||
// intent.putStringArrayListExtra("EXTRA_LIST_JOUEUR", (ArrayList<String>) toutlesjoueurs);
|
||||
// //intent.putStringArrayListExtra("EXTRA_LIST_DEFI_NON_JOUEUR", (ArrayList<String>) PhraseSansNom);
|
||||
// intent.putExtra("EXTRA_OFFSET", offset);
|
||||
// startActivity(intent);
|
||||
// }else{
|
||||
// Context context = getApplicationContext();
|
||||
// CharSequence text = "La partie ne peux pas commencer avec moins de 3 joueurs";
|
||||
// int duration = Toast.LENGTH_SHORT;
|
||||
// Toast toast = Toast.makeText(context, text, duration);
|
||||
// toast.show();
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public void OnCheckboxClicked1(View view) {
|
||||
// boolean checked = ((CheckBox) view).isChecked();
|
||||
// if (checked){
|
||||
// offset = 2;
|
||||
// //System.out.println("OFFSET DE 2");
|
||||
// }else {
|
||||
// //System.out.println("DESACTIVER : OFFSET DE 2");
|
||||
// offset = 0;
|
||||
// }}
|
||||
//
|
||||
// public void OnCheckboxClicked2(View view) {
|
||||
// boolean checked = ((CheckBox) view).isChecked();
|
||||
// if (checked){
|
||||
// offset = 4;
|
||||
// //System.out.println("OFFSET DE 4");
|
||||
// }else {
|
||||
// //System.out.println("DESACTIVER : OFFSET DE 4");
|
||||
// offset = 0;
|
||||
// }}
|
||||
//}
|
||||
@@ -1,393 +0,0 @@
|
||||
package com.example.boidelov3;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
/**
|
||||
* Service pour générer des questions via différentes IA (OpenAI, OpenRouter, Z.ai)
|
||||
* Utilise OkHttp pour les requêtes réseau asynchrones
|
||||
*/
|
||||
public class OpenAIService {
|
||||
private static final String TAG = "OpenAIService";
|
||||
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
|
||||
// API URLs pour chaque provider
|
||||
private static final String OPENAI_API_URL = "https://api.openai.com/v1/chat/completions";
|
||||
private static final String OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions";
|
||||
|
||||
// Modèles par défaut pour chaque provider
|
||||
private static final String OPENAI_MODEL = "gpt-3.5-turbo";
|
||||
private static final String OPENROUTER_MODEL = "openai/gpt-3.5-turbo";
|
||||
private static final String ZAI_MODEL = "claude-3-5-sonnet";
|
||||
|
||||
private OkHttpClient client;
|
||||
private String apiKey;
|
||||
private AIProvider provider;
|
||||
private String model;
|
||||
private Handler mainHandler;
|
||||
private Random random;
|
||||
|
||||
/**
|
||||
* Enum pour les différents fournisseurs d'IA
|
||||
*/
|
||||
public enum AIProvider {
|
||||
OPENAI("OpenAI", OPENAI_API_URL, OPENAI_MODEL),
|
||||
OPENROUTER("OpenRouter", OPENROUTER_API_URL, OPENROUTER_MODEL),
|
||||
ZAI("Z.ai", "https://api.z.ai/v1/messages", ZAI_MODEL);
|
||||
|
||||
private final String displayName;
|
||||
private final String apiUrl;
|
||||
private final String defaultModel;
|
||||
|
||||
AIProvider(String displayName, String apiUrl, String defaultModel) {
|
||||
this.displayName = displayName;
|
||||
this.apiUrl = apiUrl;
|
||||
this.defaultModel = defaultModel;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getApiUrl() {
|
||||
return apiUrl;
|
||||
}
|
||||
|
||||
public String getDefaultModel() {
|
||||
return defaultModel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur avec provider par défaut (OpenAI)
|
||||
*/
|
||||
public OpenAIService(String apiKey) {
|
||||
this(apiKey, AIProvider.OPENAI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur avec choix du provider
|
||||
* Valide la clé API avant de l'utiliser
|
||||
*/
|
||||
public OpenAIService(String apiKey, AIProvider provider) {
|
||||
// Valider la clé API avant utilisation
|
||||
if (!validateApiKey(apiKey, provider)) {
|
||||
throw new IllegalArgumentException("Clé API invalide pour " + provider.getDisplayName());
|
||||
}
|
||||
|
||||
this.apiKey = apiKey;
|
||||
this.provider = provider;
|
||||
this.model = provider.getDefaultModel();
|
||||
this.client = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||
this.random = new Random();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur avec choix du provider et du modèle
|
||||
* Valide la clé API avant de l'utiliser
|
||||
*/
|
||||
public OpenAIService(String apiKey, AIProvider provider, String model) {
|
||||
this(apiKey, provider);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide une clé API selon le provider
|
||||
* Vérifie que la clé n'est pas null, vide, et a un format valide
|
||||
*
|
||||
* @param key La clé API à valider
|
||||
* @param provider Le provider d'IA
|
||||
* @return true si la clé est valide, false sinon
|
||||
*/
|
||||
public static boolean validateApiKey(String key, AIProvider provider) {
|
||||
// Vérification basique : la clé ne doit pas être null ou vide
|
||||
if (key == null || key.trim().isEmpty()) {
|
||||
Log.w(TAG, "Clé API null ou vide pour " + provider.getDisplayName());
|
||||
return false;
|
||||
}
|
||||
|
||||
String trimmedKey = key.trim();
|
||||
|
||||
// Vérification du format selon le provider
|
||||
switch (provider) {
|
||||
case OPENAI:
|
||||
// Les clés OpenAI commencent par "sk-"
|
||||
if (!trimmedKey.startsWith("sk-")) {
|
||||
Log.w(TAG, "Clé OpenAI invalide : doit commencer par 'sk-'");
|
||||
return false;
|
||||
}
|
||||
if (trimmedKey.length() < 20) {
|
||||
Log.w(TAG, "Clé OpenAI trop courte");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPENROUTER:
|
||||
// Les clés OpenRouter commencent par "sk-or-"
|
||||
if (!trimmedKey.startsWith("sk-or-")) {
|
||||
Log.w(TAG, "Clé OpenRouter invalide : doit commencer par 'sk-or-'");
|
||||
return false;
|
||||
}
|
||||
if (trimmedKey.length() < 20) {
|
||||
Log.w(TAG, "Clé OpenRouter trop courte");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ZAI:
|
||||
// Les clés Z.ai/Anthropic commencent par "sk-ant-"
|
||||
if (!trimmedKey.startsWith("sk-ant-")) {
|
||||
Log.w(TAG, "Clé Z.ai invalide : doit commencer par 'sk-ant-'");
|
||||
return false;
|
||||
}
|
||||
if (trimmedKey.length() < 20) {
|
||||
Log.w(TAG, "Clé Z.ai trop courte");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.w(TAG, "Provider inconnu : " + provider);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Clé API validée pour " + provider.getDisplayName());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide une clé API avec le provider par défaut (OpenAI)
|
||||
*
|
||||
* @param key La clé API à valider
|
||||
* @return true si la clé est valide
|
||||
*/
|
||||
public static boolean validateApiKey(String key) {
|
||||
return validateApiKey(key, AIProvider.OPENAI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change le provider
|
||||
*/
|
||||
public void setProvider(AIProvider provider) {
|
||||
this.provider = provider;
|
||||
this.model = provider.getDefaultModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change le modèle
|
||||
*/
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une question via l'API configurée
|
||||
*/
|
||||
public void generateQuestion(List<String> players, int ajoutGorgees, OpenAICallback callback) {
|
||||
String prompt = buildPrompt(players, ajoutGorgees);
|
||||
|
||||
try {
|
||||
boolean isAnthropicFormat = (provider == AIProvider.ZAI);
|
||||
String requestBody;
|
||||
String responseParser;
|
||||
|
||||
if (isAnthropicFormat) {
|
||||
// Format Anthropic (Claude/Z.ai)
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("model", model);
|
||||
body.put("max_tokens", 150);
|
||||
body.put("temperature", 0.8);
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
JSONObject userMessage = new JSONObject();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", prompt);
|
||||
messages.put(userMessage);
|
||||
body.put("messages", messages);
|
||||
|
||||
requestBody = body.toString();
|
||||
responseParser = "anthropic";
|
||||
} else {
|
||||
// Format OpenAI-compatible
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("model", model);
|
||||
body.put("max_tokens", 150);
|
||||
body.put("temperature", 0.8);
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
JSONObject userMessage = new JSONObject();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", prompt);
|
||||
messages.put(userMessage);
|
||||
body.put("messages", messages);
|
||||
|
||||
requestBody = body.toString();
|
||||
responseParser = "openai";
|
||||
}
|
||||
|
||||
// Construire la requête avec l'URL appropriée
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(provider.getApiUrl())
|
||||
.post(RequestBody.create(requestBody, JSON));
|
||||
|
||||
// Ajouter les headers selon le provider
|
||||
switch (provider) {
|
||||
case OPENAI:
|
||||
requestBuilder.addHeader("Authorization", "Bearer " + apiKey);
|
||||
break;
|
||||
case OPENROUTER:
|
||||
requestBuilder.addHeader("Authorization", "Bearer " + apiKey);
|
||||
requestBuilder.addHeader("HTTP-Referer", "https://boidelo.app");
|
||||
requestBuilder.addHeader("X-Title", "Boidelo");
|
||||
break;
|
||||
case ZAI:
|
||||
requestBuilder.addHeader("x-api-key", apiKey);
|
||||
requestBuilder.addHeader("anthropic-version", "2023-06-01");
|
||||
break;
|
||||
}
|
||||
|
||||
Request request = requestBuilder.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
mainHandler.post(() -> callback.onError("Erreur de connexion: " + e.getMessage()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
try {
|
||||
if (!response.isSuccessful()) {
|
||||
mainHandler.post(() -> callback.onError("Erreur API " + provider.getDisplayName() + ": " + response.code()));
|
||||
return;
|
||||
}
|
||||
|
||||
String responseData = response.body().string();
|
||||
String generatedQuestion = parseResponse(responseData, responseParser);
|
||||
|
||||
mainHandler.post(() -> callback.onSuccess(generatedQuestion));
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (JSONException e) {
|
||||
mainHandler.post(() -> callback.onError("Erreur de formatage: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit le prompt pour l'IA
|
||||
*/
|
||||
private String buildPrompt(List<String> players, int ajoutGorgees) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Tu es un générateur de questions pour un jeu d'alcool en français. ");
|
||||
sb.append("Génère UNE SEULE question dans le style suivant:\n\n");
|
||||
|
||||
sb.append("Exemples:\n");
|
||||
sb.append("- Ceux qui ont un tatouage\n");
|
||||
sb.append("- Le/La plus drôle du groupe\n");
|
||||
sb.append("- <J1> doit deviner ce que <J2> a mangé aujourd'hui\n");
|
||||
sb.append("- Ceux qui écoutent du rock\n");
|
||||
sb.append("- Plutôt avoir le pouvoir ou la connaissance ? Votez tous. La minorité boit.\n");
|
||||
|
||||
if (players != null && !players.isEmpty()) {
|
||||
sb.append("\nJoueurs présents: ");
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(players.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (ajoutGorgees > 0) {
|
||||
sb.append("\nAjout de gorgées: +").append(ajoutGorgees).append(" gorgée(s)");
|
||||
}
|
||||
|
||||
sb.append("\n\nIMPORTANT: ");
|
||||
sb.append("Génère uniquement la question, sans explication. ");
|
||||
sb.append("Utilise <J1>, <J2>, <J3> pour désigner des joueurs aléatoires si nécessaire. ");
|
||||
sb.append("Ne génère PAS de questions avec <manches> (défis à durée). ");
|
||||
sb.append("Sois créatif et varié !");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse la réponse de l'API (format OpenAI-compatible ou Anthropic)
|
||||
*/
|
||||
private String parseResponse(String responseData, String format) {
|
||||
try {
|
||||
JSONObject json = new JSONObject(responseData);
|
||||
|
||||
if ("anthropic".equals(format)) {
|
||||
// Format Anthropic/Z.ai
|
||||
if (json.has("content")) {
|
||||
JSONArray contentArray = json.getJSONArray("content");
|
||||
if (contentArray.length() > 0) {
|
||||
JSONObject firstContent = contentArray.getJSONObject(0);
|
||||
if (firstContent.has("text")) {
|
||||
return firstContent.getString("text").trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Format OpenAI-compatible
|
||||
JSONArray choices = json.getJSONArray("choices");
|
||||
if (choices.length() > 0) {
|
||||
JSONObject firstChoice = choices.getJSONObject(0);
|
||||
JSONObject message = firstChoice.getJSONObject("message");
|
||||
String content = message.getString("content");
|
||||
// Nettoyer la réponse
|
||||
return content.trim().replaceAll("^\"|\"$", "");
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
String operation = "Parsing de la réponse API " + provider.getDisplayName();
|
||||
String details = "Format: " + format + ", Impossible de parser la réponse JSON";
|
||||
Log.e("OpenAIService", operation + " - " + details, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface de callback pour les réponses API
|
||||
*/
|
||||
public interface OpenAICallback {
|
||||
void onSuccess(String question);
|
||||
void onError(String errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Libère les ressources
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (client != null) {
|
||||
client.dispatcher().executorService().shutdown();
|
||||
client.connectionPool().evictAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.example.boidelov3;
|
||||
|
||||
/**
|
||||
* Centralise toutes les clés utilisées pour SharedPreferences.
|
||||
* Cette classe garantit la cohérence des clés à travers l'application.
|
||||
*/
|
||||
public final class PreferencesKeys {
|
||||
|
||||
// Préfixe pour les noms de fichiers de préférences
|
||||
public static final String PREFS_NAME_PLAYERS = "Joueurs";
|
||||
public static final String PREFS_NAME_APP = "app";
|
||||
public static final String PREFS_NAME_GAME_STATS = "game_stats";
|
||||
public static final String PREFS_NAME_MY_PREFS = "MyPrefs";
|
||||
|
||||
// Clés pour les joueurs (stockés dans PREFS_NAME_PLAYERS)
|
||||
public static final String KEY_PLAYER_1 = "J1";
|
||||
public static final String KEY_PLAYER_2 = "J2";
|
||||
public static final String KEY_PLAYER_3 = "J3";
|
||||
// Pour les joueurs supplémentaires : J4, J5, etc. (généré dynamiquement)
|
||||
|
||||
// Clés pour les statistiques de jeu (stockées dans PREFS_NAME_GAME_STATS)
|
||||
public static final String KEY_QUESTIONS_PLAYED = "questions_played";
|
||||
public static final String KEY_PLAYERS_COUNT = "players_count";
|
||||
|
||||
// Clés pour l'état de l'application (stockées dans PREFS_NAME_APP)
|
||||
public static final String KEY_ASKED_QUESTIONS = "askedQuestions";
|
||||
|
||||
// Clés pour les paramètres utilisateur (stockés dans PREFS_NAME_MY_PREFS)
|
||||
public static final String KEY_SAVED_TEXT = "savedText";
|
||||
public static final String KEY_AI_PROVIDER = "aiProvider";
|
||||
|
||||
// Clés pour la sauvegarde d'état (Bundle)
|
||||
public static final String KEY_TOTAL_QUESTIONS_ASKED = "total_questions_asked";
|
||||
public static final String KEY_CURRENT_QUESTION_TEXT = "current_question_text";
|
||||
public static final String KEY_IS_MANCHE_ACTIVE = "is_manche_active";
|
||||
public static final String KEY_MANCHES_COUNT = "manches_count";
|
||||
public static final String KEY_MANCHE_IDS = "manche_ids";
|
||||
public static final String KEY_MANCHE_COUNTS = "manche_counts";
|
||||
|
||||
// Constructeur privé pour empêcher l'instanciation
|
||||
private PreferencesKeys() {
|
||||
throw new AssertionError("Classe utilitaire, ne pas instancier");
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une clé de joueur pour les joueurs supplémentaires (J4, J5, etc.)
|
||||
*
|
||||
* @param playerNumber Le numéro du joueur (doit être >= 4)
|
||||
* @return La clé générée (ex: "J4", "J5")
|
||||
*/
|
||||
public static String getPlayerKey(int playerNumber) {
|
||||
if (playerNumber < 1) {
|
||||
throw new IllegalArgumentException("Le numéro de joueur doit être >= 1");
|
||||
}
|
||||
return "J" + playerNumber;
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,6 @@ package com.example.boidelov3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Représente une question du jeu Boidelo avec toutes ses propriétés.
|
||||
*
|
||||
* <p>Cette classe contient toutes les informations nécessaires pour afficher
|
||||
* et traiter une question lors du jeu.</p>
|
||||
*
|
||||
* <p>Propriétés principales :</p>
|
||||
* <ul>
|
||||
* <li>{@code id} : Identifiant unique de la question</li>
|
||||
* <li>{@code question} : Texte de la question (peut contenir des balises)</li>
|
||||
* <li>{@code gorger} : Nombre de gorgées à boire/distribuer</li>
|
||||
* <li>{@code distribution} : Si vrai, le joueur distribue des gorgées</li>
|
||||
* <li>{@code recois} : Si vrai, le joueur boit des gorgées</li>
|
||||
* <li>{@code manches} : Si vrai, la question est un défi à manches</li>
|
||||
* <li>{@code caliente} : Si vrai, la question est spéciale/hot</li>
|
||||
* <li>{@code variante} : Liste des choix possibles pour une variante</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Balises spéciales dans le texte :</p>
|
||||
* <ul>
|
||||
* <li>{@code <J1>}, {@code <J2>}, {@code <J3>} : Joueurs sélectionnés</li>
|
||||
* <li>{@code <manches>} : Nombre de manches pour un défi</li>
|
||||
* <li>{@code <variante>} : Choix à remplacer par une variante</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class Question {
|
||||
private int id;
|
||||
private String question;
|
||||
@@ -35,25 +10,16 @@ public class Question {
|
||||
private List<String> variante;
|
||||
private boolean recois;
|
||||
private boolean manches;
|
||||
private boolean caliente;
|
||||
private String arret; // mise à jour du type de données
|
||||
private int manchesRestantes; // pour le nombre de manches restantes
|
||||
private String arretMessage; // pour le message d'arrêt
|
||||
private String arretMessageManche; // pour le message d'arrêt pour les manches
|
||||
|
||||
/**
|
||||
* Constructeur par défaut.
|
||||
* Initialise tous les champs à leurs valeurs par défaut.
|
||||
*/
|
||||
// Constructeur par défaut
|
||||
public Question() {
|
||||
}
|
||||
|
||||
// Getters et setters pour tous les champs
|
||||
|
||||
/**
|
||||
* Retourne l'identifiant unique de la question.
|
||||
* @return L'ID de la question
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -140,12 +106,4 @@ public class Question {
|
||||
public void setArretMessageManche(String arretMessageManche) {
|
||||
this.arretMessageManche = arretMessageManche;
|
||||
}
|
||||
|
||||
public boolean isCaliente() {
|
||||
return caliente;
|
||||
}
|
||||
|
||||
public void setCaliente(boolean caliente) {
|
||||
this.caliente = caliente;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,5 @@ public class Questions {
|
||||
return questions;
|
||||
}
|
||||
|
||||
public void setQuestions(List<Question> questions) {
|
||||
this.questions = questions;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
// autres getters et setters...
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
package com.example.boidelov3.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Classe pour suivre les statistiques d'un joueur pendant une partie.
|
||||
*
|
||||
* <p>Cette classe est Parcelable pour pouvoir être passée entre les activités.
|
||||
* Elle tracke deux types de statistiques :</p>
|
||||
* <ul>
|
||||
* <li>{@code gorgeesBuves} : Nombre total de gorgées bues par le joueur</li>
|
||||
* <li>{@code gorgeesDistribuees} : Nombre total de gorgées distribuées par le joueur</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Exemple d'utilisation :</p>
|
||||
* <pre>{@code
|
||||
* PlayerStats stats = new PlayerStats("Alice");
|
||||
* stats.addGorgeesBuves(5);
|
||||
* stats.addGorgeesDistribuees(3);
|
||||
* int total = stats.getTotalGorgees(); // 8
|
||||
* }</pre>
|
||||
*
|
||||
* <p>SECURITY NOTE:</p>
|
||||
* Cette classe stocke des statistiques de jeu (gorgées, scores) qui ne sont PAS
|
||||
* considérées comme des données sensibles. Aucun chiffrement n'est nécessaire.
|
||||
*
|
||||
* Si cette classe était étendue pour stocker des données personnelles (noms réels,
|
||||
* emails, etc.), il faudrait utiliser :
|
||||
* <ul>
|
||||
* <li>AndroidX Security Library pour le chiffrement</li>
|
||||
* <li>EncryptedSharedPreferences pour le stockage persistant</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class PlayerStats implements Parcelable {
|
||||
private String playerName;
|
||||
private int gorgeesBuves; // Nombre de gorgées bues par ce joueur
|
||||
private int gorgeesDistribuees; // Nombre de gorgées distribuées par ce joueur
|
||||
|
||||
/**
|
||||
* Crée les statistiques pour un joueur.
|
||||
* Initialise les compteurs à zéro.
|
||||
*
|
||||
* @param playerName Le nom du joueur
|
||||
*/
|
||||
public PlayerStats(String playerName) {
|
||||
this.playerName = playerName;
|
||||
this.gorgeesBuves = 0;
|
||||
this.gorgeesDistribuees = 0;
|
||||
}
|
||||
|
||||
// Constructor for Parcelable
|
||||
protected PlayerStats(Parcel in) {
|
||||
playerName = in.readString();
|
||||
gorgeesBuves = in.readInt();
|
||||
gorgeesDistribuees = in.readInt();
|
||||
}
|
||||
|
||||
public static final Creator<PlayerStats> CREATOR = new Creator<PlayerStats>() {
|
||||
@Override
|
||||
public PlayerStats createFromParcel(Parcel in) {
|
||||
return new PlayerStats(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerStats[] newArray(int size) {
|
||||
return new PlayerStats[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retourne le nom du joueur.
|
||||
* @return Le nom du joueur
|
||||
*/
|
||||
public String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nombre de gorgées bues par ce joueur.
|
||||
* @return Le nombre de gorgées bues
|
||||
*/
|
||||
public int getGorgeesBuves() {
|
||||
return gorgeesBuves;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nombre de gorgées distribuées par ce joueur.
|
||||
* @return Le nombre de gorgées distribuées
|
||||
*/
|
||||
public int getGorgeesDistribuees() {
|
||||
return gorgeesDistribuees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute des gorgées bues au total du joueur.
|
||||
*
|
||||
* @param count Le nombre de gorgées à ajouter (peut être négatif)
|
||||
*/
|
||||
public void addGorgeesBuves(int count) {
|
||||
this.gorgeesBuves += count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute des gorgées distribuées au total du joueur.
|
||||
*
|
||||
* @param count Le nombre de gorgées à ajouter (peut être négatif)
|
||||
*/
|
||||
public void addGorgeesDistribuees(int count) {
|
||||
this.gorgeesDistribuees += count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le total des gorgées (buves + distribuées).
|
||||
*
|
||||
* @return La somme des gorgées bues et distribuées
|
||||
*/
|
||||
public int getTotalGorgees() {
|
||||
return gorgeesBuves + gorgeesDistribuees;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(playerName);
|
||||
dest.writeInt(gorgeesBuves);
|
||||
dest.writeInt(gorgeesDistribuees);
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.example.boidelov3.data;
|
||||
|
||||
import com.example.boidelov3.Question;
|
||||
|
||||
/**
|
||||
* Catégories de questions avec leurs couleurs associées
|
||||
* Permet de classer les questions et d'appliquer des fonds dynamiques
|
||||
*/
|
||||
public class QuestionCategory {
|
||||
|
||||
public enum Category {
|
||||
CIBLAGE("Ciblage", "Questions qui ciblent un groupe spécifique", 0xFFFF6B6B), // Rouge doux
|
||||
CLASSEMENT("Classement", "Vote pour élire le meilleur/pire", 0xFF4ECDC4), // Turquoise
|
||||
JUGEMENT("Jugement", "J1 doit juger ou comparer des joueurs", 0xFFA8E6CF), // Menthe
|
||||
DUEL("Duel J1/J2", "Compétition ou interaction entre 2 joueurs", 0xFFFFD93D), // Jaune
|
||||
INTERACTIF("Interactif", "Quiz, devinettes, jeux de groupe", 0xFF6C5CE7), // Violet
|
||||
DEFI_MANCHES("Défi", "Défi à manches avec durée limitée", 0xFF0984E3), // Bleu
|
||||
VARIANTE("Variante", "Questions avec choix multiples", 0xFF00B894), // Vert menthe
|
||||
CALIENTE("Caliente", "Questions chaudes/spéciales", 0xFFE84393), // Rouge vif
|
||||
VOTE("Vote", "Vote à main levée", 0xFFFD79A8), // Rose
|
||||
CLASSIQUE("Classique", "Question standard", 0xFFDFE6E9); // Gris clair
|
||||
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final int color;
|
||||
|
||||
Category(String name, String description, int color) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Détecte la catégorie d'une question basée sur son contenu
|
||||
*/
|
||||
public static Category detectCategory(Question question) {
|
||||
if (question == null) {
|
||||
return Category.CLASSIQUE;
|
||||
}
|
||||
|
||||
String text = question.getQuestion();
|
||||
if (text == null) {
|
||||
return Category.CLASSIQUE;
|
||||
}
|
||||
|
||||
String questionText = text.toLowerCase();
|
||||
|
||||
// 1. CALIENTE - Priorité haute
|
||||
if (question.isCaliente()) {
|
||||
return Category.CALIENTE;
|
||||
}
|
||||
|
||||
// 2. DÉFI MANCHES - Questions avec <manches>
|
||||
if (questionText.contains("<manches>") || question.isManches()) {
|
||||
return Category.DEFI_MANCHES;
|
||||
}
|
||||
|
||||
// 3. CIBLAGE - "Ceux qui", "Les joueurs qui", "Toutes celles", "Tous ceux"
|
||||
if (questionText.startsWith("ceux qui") ||
|
||||
questionText.startsWith("les joueurs qui") ||
|
||||
questionText.startsWith("toutes celles") ||
|
||||
questionText.startsWith("tous ceux") ||
|
||||
questionText.startsWith("les joueurs de") ||
|
||||
questionText.startsWith("celles") && questionText.contains("ont") ||
|
||||
questionText.startsWith("les joueurs de") ||
|
||||
questionText.startsWith("le groupe de")) {
|
||||
return Category.CIBLAGE;
|
||||
}
|
||||
|
||||
// 4. CLASSEMENT - "Le/La plus", "Élisez", "Le premier", "Qui a le plus"
|
||||
if ((questionText.startsWith("le/là plus") ||
|
||||
questionText.startsWith("le plus") ||
|
||||
questionText.startsWith("la plus") ||
|
||||
questionText.contains("élisez") ||
|
||||
questionText.startsWith("le premier") ||
|
||||
questionText.startsWith("la première") ||
|
||||
questionText.startsWith("qui a le plus") ||
|
||||
questionText.startsWith("celui/celle qui a le")) &&
|
||||
!questionText.contains("<j1>")) {
|
||||
return Category.CLASSEMENT;
|
||||
}
|
||||
|
||||
// 5. VOTE - "Votez tous en même temps", "Vote à main levée"
|
||||
if (questionText.contains("votez tous en même temps") ||
|
||||
questionText.contains("vote à main levée") ||
|
||||
questionText.contains("votez et le perdant")) {
|
||||
return Category.VOTE;
|
||||
}
|
||||
|
||||
// 6. JUGEMENT - "<J1> à toi de juger", "entre <J1> et <J2>", "qui de <J2> ou <J3>"
|
||||
if ((questionText.contains("juge") ||
|
||||
questionText.contains("entre <j1> et <j2>") ||
|
||||
questionText.contains("qui de <j2> ou <j3>") ||
|
||||
questionText.contains("selon toi")) &&
|
||||
questionText.contains("<j1>")) {
|
||||
return Category.JUGEMENT;
|
||||
}
|
||||
|
||||
// 7. DUEL J1/J2 - "<J1> et <J2> se regardent", "<J1> vs <J2>", bras de fer, etc.
|
||||
if ((questionText.contains("<j1> et <j2>") && !questionText.contains("variante")) ||
|
||||
questionText.contains("bras de fer") ||
|
||||
questionText.contains("clash") ||
|
||||
questionText.contains("duel") ||
|
||||
questionText.contains("concours")) {
|
||||
return Category.DUEL;
|
||||
}
|
||||
|
||||
// 8. INTERACTIF - Quiz, devinettes, mimes, karaoké, imitations
|
||||
if (questionText.contains("quiz") ||
|
||||
questionText.contains("devin") ||
|
||||
questionText.contains("mime") ||
|
||||
questionText.contains("karaoké") ||
|
||||
questionText.contains("imitation") ||
|
||||
questionText.contains("concours") ||
|
||||
questionText.contains("doit inventer") ||
|
||||
questionText.contains("doit créer") ||
|
||||
questionText.contains("doit deviner") ||
|
||||
questionText.contains("doit mimer") ||
|
||||
questionText.contains("doit compléter") ||
|
||||
questionText.contains("doit donner") && questionText.contains("compliment") ||
|
||||
questionText.contains("doit nommer") && questionText.contains("qualité")) {
|
||||
return Category.INTERACTIF;
|
||||
}
|
||||
|
||||
// 9. VARIANTE - Questions avec <variante>
|
||||
if (question.getVariante() != null && !question.getVariante().isEmpty()) {
|
||||
return Category.VARIANTE;
|
||||
}
|
||||
|
||||
// 10. CLASSIQUE - Par défaut
|
||||
return Category.CLASSIQUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la couleur associée à une catégorie
|
||||
*/
|
||||
public static int getColorForCategory(Category category) {
|
||||
return category.getColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nom de la catégorie
|
||||
*/
|
||||
public static String getNameForCategory(Category category) {
|
||||
return category.getName();
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package com.example.boidelov3.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.boidelov3.Question;
|
||||
import com.example.boidelov3.Questions;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Repository pour gérer l'accès aux données des questions.
|
||||
* Sépare la logique de chargement et de stockage de l'UI.
|
||||
*/
|
||||
public class QuestionRepository {
|
||||
private static final String TAG = "QuestionRepository";
|
||||
private static final String PREFS_NAME = "app";
|
||||
private static final String KEY_ASKED_QUESTIONS = "askedQuestions";
|
||||
|
||||
private final Context context;
|
||||
private final SharedPreferences prefs;
|
||||
private final Gson gson;
|
||||
private Questions cachedQuestions;
|
||||
|
||||
public QuestionRepository(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge toutes les questions depuis le fichier JSON.
|
||||
*
|
||||
* @return Result contenant les questions ou une erreur
|
||||
*/
|
||||
public Result<Questions, QuestionLoadException> loadQuestions() {
|
||||
if (cachedQuestions != null) {
|
||||
return Result.success(cachedQuestions);
|
||||
}
|
||||
|
||||
try {
|
||||
try (InputStream is = context.getAssets().open("question.json")) {
|
||||
byte[] buffer = new byte[is.available()];
|
||||
is.read(buffer);
|
||||
|
||||
String json = new String(buffer, "UTF-8");
|
||||
cachedQuestions = gson.fromJson(json, Questions.class);
|
||||
|
||||
if (cachedQuestions == null || cachedQuestions.getQuestions() == null) {
|
||||
return Result.failure(new QuestionLoadException("Impossible de parser le JSON"));
|
||||
}
|
||||
|
||||
Log.d(TAG, "Chargé " + cachedQuestions.getQuestions().size() + " questions");
|
||||
return Result.success(cachedQuestions);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Erreur de chargement des questions", e);
|
||||
return Result.failure(new QuestionLoadException("Erreur de lecture du fichier questions.json", e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient une question aléatoire qui n'a pas encore été posée.
|
||||
*
|
||||
* @param questions La liste complète des questions
|
||||
* @return Une question ou null si toutes ont été posées
|
||||
*/
|
||||
public Result<Question, QuestionLoadException> getRandomUnaskedQuestion(List<Question> questions) {
|
||||
Set<String> askedQuestions = prefs.getStringSet(KEY_ASKED_QUESTIONS, new HashSet<>());
|
||||
|
||||
List<Question> unaskedQuestions = new ArrayList<>();
|
||||
for (Question question : questions) {
|
||||
if (!askedQuestions.contains(String.valueOf(question.getId()))) {
|
||||
unaskedQuestions.add(question);
|
||||
}
|
||||
}
|
||||
|
||||
if (unaskedQuestions.isEmpty()) {
|
||||
return Result.failure(new QuestionLoadException("Toutes les questions ont été posées"));
|
||||
}
|
||||
|
||||
// Sélection aléatoire
|
||||
java.util.Random random = new java.util.Random();
|
||||
Question selected = unaskedQuestions.get(random.nextInt(unaskedQuestions.size()));
|
||||
|
||||
// Marquer comme posée
|
||||
askedQuestions.add(String.valueOf(selected.getId()));
|
||||
prefs.edit().putStringSet(KEY_ASKED_QUESTIONS, askedQuestions).apply();
|
||||
|
||||
return Result.success(selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marque une question comme étant posée.
|
||||
*/
|
||||
public void markQuestionAsAsked(int questionId) {
|
||||
Set<String> askedQuestions = prefs.getStringSet(KEY_ASKED_QUESTIONS, new HashSet<>());
|
||||
askedQuestions.add(String.valueOf(questionId));
|
||||
prefs.edit().putStringSet(KEY_ASKED_QUESTIONS, askedQuestions).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise la liste des questions posées.
|
||||
*/
|
||||
public void resetAskedQuestions() {
|
||||
prefs.edit().remove(KEY_ASKED_QUESTIONS).apply();
|
||||
Log.d(TAG, "Questions posées réinitialisées");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sauvegarde les statistiques d'une partie.
|
||||
*/
|
||||
public void saveGameStats(int questionsPlayed, int playersCount) {
|
||||
SharedPreferences statsPrefs = context.getSharedPreferences("game_stats", Context.MODE_PRIVATE);
|
||||
statsPrefs.edit()
|
||||
.putInt("questions_played", questionsPlayed)
|
||||
.putInt("players_count", playersCount)
|
||||
.apply();
|
||||
Log.d(TAG, "Stats sauvegardées: " + questionsPlayed + " questions, " + playersCount + " joueurs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception personnalisée pour les erreurs de chargement.
|
||||
*/
|
||||
public static class QuestionLoadException extends Exception {
|
||||
public QuestionLoadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public QuestionLoadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.example.boidelov3.data;
|
||||
|
||||
/**
|
||||
* Wrapper pour représenter le résultat d'une opération qui peut échouer.
|
||||
* Remplace les exceptions par des valeurs de retour typées.
|
||||
*
|
||||
* @param <T> Le type de donnée en cas de succès
|
||||
* @param <E> Le type d'erreur en cas d'échec
|
||||
*/
|
||||
public class Result<T, E extends Exception> {
|
||||
private final T data;
|
||||
private final E error;
|
||||
|
||||
private Result(T data, E error) {
|
||||
this.data = data;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un résultat réussi
|
||||
*/
|
||||
public static <T, E extends Exception> Result<T, E> success(T data) {
|
||||
return new Result<>(data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un résultat d'erreur
|
||||
*/
|
||||
public static <T, E extends Exception> Result<T, E> failure(E error) {
|
||||
return new Result<>(null, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true si l'opération a réussi
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return error == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true si l'opération a échoué
|
||||
*/
|
||||
public boolean isFailure() {
|
||||
return error != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return les données en cas de succès, null sinon
|
||||
*/
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return l'erreur en cas d'échec, null sinon
|
||||
*/
|
||||
public E getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return les données ou null si échec
|
||||
*/
|
||||
public T getOrNull() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return les données ou une valeur par défaut
|
||||
*/
|
||||
public T getOrElse(T defaultValue) {
|
||||
return isSuccess() ? data : defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
package com.example.boidelov3.game;
|
||||
|
||||
import com.example.boidelov3.Question;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Moteur de jeu contenant toute la logique métier.
|
||||
* Indépendant de l'UI et du framework Android.
|
||||
*/
|
||||
public class GameEngine {
|
||||
private final Random random;
|
||||
private final List<Question> activeManches;
|
||||
|
||||
public GameEngine() {
|
||||
this.random = new Random();
|
||||
this.activeManches = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite une question en remplaçant les variables.
|
||||
*
|
||||
* @param question La question à traiter
|
||||
* @param players La liste des joueurs
|
||||
* @param addedGorgees Nombre de gorgées additionnelles à ajouter
|
||||
* @return La question traitée avec les variables remplacées
|
||||
*/
|
||||
public ProcessedQuestion processQuestion(Question question, List<String> players, int addedGorgees) {
|
||||
String questionText = question.getQuestion();
|
||||
boolean isManche = false;
|
||||
|
||||
// Remplacer les variantes
|
||||
if (question.getVariante() != null && !question.getVariante().isEmpty()) {
|
||||
String chosenVariante = question.getVariante().get(random.nextInt(question.getVariante().size()));
|
||||
questionText = questionText.replace("<variante>", chosenVariante);
|
||||
}
|
||||
|
||||
// Gérer les manches
|
||||
if (questionText.contains("<manches>")) {
|
||||
int manchesCount = random.nextInt(7) + 4; // 4-10 manches
|
||||
questionText = questionText.replace("<manches>", String.valueOf(manchesCount));
|
||||
|
||||
// Créer une copie de la question pour la manche active
|
||||
Question mancheQuestion = copyQuestion(question);
|
||||
mancheQuestion.setQuestion(questionText); // Question avec le nombre de manches
|
||||
mancheQuestion.setManchesRestantes(manchesCount);
|
||||
|
||||
// Message de fin de manche
|
||||
String stopMessage = question.getArret() != null ? question.getArret() : "Fin du défi!";
|
||||
mancheQuestion.setArretMessageManche("Fin de défi!\n" + stopMessage);
|
||||
|
||||
activeManches.add(mancheQuestion);
|
||||
isManche = true;
|
||||
}
|
||||
|
||||
// Remplacer les joueurs et récupérer le nombre pour l'accord
|
||||
PlayerReplaceResult playerResult = replacePlayerPlaceholders(questionText, players);
|
||||
questionText = playerResult.questionText;
|
||||
|
||||
// Ajouter les gorgées (en passant le nombre de joueurs pour l'accord)
|
||||
questionText = addGorgeesText(question, questionText, addedGorgees, playerResult.playerCount);
|
||||
|
||||
// Mettre à jour la question avec le texte traité
|
||||
Question resultQuestion = isManche ? activeManches.get(activeManches.size() - 1) : copyQuestion(question);
|
||||
resultQuestion.setQuestion(questionText);
|
||||
|
||||
return new ProcessedQuestion(resultQuestion, isManche);
|
||||
}
|
||||
|
||||
/**
|
||||
* Résultat du remplacement des joueurs avec le nombre de joueurs
|
||||
*/
|
||||
private static class PlayerReplaceResult {
|
||||
String questionText;
|
||||
int playerCount;
|
||||
|
||||
PlayerReplaceResult(String questionText, int playerCount) {
|
||||
this.questionText = questionText;
|
||||
this.playerCount = playerCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remplace les placeholders de joueurs dans la question et retourne le nombre de joueurs.
|
||||
*/
|
||||
private PlayerReplaceResult replacePlayerPlaceholders(String questionText, List<String> players) {
|
||||
boolean hasJ1 = questionText.contains("<J1>");
|
||||
boolean hasJ2 = questionText.contains("<J2>");
|
||||
boolean hasJ3 = questionText.contains("<J3>");
|
||||
|
||||
if (!hasJ1 && !hasJ2 && !hasJ3) {
|
||||
return new PlayerReplaceResult(questionText, 0);
|
||||
}
|
||||
|
||||
List<String> selectedPlayers = selectRandomPlayers(players, 3);
|
||||
String result = questionText;
|
||||
int playerCount = 0;
|
||||
|
||||
if (hasJ1 && hasJ2 && hasJ3 && selectedPlayers.size() >= 3) {
|
||||
playerCount = 3;
|
||||
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||
result = result.replace("<J2>", selectedPlayers.get(1));
|
||||
result = result.replace("<J3>", selectedPlayers.get(2));
|
||||
} else if (hasJ1 && hasJ2 && selectedPlayers.size() >= 2) {
|
||||
playerCount = 2;
|
||||
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||
result = result.replace("<J2>", selectedPlayers.get(1));
|
||||
} else if (hasJ1 && selectedPlayers.size() >= 1) {
|
||||
playerCount = 1;
|
||||
result = result.replace("<J1>", selectedPlayers.get(0));
|
||||
}
|
||||
|
||||
return new PlayerReplaceResult(result, playerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute le texte des gorgées à la question.
|
||||
*/
|
||||
private String addGorgeesText(Question question, String questionText, int addedGorgees, int playerCount) {
|
||||
if (!question.isDistribution() && !question.isRecois()) {
|
||||
return questionText;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder(questionText);
|
||||
sb.append(" ");
|
||||
|
||||
int totalGorgees = question.getGorger() + addedGorgees;
|
||||
|
||||
// Accord du verbe selon le nombre de joueurs
|
||||
String boisVerb = (playerCount > 1) ? "boivent" : "boit";
|
||||
String distribueVerb = (playerCount > 1) ? "distribuent" : "distribue";
|
||||
|
||||
// Déterminer si boire ou distribuer
|
||||
if (question.isRecois() && question.isDistribution()) {
|
||||
sb.append(random.nextBoolean() ? "<b>" + boisVerb + "</b>" : "<b>" + distribueVerb + "</b>");
|
||||
} else if (question.isRecois()) {
|
||||
sb.append("<b>" + boisVerb + "</b>");
|
||||
} else {
|
||||
sb.append("<b>" + distribueVerb + "</b>");
|
||||
}
|
||||
|
||||
sb.append(" ").append(totalGorgees).append(" gorgée");
|
||||
if (totalGorgees > 1) {
|
||||
sb.append("s");
|
||||
}
|
||||
sb.append(".");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne n joueurs aléatoires uniques.
|
||||
*/
|
||||
public List<String> selectRandomPlayers(List<String> allPlayers, int count) {
|
||||
Set<String> selected = new HashSet<>();
|
||||
int available = Math.min(count, allPlayers.size());
|
||||
|
||||
while (selected.size() < available) {
|
||||
selected.add(allPlayers.get(random.nextInt(allPlayers.size())));
|
||||
}
|
||||
|
||||
return new ArrayList<>(selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les manches actives et retourne l'état.
|
||||
*
|
||||
* @return MancheState contenant la manche à afficher ou null si terminée
|
||||
*/
|
||||
public MancheState updateManches() {
|
||||
if (activeManches.isEmpty()) {
|
||||
return new MancheState(null, false, null);
|
||||
}
|
||||
|
||||
Question mancheQuestion = activeManches.get(0);
|
||||
mancheQuestion.setManchesRestantes(mancheQuestion.getManchesRestantes() - 1);
|
||||
|
||||
if (mancheQuestion.getManchesRestantes() <= 0) {
|
||||
// Manche terminée
|
||||
String endMessage = mancheQuestion.getArretMessageManche();
|
||||
activeManches.remove(0);
|
||||
return new MancheState(null, false, endMessage);
|
||||
} else {
|
||||
// Manche toujours active
|
||||
return new MancheState(mancheQuestion, true, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true si une manche est active
|
||||
*/
|
||||
public boolean hasActiveManche() {
|
||||
return !activeManches.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return le nombre de manches actives
|
||||
*/
|
||||
public int getActiveManchesCount() {
|
||||
return activeManches.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide toutes les manches actives.
|
||||
*/
|
||||
public void clearManches() {
|
||||
activeManches.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une copie d'une question (pour éviter de modifier l'original).
|
||||
*/
|
||||
private Question copyQuestion(Question source) {
|
||||
Question copy = new Question();
|
||||
copy.setId(source.getId());
|
||||
copy.setQuestion(source.getQuestion());
|
||||
copy.setArret(source.getArret());
|
||||
copy.setVariante(source.getVariante());
|
||||
copy.setDistribution(source.isDistribution());
|
||||
copy.setRecois(source.isRecois());
|
||||
copy.setGorger(source.getGorger());
|
||||
copy.setManchesRestantes(source.getManchesRestantes());
|
||||
copy.setArretMessageManche(source.getArretMessageManche());
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conteneur pour une question traitée.
|
||||
*/
|
||||
public static class ProcessedQuestion {
|
||||
public final Question question;
|
||||
public final boolean isManche;
|
||||
|
||||
public ProcessedQuestion(Question question, boolean isManche) {
|
||||
this.question = question;
|
||||
this.isManche = isManche;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* État des manches après mise à jour.
|
||||
*/
|
||||
public static class MancheState {
|
||||
public final Question activeManche;
|
||||
public final boolean hasManche;
|
||||
public final String endMessage;
|
||||
|
||||
public MancheState(Question activeManche, boolean hasManche, String endMessage) {
|
||||
this.activeManche = activeManche;
|
||||
this.hasManche = hasManche;
|
||||
this.endMessage = endMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
-1110
File diff suppressed because it is too large
Load Diff
-501
@@ -1,501 +0,0 @@
|
||||
package com.example.boidelov3.games.boideloclassic;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import android.widget.EditText;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.OpenAIService;
|
||||
import com.example.boidelov3.R;
|
||||
import com.example.boidelov3.utils.ErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class BoideloClassicParamsActivity extends AppCompatActivity {
|
||||
|
||||
private SeekBar seekBar1, seekBar2, seekBar3, seekBarDuration;
|
||||
private TextView textView1, textView2, textView5, textViewRatioGen, questionCountValue, gorgeesValue, durationValue;
|
||||
private SwitchMaterial checkBoxGPT;
|
||||
private Button buttonTestApi;
|
||||
private EditText editTextKeyGPT;
|
||||
private AutoCompleteTextView autoCompleteProvider;
|
||||
private com.google.android.material.card.MaterialCardView openaiCard;
|
||||
private LinearLayout openaiContentLayout;
|
||||
private String keyGPT;
|
||||
private OpenAIService.AIProvider selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
private int nbQuestions;
|
||||
|
||||
private ArrayList<String> toutlesjoueurs;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_boidelo_classic_params);
|
||||
|
||||
// Configure la barre d'action avec un bouton retour
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.parameters);
|
||||
}
|
||||
|
||||
// Initialisation des vues
|
||||
seekBar1 = findViewById(R.id.seekBar1);
|
||||
seekBar2 = findViewById(R.id.seekBar2);
|
||||
seekBar3 = findViewById(R.id.seekBar3);
|
||||
seekBarDuration = findViewById(R.id.seekBarDuration);
|
||||
textView1 = findViewById(R.id.textView1);
|
||||
textView2 = findViewById(R.id.textView2);
|
||||
textView5 = findViewById(R.id.textView5);
|
||||
editTextKeyGPT = findViewById(R.id.editTextGPT);
|
||||
autoCompleteProvider = findViewById(R.id.autoCompleteProvider);
|
||||
buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
textViewRatioGen = findViewById(R.id.textViewRatioGen);
|
||||
questionCountValue = findViewById(R.id.questionCountValue);
|
||||
gorgeesValue = findViewById(R.id.gorgeesValue);
|
||||
durationValue = findViewById(R.id.durationValue);
|
||||
openaiCard = findViewById(R.id.openaiCard);
|
||||
|
||||
// Récupérer le LinearLayout qui contient tous les éléments de la carte IA
|
||||
openaiContentLayout = findViewById(R.id.openaiCardContent);
|
||||
|
||||
// Configuration du dropdown pour le provider IA
|
||||
String[] providers = new String[]{
|
||||
OpenAIService.AIProvider.OPENAI.getDisplayName(),
|
||||
OpenAIService.AIProvider.OPENROUTER.getDisplayName(),
|
||||
OpenAIService.AIProvider.ZAI.getDisplayName()
|
||||
};
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, providers);
|
||||
autoCompleteProvider.setAdapter(adapter);
|
||||
|
||||
// Charger le provider sauvegardé
|
||||
SharedPreferences providerPrefs = getSharedPreferences("MyPrefs", MODE_PRIVATE);
|
||||
String savedProvider = providerPrefs.getString("aiProvider", OpenAIService.AIProvider.OPENAI.getDisplayName());
|
||||
autoCompleteProvider.setText(savedProvider, false);
|
||||
|
||||
// Définir le provider sélectionné
|
||||
if (savedProvider.equals(OpenAIService.AIProvider.OPENROUTER.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENROUTER;
|
||||
} else if (savedProvider.equals(OpenAIService.AIProvider.ZAI.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.ZAI;
|
||||
} else {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
}
|
||||
|
||||
// Listener pour le changement de provider
|
||||
autoCompleteProvider.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
String selected = (String) parent.getItemAtPosition(position);
|
||||
SharedPreferences.Editor editor = providerPrefs.edit();
|
||||
editor.putString("aiProvider", selected);
|
||||
editor.apply();
|
||||
|
||||
if (selected.equals(OpenAIService.AIProvider.OPENROUTER.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENROUTER;
|
||||
} else if (selected.equals(OpenAIService.AIProvider.ZAI.getDisplayName())) {
|
||||
selectedProvider = OpenAIService.AIProvider.ZAI;
|
||||
} else {
|
||||
selectedProvider = OpenAIService.AIProvider.OPENAI;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialiser les TextView avec les valeurs par défaut
|
||||
int initialQuestions = 50;
|
||||
int initialGorgees = 0;
|
||||
int initialRatio = 8;
|
||||
int initialDuration = 0; // 0 pour avoir 4-10 tours par défaut (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
questionCountValue.setText(String.valueOf(initialQuestions));
|
||||
gorgeesValue.setText(String.valueOf(initialGorgees));
|
||||
durationValue.setText("0"); // Afficher 0 par défaut pour avoir 4-10 tours
|
||||
textView5.setText("Palier : Grosse merde");
|
||||
textViewRatioGen.setText("Ratio BDD/OPENAI : 1/" + initialRatio);
|
||||
|
||||
// Configuration de la seekBar1
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBar1.setMin(20);
|
||||
}
|
||||
seekBar1.setMax(150);
|
||||
seekBar1.setProgress(50);
|
||||
|
||||
// Configuration de la seekBar2
|
||||
seekBar2.setMax(20);
|
||||
seekBar2.setProgress(0);
|
||||
|
||||
// Configuration de la seekBar3
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBar2.setMin(0);
|
||||
seekBar3.setMin(1);
|
||||
}
|
||||
seekBar3.setMax(15);
|
||||
seekBar3.setProgress(8);
|
||||
|
||||
// Configuration de la seekBarDuration (permet valeurs négatives pour offset)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
seekBarDuration.setMin(-5); // Permet un offset négatif jusqu'à -5
|
||||
}
|
||||
seekBarDuration.setMax(15);
|
||||
seekBarDuration.setProgress(0); // Valeur par défaut à 0 pour avoir 4-10 tours (MIN_DEFI_ROUNDS=4)
|
||||
|
||||
// Configuration des listeners pour les seekBars
|
||||
seekBar1.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Ajustement de la valeur au multiple de 10 le plus proche
|
||||
int adjustedProgress = Math.round(progress / 10) * 10;
|
||||
seekBar.setProgress(adjustedProgress);
|
||||
questionCountValue.setText(String.valueOf(adjustedProgress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Mise à jour du gorgeesValue en fonction de la valeur de la seekBar2
|
||||
gorgeesValue.setText(String.valueOf(progress));
|
||||
// Mise à jour du textView5 en fonction de la valeur de la seekBar2
|
||||
switch (progress) {
|
||||
case 0:
|
||||
textView5.setText("Palier : Grosse merde");
|
||||
break;
|
||||
case 2:
|
||||
textView5.setText("Palier : Petite merde");
|
||||
break;
|
||||
case 4:
|
||||
textView5.setText("Palier : Petit joueur");
|
||||
break;
|
||||
case 6:
|
||||
textView5.setText("Palier : Un p'tit verre ?!");
|
||||
break;
|
||||
case 8:
|
||||
textView5.setText("Palier : ça commence à aller");
|
||||
break;
|
||||
case 10:
|
||||
textView5.setText("Palier : Alcoolique");
|
||||
break;
|
||||
case 12:
|
||||
textView5.setText("Palier : COMA ETHYLIX");
|
||||
break;
|
||||
case 13:
|
||||
textView5.setText("Palier : APÉROOOOO !!");
|
||||
break;
|
||||
case 14:
|
||||
textView5.setText("Palier : LA J'SUIS BIENG");
|
||||
break;
|
||||
case 15:
|
||||
textView5.setText("Palier : J'VOIS PLUS RIENG");
|
||||
break;
|
||||
case 17:
|
||||
textView5.setText("Palier : J'AI PLUS DE VERRES");
|
||||
break;
|
||||
case 18 :
|
||||
textView5.setText("Palier : Soirée Murge");
|
||||
break;
|
||||
case 19:
|
||||
textView5.setText("Palier : Soirée Pétée");
|
||||
break;
|
||||
case 20:
|
||||
textView5.setText("Palier : L'ENDER DRAGON");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration du checkBox // Q : IL sert à quoi ?
|
||||
// R : Il sert à activer/désactiver les vues en dessous
|
||||
|
||||
buttonTestApi = findViewById(R.id.ButtonTestApi);
|
||||
|
||||
checkBoxGPT = findViewById(R.id.checkBoxGPT);
|
||||
checkBoxGPT.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
updateOpenAICardState(isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialiser l'état de la carte IA selon l'état initial du checkBox
|
||||
updateOpenAICardState(checkBoxGPT.isChecked());
|
||||
|
||||
// Configuration de la seekBar3
|
||||
seekBar3.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar3, int progress, boolean fromUser) {
|
||||
textViewRatioGen.setText("Ratio BDD/OPENAI : 1/" + progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar3) {
|
||||
}
|
||||
});
|
||||
|
||||
// Configuration de la seekBarDuration
|
||||
seekBarDuration.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
// Afficher avec signe +/- pour bien voir l'offset, mais sans signe pour 0
|
||||
String displayValue;
|
||||
if (progress > 0) {
|
||||
displayValue = "+" + progress;
|
||||
} else {
|
||||
displayValue = String.valueOf(progress); // Affiche "0" ou "-X"
|
||||
}
|
||||
durationValue.setText(displayValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
// Partie OpenAI : enregistrement de la clé en dur.
|
||||
// Récupérer une instance des SharedPreferences
|
||||
SharedPreferences sharedPreferences = getSharedPreferences("MyPrefs", MODE_PRIVATE);
|
||||
final SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
|
||||
// Récupérer la valeur enregistrée dans les SharedPreferences
|
||||
String savedText = sharedPreferences.getString("savedText", "");
|
||||
editTextKeyGPT.setText(savedText);
|
||||
|
||||
// Enregistrer le contenu de l'EditText lorsque l'utilisateur modifie le texte
|
||||
editTextKeyGPT.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// Enregistrer le texte dans les SharedPreferences
|
||||
editor.putString("savedText", editTextKeyGPT.getText().toString());
|
||||
editor.apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onClickButtonTestAPI(View view) {
|
||||
String apiKey = editTextKeyGPT.getText().toString();
|
||||
|
||||
if (apiKey == null || apiKey.isEmpty()) {
|
||||
Toast.makeText(this, "Veuillez entrer une clé API", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Créer un client OkHttpClient pour effectuer la requête
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
// Déterminer l'URL, le modèle et le format selon le provider sélectionné
|
||||
String testUrl;
|
||||
String testModel;
|
||||
String jsonBody;
|
||||
boolean isAnthropicFormat = (selectedProvider == OpenAIService.AIProvider.ZAI);
|
||||
|
||||
switch (selectedProvider) {
|
||||
case OPENROUTER:
|
||||
testUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||
testModel = "openai/gpt-3.5-turbo";
|
||||
// Format OpenAI
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
case ZAI:
|
||||
testUrl = "https://api.z.ai/v1/messages";
|
||||
testModel = "claude-3-5-sonnet";
|
||||
// Format Anthropic
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
case OPENAI:
|
||||
default:
|
||||
testUrl = "https://api.openai.com/v1/chat/completions";
|
||||
testModel = "gpt-3.5-turbo";
|
||||
jsonBody = "{\"model\":\"" + testModel + "\",\"messages\":[{\"role\":\"user\",\"content\":\"Test\"}],\"max_tokens\":5}";
|
||||
break;
|
||||
}
|
||||
|
||||
// Construire la requête
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(testUrl)
|
||||
.addHeader("Content-Type", "application/json");
|
||||
|
||||
// Ajouter les headers selon le provider
|
||||
switch (selectedProvider) {
|
||||
case OPENAI:
|
||||
case OPENROUTER:
|
||||
requestBuilder.addHeader("Authorization", "Bearer " + apiKey);
|
||||
break;
|
||||
case ZAI:
|
||||
requestBuilder.addHeader("x-api-key", apiKey);
|
||||
requestBuilder.addHeader("anthropic-version", "2023-06-01");
|
||||
break;
|
||||
}
|
||||
|
||||
// Headers spécifiques pour OpenRouter
|
||||
if (selectedProvider == OpenAIService.AIProvider.OPENROUTER) {
|
||||
requestBuilder.addHeader("HTTP-Referer", "https://boidelo.app");
|
||||
requestBuilder.addHeader("X-Title", "Boidelo");
|
||||
}
|
||||
|
||||
Request request = requestBuilder
|
||||
.post(okhttp3.RequestBody.create(jsonBody, okhttp3.MediaType.get("application/json")))
|
||||
.build();
|
||||
|
||||
// Exécuter la requête de test
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, IOException e) {
|
||||
String operation = "Test de connexion API " + selectedProvider.getDisplayName();
|
||||
String details = "Échec de connexion lors du test de l'API";
|
||||
ErrorHandler.logErrorOnly("BoideloClassicParamsActivity", operation + " - " + details, e);
|
||||
runOnUiThread(() -> {
|
||||
String userMessage = "Échec de connexion " + selectedProvider.getDisplayName() + " : " + e.getMessage();
|
||||
Toast.makeText(getApplicationContext(), userMessage, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Connexion " + selectedProvider.getDisplayName() + " réussie !",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Erreur " + selectedProvider.getDisplayName() + " (HTTP " + response.code() + ")",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
response.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour l'état de la carte OpenAI (visibilité et taille)
|
||||
* Quand l'IA est désactivée, cache tous les éléments sauf le titre et le switch
|
||||
*/
|
||||
private void updateOpenAICardState(boolean isOpenAIEnabled) {
|
||||
// Activation/désactivation des vues en fonction de l'état du checkBox
|
||||
autoCompleteProvider.setEnabled(isOpenAIEnabled);
|
||||
// Pour le champ API key : on garde le layout activé pour le toggle password,
|
||||
// mais on désactive l'édition du texte
|
||||
editTextKeyGPT.setFocusable(isOpenAIEnabled);
|
||||
editTextKeyGPT.setFocusableInTouchMode(isOpenAIEnabled);
|
||||
editTextKeyGPT.setClickable(isOpenAIEnabled);
|
||||
editTextKeyGPT.setCursorVisible(isOpenAIEnabled);
|
||||
if (!isOpenAIEnabled) {
|
||||
editTextKeyGPT.clearFocus();
|
||||
}
|
||||
textViewRatioGen.setEnabled(isOpenAIEnabled);
|
||||
seekBar3.setEnabled(isOpenAIEnabled);
|
||||
buttonTestApi.setEnabled(isOpenAIEnabled);
|
||||
|
||||
// Cacher/montrer les éléments pour réduire la taille de la carte
|
||||
// Les éléments à cacher quand l'IA est désactivée (index 2 à 6 dans le LinearLayout)
|
||||
// Index: 0=titreLinearLayout, 1=textInputLayoutProvider, 2=textInputLayoutApiKey,
|
||||
// 3=ratioSeekBarLinearLayout, 4=buttonTestApi
|
||||
if (openaiContentLayout != null) {
|
||||
for (int i = 1; i < openaiContentLayout.getChildCount(); i++) {
|
||||
View child = openaiContentLayout.getChildAt(i);
|
||||
if (child != null) {
|
||||
child.setVisibility(isOpenAIEnabled ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onClickButtonStart(View view) {
|
||||
// Récupérer les paramètres de la partie
|
||||
int nombreQuestions = seekBar1.getProgress();
|
||||
int ajoutGorgees = seekBar2.getProgress();
|
||||
int ratioBddOpenAI = seekBar3.getProgress();
|
||||
int durationDefis = seekBarDuration.getProgress();
|
||||
boolean openAI = checkBoxGPT.isChecked();
|
||||
|
||||
// Récupérer les joueurs depuis l'intent
|
||||
toutlesjoueurs = getIntent().getStringArrayListExtra("EXTRA_LIST_JOUEUR");
|
||||
|
||||
if (toutlesjoueurs == null || toutlesjoueurs.isEmpty()) {
|
||||
Toast.makeText(this, "Erreur: Aucun joueur trouvé", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Lancer l'activité BoideloClassicGameActivity avec les paramètres
|
||||
Intent intent = new Intent(this, BoideloClassicGameActivity.class);
|
||||
intent.putExtra("EXTRA_NOMBRE_QUESTIONS", nombreQuestions);
|
||||
intent.putExtra("EXTRA_AJOUT_GORGEE", ajoutGorgees);
|
||||
intent.putExtra("EXTRA_RATIO_OPENAI", ratioBddOpenAI);
|
||||
intent.putExtra("EXTRA_DURATION_DEFIS", durationDefis);
|
||||
intent.putExtra("EXTRA_OPENAI", openAI);
|
||||
intent.putExtra("EXTRA_KEY_OPENAI", editTextKeyGPT.getText().toString());
|
||||
intent.putExtra("EXTRA_AI_PROVIDER", selectedProvider.name());
|
||||
intent.putStringArrayListExtra("PLAYERS", toutlesjoueurs);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
package com.example.boidelov3.games.boideloclassic;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BoideloClassicSetupActivity - Écran de configuration du jeu Boidelo Classic
|
||||
* Version simplifiée comme 89++
|
||||
*/
|
||||
public class BoideloClassicSetupActivity extends AppCompatActivity {
|
||||
|
||||
private static final int MIN_PLAYERS = 3;
|
||||
private static final int MAX_PLAYERS = 15;
|
||||
|
||||
private LinearLayout playersContainer;
|
||||
private MaterialButton addPlayerButton;
|
||||
private MaterialButton startGameButton;
|
||||
private TextView playerCountText;
|
||||
private MaterialToolbar toolbar;
|
||||
|
||||
private final List<String> playerNames = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_boidelo_classic_setup);
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupListeners();
|
||||
|
||||
// Ajouter 3 joueurs par défaut
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
|
||||
// Initialiser le compteur
|
||||
updatePlayerCountText();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
playersContainer = findViewById(R.id.playersContainer);
|
||||
addPlayerButton = findViewById(R.id.addPlayerButton);
|
||||
startGameButton = findViewById(R.id.startGameButton);
|
||||
playerCountText = findViewById(R.id.playerCountText);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
addPlayerButton.setOnClickListener(v -> {
|
||||
if (playersContainer.getChildCount() < MAX_PLAYERS) {
|
||||
addPlayerRow();
|
||||
} else {
|
||||
Toast.makeText(this, "Maximum " + MAX_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
startGameButton.setOnClickListener(v -> goToParams());
|
||||
}
|
||||
|
||||
private void addPlayerRow() {
|
||||
View playerRow = LayoutInflater.from(this).inflate(R.layout.item_player_row, playersContainer, false);
|
||||
|
||||
TextInputEditText playerNameEdit = playerRow.findViewById(R.id.playerName);
|
||||
MaterialButton removeButton = playerRow.findViewById(R.id.removePlayerButton);
|
||||
TextView playerNumber = playerRow.findViewById(R.id.playerNumber);
|
||||
|
||||
int position = playersContainer.getChildCount();
|
||||
playerNumber.setText(String.valueOf(position + 1));
|
||||
|
||||
// Cacher le bouton de suppression pour les 3 premiers joueurs (minimum requis)
|
||||
if (position < MIN_PLAYERS) {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
removeButton.setOnClickListener(v -> {
|
||||
if (playersContainer.getChildCount() > MIN_PLAYERS) {
|
||||
playersContainer.removeView(playerRow);
|
||||
updatePlayerNumbers();
|
||||
updatePlayerNames();
|
||||
} else {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
playerNameEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
updatePlayerNames();
|
||||
}
|
||||
});
|
||||
|
||||
playersContainer.addView(playerRow);
|
||||
updatePlayerCountText();
|
||||
updateStartButton();
|
||||
}
|
||||
|
||||
private void updatePlayerNumbers() {
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextView playerNumber = row.findViewById(R.id.playerNumber);
|
||||
playerNumber.setText(String.valueOf(i + 1));
|
||||
|
||||
// Afficher le bouton de suppression uniquement au-delà du minimum
|
||||
MaterialButton removeButton = row.findViewById(R.id.removePlayerButton);
|
||||
if (i >= MIN_PLAYERS) {
|
||||
removeButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
updatePlayerCountText();
|
||||
}
|
||||
|
||||
private void updatePlayerNames() {
|
||||
playerNames.clear();
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextInputEditText edit = row.findViewById(R.id.playerName);
|
||||
String name = edit.getText().toString().trim();
|
||||
if (!TextUtils.isEmpty(name)) {
|
||||
playerNames.add(name);
|
||||
} else {
|
||||
playerNames.add("Joueur " + (i + 1));
|
||||
}
|
||||
}
|
||||
updateStartButton();
|
||||
}
|
||||
|
||||
private void updatePlayerCountText() {
|
||||
int count = playersContainer.getChildCount();
|
||||
playerCountText.setText("Joueurs: " + count + " / min. " + MIN_PLAYERS);
|
||||
}
|
||||
|
||||
private void updateStartButton() {
|
||||
int validPlayers = playersContainer.getChildCount();
|
||||
boolean canStart = validPlayers >= MIN_PLAYERS;
|
||||
startGameButton.setEnabled(canStart);
|
||||
startGameButton.setText(canStart ? "PARAMÈTRES ET JEU (" + validPlayers + ")" : "Ajoutez des joueurs");
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirige vers l'écran des paramètres avant de lancer le jeu
|
||||
*/
|
||||
private void goToParams() {
|
||||
// Vérifier que tous les champs minimums sont remplis
|
||||
ArrayList<String> validNames = new ArrayList<>();
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextInputEditText edit = row.findViewById(R.id.playerName);
|
||||
String name = edit.getText().toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
Toast.makeText(this, "Veuillez remplir le nom du joueur " + (i + 1), Toast.LENGTH_SHORT).show();
|
||||
edit.requestFocus();
|
||||
return;
|
||||
}
|
||||
validNames.add(name);
|
||||
}
|
||||
|
||||
if (validNames.size() < MIN_PLAYERS) {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs requis", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Rediriger vers l'écran des paramètres
|
||||
Intent intent = new Intent(this, BoideloClassicParamsActivity.class);
|
||||
intent.putStringArrayListExtra("EXTRA_LIST_JOUEUR", validNames);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
package com.example.boidelov3.games.boideloclassic.manager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* BoideloPlayerManager - Gestionnaire des joueurs pour Boidelo Classic
|
||||
* Cette classe gère la liste des joueurs et leurs informations
|
||||
*/
|
||||
public class BoideloPlayerManager {
|
||||
|
||||
private ArrayList<String> players;
|
||||
|
||||
public BoideloPlayerManager() {
|
||||
this.players = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un joueur à la liste
|
||||
* @param playerName Le nom du joueur à ajouter
|
||||
*/
|
||||
public void addPlayer(String playerName) {
|
||||
if (playerName != null && !playerName.trim().isEmpty()) {
|
||||
players.add(playerName.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un joueur de la liste
|
||||
* @param index L'index du joueur à supprimer
|
||||
*/
|
||||
public void removePlayer(int index) {
|
||||
if (index >= 0 && index < players.size()) {
|
||||
players.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la liste des joueurs
|
||||
* @return La liste des noms des joueurs
|
||||
*/
|
||||
public ArrayList<String> getPlayers() {
|
||||
return new ArrayList<>(players);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nombre de joueurs
|
||||
* @return Le nombre de joueurs
|
||||
*/
|
||||
public int getPlayerCount() {
|
||||
return players.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide la liste des joueurs
|
||||
*/
|
||||
public void clearPlayers() {
|
||||
players.clear();
|
||||
}
|
||||
}
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
package com.example.boidelov3.games.boideloclassic.manager;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import com.example.boidelov3.R;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* BoideloPlayerRowManager - Gestionnaire des lignes de saisie de joueurs
|
||||
* Cette classe gère l'ajout et la suppression dynamique de champs de saisie
|
||||
* avec Material Design
|
||||
*/
|
||||
public class BoideloPlayerRowManager {
|
||||
|
||||
private Activity activity;
|
||||
private LinearLayout nameEntryLayout;
|
||||
private BoideloPlayerManager playerManager;
|
||||
private ArrayList<LinearLayout> playerRowList;
|
||||
private ArrayList<TextInputEditText> editTextList;
|
||||
|
||||
private int offset = 4; // Nombre initial de joueurs fixes (J1, J2, J3)
|
||||
|
||||
public BoideloPlayerRowManager(Activity activity, LinearLayout nameEntryLayout,
|
||||
BoideloPlayerManager playerManager) {
|
||||
this.activity = activity;
|
||||
this.nameEntryLayout = nameEntryLayout;
|
||||
this.playerManager = playerManager;
|
||||
this.playerRowList = new ArrayList<>();
|
||||
this.editTextList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une nouvelle ligne de saisie pour un joueur
|
||||
*/
|
||||
public void addPlayerRow() {
|
||||
// Inflate le layout Material Design
|
||||
LayoutInflater inflater = LayoutInflater.from(activity);
|
||||
LinearLayout newRow = (LinearLayout) inflater.inflate(R.layout.item_player_row, nameEntryLayout, false);
|
||||
|
||||
// Configure le numéro du joueur
|
||||
TextView playerNumber = newRow.findViewById(R.id.playerNumber);
|
||||
playerNumber.setText(String.valueOf(offset));
|
||||
|
||||
// Récupère le champ de saisie
|
||||
TextInputEditText editText = newRow.findViewById(R.id.playerName);
|
||||
editText.setHint("Joueur " + offset);
|
||||
editTextList.add(editText);
|
||||
|
||||
// Configure le bouton de suppression
|
||||
MaterialButton removeButton = newRow.findViewById(R.id.removePlayerButton);
|
||||
removeButton.setOnClickListener(v -> removePlayerRow(newRow));
|
||||
|
||||
// Ajoute la ligne au conteneur
|
||||
nameEntryLayout.addView(newRow);
|
||||
playerRowList.add(newRow);
|
||||
offset++;
|
||||
|
||||
// Notifie le gestionnaire de joueurs
|
||||
playerManager.addPlayer("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une ligne de joueur
|
||||
* @param row La ligne à supprimer
|
||||
*/
|
||||
public void removePlayerRow(LinearLayout row) {
|
||||
int index = playerRowList.indexOf(row);
|
||||
if (index >= 0) {
|
||||
nameEntryLayout.removeView(row);
|
||||
playerRowList.remove(index);
|
||||
if (index < editTextList.size()) {
|
||||
editTextList.remove(index);
|
||||
}
|
||||
offset--;
|
||||
|
||||
// Met à jour les numéros de joueurs
|
||||
updatePlayerNumbers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour les numéros affichés pour tous les joueurs
|
||||
*/
|
||||
private void updatePlayerNumbers() {
|
||||
for (int i = 0; i < playerRowList.size(); i++) {
|
||||
LinearLayout row = playerRowList.get(i);
|
||||
TextView playerNumber = row.findViewById(R.id.playerNumber);
|
||||
if (playerNumber != null) {
|
||||
playerNumber.setText(String.valueOf(4 + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne les noms des joueurs depuis les champs dynamiques
|
||||
* @return La liste des noms des joueurs
|
||||
*/
|
||||
public ArrayList<String> getPlayerNames() {
|
||||
ArrayList<String> names = new ArrayList<>();
|
||||
for (TextInputEditText editText : editTextList) {
|
||||
String name = editText.getText() != null ? editText.getText().toString().trim() : "";
|
||||
if (!name.isEmpty()) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nombre total de lignes
|
||||
* @return Le nombre de lignes
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return playerRowList.size();
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package com.example.boidelov3.games.game89;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Gère les défis du jeu 89++
|
||||
*/
|
||||
public class Game89ChallengeManager {
|
||||
private final Random random;
|
||||
private final List<Challenge> availableChallenges;
|
||||
private int currentIndex = 0;
|
||||
|
||||
public Game89ChallengeManager() {
|
||||
this.random = new Random();
|
||||
this.availableChallenges = new ArrayList<>();
|
||||
initializeChallenges();
|
||||
shuffleChallenges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mélange la liste des défis disponibles
|
||||
*/
|
||||
private void shuffleChallenges() {
|
||||
Collections.shuffle(availableChallenges, random);
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise la liste des défis disponibles
|
||||
*/
|
||||
private void initializeChallenges() {
|
||||
// Changer le sens
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.REVERSE_DIRECTION,
|
||||
"Changement de sens !",
|
||||
"Le sens du jeu est inversé !"
|
||||
));
|
||||
|
||||
// Immunité
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.IMMUNITY,
|
||||
"Immunité !",
|
||||
"Le joueur actuel est immunisé contre les gorgées pendant 5 minutes !"
|
||||
));
|
||||
|
||||
// Distribuer des gorgées
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.DRINK_GORGEE,
|
||||
"Gorgée surprise !",
|
||||
"Le joueur actuel boit 3 gorgées !"
|
||||
));
|
||||
|
||||
// Joker : passer son tour
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.SKIP_TURN,
|
||||
"Joker !",
|
||||
"Le joueur actuel passe son tour !"
|
||||
));
|
||||
|
||||
// Échange de main
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.SWAP_HAND,
|
||||
"Échange de mains !",
|
||||
"Le joueur actuel échange sa main avec le joueur de son choix !"
|
||||
));
|
||||
|
||||
// Double prochaine valeur
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.DOUBLE_NEXT,
|
||||
"Double !",
|
||||
"La prochaine carte jouée compte double !"
|
||||
));
|
||||
|
||||
// Compteur aléatoire
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.RANDOM_COUNT,
|
||||
"Compte mystère !",
|
||||
"Ajoutez un nombre aléatoire entre 1 et 10 au compteur !"
|
||||
));
|
||||
|
||||
// Tout le monde boit
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.EVERYONE_DRINKS,
|
||||
"Tour générale !",
|
||||
"Tout le monde boit 2 gorgées !"
|
||||
));
|
||||
|
||||
// Choisissez une victime
|
||||
availableChallenges.add(new Challenge(
|
||||
ChallengeType.PICK_VICTIM,
|
||||
"Victime !",
|
||||
"Le joueur actuel choisit quelqu'un qui boit 3 gorgées !"
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le prochain défi (sans répétition tant que tous n'ont pas été utilisés)
|
||||
*/
|
||||
public Challenge getRandomChallenge() {
|
||||
if (availableChallenges.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (currentIndex >= availableChallenges.size()) {
|
||||
shuffleChallenges();
|
||||
}
|
||||
return availableChallenges.get(currentIndex++);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la description d'un défi pour l'afficher
|
||||
*/
|
||||
public static String getChallengeDisplay(Challenge challenge) {
|
||||
if (challenge == null) {
|
||||
return "";
|
||||
}
|
||||
return challenge.getTitle() + "\n" + challenge.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe représentant un défi
|
||||
*/
|
||||
public static class Challenge {
|
||||
private final ChallengeType type;
|
||||
private final String title;
|
||||
private final String description;
|
||||
|
||||
public Challenge(ChallengeType type, String title, String description) {
|
||||
this.type = type;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public ChallengeType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Types de défis disponibles
|
||||
*/
|
||||
public enum ChallengeType {
|
||||
REVERSE_DIRECTION,
|
||||
IMMUNITY,
|
||||
DRINK_GORGEE,
|
||||
SKIP_TURN,
|
||||
SWAP_HAND,
|
||||
DOUBLE_NEXT,
|
||||
RANDOM_COUNT,
|
||||
EVERYONE_DRINKS,
|
||||
PICK_VICTIM
|
||||
}
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
package com.example.boidelov3.games.game89;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Assistant pour le jeu 89++ - Affiche les défis et gère le timer
|
||||
* Les joueurs jouent avec de vraies cartes, l'application est juste un assistant
|
||||
*/
|
||||
public class Game89GameActivity extends AppCompatActivity {
|
||||
|
||||
// UI Components
|
||||
private MaterialToolbar toolbar;
|
||||
private TextView timerTextView;
|
||||
private TextView challengeTitleTextView;
|
||||
private TextView challengeDescriptionTextView;
|
||||
private View challengeContainer;
|
||||
private MaterialButton recordDrinksButton;
|
||||
private MaterialButton showStatsButton;
|
||||
|
||||
// Game Data
|
||||
private List<Game89Player> players;
|
||||
private Game89ChallengeManager challengeManager;
|
||||
private int challengeTimerMinutes;
|
||||
private CountDownTimer challengeTimer;
|
||||
private long timeUntilNextChallenge;
|
||||
private Game89ChallengeManager.Challenge currentChallenge;
|
||||
private boolean challengeActive = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_game89_game);
|
||||
|
||||
// Get intent data
|
||||
ArrayList<String> playerNames = getIntent().getStringArrayListExtra("PLAYERS");
|
||||
challengeTimerMinutes = getIntent().getIntExtra("CHALLENGE_TIMER_MINUTES", 1);
|
||||
|
||||
if (playerNames == null || playerNames.isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupGame(playerNames);
|
||||
setupListeners();
|
||||
startChallengeTimer();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
timerTextView = findViewById(R.id.timerTextView);
|
||||
challengeTitleTextView = findViewById(R.id.challengeTitleTextView);
|
||||
challengeDescriptionTextView = findViewById(R.id.challengeDescriptionTextView);
|
||||
challengeContainer = findViewById(R.id.challengeContainer);
|
||||
recordDrinksButton = findViewById(R.id.recordDrinksButton);
|
||||
showStatsButton = findViewById(R.id.showStatsButton);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void setupGame(ArrayList<String> playerNames) {
|
||||
players = new ArrayList<>();
|
||||
for (String name : playerNames) {
|
||||
players.add(new Game89Player(name));
|
||||
}
|
||||
|
||||
challengeManager = new Game89ChallengeManager();
|
||||
|
||||
// Show initial state
|
||||
challengeContainer.setVisibility(View.GONE);
|
||||
timerTextView.setText("En attente du premier défi...");
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
recordDrinksButton.setOnClickListener(v -> showRecordDrinksDialog());
|
||||
showStatsButton.setOnClickListener(v -> showStatsDialog());
|
||||
|
||||
// Click on challenge to dismiss it and resume timer
|
||||
challengeContainer.setOnClickListener(v -> dismissChallenge());
|
||||
}
|
||||
|
||||
private void startChallengeTimer() {
|
||||
// Ne pas démarrer si un défi est actif
|
||||
if (challengeActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeUntilNextChallenge = challengeTimerMinutes * 60 * 1000L;
|
||||
|
||||
challengeTimer = new CountDownTimer(timeUntilNextChallenge, 1000) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
// Arrêter le tick si un défi est devenu actif
|
||||
if (challengeActive) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
timeUntilNextChallenge = millisUntilFinished;
|
||||
long minutes = millisUntilFinished / 60000;
|
||||
long seconds = (millisUntilFinished % 60000) / 1000;
|
||||
timerTextView.setText(String.format(Locale.getDefault(),
|
||||
"Prochain défi dans: %d:%02d", minutes, seconds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
// Ne déclencher que si aucun défi n'est actif
|
||||
if (!challengeActive) {
|
||||
triggerChallenge();
|
||||
startChallengeTimer(); // Restart timer for next challenge
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void triggerChallenge() {
|
||||
currentChallenge = challengeManager.getRandomChallenge();
|
||||
if (currentChallenge == null) return;
|
||||
|
||||
challengeActive = true;
|
||||
|
||||
// Pause the timer
|
||||
if (challengeTimer != null) {
|
||||
challengeTimer.cancel();
|
||||
}
|
||||
|
||||
// Update UI - Hide timer text when challenge is active
|
||||
timerTextView.setVisibility(View.GONE);
|
||||
challengeContainer.setVisibility(View.VISIBLE);
|
||||
challengeTitleTextView.setText(currentChallenge.getTitle());
|
||||
challengeDescriptionTextView.setText(currentChallenge.getDescription());
|
||||
|
||||
// Vibrate to alert players
|
||||
try {
|
||||
android.os.Vibrator vibrator = (android.os.Vibrator) getSystemService(android.content.Context.VIBRATOR_SERVICE);
|
||||
if (vibrator != null) {
|
||||
vibrator.vibrate(new long[]{0, 300, 200, 300}, -1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore vibration errors
|
||||
}
|
||||
}
|
||||
|
||||
private void dismissChallenge() {
|
||||
challengeContainer.setVisibility(View.GONE);
|
||||
challengeActive = false;
|
||||
timerTextView.setVisibility(View.VISIBLE);
|
||||
|
||||
// Resume the timer
|
||||
startChallengeTimer();
|
||||
}
|
||||
|
||||
private void showRecordDrinksDialog() {
|
||||
if (players.isEmpty()) return;
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Enregistrer des gorgées");
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(50, 40, 50, 10);
|
||||
|
||||
// Create player selection
|
||||
String[] playerNames = new String[players.size()];
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
playerNames[i] = players.get(i).getName();
|
||||
}
|
||||
|
||||
final int[] selectedPlayer = {0};
|
||||
|
||||
TextView label = new TextView(this);
|
||||
label.setText("Sélectionnez le joueur:");
|
||||
label.setPadding(0, 0, 0, 20);
|
||||
layout.addView(label);
|
||||
|
||||
// Create a grid of buttons for each player
|
||||
GridLayout playerGrid = new GridLayout(this);
|
||||
playerGrid.setColumnCount(2);
|
||||
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
final int playerIndex = i;
|
||||
MaterialButton playerButton = new MaterialButton(this);
|
||||
playerButton.setText(players.get(i).getName());
|
||||
playerButton.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||
params.setMargins(8, 8, 8, 8);
|
||||
playerButton.setLayoutParams(params);
|
||||
|
||||
playerButton.setOnClickListener(v -> {
|
||||
selectedPlayer[0] = playerIndex;
|
||||
showGorgeesCountDialog(playerIndex);
|
||||
});
|
||||
|
||||
playerGrid.addView(playerButton);
|
||||
}
|
||||
|
||||
layout.addView(playerGrid);
|
||||
|
||||
builder.setView(layout);
|
||||
builder.setNegativeButton("Fermer", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showGorgeesCountDialog(int playerIndex) {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Gorgées pour " + players.get(playerIndex).getName());
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(60, 50, 60, 20);
|
||||
|
||||
// Label pour afficher la valeur actuelle
|
||||
TextView valueLabel = new TextView(this);
|
||||
valueLabel.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
valueLabel.setTextSize(24);
|
||||
valueLabel.setText("1 gorgée");
|
||||
valueLabel.setPadding(0, 0, 0, 20);
|
||||
layout.addView(valueLabel);
|
||||
|
||||
// SeekBar de 1 à 8
|
||||
SeekBar seekBar = new SeekBar(this);
|
||||
seekBar.setMax(7); // 0-7, donc 1-8 avec le +1
|
||||
seekBar.setProgress(0); // Commence à 1 (0 + 1)
|
||||
seekBar.setPadding(0, 0, 0, 20);
|
||||
|
||||
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
int value = progress + 1; // 1-8
|
||||
valueLabel.setText(value + " gorgée" + (value > 1 ? "s" : ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
|
||||
layout.addView(seekBar);
|
||||
|
||||
builder.setView(layout);
|
||||
|
||||
builder.setPositiveButton("Valider", (dialog, which) -> {
|
||||
int count = seekBar.getProgress() + 1; // 1-8
|
||||
players.get(playerIndex).addGorgees(count);
|
||||
Toast.makeText(this,
|
||||
players.get(playerIndex).getName() + " boit " + count + " gorgée(s)!",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Annuler", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showStatsDialog() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Statistiques des gorgées");
|
||||
|
||||
StringBuilder stats = new StringBuilder();
|
||||
for (Game89Player player : players) {
|
||||
stats.append(player.getName())
|
||||
.append(": ")
|
||||
.append(player.getTotalGorgees())
|
||||
.append(" gorgée(s)\n\n");
|
||||
}
|
||||
|
||||
builder.setMessage(stats.toString());
|
||||
builder.setPositiveButton("Fermer", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (challengeTimer != null) {
|
||||
challengeTimer.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.example.boidelov3.games.game89;
|
||||
|
||||
/**
|
||||
* Représente un joueur du jeu 89++ (version simplifiée pour assistant)
|
||||
*/
|
||||
public class Game89Player {
|
||||
private final String name;
|
||||
private int totalGorgees;
|
||||
|
||||
public Game89Player(String name) {
|
||||
this.name = name;
|
||||
this.totalGorgees = 0;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute des gorgées aux statistiques du joueur
|
||||
*/
|
||||
public void addGorgees(int count) {
|
||||
this.totalGorgees += count;
|
||||
}
|
||||
|
||||
public int getTotalGorgees() {
|
||||
return totalGorgees;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (" + totalGorgees + " gorgées)";
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package com.example.boidelov3.games.game89;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Activity de configuration pour le jeu 89++
|
||||
*/
|
||||
public class Game89SetupActivity extends AppCompatActivity {
|
||||
|
||||
private static final int MIN_PLAYERS = 2;
|
||||
private static final int MAX_PLAYERS = 10;
|
||||
|
||||
private LinearLayout playersContainer;
|
||||
private MaterialButton addPlayerButton;
|
||||
private MaterialButton startGameButton;
|
||||
private SeekBar timerSeekBar;
|
||||
private TextView timerText;
|
||||
private MaterialToolbar toolbar;
|
||||
|
||||
private final List<String> playerNames = new ArrayList<>();
|
||||
private int challengeTimerMinutes = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_game89_setup);
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupListeners();
|
||||
|
||||
// Ajouter 2 joueurs par défaut
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
playersContainer = findViewById(R.id.playersContainer);
|
||||
addPlayerButton = findViewById(R.id.addPlayerButton);
|
||||
startGameButton = findViewById(R.id.startGameButton);
|
||||
timerSeekBar = findViewById(R.id.timerSeekBar);
|
||||
timerText = findViewById(R.id.timerText);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
addPlayerButton.setOnClickListener(v -> {
|
||||
if (playerNames.size() < MAX_PLAYERS) {
|
||||
addPlayerRow();
|
||||
} else {
|
||||
Toast.makeText(this, "Maximum " + MAX_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
startGameButton.setOnClickListener(v -> startGame());
|
||||
|
||||
timerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
challengeTimerMinutes = progress;
|
||||
updateTimerText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void addPlayerRow() {
|
||||
View playerRow = LayoutInflater.from(this).inflate(R.layout.item_player_row, playersContainer, false);
|
||||
|
||||
TextInputEditText playerNameEdit = playerRow.findViewById(R.id.playerName);
|
||||
MaterialButton removeButton = playerRow.findViewById(R.id.removePlayerButton);
|
||||
TextView playerNumber = playerRow.findViewById(R.id.playerNumber);
|
||||
|
||||
int position = playersContainer.getChildCount();
|
||||
playerNumber.setText(String.valueOf(position + 1));
|
||||
|
||||
// Cacher le bouton de suppression pour les 2 premiers joueurs (minimum requis)
|
||||
if (position < MIN_PLAYERS) {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
removeButton.setOnClickListener(v -> {
|
||||
if (playersContainer.getChildCount() > MIN_PLAYERS) {
|
||||
playersContainer.removeView(playerRow);
|
||||
updatePlayerNumbers();
|
||||
updatePlayerNames();
|
||||
} else {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
playerNameEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
updatePlayerNames();
|
||||
}
|
||||
});
|
||||
|
||||
playersContainer.addView(playerRow);
|
||||
}
|
||||
|
||||
private void updatePlayerNumbers() {
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextView playerNumber = row.findViewById(R.id.playerNumber);
|
||||
playerNumber.setText(String.valueOf(i + 1));
|
||||
|
||||
// Afficher le bouton de suppression uniquement au-delà du minimum
|
||||
MaterialButton removeButton = row.findViewById(R.id.removePlayerButton);
|
||||
if (i >= MIN_PLAYERS) {
|
||||
removeButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayerNames() {
|
||||
playerNames.clear();
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextInputEditText edit = row.findViewById(R.id.playerName);
|
||||
String name = edit.getText().toString().trim();
|
||||
if (!TextUtils.isEmpty(name)) {
|
||||
playerNames.add(name);
|
||||
} else {
|
||||
playerNames.add("Joueur " + (i + 1));
|
||||
}
|
||||
}
|
||||
updateStartButton();
|
||||
}
|
||||
|
||||
private void updateStartButton() {
|
||||
int validPlayers = playerNames.size();
|
||||
boolean canStart = validPlayers >= MIN_PLAYERS;
|
||||
startGameButton.setEnabled(canStart);
|
||||
startGameButton.setText(canStart ? "JOUER (" + validPlayers + ")" : "Ajoutez des joueurs");
|
||||
}
|
||||
|
||||
private void updateTimerText() {
|
||||
timerText.setText(challengeTimerMinutes + " minute" + (challengeTimerMinutes > 1 ? "s" : ""));
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
updatePlayerNames();
|
||||
|
||||
if (playerNames.size() < MIN_PLAYERS) {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs requis", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, Game89GameActivity.class);
|
||||
intent.putStringArrayListExtra("PLAYERS", new ArrayList<>(playerNames));
|
||||
intent.putExtra("CHALLENGE_TIMER_MINUTES", challengeTimerMinutes);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Logique du jeu Papelito (Undercover)
|
||||
*/
|
||||
public class PapelitoGame {
|
||||
|
||||
private final List<PapelitoPlayer> players;
|
||||
private final List<PapelitoPlayer> alivePlayers;
|
||||
private final List<String> wordPairs;
|
||||
private String currentCivilWord;
|
||||
private String currentUndercoverWord;
|
||||
private int currentPlayerIndex;
|
||||
private final Random random;
|
||||
private GameState gameState;
|
||||
|
||||
public enum GameState {
|
||||
SETUP,
|
||||
DISCUSSION,
|
||||
VOTING,
|
||||
RESULT,
|
||||
GAME_OVER
|
||||
}
|
||||
|
||||
// Paires de mots pour le jeu (civil / undercover)
|
||||
private static final String[][] DEFAULT_WORD_PAIRS = {
|
||||
{"Pizza", "Burger"},
|
||||
{"Facebook", "Instagram"},
|
||||
{"Chat", "Chien"},
|
||||
{"Foot", "Basket"},
|
||||
{"Vin", "Bière"},
|
||||
{"Mer", "Montagne"},
|
||||
{"Avion", "Hélicoptère"},
|
||||
{"Piano", "Guitare"},
|
||||
{"Fromage", "Dessert"},
|
||||
{"École", "Fac"},
|
||||
{"Mariage", "Divorce"},
|
||||
{"Hôpital", "Cabinet"},
|
||||
{"Boulangerie", "Boucherie"},
|
||||
{"Zombie", "Vampire"},
|
||||
{"Pirate", "Ninja"},
|
||||
{"Cowboy", "Indien"},
|
||||
{"Fée", "Sorcière"},
|
||||
{"Robot", "Alien"},
|
||||
{"Dragon", "Licorne"}
|
||||
};
|
||||
|
||||
public PapelitoGame() {
|
||||
this.players = new ArrayList<>();
|
||||
this.alivePlayers = new ArrayList<>();
|
||||
this.wordPairs = new ArrayList<>();
|
||||
this.random = new Random();
|
||||
this.gameState = GameState.SETUP;
|
||||
|
||||
// Ajouter les paires de mots par défaut
|
||||
for (String[] pair : DEFAULT_WORD_PAIRS) {
|
||||
wordPairs.add(pair[0] + "|" + pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure une nouvelle partie
|
||||
*/
|
||||
public void setupGame(List<String> playerNames, int undercoverCount) {
|
||||
players.clear();
|
||||
|
||||
// Créer les joueurs
|
||||
for (String name : playerNames) {
|
||||
players.add(new PapelitoPlayer(name));
|
||||
}
|
||||
|
||||
// Vérifier qu'on a assez de joueurs
|
||||
if (undercoverCount >= players.size()) {
|
||||
throw new IllegalArgumentException("Il faut plus de joueurs que d'undercovers");
|
||||
}
|
||||
|
||||
// Choisir une paire de mots aléatoire
|
||||
String[] words = selectRandomWordPair();
|
||||
currentCivilWord = words[0];
|
||||
currentUndercoverWord = words[1];
|
||||
|
||||
// Assigner les rôles
|
||||
assignRoles(undercoverCount);
|
||||
|
||||
// Initialiser la liste des joueurs vivants
|
||||
alivePlayers.clear();
|
||||
alivePlayers.addAll(players);
|
||||
|
||||
gameState = GameState.DISCUSSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne une paire de mots aléatoire
|
||||
*/
|
||||
private String[] selectRandomWordPair() {
|
||||
String pair = wordPairs.get(random.nextInt(wordPairs.size()));
|
||||
String[] words = pair.split("\\|");
|
||||
if (words.length != 2) {
|
||||
throw new IllegalStateException("Invalid word pair format: " + pair);
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigne les rôles aux joueurs
|
||||
*/
|
||||
private void assignRoles(int undercoverCount) {
|
||||
if (players.isEmpty()) return;
|
||||
|
||||
// Mélanger les joueurs
|
||||
List<PapelitoPlayer> shuffled = new ArrayList<>(players);
|
||||
Collections.shuffle(shuffled);
|
||||
|
||||
// Assigner les undercovers
|
||||
for (int i = 0; i < undercoverCount; i++) {
|
||||
shuffled.get(i).setRole(PapelitoPlayer.Role.UNDERCOVER);
|
||||
shuffled.get(i).setSecretWord(currentUndercoverWord);
|
||||
}
|
||||
|
||||
// Le reste sont des civils
|
||||
for (int i = undercoverCount; i < shuffled.size(); i++) {
|
||||
shuffled.get(i).setRole(PapelitoPlayer.Role.CIVIL);
|
||||
shuffled.get(i).setSecretWord(currentCivilWord);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre un vote contre un joueur
|
||||
*/
|
||||
public boolean vote(PapelitoPlayer voter, PapelitoPlayer votedPlayer) {
|
||||
if (!voter.isAlive() || !votedPlayer.isAlive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gameState.equals(GameState.VOTING)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier que le joueur n'a pas déjà voté
|
||||
// (pour simplifier, on ne track pas qui a voté, chaque joueur peut voter une fois)
|
||||
|
||||
votedPlayer.addVote();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Élimine le joueur avec le plus de votes
|
||||
* Retourne null si pas de votes ou en cas d'égalité
|
||||
*/
|
||||
public PapelitoPlayer eliminateMostVoted() {
|
||||
if (alivePlayers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PapelitoPlayer mostVoted = null;
|
||||
int maxVotes = -1;
|
||||
int playersWithMaxVotes = 0;
|
||||
|
||||
// Trouver le nombre maximum de votes
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.getVotesReceived() > maxVotes) {
|
||||
maxVotes = player.getVotesReceived();
|
||||
mostVoted = player;
|
||||
playersWithMaxVotes = 1;
|
||||
} else if (player.getVotesReceived() == maxVotes && maxVotes > 0) {
|
||||
// Égalité détectée
|
||||
playersWithMaxVotes++;
|
||||
}
|
||||
}
|
||||
|
||||
// Si pas de votes ou égalité entre plusieurs joueurs, personne n'est éliminé
|
||||
if (maxVotes <= 0 || playersWithMaxVotes > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mostVoted != null) {
|
||||
mostVoted.eliminate();
|
||||
alivePlayers.remove(mostVoted);
|
||||
|
||||
// Révéler son rôle
|
||||
return mostVoted;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la partie est terminée
|
||||
*/
|
||||
public boolean checkGameOver() {
|
||||
int civilsCount = 0;
|
||||
int undercoversCount = 0;
|
||||
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.isCivil()) {
|
||||
civilsCount++;
|
||||
} else if (player.isUndercover()) {
|
||||
undercoversCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Les civils gagnent s'il n'y a plus d'undercovers
|
||||
if (undercoversCount == 0) {
|
||||
gameState = GameState.GAME_OVER;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les undercovers gagnent s'ils sont en nombre égal ou supérieur aux civils
|
||||
// Explication: Si les undercovers sont égal ou plus nombreux, les civils ne peuvent plus gagner
|
||||
// car il n'y aurait plus assez de civils pour voter et éliminer tous les undercovers
|
||||
if (undercoversCount >= civilsCount) {
|
||||
gameState = GameState.GAME_OVER;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le joueur actuel
|
||||
*/
|
||||
public PapelitoPlayer getCurrentPlayer() {
|
||||
if (!alivePlayers.isEmpty()) {
|
||||
// Utilise alivePlayers pour les joueurs vivants
|
||||
if (currentPlayerIndex >= 0 && currentPlayerIndex < alivePlayers.size()) {
|
||||
return alivePlayers.get(currentPlayerIndex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passe au joueur suivant
|
||||
*/
|
||||
public void nextPlayer() {
|
||||
if (!alivePlayers.isEmpty()) {
|
||||
currentPlayerIndex = (currentPlayerIndex + 1) % alivePlayers.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le nombre de civils vivants
|
||||
*/
|
||||
public int getAliveCivilsCount() {
|
||||
int count = 0;
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.isCivil()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le nombre d'undercovers vivants
|
||||
*/
|
||||
public int getAliveUndercoversCount() {
|
||||
int count = 0;
|
||||
for (PapelitoPlayer player : alivePlayers) {
|
||||
if (player.isUndercover()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les gagnants
|
||||
*/
|
||||
public PapelitoPlayer.Role getWinningTeam() {
|
||||
int civilsCount = getAliveCivilsCount();
|
||||
int undercoversCount = getAliveUndercoversCount();
|
||||
|
||||
if (undercoversCount == 0) {
|
||||
return PapelitoPlayer.Role.CIVIL;
|
||||
} else if (undercoversCount >= civilsCount) {
|
||||
return PapelitoPlayer.Role.UNDERCOVER;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialise les votes pour un nouveau tour
|
||||
*/
|
||||
public void resetVotes() {
|
||||
for (PapelitoPlayer player : players) {
|
||||
player.resetVotes();
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
public List<PapelitoPlayer> getPlayers() {
|
||||
return new ArrayList<>(players);
|
||||
}
|
||||
|
||||
public List<PapelitoPlayer> getAlivePlayers() {
|
||||
return new ArrayList<>(alivePlayers);
|
||||
}
|
||||
|
||||
public String getCurrentCivilWord() {
|
||||
return currentCivilWord;
|
||||
}
|
||||
|
||||
public String getCurrentUndercoverWord() {
|
||||
return currentUndercoverWord;
|
||||
}
|
||||
|
||||
public GameState getGameState() {
|
||||
return gameState;
|
||||
}
|
||||
|
||||
public void setGameState(GameState state) {
|
||||
// Validation des transitions légales entre états
|
||||
GameState current = this.gameState;
|
||||
|
||||
// Transitions légales autorisées
|
||||
if (current == GameState.SETUP && state != GameState.DISCUSSION && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from SETUP to " + state);
|
||||
}
|
||||
if (current == GameState.DISCUSSION && state != GameState.VOTING && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from DISCUSSION to " + state);
|
||||
}
|
||||
if (current == GameState.VOTING && state != GameState.RESULT && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from VOTING to " + state);
|
||||
}
|
||||
if (current == GameState.RESULT && state != GameState.DISCUSSION && state != GameState.GAME_OVER && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from RESULT to " + state);
|
||||
}
|
||||
if (current == GameState.GAME_OVER && state != GameState.SETUP) {
|
||||
throw new IllegalStateException("Cannot transition from GAME_OVER to " + state);
|
||||
}
|
||||
|
||||
this.gameState = state;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
players.clear();
|
||||
alivePlayers.clear();
|
||||
currentPlayerIndex = 0;
|
||||
gameState = GameState.SETUP;
|
||||
}
|
||||
}
|
||||
@@ -1,571 +0,0 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Activité principale du jeu Papelito (Undercover)
|
||||
*
|
||||
* Gère le déroulement complet du jeu :
|
||||
* - Affichage du mot secret à chaque joueur
|
||||
* - Phase de discussion avec timer
|
||||
* - Phase de vote
|
||||
* - Affichage des résultats
|
||||
* - Condition de victoire
|
||||
*/
|
||||
public class PapelitoGameActivity extends AppCompatActivity {
|
||||
|
||||
// UI Components
|
||||
private MaterialToolbar toolbar;
|
||||
private TextView phaseTextView;
|
||||
private TextView infoTextView;
|
||||
private TextView timerTextView;
|
||||
private MaterialCardView wordCard;
|
||||
private TextView wordTextView;
|
||||
private TextView currentWordPlayerTextView;
|
||||
private MaterialButton showWordButton;
|
||||
private MaterialButton startDiscussionButton;
|
||||
private MaterialButton startVotingButton;
|
||||
private MaterialButton nextPlayerButton;
|
||||
private MaterialButton endGameButton;
|
||||
private View mainContent;
|
||||
|
||||
// Game Logic
|
||||
private PapelitoGame game;
|
||||
private List<String> playerNames;
|
||||
private int undercoverCount;
|
||||
private int discussionTimeSeconds;
|
||||
private CountDownTimer discussionTimer;
|
||||
private int currentPlayerViewIndex;
|
||||
private boolean hasShownWordToPlayer;
|
||||
private List<String> playersWhoVoted;
|
||||
private int currentVoterIndex = 0;
|
||||
private int totalRoundsPlayed = 0;
|
||||
|
||||
// Constants
|
||||
private static final int DEFAULT_DISCUSSION_TIME = 60; // 60 secondes
|
||||
private static final int DEFAULT_UNDERCOVER_COUNT = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_papelito_game);
|
||||
|
||||
// Get intent data with validation
|
||||
playerNames = getIntent().getStringArrayListExtra("PLAYERS");
|
||||
undercoverCount = getIntent().getIntExtra("UNDERCOVER_COUNT", DEFAULT_UNDERCOVER_COUNT);
|
||||
discussionTimeSeconds = getIntent().getIntExtra("DISCUSSION_TIME", DEFAULT_DISCUSSION_TIME);
|
||||
|
||||
// Validate all intent extras
|
||||
if (playerNames == null || playerNames.isEmpty()) {
|
||||
Toast.makeText(this, "Erreur: Aucun joueur spécifié pour la partie", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (undercoverCount <= 0) {
|
||||
Toast.makeText(this, "Erreur: Le nombre d'undercovers doit être au moins de 1", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (undercoverCount >= playerNames.size()) {
|
||||
Toast.makeText(this, "Erreur: Il doit y avoir plus de joueurs que d'undercovers", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (discussionTimeSeconds <= 0) {
|
||||
Toast.makeText(this, "Erreur: La durée de discussion doit être positive", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupGame();
|
||||
setupListeners();
|
||||
|
||||
// Commencer par afficher le mot au premier joueur
|
||||
startWordRevealPhase();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
phaseTextView = findViewById(R.id.phaseTextView);
|
||||
infoTextView = findViewById(R.id.infoTextView);
|
||||
timerTextView = findViewById(R.id.timerTextView);
|
||||
wordCard = findViewById(R.id.wordCard);
|
||||
wordTextView = findViewById(R.id.wordTextView);
|
||||
currentWordPlayerTextView = findViewById(R.id.currentWordPlayerTextView);
|
||||
showWordButton = findViewById(R.id.showWordButton);
|
||||
startDiscussionButton = findViewById(R.id.startDiscussionButton);
|
||||
startVotingButton = findViewById(R.id.startVotingButton);
|
||||
nextPlayerButton = findViewById(R.id.nextPlayerButton);
|
||||
endGameButton = findViewById(R.id.endGameButton);
|
||||
mainContent = findViewById(R.id.mainContent);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
toolbar.setNavigationOnClickListener(v -> showExitConfirmationDialog());
|
||||
}
|
||||
|
||||
private void setupGame() {
|
||||
game = new PapelitoGame();
|
||||
game.setupGame(playerNames, undercoverCount);
|
||||
currentPlayerViewIndex = 0;
|
||||
hasShownWordToPlayer = false;
|
||||
playersWhoVoted = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
showWordButton.setOnClickListener(v -> showSecretWord());
|
||||
nextPlayerButton.setOnClickListener(v -> moveToNextPlayer());
|
||||
startDiscussionButton.setOnClickListener(v -> startDiscussionPhase());
|
||||
startVotingButton.setOnClickListener(v -> startVotingPhase());
|
||||
endGameButton.setOnClickListener(v -> showGameOverDialog());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PHASE 1: RÉVÉLATION DES MOTS
|
||||
// ============================================================
|
||||
|
||||
private void startWordRevealPhase() {
|
||||
game.setGameState(PapelitoGame.GameState.SETUP);
|
||||
// Reset game's currentPlayerIndex to prevent wrong player start
|
||||
currentPlayerViewIndex = 0;
|
||||
hasShownWordToPlayer = false;
|
||||
|
||||
updateUIForWordReveal();
|
||||
showCurrentPlayerPrompt();
|
||||
}
|
||||
|
||||
private void updateUIForWordReveal() {
|
||||
phaseTextView.setText("Phase 1: Révélation des mots");
|
||||
timerTextView.setVisibility(View.GONE);
|
||||
|
||||
// Cacher la carte du mot
|
||||
wordCard.setVisibility(View.GONE);
|
||||
wordTextView.setText("");
|
||||
|
||||
// Afficher le bouton pour montrer le mot
|
||||
showWordButton.setVisibility(View.VISIBLE);
|
||||
nextPlayerButton.setVisibility(View.VISIBLE);
|
||||
|
||||
// Cacher les boutons de phase suivante
|
||||
startDiscussionButton.setVisibility(View.GONE);
|
||||
startVotingButton.setVisibility(View.GONE);
|
||||
endGameButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showCurrentPlayerPrompt() {
|
||||
PapelitoPlayer player = game.getPlayers().get(currentPlayerViewIndex);
|
||||
currentWordPlayerTextView.setText(
|
||||
String.format("Tour de %s", player.getName())
|
||||
);
|
||||
infoTextView.setText(
|
||||
"Passe le téléphone à " + player.getName() + "\n\n" +
|
||||
"Appuie sur 'Voir mon mot' pour découvrir ton mot secret."
|
||||
);
|
||||
hasShownWordToPlayer = false;
|
||||
showWordButton.setEnabled(true);
|
||||
}
|
||||
|
||||
private void showSecretWord() {
|
||||
PapelitoPlayer player = game.getPlayers().get(currentPlayerViewIndex);
|
||||
String secretWord = player.getSecretWord();
|
||||
|
||||
wordTextView.setText(secretWord);
|
||||
wordCard.setVisibility(View.VISIBLE);
|
||||
showWordButton.setEnabled(false);
|
||||
hasShownWordToPlayer = true;
|
||||
|
||||
infoTextView.setText(
|
||||
"Ton mot est: " + secretWord + "\n\n" +
|
||||
"Mémorise-le bien et appuie sur 'Joueur suivant' " +
|
||||
"quand tu es prêt."
|
||||
);
|
||||
}
|
||||
|
||||
private void moveToNextPlayer() {
|
||||
if (!hasShownWordToPlayer) {
|
||||
Toast.makeText(this,
|
||||
"Tu dois d'abord voir ton mot!",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
currentPlayerViewIndex++;
|
||||
|
||||
if (currentPlayerViewIndex >= game.getPlayers().size()) {
|
||||
// Tous les joueurs ont vu leur mot
|
||||
showReadyForDiscussionDialog();
|
||||
} else {
|
||||
showCurrentPlayerPrompt();
|
||||
wordCard.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void showReadyForDiscussionDialog() {
|
||||
currentWordPlayerTextView.setText("Prêts!");
|
||||
infoTextView.setText(
|
||||
"Tous les joueurs ont vu leur mot.\n\n" +
|
||||
"Préparez-vous pour la phase de discussion!"
|
||||
);
|
||||
wordCard.setVisibility(View.GONE);
|
||||
showWordButton.setVisibility(View.GONE);
|
||||
nextPlayerButton.setVisibility(View.GONE);
|
||||
startDiscussionButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PHASE 2: DISCUSSION
|
||||
// ============================================================
|
||||
|
||||
private void startDiscussionPhase() {
|
||||
game.setGameState(PapelitoGame.GameState.DISCUSSION);
|
||||
updateUIForDiscussion();
|
||||
startDiscussionTimer();
|
||||
}
|
||||
|
||||
private void updateUIForDiscussion() {
|
||||
phaseTextView.setText("Phase 2: Discussion");
|
||||
currentWordPlayerTextView.setText("Discutez!");
|
||||
|
||||
wordCard.setVisibility(View.GONE);
|
||||
showWordButton.setVisibility(View.GONE);
|
||||
nextPlayerButton.setVisibility(View.GONE);
|
||||
startDiscussionButton.setVisibility(View.GONE);
|
||||
|
||||
timerTextView.setVisibility(View.VISIBLE);
|
||||
startVotingButton.setVisibility(View.VISIBLE);
|
||||
|
||||
infoTextView.setText(
|
||||
"Chaque joueur décrit son mot tour à tour.\n\n" +
|
||||
"Les Undercovers doivent essayer de deviner le mot des civils " +
|
||||
"sans se faire repérer.\n\n" +
|
||||
"Les civils doivent essayer d'identifier les Undercovers."
|
||||
);
|
||||
}
|
||||
|
||||
private void startDiscussionTimer() {
|
||||
discussionTimer = new CountDownTimer(
|
||||
discussionTimeSeconds * 1000L, 1000
|
||||
) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
long minutes = millisUntilFinished / 60000;
|
||||
long seconds = (millisUntilFinished % 60000) / 1000;
|
||||
timerTextView.setText(
|
||||
String.format(Locale.getDefault(),
|
||||
"%d:%02d", minutes, seconds)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
timerTextView.setText("0:00");
|
||||
Toast.makeText(PapelitoGameActivity.this,
|
||||
"Temps écoulé! Passez au vote.",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PHASE 3: VOTE
|
||||
// ============================================================
|
||||
|
||||
private void startVotingPhase() {
|
||||
// Arrêter le timer de discussion
|
||||
if (discussionTimer != null) {
|
||||
discussionTimer.cancel();
|
||||
}
|
||||
|
||||
game.setGameState(PapelitoGame.GameState.VOTING);
|
||||
game.resetVotes();
|
||||
playersWhoVoted.clear();
|
||||
currentVoterIndex = 0;
|
||||
totalRoundsPlayed++;
|
||||
|
||||
updateUIForVoting();
|
||||
showVotingDialog();
|
||||
}
|
||||
|
||||
private void updateUIForVoting() {
|
||||
phaseTextView.setText("Phase 3: Vote");
|
||||
timerTextView.setVisibility(View.GONE);
|
||||
startVotingButton.setVisibility(View.GONE);
|
||||
endGameButton.setVisibility(View.VISIBLE);
|
||||
|
||||
infoTextView.setText(
|
||||
"Chaque joueur vivant vote pour éliminer quelqu'un.\n\n" +
|
||||
"Le joueur avec le plus de votes est éliminé."
|
||||
);
|
||||
}
|
||||
|
||||
private void showVotingDialog() {
|
||||
List<PapelitoPlayer> alivePlayers = game.getAlivePlayers();
|
||||
|
||||
if (alivePlayers.isEmpty()) {
|
||||
checkGameEnd();
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
// Determine current voter name for dialog title
|
||||
String currentVoterName = alivePlayers.get(currentVoterIndex).getName();
|
||||
builder.setTitle("Vote de " + currentVoterName + " - Qui voulez-vous éliminer?");
|
||||
|
||||
// Créer le layout personnalisé
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setPadding(50, 40, 50, 10);
|
||||
|
||||
TextView label = new TextView(this);
|
||||
label.setText("Qui voulez-vous éliminer?");
|
||||
label.setPadding(0, 0, 0, 20);
|
||||
layout.addView(label);
|
||||
|
||||
// Créer une grille de boutons pour chaque joueur vivant
|
||||
GridLayout playerGrid = new GridLayout(this);
|
||||
playerGrid.setColumnCount(2);
|
||||
|
||||
for (int i = 0; i < alivePlayers.size(); i++) {
|
||||
PapelitoPlayer player = alivePlayers.get(i);
|
||||
// Skip the current voter so they can't vote for themselves
|
||||
if (i == currentVoterIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MaterialButton playerButton = new MaterialButton(this);
|
||||
playerButton.setText(player.getName());
|
||||
playerButton.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
GridLayout.LayoutParams params = new GridLayout.LayoutParams();
|
||||
params.setMargins(8, 8, 8, 8);
|
||||
playerButton.setLayoutParams(params);
|
||||
|
||||
playerButton.setOnClickListener(v -> {
|
||||
recordVote(player);
|
||||
});
|
||||
|
||||
playerGrid.addView(playerButton);
|
||||
}
|
||||
|
||||
layout.addView(playerGrid);
|
||||
|
||||
builder.setView(layout);
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void recordVote(PapelitoPlayer votedPlayer) {
|
||||
if (playersWhoVoted.size() >= game.getAlivePlayers().size()) {
|
||||
// Tous ont voté
|
||||
return;
|
||||
}
|
||||
|
||||
// Enregistrer le vote (simplifié : on ne track pas QUI a voté)
|
||||
votedPlayer.addVote();
|
||||
playersWhoVoted.add("vote");
|
||||
currentVoterIndex++;
|
||||
|
||||
Toast.makeText(this,
|
||||
"Vote enregistré pour " + votedPlayer.getName(),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
|
||||
// Vérifier si tous les joueurs vivants ont voté
|
||||
if (playersWhoVoted.size() >= game.getAlivePlayers().size()) {
|
||||
// Tous les votes sont en, afficher le résultat
|
||||
showVotingResult();
|
||||
} else {
|
||||
// Continuer avec le prochain voteur
|
||||
showNextVoterDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showNextVoterDialog() {
|
||||
int votesRemaining = game.getAlivePlayers().size() - playersWhoVoted.size();
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Vote en cours");
|
||||
builder.setMessage(
|
||||
"Joueurs ayant voté: " + playersWhoVoted.size() + " / " +
|
||||
game.getAlivePlayers().size() + "\n\n" +
|
||||
"Passe le téléphone au prochain joueur."
|
||||
);
|
||||
builder.setPositiveButton("Continuer", (dialog, which) -> {
|
||||
showVotingDialog();
|
||||
});
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showVotingResult() {
|
||||
PapelitoPlayer eliminated = game.eliminateMostVoted();
|
||||
|
||||
if (eliminated == null) {
|
||||
// Personne n'a été éliminé (pas de votes ou égalité)
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Vote nul!");
|
||||
builder.setMessage(
|
||||
"Aucun joueur n'a été éliminé.\n\n" +
|
||||
"Soit il n'y avait pas de votes, soit il y a une égalité.\n\n" +
|
||||
"La partie continue!"
|
||||
);
|
||||
builder.setPositiveButton("Continuer", (dialog, which) -> {
|
||||
checkGameEnd();
|
||||
});
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher le résultat de l'élimination
|
||||
showEliminationResult(eliminated);
|
||||
}
|
||||
|
||||
private void showEliminationResult(PapelitoPlayer eliminated) {
|
||||
game.setGameState(PapelitoGame.GameState.RESULT);
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Joueur éliminé!");
|
||||
|
||||
String message = String.format(
|
||||
"%s a été éliminé!\n\n" +
|
||||
"Son rôle était: %s\n\n" +
|
||||
"Son mot était: %s",
|
||||
eliminated.getName(),
|
||||
eliminated.getRole().getDisplayName(),
|
||||
eliminated.getSecretWord()
|
||||
);
|
||||
|
||||
builder.setMessage(message);
|
||||
|
||||
builder.setPositiveButton("Continuer", (dialog, which) -> {
|
||||
checkGameEnd();
|
||||
});
|
||||
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// VÉRIFICATION DE FIN DE JEU
|
||||
// ============================================================
|
||||
|
||||
private void checkGameEnd() {
|
||||
boolean gameOver = game.checkGameOver();
|
||||
|
||||
if (gameOver) {
|
||||
showGameOverDialog();
|
||||
} else {
|
||||
// Continuer avec un nouveau tour
|
||||
showNextRoundDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showNextRoundDialog() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Nouveau tour");
|
||||
|
||||
StringBuilder status = new StringBuilder();
|
||||
status.append("Joueurs vivants:\n\n");
|
||||
|
||||
for (PapelitoPlayer player : game.getAlivePlayers()) {
|
||||
status.append("• ").append(player.getName()).append("\n");
|
||||
}
|
||||
|
||||
status.append("\nCivils: ").append(game.getAliveCivilsCount());
|
||||
status.append("\nUndercovers: ").append(game.getAliveUndercoversCount());
|
||||
|
||||
builder.setMessage(status.toString());
|
||||
|
||||
builder.setPositiveButton("Commencer", (dialog, which) -> {
|
||||
startWordRevealPhase();
|
||||
});
|
||||
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showGameOverDialog() {
|
||||
game.setGameState(PapelitoGame.GameState.GAME_OVER);
|
||||
|
||||
// Lancer l'activité de résultat
|
||||
Intent intent = new Intent(this, PapelitoResultActivity.class);
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_PLAYERS, new ArrayList<>(game.getPlayers()));
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_WINNING_TEAM, game.getWinningTeam());
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_CIVIL_WORD, game.getCurrentCivilWord());
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_UNDERCOVER_WORD, game.getCurrentUndercoverWord());
|
||||
intent.putExtra(PapelitoResultActivity.EXTRA_TOTAL_ROUNDS, totalRoundsPlayed);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// DIVERS
|
||||
// ============================================================
|
||||
|
||||
private void showExitConfirmationDialog() {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setTitle("Quitter la partie?");
|
||||
builder.setMessage(
|
||||
"La partie sera perdue si vous quittez.\n\n" +
|
||||
"Voulez-vous vraiment quitter?"
|
||||
);
|
||||
|
||||
builder.setPositiveButton("Quitter", (dialog, which) -> {
|
||||
finish();
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Continuer", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (discussionTimer != null) {
|
||||
discussionTimer.cancel();
|
||||
discussionTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (discussionTimer != null) {
|
||||
discussionTimer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
showExitConfirmationDialog();
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
/**
|
||||
* Représente un joueur du jeu Papelito (Undercover)
|
||||
*/
|
||||
public class PapelitoPlayer implements java.io.Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String name;
|
||||
private String secretWord;
|
||||
private Role role;
|
||||
private boolean isAlive;
|
||||
private int votesReceived;
|
||||
|
||||
public enum Role {
|
||||
CIVIL("Civil"),
|
||||
UNDERCOVER("Undercover"),
|
||||
MR_WHITE("Mr. White");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
Role(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
|
||||
public PapelitoPlayer(String name) {
|
||||
this.name = name;
|
||||
this.isAlive = true;
|
||||
this.votesReceived = 0;
|
||||
this.role = null; // Sera assigné au début du jeu
|
||||
}
|
||||
|
||||
public PapelitoPlayer(String name, Role role, String secretWord) {
|
||||
this.name = name;
|
||||
this.role = role;
|
||||
this.secretWord = secretWord;
|
||||
this.isAlive = true;
|
||||
this.votesReceived = 0;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getSecretWord() {
|
||||
return secretWord;
|
||||
}
|
||||
|
||||
public void setSecretWord(String secretWord) {
|
||||
this.secretWord = secretWord;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
/**
|
||||
* Retourne le rôle du joueur.
|
||||
* @return le rôle du joueur, peut être null si non assigné
|
||||
*/
|
||||
|
||||
public void setRole(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return isAlive;
|
||||
}
|
||||
|
||||
public void setAlive(boolean alive) {
|
||||
isAlive = alive;
|
||||
}
|
||||
|
||||
public void eliminate() {
|
||||
this.isAlive = false;
|
||||
}
|
||||
|
||||
public int getVotesReceived() {
|
||||
return votesReceived;
|
||||
}
|
||||
|
||||
public void addVote() {
|
||||
this.votesReceived++;
|
||||
}
|
||||
|
||||
public void resetVotes() {
|
||||
this.votesReceived = 0;
|
||||
}
|
||||
|
||||
public boolean isMrWhite() {
|
||||
return role != null && role == Role.MR_WHITE;
|
||||
}
|
||||
|
||||
public boolean isUndercover() {
|
||||
return role != null && role == Role.UNDERCOVER;
|
||||
}
|
||||
|
||||
public boolean isCivil() {
|
||||
return role != null && role == Role.CIVIL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " (" + (role != null ? role.getDisplayName() : "?") + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.example.boidelov3.hub.GameSelectionActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Activité de fin de partie Papelito
|
||||
* Affiche les résultats, révèle tous les rôles et permet de rejouer
|
||||
*/
|
||||
public class PapelitoResultActivity extends AppCompatActivity {
|
||||
|
||||
public static final String EXTRA_PLAYERS = "extra_players";
|
||||
public static final String EXTRA_WINNING_TEAM = "extra_winning_team";
|
||||
public static final String EXTRA_CIVIL_WORD = "extra_civil_word";
|
||||
public static final String EXTRA_UNDERCOVER_WORD = "extra_undercover_word";
|
||||
public static final String EXTRA_TOTAL_ROUNDS = "extra_total_rounds";
|
||||
|
||||
private ArrayList<PapelitoPlayer> players;
|
||||
private PapelitoPlayer.Role winningTeam;
|
||||
private String civilWord;
|
||||
private String undercoverWord;
|
||||
private int totalRounds;
|
||||
|
||||
private TextView textViewWinner;
|
||||
private TextView textViewWords;
|
||||
private TextView textViewRounds;
|
||||
private RecyclerView recyclerViewResults;
|
||||
private Button buttonNewGame;
|
||||
private Button buttonHome;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_papelito_result);
|
||||
|
||||
// Récupérer les données (compatibilité API 33+)
|
||||
ArrayList<PapelitoPlayer> players;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
players = getIntent().getSerializableExtra(EXTRA_PLAYERS, ArrayList.class);
|
||||
} else {
|
||||
players = (ArrayList<PapelitoPlayer>) getIntent().getSerializableExtra(EXTRA_PLAYERS);
|
||||
}
|
||||
this.players = players;
|
||||
|
||||
PapelitoPlayer.Role winningTeam;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
winningTeam = getIntent().getSerializableExtra(EXTRA_WINNING_TEAM, PapelitoPlayer.Role.class);
|
||||
} else {
|
||||
winningTeam = (PapelitoPlayer.Role) getIntent().getSerializableExtra(EXTRA_WINNING_TEAM);
|
||||
}
|
||||
this.winningTeam = winningTeam;
|
||||
civilWord = getIntent().getStringExtra(EXTRA_CIVIL_WORD);
|
||||
undercoverWord = getIntent().getStringExtra(EXTRA_UNDERCOVER_WORD);
|
||||
totalRounds = getIntent().getIntExtra(EXTRA_TOTAL_ROUNDS, 0);
|
||||
|
||||
// Add validation
|
||||
if (players == null || players.isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialiser les vues
|
||||
initViews();
|
||||
|
||||
// Afficher les résultats
|
||||
displayResults();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
textViewWinner = findViewById(R.id.winnerTextView);
|
||||
textViewWords = findViewById(R.id.undercoverWordTextView);
|
||||
textViewRounds = findViewById(R.id.roundsTextView);
|
||||
recyclerViewResults = findViewById(R.id.rolesRecyclerView);
|
||||
buttonNewGame = findViewById(R.id.newGameButton);
|
||||
buttonHome = findViewById(R.id.homeButton);
|
||||
|
||||
// Configurer le RecyclerView
|
||||
recyclerViewResults.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
// Bouton Nouvelle Partie
|
||||
buttonNewGame.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(this, PapelitoSetupActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
|
||||
// Bouton Retour Hub
|
||||
buttonHome.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(this, GameSelectionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void displayResults() {
|
||||
// Afficher l'équipe gagnante
|
||||
if (winningTeam != null) {
|
||||
String winnerText;
|
||||
int backgroundColor;
|
||||
|
||||
if (winningTeam == PapelitoPlayer.Role.CIVIL) {
|
||||
winnerText = "🎉 LES CIVILS ONT GAGNÉ !";
|
||||
backgroundColor = getColor(R.color.civil_bg);
|
||||
} else {
|
||||
winnerText = "🔴 LES UNDERCOVERS ONT GAGNÉ !";
|
||||
backgroundColor = getColor(R.color.undercover_bg);
|
||||
}
|
||||
|
||||
textViewWinner.setText(winnerText);
|
||||
findViewById(R.id.winnerCard).setBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
// Afficher les mots (civilWordTextView et undercoverWordTextView)
|
||||
TextView civilWordView = findViewById(R.id.civilWordTextView);
|
||||
TextView undercoverWordView = findViewById(R.id.undercoverWordTextView);
|
||||
|
||||
if (civilWordView != null) {
|
||||
civilWordView.setText("Mot Civil: " + (civilWord != null ? civilWord : "Non défini"));
|
||||
}
|
||||
if (undercoverWordView != null) {
|
||||
undercoverWordView.setText("Mot Undercover: " + (undercoverWord != null ? undercoverWord : "Non défini"));
|
||||
}
|
||||
|
||||
// Afficher le nombre de tours
|
||||
String roundsText = "Nombre de manches: " + totalRounds;
|
||||
textViewRounds.setText(roundsText);
|
||||
|
||||
// Configurer l'adaptateur pour afficher tous les joueurs
|
||||
PapelitoResultAdapter adapter = new PapelitoResultAdapter(players, civilWord, undercoverWord);
|
||||
recyclerViewResults.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
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 com.example.boidelov3.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Adaptateur pour afficher les résultats finaux de tous les joueurs
|
||||
*/
|
||||
public class PapelitoResultAdapter extends RecyclerView.Adapter<PapelitoResultAdapter.ResultViewHolder> {
|
||||
|
||||
private final ArrayList<PapelitoPlayer> players;
|
||||
private final String civilWord;
|
||||
private final String undercoverWord;
|
||||
|
||||
public PapelitoResultAdapter(ArrayList<PapelitoPlayer> players, String civilWord, String undercoverWord) {
|
||||
this.players = players;
|
||||
this.civilWord = civilWord;
|
||||
this.undercoverWord = undercoverWord;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ResultViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_papelito_result, parent, false);
|
||||
return new ResultViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ResultViewHolder holder, int position) {
|
||||
PapelitoPlayer player = players.get(position);
|
||||
|
||||
// Nom du joueur
|
||||
holder.textViewPlayerName.setText(player.getName());
|
||||
|
||||
// Rôle avec vérification null
|
||||
if (player.getRole() != null) {
|
||||
holder.textViewRole.setText(player.getRole().getDisplayName());
|
||||
} else {
|
||||
holder.textViewRole.setText("?");
|
||||
}
|
||||
|
||||
// Statut (éliminé ou survivant)
|
||||
if (player.isAlive()) {
|
||||
holder.textViewStatus.setText("✅ Vivant");
|
||||
holder.textViewStatus.setTextColor(
|
||||
holder.itemView.getContext().getColor(R.color.success_green)
|
||||
);
|
||||
} else {
|
||||
holder.textViewStatus.setText("❌ Éliminé");
|
||||
holder.textViewStatus.setTextColor(
|
||||
holder.itemView.getContext().getColor(R.color.error_red)
|
||||
);
|
||||
}
|
||||
|
||||
// Avatar avec première lettre (vérification null et bounds)
|
||||
String name = player.getName();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
holder.playerAvatar.setText(name.substring(0, 1).toUpperCase());
|
||||
} else {
|
||||
holder.playerAvatar.setText("?");
|
||||
}
|
||||
|
||||
// Couleur de fond selon le rôle
|
||||
int backgroundColor;
|
||||
int roleColor;
|
||||
if (player.isCivil()) {
|
||||
backgroundColor = holder.itemView.getContext().getColor(R.color.civil_bg_light);
|
||||
roleColor = holder.itemView.getContext().getColor(R.color.civil_bg);
|
||||
} else if (player.isUndercover()) {
|
||||
backgroundColor = holder.itemView.getContext().getColor(R.color.undercover_bg_light);
|
||||
roleColor = holder.itemView.getContext().getColor(R.color.undercover_bg);
|
||||
} else {
|
||||
backgroundColor = holder.itemView.getContext().getColor(R.color.mr_white_bg_light);
|
||||
roleColor = holder.itemView.getContext().getColor(R.color.mr_white_bg);
|
||||
}
|
||||
|
||||
// Définir la couleur de fond de la carte
|
||||
if (holder.cardView != null) {
|
||||
holder.cardView.setCardBackgroundColor(backgroundColor);
|
||||
}
|
||||
|
||||
// Définir la couleur du texte du rôle
|
||||
holder.textViewRole.setTextColor(roleColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return players.size();
|
||||
}
|
||||
|
||||
static class ResultViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView playerAvatar;
|
||||
TextView textViewPlayerName;
|
||||
TextView textViewRole;
|
||||
TextView textViewStatus;
|
||||
com.google.android.material.card.MaterialCardView cardView;
|
||||
|
||||
public ResultViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
cardView = (com.google.android.material.card.MaterialCardView) itemView;
|
||||
playerAvatar = itemView.findViewById(R.id.playerAvatarTextView);
|
||||
textViewPlayerName = itemView.findViewById(R.id.playerNameTextView);
|
||||
textViewRole = itemView.findViewById(R.id.playerRoleTextView);
|
||||
textViewStatus = itemView.findViewById(R.id.playerStatusBadge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
package com.example.boidelov3.games.papelito;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.boidelov3.R;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Activity de configuration pour le jeu Papelito (Undercover)
|
||||
*/
|
||||
public class PapelitoSetupActivity extends AppCompatActivity {
|
||||
|
||||
private static final int MIN_PLAYERS = 3;
|
||||
private static final int MAX_PLAYERS = 12;
|
||||
private static final int MIN_UNDERCOVERS = 1;
|
||||
private static final int MAX_UNDERCOVERS = 3;
|
||||
|
||||
private LinearLayout playersContainer;
|
||||
private MaterialButton addPlayerButton;
|
||||
private MaterialButton startGameButton;
|
||||
private SeekBar undercoverSeekBar;
|
||||
private TextView undercoverText;
|
||||
private MaterialSwitch mrWhiteSwitch;
|
||||
private MaterialToolbar toolbar;
|
||||
|
||||
private static final int DEFAULT_DISCUSSION_TIME_SECONDS = 120; // 2 minutes par défaut
|
||||
private final List<String> playerNames = new ArrayList<>();
|
||||
private int undercoverCount = 1;
|
||||
private boolean mrWhiteEnabled = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_papelito_setup);
|
||||
|
||||
initViews();
|
||||
setupToolbar();
|
||||
setupListeners();
|
||||
|
||||
// Ajouter 3 joueurs par défaut
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
addPlayerRow();
|
||||
|
||||
updateUndercoverText();
|
||||
updatePlayerNames();
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
playersContainer = findViewById(R.id.playersContainer);
|
||||
addPlayerButton = findViewById(R.id.addPlayerButton);
|
||||
startGameButton = findViewById(R.id.startGameButton);
|
||||
undercoverSeekBar = findViewById(R.id.undercoverSeekBar);
|
||||
undercoverText = findViewById(R.id.undercoverText);
|
||||
mrWhiteSwitch = findViewById(R.id.mrWhiteSwitch);
|
||||
}
|
||||
|
||||
private void setupToolbar() {
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
addPlayerButton.setOnClickListener(v -> {
|
||||
if (playerNames.size() < MAX_PLAYERS) {
|
||||
addPlayerRow();
|
||||
} else {
|
||||
Toast.makeText(this, "Maximum " + MAX_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
startGameButton.setOnClickListener(v -> startGame());
|
||||
|
||||
undercoverSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
undercoverCount = progress + 1; // +1 car le SeekBar commence à 0
|
||||
updateUndercoverText();
|
||||
updateMaxUndercoverLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
|
||||
mrWhiteSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
mrWhiteEnabled = isChecked;
|
||||
if (isChecked) {
|
||||
// Vérifier qu'on a assez de joueurs pour Mr White
|
||||
int requiredPlayers = undercoverCount + 2; // Au moins 2 civils
|
||||
if (playerNames.size() < requiredPlayers) {
|
||||
Toast.makeText(this,
|
||||
"Mr White nécessite au moins " + requiredPlayers + " joueurs",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
mrWhiteSwitch.setChecked(false);
|
||||
mrWhiteEnabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addPlayerRow() {
|
||||
View playerRow = LayoutInflater.from(this).inflate(R.layout.item_player_row, playersContainer, false);
|
||||
|
||||
TextInputEditText playerNameEdit = playerRow.findViewById(R.id.playerName);
|
||||
MaterialButton removeButton = playerRow.findViewById(R.id.removePlayerButton);
|
||||
TextView playerNumber = playerRow.findViewById(R.id.playerNumber);
|
||||
|
||||
int position = playersContainer.getChildCount();
|
||||
playerNumber.setText(String.valueOf(position + 1));
|
||||
|
||||
// Cacher le bouton de suppression pour les 3 premiers joueurs (minimum requis)
|
||||
if (position < MIN_PLAYERS) {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
removeButton.setOnClickListener(v -> {
|
||||
if (playersContainer.getChildCount() > MIN_PLAYERS) {
|
||||
playersContainer.removeView(playerRow);
|
||||
updatePlayerNumbers();
|
||||
updatePlayerNames();
|
||||
updateMaxUndercoverLimit();
|
||||
} else {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
playerNameEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
updatePlayerNames();
|
||||
}
|
||||
});
|
||||
|
||||
playersContainer.addView(playerRow);
|
||||
}
|
||||
|
||||
private void updatePlayerNumbers() {
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextView playerNumber = row.findViewById(R.id.playerNumber);
|
||||
playerNumber.setText(String.valueOf(i + 1));
|
||||
|
||||
// Afficher le bouton de suppression uniquement au-delà du minimum
|
||||
MaterialButton removeButton = row.findViewById(R.id.removePlayerButton);
|
||||
if (i >= MIN_PLAYERS) {
|
||||
removeButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayerNames() {
|
||||
playerNames.clear();
|
||||
for (int i = 0; i < playersContainer.getChildCount(); i++) {
|
||||
View row = playersContainer.getChildAt(i);
|
||||
TextInputEditText edit = row.findViewById(R.id.playerName);
|
||||
String name = edit.getText().toString().trim();
|
||||
if (!TextUtils.isEmpty(name)) {
|
||||
playerNames.add(name);
|
||||
} else {
|
||||
playerNames.add("Joueur " + (i + 1));
|
||||
}
|
||||
}
|
||||
updateStartButton();
|
||||
updateMaxUndercoverLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la limite maximale d'undercovers selon le nombre de joueurs
|
||||
* Il faut toujours au moins 2 civils (plus Mr White si activé)
|
||||
*/
|
||||
private void updateMaxUndercoverLimit() {
|
||||
int playerCount = playerNames.size();
|
||||
int maxAllowed;
|
||||
|
||||
if (mrWhiteEnabled) {
|
||||
// Avec Mr White: max = joueurs - 2 (Mr White + 1 civil minimum)
|
||||
maxAllowed = Math.max(MIN_UNDERCOVERS, playerCount - 2);
|
||||
} else {
|
||||
// Sans Mr White: max = joueurs - 2 (2 civils minimum)
|
||||
maxAllowed = Math.max(MIN_UNDERCOVERS, playerCount - 2);
|
||||
}
|
||||
|
||||
// Ajuster si nécessaire
|
||||
if (undercoverCount > maxAllowed) {
|
||||
undercoverCount = maxAllowed;
|
||||
undercoverSeekBar.setProgress(undercoverCount - 1);
|
||||
updateUndercoverText();
|
||||
}
|
||||
|
||||
// Mettre à jour le max du SeekBar
|
||||
int seekBarMax = Math.min(MAX_UNDERCOVERS, maxAllowed);
|
||||
undercoverSeekBar.setMax(seekBarMax - 1); // -1 car le SeekBar commence à 0
|
||||
}
|
||||
|
||||
private void updateUndercoverText() {
|
||||
String text = undercoverCount + " undercover" + (undercoverCount > 1 ? "s" : "");
|
||||
undercoverText.setText(text);
|
||||
}
|
||||
|
||||
private void updateStartButton() {
|
||||
int validPlayers = playerNames.size();
|
||||
boolean canStart = validPlayers >= MIN_PLAYERS;
|
||||
startGameButton.setEnabled(canStart);
|
||||
startGameButton.setText(canStart ? "JOUER (" + validPlayers + ")" : "Ajoutez des joueurs");
|
||||
}
|
||||
|
||||
private void startGame() {
|
||||
updatePlayerNames();
|
||||
|
||||
if (playerNames.size() < MIN_PLAYERS) {
|
||||
Toast.makeText(this, "Minimum " + MIN_PLAYERS + " joueurs requis", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier qu'on a assez de joueurs pour la configuration
|
||||
int requiredPlayers = undercoverCount + 2; // Au moins 2 civils
|
||||
if (mrWhiteEnabled) {
|
||||
requiredPlayers++; // +1 pour Mr White
|
||||
}
|
||||
|
||||
if (playerNames.size() < requiredPlayers) {
|
||||
Toast.makeText(this,
|
||||
"Configuration invalide: il faut au moins " + requiredPlayers + " joueurs",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Lancer l'activité de jeu
|
||||
Intent intent = new Intent(this, PapelitoGameActivity.class);
|
||||
intent.putStringArrayListExtra("PLAYERS", new ArrayList<>(playerNames));
|
||||
intent.putExtra("UNDERCOVER_COUNT", undercoverCount);
|
||||
intent.putExtra("MR_WHITE_ENABLED", mrWhiteEnabled);
|
||||
intent.putExtra("DISCUSSION_TIME", DEFAULT_DISCUSSION_TIME_SECONDS);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.example.boidelov3.hub;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.boidelov3.R;
|
||||
import com.example.boidelov3.games.boideloclassic.BoideloClassicSetupActivity;
|
||||
import com.example.boidelov3.games.papelito.PapelitoSetupActivity;
|
||||
import com.example.boidelov3.hub.adapter.GameAdapter;
|
||||
import com.example.boidelov3.hub.model.GameInfo;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* GameSelectionActivity - Hub principal de sélection des jeux
|
||||
* Cette activité remplace l'ancienne MainActivity et sert de point d'entrée
|
||||
* pour tous les jeux disponibles dans Boidelo.
|
||||
*/
|
||||
public class GameSelectionActivity extends AppCompatActivity implements GameAdapter.OnItemClickListener {
|
||||
|
||||
private RecyclerView gamesRecyclerView;
|
||||
private GameAdapter gameAdapter;
|
||||
private List<GameInfo> gamesList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_game_selection);
|
||||
|
||||
initViews();
|
||||
setupGamesList();
|
||||
setupRecyclerView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise les vues de l'activité
|
||||
*/
|
||||
private void initViews() {
|
||||
gamesRecyclerView = findViewById(R.id.gamesRecyclerView);
|
||||
gamesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure la liste des jeux disponibles
|
||||
*/
|
||||
private void setupGamesList() {
|
||||
gamesList = new ArrayList<>();
|
||||
|
||||
// Boidelo Classic - Le jeu original
|
||||
gamesList.add(new GameInfo(
|
||||
"Boidelo Classic",
|
||||
"Le jeu original de questions et défis",
|
||||
R.drawable.ic_boidelo_classic,
|
||||
GameInfo.GameType.BOIDELO_CLASSIC,
|
||||
true
|
||||
));
|
||||
|
||||
// 89++ - Jeu de cartes
|
||||
gamesList.add(new GameInfo(
|
||||
"89++",
|
||||
"Jeu de cartes avec timer et défis",
|
||||
R.drawable.ic_game_89,
|
||||
GameInfo.GameType.GAME_89,
|
||||
true // Available now
|
||||
));
|
||||
|
||||
// Papelito (Undercover) - Jeu de déduction
|
||||
gamesList.add(new GameInfo(
|
||||
"Papelito",
|
||||
"Trouvez l'undercover avant qu'il ne soit trop tard!",
|
||||
R.drawable.ic_papelito,
|
||||
GameInfo.GameType.UNDERCOVER,
|
||||
true // Available now
|
||||
));
|
||||
|
||||
// Jeux de règles
|
||||
gamesList.add(new GameInfo(
|
||||
"Règles de jeux",
|
||||
"Découvrez les règles des jeux d'ambiance populaires",
|
||||
R.drawable.ic_rules,
|
||||
GameInfo.GameType.RULES,
|
||||
true // Available now!
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure le RecyclerView avec l'adaptateur
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
gameAdapter = new GameAdapter(gamesList, this);
|
||||
gamesRecyclerView.setAdapter(gameAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère le clic sur un jeu de la liste
|
||||
* @param gameInfo Le jeu sélectionné
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(GameInfo gameInfo) {
|
||||
if (!gameInfo.isAvailable()) {
|
||||
// Afficher un message "Coming soon"
|
||||
android.widget.Toast.makeText(this, gameInfo.getName() + " - Bientôt disponible!",
|
||||
android.widget.Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (gameInfo.getGameType()) {
|
||||
case BOIDELO_CLASSIC:
|
||||
startActivity(new Intent(this, BoideloClassicSetupActivity.class));
|
||||
break;
|
||||
case GAME_89:
|
||||
startActivity(new Intent(this, com.example.boidelov3.games.game89.Game89SetupActivity.class));
|
||||
break;
|
||||
case UNDERCOVER:
|
||||
startActivity(new Intent(this, com.example.boidelov3.games.papelito.PapelitoSetupActivity.class));
|
||||
break;
|
||||
case RULES:
|
||||
startActivity(new Intent(this, com.example.boidelov3.rules.RulesListActivity.class));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.example.boidelov3.hub.adapter;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.boidelov3.R;
|
||||
import com.example.boidelov3.hub.model.GameInfo;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* GameAdapter - Adaptateur pour afficher la liste des jeux dans le RecyclerView
|
||||
*/
|
||||
public class GameAdapter extends RecyclerView.Adapter<GameAdapter.GameViewHolder> {
|
||||
|
||||
private List<GameInfo> gamesList;
|
||||
private OnItemClickListener listener;
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(GameInfo gameInfo);
|
||||
}
|
||||
|
||||
public GameAdapter(List<GameInfo> gamesList, OnItemClickListener listener) {
|
||||
this.gamesList = gamesList;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GameViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_game_card, parent, false);
|
||||
return new GameViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull GameViewHolder holder, int position) {
|
||||
GameInfo gameInfo = gamesList.get(position);
|
||||
holder.bind(gameInfo, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return gamesList.size();
|
||||
}
|
||||
|
||||
static class GameViewHolder extends RecyclerView.ViewHolder {
|
||||
private ImageView gameIcon;
|
||||
private TextView gameName;
|
||||
private TextView gameDescription;
|
||||
private View statusText;
|
||||
|
||||
public GameViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
gameIcon = itemView.findViewById(R.id.gameIcon);
|
||||
gameName = itemView.findViewById(R.id.gameName);
|
||||
gameDescription = itemView.findViewById(R.id.gameDescription);
|
||||
statusText = itemView.findViewById(R.id.statusText);
|
||||
}
|
||||
|
||||
public void bind(GameInfo gameInfo, OnItemClickListener listener) {
|
||||
gameName.setText(gameInfo.getName());
|
||||
gameDescription.setText(gameInfo.getDescription());
|
||||
gameIcon.setImageResource(gameInfo.getIconResId());
|
||||
|
||||
if (gameInfo.isAvailable()) {
|
||||
statusText.setVisibility(View.GONE);
|
||||
itemView.setEnabled(true);
|
||||
itemView.setAlpha(1.0f);
|
||||
itemView.setOnClickListener(v -> listener.onItemClick(gameInfo));
|
||||
} else {
|
||||
statusText.setVisibility(View.VISIBLE);
|
||||
itemView.setEnabled(false);
|
||||
itemView.setAlpha(0.6f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.example.boidelov3.hub.model;
|
||||
|
||||
/**
|
||||
* GameInfo - Modèle représentant un jeu dans le hub
|
||||
* Contient les informations de base pour afficher et lancer un jeu
|
||||
*/
|
||||
public class GameInfo {
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
private int iconResId;
|
||||
private GameType gameType;
|
||||
private boolean available;
|
||||
|
||||
public enum GameType {
|
||||
BOIDELO_CLASSIC,
|
||||
GAME_89,
|
||||
UNDERCOVER,
|
||||
RULES
|
||||
}
|
||||
|
||||
public GameInfo(String name, String description, int iconResId, GameType gameType, boolean available) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.iconResId = iconResId;
|
||||
this.gameType = gameType;
|
||||
this.available = available;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public int getIconResId() { return iconResId; }
|
||||
public GameType getGameType() { return gameType; }
|
||||
public boolean isAvailable() { return available; }
|
||||
|
||||
// Setters
|
||||
public void setAvailable(boolean available) { this.available = available; }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.example.boidelov3.rules;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
|
||||
public class RuleDetailActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// TODO: Implement rule detail screen
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.example.boidelov3.rules;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
|
||||
public class RulesListActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// TODO: Implement rules list screen
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package com.example.boidelov3.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
* Utilitaire centralisé pour la gestion des erreurs
|
||||
* Fournit des méthodes cohérentes pour logger et afficher les erreurs
|
||||
*/
|
||||
public class ErrorHandler {
|
||||
|
||||
private static final String DEFAULT_TAG = "BoideloError";
|
||||
|
||||
/**
|
||||
* Logger une erreur avec un TAG personnalisé
|
||||
* @param tag Le tag pour les logs
|
||||
* @param message Message descriptif de l'erreur
|
||||
* @param throwable L'exception capturée
|
||||
*/
|
||||
public static void logError(String tag, String message, Throwable throwable) {
|
||||
Log.e(tag, message, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une erreur avec le TAG par défaut
|
||||
* @param message Message descriptif de l'erreur
|
||||
* @param throwable L'exception capturée
|
||||
*/
|
||||
public static void logError(String message, Throwable throwable) {
|
||||
Log.e(DEFAULT_TAG, message, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une erreur et afficher un Toast à l'utilisateur
|
||||
* @param context Le contexte de l'application
|
||||
* @param tag Le tag pour les logs
|
||||
* @param logMessage Message technique pour les logs
|
||||
* @param userMessage Message convivial pour l'utilisateur
|
||||
* @param throwable L'exception capturée
|
||||
*/
|
||||
public static void showError(Context context, String tag, String logMessage,
|
||||
String userMessage, Throwable throwable) {
|
||||
logError(tag, logMessage, throwable);
|
||||
Toast.makeText(context, userMessage, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une erreur et afficher un Toast à l'utilisateur (TAG par défaut)
|
||||
* @param context Le contexte de l'application
|
||||
* @param logMessage Message technique pour les logs
|
||||
* @param userMessage Message convivial pour l'utilisateur
|
||||
* @param throwable L'exception capturée
|
||||
*/
|
||||
public static void showError(Context context, String logMessage,
|
||||
String userMessage, Throwable throwable) {
|
||||
showError(context, DEFAULT_TAG, logMessage, userMessage, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger une erreur sans afficher de Toast
|
||||
* @param tag Le tag pour les logs
|
||||
* @param message Message descriptif de l'erreur
|
||||
* @param throwable L'exception capturée
|
||||
*/
|
||||
public static void logErrorOnly(String tag, String message, Throwable throwable) {
|
||||
logError(tag, message, throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer un message d'erreur détaillé pour les logs avec contexte
|
||||
* @param operation L'opération qui a échoué
|
||||
* @param details Détails supplémentaires sur l'erreur
|
||||
* @return Message formaté pour les logs
|
||||
*/
|
||||
public static String buildErrorMessage(String operation, String details) {
|
||||
if (details != null && !details.isEmpty()) {
|
||||
return operation + " - " + details;
|
||||
}
|
||||
return operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Échapper une chaîne de caractères pour l'utiliser en toute sécurité dans HTML
|
||||
* @param input La chaîne à échapper
|
||||
* @return La chaîne échappée
|
||||
*/
|
||||
public static String escapeHtml(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
return input
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
package com.example.boidelov3.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Classe utilitaire pour la gestion sécurisée des clés API et configuration sensible.
|
||||
*
|
||||
* SECURITY PRINCIPLES:
|
||||
* - Les clés API ne sont JAMAIS stockées en dur dans le code
|
||||
* - Utilise SharedPreferences chiffrés (doit être combiné avec AndroidX Security pour un chiffrage réel)
|
||||
* - Valide les clés API avant utilisation
|
||||
* - Fournit des méthodes pour nettoyer les données sensibles
|
||||
*
|
||||
* RECOMMANDATION: Pour une production sécurisée, utilisez AndroidX Security Library:
|
||||
* implementation "androidx.security:security-crypto:1.1.0-alpha06"
|
||||
* Et remplace les SharedPreferences par EncryptedSharedPreferences
|
||||
*/
|
||||
public class SecureConfig {
|
||||
|
||||
private static final String TAG = "SecureConfig";
|
||||
private static final String PREFS_NAME = "SecureConfig";
|
||||
private static final String KEY_API_KEY = "api_key_openai";
|
||||
private static final String KEY_API_KEY_OPENROUTER = "api_key_openrouter";
|
||||
private static final String KEY_API_KEY_ZAI = "api_key_zai";
|
||||
|
||||
private final SharedPreferences sharedPreferences;
|
||||
private final SecureRandom secureRandom;
|
||||
|
||||
/**
|
||||
* Constructeur
|
||||
* @param context Contexte de l'application
|
||||
*/
|
||||
public SecureConfig(Context context) {
|
||||
// Pour plus de sécurité, utiliser EncryptedSharedPreferences d'AndroidX Security
|
||||
// Pour l'instant, on utilise des SharedPreferences standards avec des avertissements
|
||||
this.sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
this.secureRandom = new SecureRandom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sauvegarde une clé API de manière sécurisée
|
||||
* NOTE: Pour une vraie sécurité, utilisez EncryptedSharedPreferences d'AndroidX Security
|
||||
*
|
||||
* @param provider Le fournisseur (openai, openrouter, zai)
|
||||
* @param apiKey La clé API à stocker
|
||||
* @return true si sauvegardé avec succès
|
||||
*/
|
||||
public boolean saveApiKey(String provider, String apiKey) {
|
||||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||||
Log.w(TAG, "Tentative de sauvegarder une clé API vide");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Valider la clé avant sauvegarde
|
||||
if (!validateApiKeyFormat(provider, apiKey)) {
|
||||
Log.w(TAG, "Format de clé API invalide pour " + provider);
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
editor.putString(key, apiKey);
|
||||
boolean success = editor.commit();
|
||||
|
||||
if (success) {
|
||||
Log.i(TAG, "Clé API sauvegardée pour " + provider);
|
||||
} else {
|
||||
Log.e(TAG, "Erreur lors de la sauvegarde de la clé API");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une clé API stockée
|
||||
*
|
||||
* @param provider Le fournisseur (openai, openrouter, zai)
|
||||
* @return La clé API ou null si non trouvée
|
||||
*/
|
||||
public String getApiKey(String provider) {
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
return sharedPreferences.getString(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime une clé API stockée
|
||||
*
|
||||
* @param provider Le fournisseur
|
||||
* @return true si supprimée avec succès
|
||||
*/
|
||||
public boolean removeApiKey(String provider) {
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
Log.w(TAG, "Provider null ou vide pour removeApiKey");
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
editor.remove(key);
|
||||
boolean success = editor.commit();
|
||||
|
||||
if (success) {
|
||||
Log.i(TAG, "Clé API supprimée pour " + provider);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime toutes les clés API stockées
|
||||
* Utiliser cette méthode lors de la déconnexion ou pour nettoyer les données
|
||||
*/
|
||||
public void clearAllApiKeys() {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.remove(KEY_API_KEY);
|
||||
editor.remove(KEY_API_KEY_OPENROUTER);
|
||||
editor.remove(KEY_API_KEY_ZAI);
|
||||
editor.apply();
|
||||
Log.i(TAG, "Toutes les clés API ont été supprimées");
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une clé API existe pour un provider
|
||||
*
|
||||
* @param provider Le fournisseur
|
||||
* @return true si une clé existe
|
||||
*/
|
||||
public boolean hasApiKey(String provider) {
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String key = getPrefKeyForProvider(provider);
|
||||
return sharedPreferences.contains(key) && sharedPreferences.getString(key, null) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide le format d'une clé API selon le provider
|
||||
*
|
||||
* @param provider Le fournisseur (openai, openrouter, zai)
|
||||
* @param apiKey La clé API à valider
|
||||
* @return true si le format est valide
|
||||
*/
|
||||
public boolean validateApiKeyFormat(String provider, String apiKey) {
|
||||
if (apiKey == null || apiKey.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (provider == null || provider.trim().isEmpty()) {
|
||||
Log.w(TAG, "Provider null ou vide");
|
||||
return false;
|
||||
}
|
||||
|
||||
String trimmedKey = apiKey.trim();
|
||||
|
||||
switch (provider.toLowerCase().trim()) {
|
||||
case "openai":
|
||||
// Les clés OpenAI commencent par "sk-"
|
||||
return trimmedKey.startsWith("sk-") && trimmedKey.length() >= 20;
|
||||
|
||||
case "openrouter":
|
||||
// Les clés OpenRouter commencent par "sk-or-"
|
||||
return trimmedKey.startsWith("sk-or-") && trimmedKey.length() >= 20;
|
||||
|
||||
case "zai":
|
||||
// Les clés Z.ai/Anthropic commencent par "sk-ant-"
|
||||
return trimmedKey.startsWith("sk-ant-") && trimmedKey.length() >= 20;
|
||||
|
||||
default:
|
||||
Log.w(TAG, "Provider inconnu: " + provider);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un hash sécurisé d'une clé pour vérification (stockage local)
|
||||
* Ne stocke JAMAIS la clé en clair dans les logs
|
||||
*
|
||||
* @param apiKey La clé API
|
||||
* @return Le hash de la clé
|
||||
*/
|
||||
public String hashApiKey(String apiKey) {
|
||||
if (apiKey == null) {
|
||||
Log.w(TAG, "Tentative de hasher une clé API null");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (apiKey.trim().isEmpty()) {
|
||||
Log.w(TAG, "Tentative de hasher une clé API vide");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(apiKey.getBytes("UTF-8"));
|
||||
return android.util.Base64.encodeToString(hash, android.util.Base64.NO_WRAP);
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "SHA-256 non disponible sur cet appareil - hash impossible", e);
|
||||
return null;
|
||||
} catch (java.io.UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "UTF-8 encoding non supporté - impossible", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une chaîne aléatoire sécurisée pour utilisation comme nonce ou token
|
||||
*
|
||||
* @param length Longueur de la chaîne
|
||||
* @return Chaîne aléatoire sécurisée
|
||||
*/
|
||||
public String generateSecureToken(int length) {
|
||||
byte[] token = new byte[length];
|
||||
secureRandom.nextBytes(token);
|
||||
return android.util.Base64.encodeToString(token, android.util.Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la clé SharedPreferences appropriée selon le provider
|
||||
*/
|
||||
private String getPrefKeyForProvider(String provider) {
|
||||
if (provider == null) {
|
||||
return KEY_API_KEY; // Default to OpenAI key
|
||||
}
|
||||
switch (provider.toLowerCase().trim()) {
|
||||
case "openrouter":
|
||||
return KEY_API_KEY_OPENROUTER;
|
||||
case "zai":
|
||||
return KEY_API_KEY_ZAI;
|
||||
case "openai":
|
||||
default:
|
||||
return KEY_API_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si des clés API sont stockées (pour vérifier la configuration)
|
||||
*
|
||||
* @return true si au moins une clé API est configurée
|
||||
*/
|
||||
public boolean isAnyApiKeyConfigured() {
|
||||
return hasApiKey("openai") || hasApiKey("openrouter") || hasApiKey("zai");
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.example.boidelov3.utils;
|
||||
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.SoundPool;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Générateur de sons utilisant ToneGenerator
|
||||
* Permet de créer des sons sans fichiers audio externes
|
||||
*/
|
||||
public class SoundGenerator {
|
||||
private android.media.ToneGenerator toneGenerator;
|
||||
private Handler handler;
|
||||
private boolean isMuted = false;
|
||||
|
||||
public SoundGenerator() {
|
||||
// Volume: 0-100
|
||||
toneGenerator = new android.media.ToneGenerator(
|
||||
android.media.AudioManager.STREAM_MUSIC, 80
|
||||
);
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Son de clic - court et léger
|
||||
*/
|
||||
public synchronized void playClick() {
|
||||
if (isMuted || toneGenerator == null) return;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_BEEP,
|
||||
50
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e("SoundGenerator", "Erreur lors de la lecture du son de clic", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Son de succès - mélange ascendant
|
||||
*/
|
||||
public synchronized void playSuccess() {
|
||||
if (isMuted || toneGenerator == null) return;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(0);
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_NACK,
|
||||
100
|
||||
);
|
||||
Thread.sleep(120);
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_ACK,
|
||||
150
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e("SoundGenerator", "Erreur lors de la lecture du son de succès", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Son de manche - dramatique pour annoncer un défi
|
||||
*/
|
||||
public synchronized void playManche() {
|
||||
if (isMuted || toneGenerator == null) return;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Premier ton grave
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_BEEP2,
|
||||
200
|
||||
);
|
||||
Thread.sleep(250);
|
||||
// Deuxième ton plus aigu
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_BEEP,
|
||||
300
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e("SoundGenerator", "Erreur lors de la lecture du son de manche", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Son de fin - célébration
|
||||
*/
|
||||
public synchronized void playFin() {
|
||||
if (isMuted || toneGenerator == null) return;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Séquence festive
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_ACK,
|
||||
150
|
||||
);
|
||||
Thread.sleep(180);
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_NACK,
|
||||
150
|
||||
);
|
||||
Thread.sleep(180);
|
||||
toneGenerator.startTone(
|
||||
android.media.ToneGenerator.TONE_PROP_ACK,
|
||||
250
|
||||
);
|
||||
} catch (Exception e) {
|
||||
Log.e("SoundGenerator", "Erreur lors de la lecture du son de fin", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Activer/Désactiver le son
|
||||
*/
|
||||
public synchronized void setMuted(boolean muted) {
|
||||
this.isMuted = muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Libérer les ressources
|
||||
*/
|
||||
public synchronized void release() {
|
||||
if (toneGenerator != null) {
|
||||
toneGenerator.release();
|
||||
toneGenerator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.example.boidelov3.utils;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Gestionnaire de sons pour l'application
|
||||
* Utilise SoundGenerator pour créer des sons sans fichiers externes
|
||||
*/
|
||||
public class SoundManager {
|
||||
private static SoundManager instance;
|
||||
private SoundGenerator soundGenerator;
|
||||
private Context context;
|
||||
|
||||
/**
|
||||
* Obtient l'instance unique du SoundManager (Singleton)
|
||||
*/
|
||||
public static synchronized SoundManager getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new SoundManager(context.getApplicationContext());
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur privé
|
||||
*/
|
||||
private SoundManager(Context context) {
|
||||
this.context = context;
|
||||
this.soundGenerator = new SoundGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Joue le son de clic
|
||||
*/
|
||||
public void playClick() {
|
||||
if (soundGenerator != null) {
|
||||
soundGenerator.playClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joue le son de succès
|
||||
*/
|
||||
public void playSuccess() {
|
||||
if (soundGenerator != null) {
|
||||
soundGenerator.playSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joue le son de manche (nouveau défi)
|
||||
*/
|
||||
public void playManche() {
|
||||
if (soundGenerator != null) {
|
||||
soundGenerator.playManche();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joue le son de fin de partie
|
||||
*/
|
||||
public void playFin() {
|
||||
if (soundGenerator != null) {
|
||||
soundGenerator.playFin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Active ou désactive le son
|
||||
*/
|
||||
public void setMuted(boolean muted) {
|
||||
if (soundGenerator != null) {
|
||||
soundGenerator.setMuted(muted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Libère les ressources
|
||||
*/
|
||||
public void release() {
|
||||
if (soundGenerator != null) {
|
||||
soundGenerator.release();
|
||||
soundGenerator = null;
|
||||
}
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<scale
|
||||
android:fromXScale="1.0"
|
||||
android:toXScale="0.95"
|
||||
android:fromYScale="1.0"
|
||||
android:toYScale="0.95"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:duration="100"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<scale
|
||||
android:fromXScale="0.95"
|
||||
android:toXScale="1.0"
|
||||
android:fromYScale="0.95"
|
||||
android:toYScale="1.0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:duration="100"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromYDelta="0%"
|
||||
android:toYDelta="100%"
|
||||
android:duration="400"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
<alpha
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromXDelta="-100%"
|
||||
android:toXDelta="0%"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
<alpha
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="200"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromXDelta="100%"
|
||||
android:toXDelta="0%"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
<alpha
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="200"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="-100%"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
<alpha
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="200"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:toXDelta="100%"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
<alpha
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0"
|
||||
android:duration="200"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<translate
|
||||
android:fromYDelta="100%"
|
||||
android:toYDelta="0%"
|
||||
android:duration="400"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
<alpha
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
</set>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/primary"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/accent"/>
|
||||
<corners android:radius="12dp"/>
|
||||
</shape>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/card_background"/>
|
||||
<corners android:radius="16dp"/>
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/surface_variant"/>
|
||||
<padding
|
||||
android:left="16dp"
|
||||
android:top="16dp"
|
||||
android:right="16dp"
|
||||
android:bottom="16dp"/>
|
||||
</shape>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/primary"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/accent"
|
||||
android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2zM7,7h2v2H7V7zM11,7h2v2h-2V7zM15,7h2v2h-2V7zM7,11h2v2H7v-2zM11,11h2v2h-2v-2zM15,11h2v2h-2v-2zM7,15h10v2H7v-2z" />
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/text_secondary"
|
||||
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
|
||||
</vector>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- Tête du personnage mystère -->
|
||||
<path
|
||||
android:fillColor="@color/primary"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
|
||||
<!-- Point d'interrogation au centre -->
|
||||
<path
|
||||
android:fillColor="@color/accent"
|
||||
android:pathData="M12.5,13.5c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5zM12.5,7.5c0,-0.28 -0.22,-0.5 -0.5,-0.5s-0.5,0.22 -0.5,0.5v1c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5v-1zM12,9c-0.83,0 -1.5,0.67 -1.5,1.5v1h1v-1c0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5v1h1v-1c0,-0.83 -0.67,-1.5 -1.5,-1.5z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/text_secondary"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/text_secondary"
|
||||
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM12,11c1.66,0 2.99,-1.34 2.99,-3S13.66,5 12,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM6,13c-2.33,0 -7,1.17 -7,3.5V19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM18,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45V19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/text_secondary"
|
||||
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5V19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45V19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
|
||||
</vector>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/text_on_primary"
|
||||
android:pathData="M14,2H6C4.9,2 4,2.9 4,4v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8L14,2zM6,20V4h7v5h5v11H6z"/>
|
||||
<path
|
||||
android:fillColor="@color/text_on_primary"
|
||||
android:pathData="M8,12h8v2H8zM8,16h8v2H8zM8,8h5v2H8z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/primary"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z" />
|
||||
</vector>
|
||||
@@ -1,213 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".games.boideloclassic.BoideloClassicGameActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@android:drawable/ic_menu_revert"
|
||||
app:title="Boidelo Classic"
|
||||
app:titleTextColor="@color/text_on_primary" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressTint="@color/accent"
|
||||
android:progressBackgroundTint="@color/surface_variant"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Progress Text -->
|
||||
<TextView
|
||||
android:id="@+id/progressTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Question 1 / 50"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar" />
|
||||
|
||||
<!-- Question Type Indicator -->
|
||||
<LinearLayout
|
||||
android:id="@+id/questionIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressTextView">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/indicatorIcon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_player_one"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/indicatorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="1 joueur"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Question Container -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/questionCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/buttonContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/questionIndicator">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Manche Counter -->
|
||||
<TextView
|
||||
android:id="@+id/mancheCounterTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="Manches: 5"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Manche Question Text (petit) -->
|
||||
<TextView
|
||||
android:id="@+id/mancheQuestionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:text="Défi en cours"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="italic"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Question Text -->
|
||||
<TextView
|
||||
android:id="@+id/questionTextView"
|
||||
style="@style/BoideloQuestionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Question"
|
||||
android:textSize="26sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Button Container -->
|
||||
<LinearLayout
|
||||
android:id="@+id/buttonContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<!-- Skip Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/skipButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="229dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:onClick="onSkipClick"
|
||||
android:text="Abandonner le défi"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/error"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/surface_variant"
|
||||
app:cornerRadius="28dp" />
|
||||
|
||||
<!-- Next Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/suivantButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="2"
|
||||
android:onClick="OnClickButton1"
|
||||
android:text="Suivant"
|
||||
android:textSize="18sp"
|
||||
app:cornerRadius="28dp"
|
||||
app:icon="@android:drawable/ic_media_play"
|
||||
app:iconGravity="textEnd" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,398 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/surface"
|
||||
tools:context=".games.boideloclassic.BoideloClassicParamsActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark"/>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:id="@+id/Titre"
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/param_tres_du_jeu" />
|
||||
|
||||
<!-- Game Settings Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Number of Questions -->
|
||||
<TextView
|
||||
style="@style/BoideloSubtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Paramètres de partie"
|
||||
app:drawableStartCompat="@android:drawable/ic_menu_edit"
|
||||
app:drawableTint="@color/primary"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Nombre de questions"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/questionCountValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="50"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Drink Addition -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Ajout de gorgées"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gorgeesValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="0"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent"/>
|
||||
|
||||
<!-- Palier Indicator -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:cardBackgroundColor="@color/surface_variant"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="Palier : Grosse merde"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Duration of Challenges -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewDuration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Durée des défis"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/durationValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBarDuration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- OpenAI Settings Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/openaiCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp"
|
||||
app:strokeColor="@color/surface_variant"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/openaiCardContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@android:drawable/ic_menu_edit"
|
||||
app:tint="@color/primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/openai"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/checkBoxGPT"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Provider Selection -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutProvider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:enabled="false"
|
||||
android:hint="Fournisseur IA"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/autoCompleteProvider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:text="OpenAI" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- API Key Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutApiKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="Clé API"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:endIconMode="password_toggle"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editTextGPT"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Ratio SeekBar -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewRatioGen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:enabled="false"
|
||||
android:text="Ratio BDD/OPENAI : 1/8"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Test API Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/ButtonTestApi"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:onClick="onClickButtonTestAPI"
|
||||
android:text="@string/test_de_connectivit_openai"
|
||||
app:backgroundTint="@color/surface_variant"
|
||||
app:icon="@android:drawable/ic_menu_info_details"
|
||||
app:iconGravity="textStart" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Start Game Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/Go2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:onClick="onClickButtonStart"
|
||||
android:text="@string/commencer_a_vous_mettre_une_mine"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/primary"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@android:drawable/ic_media_play"
|
||||
app:iconTint="@color/text_on_primary"
|
||||
app:iconGravity="textStart"
|
||||
android:elevation="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,147 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".games.boideloclassic.BoideloClassicSetupActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@android:drawable/ic_menu_revert"
|
||||
app:title="Boidelo Classic"
|
||||
app:titleTextColor="@android:color/white" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/boidelo_classic_setup_title"
|
||||
android:textColor="@color/primary" />
|
||||
|
||||
<!-- Game Description Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Le jeu original de questions et défis"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Players Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/players"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerCountText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/min_players_required"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playersContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/addPlayerButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/add_player"
|
||||
app:icon="@android:drawable/ic_input_add" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Start Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/startGameButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/start_game"
|
||||
android:textSize="18sp"
|
||||
app:icon="@android:drawable/ic_media_play" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,388 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/surface"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".EndGameActivity">
|
||||
|
||||
<!-- Background Gradient -->
|
||||
<View
|
||||
android:id="@+id/topBackground"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/primary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/guideline"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.4" />
|
||||
|
||||
<!-- Main Content -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<!-- Trophy Icon -->
|
||||
<ImageView
|
||||
android:id="@+id/trophyIcon"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:contentDescription="Trophée"
|
||||
android:src="@android:drawable/ic_menu_agenda"
|
||||
app:tint="@color/accent" />
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:id="@+id/titleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="Partie Terminée !"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Subtitle -->
|
||||
<TextView
|
||||
android:id="@+id/subtitleText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:text="Bien joué à tous !"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<!-- Stats Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/white"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Questions Played -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@android:drawable/ic_menu_edit"
|
||||
app:tint="@color/primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Questions jouées"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/questionsPlayedValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="50"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Divider -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@color/surface_variant" />
|
||||
|
||||
<!-- Players Count -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@drawable/ic_player_three"
|
||||
app:tint="@color/accent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Joueurs"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playersCountValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="5"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Divider -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@color/surface_variant" />
|
||||
|
||||
<!-- Total Gorgees -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@android:drawable/ic_menu_recent_history"
|
||||
app:tint="@color/warning" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Gorgées totales"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gorgeesTotalValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Divider -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@color/surface_variant" />
|
||||
|
||||
<!-- Plus Bu -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@android:drawable/ic_menu_add"
|
||||
app:tint="@color/accent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="A le plus bu"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plusBuValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Plus Distribué -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:src="@android:drawable/ic_menu_send"
|
||||
app:tint="@color/primary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="A le plus distribué"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plusDistribueValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Players List -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/playersCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/white"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="Participants"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/playersRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Home Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/homeButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Accueil"
|
||||
app:backgroundTint="@color/surface_variant"
|
||||
app:cornerRadius="28dp"
|
||||
app:icon="@android:drawable/ic_menu_revert"
|
||||
app:iconGravity="textStart" />
|
||||
|
||||
<!-- Replay Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/replayButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Rejouer"
|
||||
app:cornerRadius="28dp"
|
||||
app:icon="@android:drawable/ic_media_play"
|
||||
app:iconGravity="textEnd" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,240 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
tools:context=".games.game89.Game89GameActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@android:drawable/ic_menu_revert"
|
||||
app:title="89++ - Assistant"
|
||||
app:titleTextColor="@android:color/white" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<!-- Timer Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/timerCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="@color/primary"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Assistant 89++"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timerTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Prochain défi dans: 2:00"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Jouez avec vos cartes physiques !"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:alpha="0.9" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Challenge Display -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/challengeContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardBackgroundColor="@color/accent"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/timerCard"
|
||||
tools:visibility="visible">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="⚠️ DÉFI !"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/challengeTitleTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Changement de sens !"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/challengeDescriptionTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Le sens du jeu est inversé !"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="👆 Tapez pour continuer"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="12sp"
|
||||
android:alpha="0.7"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/actionButtons"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/challengeContainer">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/recordDrinksButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="🍺 Enregistrer\ngorgées"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp"
|
||||
app:backgroundTint="@android:color/white"
|
||||
app:cornerRadius="20dp"
|
||||
app:strokeColor="@color/primary"
|
||||
app:strokeWidth="2dp"
|
||||
android:elevation="2dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/showStatsButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="📊 Voir\nstatistiques"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp"
|
||||
app:backgroundTint="@android:color/white"
|
||||
app:cornerRadius="20dp"
|
||||
app:strokeColor="@color/primary"
|
||||
app:strokeWidth="2dp"
|
||||
android:elevation="2dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Info Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="@android:color/white"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/actionButtons">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="💡 Rappel des règles"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="• Valet = -10\n• Dame = Change sens\n• Roi = Choisissez la valeur\n• Dizaine (10, 20...) = distribuez des gorgées\n• 70 = DOUBLE (14 gorgées!)"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,191 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/surface"
|
||||
tools:context=".games.game89.Game89SetupActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@android:drawable/ic_menu_revert"
|
||||
app:title="89++ - Configuration"
|
||||
app:titleTextColor="@color/text_on_primary" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/game_89_title"
|
||||
android:textColor="@color/primary" />
|
||||
|
||||
<!-- Game Rules Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/game_rules"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/game_89_rules_summary"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Players Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/players"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playersContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/addPlayerButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/add_player"
|
||||
app:icon="@android:drawable/ic_input_add" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Challenge Timer Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/challenge_timer"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/timerSeekBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:max="2"
|
||||
android:min="1"
|
||||
android:progress="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timerText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center"
|
||||
android:text="1 minute"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Start Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/startGameButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/start_game"
|
||||
android:textSize="18sp"
|
||||
app:icon="@android:drawable/ic_media_play" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,75 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context=".hub.GameSelectionActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:title="@string/app_name"
|
||||
app:titleTextColor="@color/text_on_primary"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Welcome Title -->
|
||||
<TextView
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/welcome_to_hub" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/select_game_subtitle"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- Games RecyclerView -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gamesRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_game_card" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -4,184 +4,41 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/rootLayout"
|
||||
android:fitsSystemWindows="true"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:foregroundGravity="center|center_horizontal"
|
||||
tools:context=".Jeux">
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressTint="@color/accent"
|
||||
android:progressBackgroundTint="@color/surface_variant"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Progress Text -->
|
||||
<TextView
|
||||
android:id="@+id/progressText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Question 1 / 50"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar" />
|
||||
|
||||
<!-- Question Type Indicator -->
|
||||
<LinearLayout
|
||||
android:id="@+id/questionIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressText">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/indicatorIcon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@drawable/ic_player_one"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/indicatorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="1 joueur"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Main Question Container -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/questionCard"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/buttonContainer"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/questionIndicator">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="536dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Manche Question Text (petit) -->
|
||||
<TextView
|
||||
android:id="@+id/mancheCounter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="Manches: 5"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
||||
<TextView
|
||||
android:id="@+id/mancheQuestionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:text="Défi en cours"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="italic"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Question Text -->
|
||||
<TextView
|
||||
android:id="@+id/textView1"
|
||||
style="@style/BoideloQuestionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Question"
|
||||
android:textSize="26sp"/>
|
||||
|
||||
<!-- Manche Counter (if applicable) -->
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Button Container -->
|
||||
<LinearLayout
|
||||
android:id="@+id/buttonContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:orientation="horizontal"
|
||||
android:autoText="true"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.35000002" />
|
||||
|
||||
<!-- Skip Button -->
|
||||
|
||||
<!-- Next Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/skipButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="229dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:onClick="onSkipClick"
|
||||
android:text="Abandonner le défi"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/error"
|
||||
app:backgroundTint="@color/surface_variant"
|
||||
app:cornerRadius="28dp"/>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="2"
|
||||
android:layout_width="151dp"
|
||||
android:layout_height="88dp"
|
||||
android:bufferType="normal"
|
||||
android:onClick="OnClickButton1"
|
||||
android:text="Suivant"
|
||||
android:textSize="18sp"
|
||||
app:cornerRadius="28dp"
|
||||
app:icon="@android:drawable/ic_media_play"
|
||||
app:iconGravity="textEnd"/>
|
||||
|
||||
</LinearLayout>
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.81" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,397 +1,191 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/surface"
|
||||
tools:context=".JeuxParametres">
|
||||
android:layout_alignParentLeft="false"
|
||||
android:layout_alignParentTop="false"
|
||||
android:layout_alignParentRight="false"
|
||||
android:layout_alignParentBottom="false"
|
||||
tools:context=".MainActivity"
|
||||
android:layout_alignParentStart="false"
|
||||
android:layout_alignParentEnd="false">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark"/>
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:id="@+id/Titre"
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/param_tres_du_jeu" />
|
||||
|
||||
<!-- Game Settings Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Number of Questions -->
|
||||
<TextView
|
||||
style="@style/BoideloSubtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Paramètres de partie"
|
||||
app:drawableStartCompat="@android:drawable/ic_menu_edit"
|
||||
app:drawableTint="@color/primary"/>
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/param_tres_du_jeu"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/toolbar"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/PremsLinearV"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="476dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar"
|
||||
app:layout_constraintVertical_bias="0.100000024"
|
||||
tools:visibility="visible">
|
||||
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:padding="3dp"
|
||||
android:scrollbarSize="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Nombre de questions"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/questionCountValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="50"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:text="Nombre de questions avant la fin de partie : 50"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent"/>
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Drink Addition -->
|
||||
<LinearLayout
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="35dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Ajout de gorgées"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gorgeesValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="0"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:text="Ajout de gorgées : 0 "
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent"/>
|
||||
|
||||
<!-- Palier Indicator -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:cardBackgroundColor="@color/surface_variant"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="0dp">
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="Palier : Grosse merde"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="center"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="51dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Duration of Challenges -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_height="271dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewDuration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Durée des défis"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/durationValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBarDuration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- OpenAI Settings Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/openaiCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp"
|
||||
app:strokeColor="@color/surface_variant"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@android:drawable/ic_menu_edit"
|
||||
app:tint="@color/primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/openai"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
android:textSize="16sp" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
<CheckBox
|
||||
android:id="@+id/checkBoxGPT"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Provider Selection -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutProvider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:enabled="false"
|
||||
android:hint="Fournisseur IA"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu">
|
||||
android:text="@string/activer_les_questions_par_chatgpt" />
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/autoCompleteProvider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:text="OpenAI" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- API Key Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutApiKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="Clé API"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:endIconMode="password_toggle"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/editTextGPT"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1" />
|
||||
android:enabled="false"
|
||||
android:hint="@string/cl_api_openai" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Ratio SeekBar -->
|
||||
<LinearLayout
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="15dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textViewRatioGen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:enabled="false"
|
||||
android:text="Ratio BDD/OPENAI : 1/8"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
android:text="Le ratio du nombre de questions générées par l'IA."
|
||||
android:textSize="16sp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekBar3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:progressTint="@color/accent"
|
||||
android:thumbTint="@color/accent" />
|
||||
android:enabled="false" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Test API Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/ButtonTestApi"
|
||||
style="@style/BoideloButton"
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/ButtonTestApi"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:enabled="false"
|
||||
android:onClick="onClickButtonTestAPI"
|
||||
android:text="@string/test_de_connectivit_openai"
|
||||
app:backgroundTint="@color/surface_variant"
|
||||
app:icon="@android:drawable/ic_menu_info_details"
|
||||
app:iconGravity="textStart" />
|
||||
android:text="@string/test_de_connectivit_openai" />
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Bottom Spacer -->
|
||||
<View
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<!-- Floating Action Button -->
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/Go2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:onClick="onClickButtonStart"
|
||||
android:text="@string/commencer_a_vous_mettre_une_mine"
|
||||
android:textColor="@color/text_primary"
|
||||
app:backgroundTint="@color/accent"
|
||||
app:icon="@android:drawable/ic_media_play"
|
||||
app:iconTint="@color/text_primary" />
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
tools:srcCompat="@android:drawable/ic_menu_slideshow" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,218 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/surface"
|
||||
tools:context=".MainActivity">
|
||||
android:layout_alignParentLeft="false"
|
||||
android:layout_alignParentTop="false"
|
||||
android:layout_alignParentRight="false"
|
||||
android:layout_alignParentBottom="false"
|
||||
tools:context=".MainActivity"
|
||||
android:layout_alignParentStart="false"
|
||||
android:layout_alignParentEnd="false">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark"/>
|
||||
|
||||
<!-- Main Content -->
|
||||
|
||||
<!-- Floating Action Button -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/PremsLinearV"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.52"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:id="@+id/Titre"
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/bienvenue"/>
|
||||
|
||||
<!-- Player Count Indicator -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/accent"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:src="@drawable/ic_player_three"
|
||||
app:tint="@color/white"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerCountText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Joueurs: 0 / min. 3"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Players Input Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/white"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp"
|
||||
app:strokeColor="@color/surface_variant"
|
||||
app:strokeWidth="1dp">
|
||||
android:layout_height="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/bienvenue"
|
||||
android:textAllCaps="false"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="30sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="visible"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background"
|
||||
android:padding="16dp">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="7"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/namesContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
style="@style/BoideloCaption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/laisse_vide_si_il_y_a_pas_assez_de_joueur"/>
|
||||
|
||||
<!-- Default Player Inputs -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutJ1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:hint="@string/nom"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:boxStrokeWidth="2dp"
|
||||
app:endIconMode="clear_text"
|
||||
app:errorEnabled="false"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_weight="1"
|
||||
android:text="@string/laisse_vide_si_il_y_a_pas_assez_de_joueur"
|
||||
android:textAlignment="center"
|
||||
android:visibility="visible"
|
||||
tools:visibility="visible" />
|
||||
<EditText
|
||||
android:id="@+id/J1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutJ2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:autofillHints=""
|
||||
android:ems="10"
|
||||
android:hint="@string/nom"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:boxStrokeWidth="2dp"
|
||||
app:endIconMode="clear_text"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||
android:inputType="textPersonName"
|
||||
android:singleLine="true" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/J2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutJ3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:autofillHints=""
|
||||
android:ems="10"
|
||||
android:hint="@string/nom"
|
||||
app:boxBackgroundColor="@color/surface"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:boxStrokeWidth="2dp"
|
||||
app:endIconMode="clear_text"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||
android:inputType="textPersonName" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<EditText
|
||||
android:id="@+id/J3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Dynamic Player Input Container -->
|
||||
android:autofillHints=""
|
||||
android:ems="10"
|
||||
android:hint="@string/nom"
|
||||
android:inputType="textPersonName" />
|
||||
<LinearLayout
|
||||
android:id="@+id/nameEntryLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<!-- Add Player Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/Validation"
|
||||
style="@style/BoideloButton.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:onClick="onClickButton1"
|
||||
android:text="@string/c_ok"
|
||||
app:icon="@android:drawable/ic_input_add"
|
||||
app:iconGravity="textStart"/>
|
||||
android:textColor="#009688" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Bottom Spacer for FAB -->
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:id="@+id/Go"
|
||||
android:layout_width="172dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:onClick="onClickButtonStart"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/go"
|
||||
android:textColor="@color/text_primary"
|
||||
app:backgroundTint="@color/accent"
|
||||
app:icon="@android:drawable/ic_media_play"
|
||||
app:iconTint="@color/text_primary"/>
|
||||
android:visibility="visible"
|
||||
android:onClick="onClickButtonStart"
|
||||
tools:srcCompat="@android:drawable/ic_menu_slideshow" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,339 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
tools:context=".games.papelito.PapelitoGameActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@android:drawable/ic_menu_revert"
|
||||
app:title="Papelito"
|
||||
app:titleTextColor="@android:color/white" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/mainContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<!-- Phase Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="@color/primary"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/phaseTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Phase 1: Révélation des mots"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Timer Display -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/timerCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="@color/accent"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Temps de discussion"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:alpha="0.9" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timerTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="1:00"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="48sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="monospace" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Current Player / Info Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="@android:color/white"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currentWordPlayerTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tour du joueur"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/infoTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Informations de la phase"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="15sp"
|
||||
android:lineSpacingExtra="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Secret Word Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/wordCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardBackgroundColor="@color/accent"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ton mot secret est:"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:alpha="0.9" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wordTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="MOT"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="42sp"
|
||||
android:textStyle="bold"
|
||||
android:fontFamily="monospace" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Mémorise-le bien!"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:alpha="0.8" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Action Buttons Container -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Show Word Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/showWordButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:text="👁️ Voir mon mot"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/accent"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@android:drawable/ic_menu_info_details"
|
||||
app:iconTint="@android:color/white"
|
||||
android:elevation="4dp" />
|
||||
|
||||
<!-- Next Player Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/nextPlayerButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Joueur suivant ➡️"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/primary"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@android:drawable/ic_media_next"
|
||||
app:iconTint="@android:color/white"
|
||||
android:elevation="4dp" />
|
||||
|
||||
<!-- Start Discussion Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/startDiscussionButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="💬 Commencer la discussion"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/success"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@android:drawable/ic_menu_edit"
|
||||
app:iconTint="@android:color/white"
|
||||
android:visibility="gone"
|
||||
android:elevation="4dp" />
|
||||
|
||||
<!-- Start Voting Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/startVotingButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="🗳️ Passer au vote"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/accent"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@android:drawable/ic_menu_agenda"
|
||||
app:iconTint="@android:color/white"
|
||||
android:visibility="gone"
|
||||
android:elevation="4dp" />
|
||||
|
||||
<!-- End Game Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/endGameButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="🏁 Voir les résultats"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/error"
|
||||
app:cornerRadius="16dp"
|
||||
app:icon="@android:drawable/ic_menu_info_details"
|
||||
app:iconTint="@android:color/white"
|
||||
android:visibility="gone"
|
||||
android:elevation="4dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Game Info -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:cardBackgroundColor="@android:color/white"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="💡 Rappel des règles"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="• Chaque joueur reçoit un mot secret\n• Les Civils ont tous le même mot\n• Les Undercovers ont un mot différent\n• Discutez pour trouver les Undercovers\n• Votez pour éliminer les suspects\n• Les Undercovers gagnent s'ils sont ≥ aux Civils"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp"
|
||||
android:lineSpacingExtra="2dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,271 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background"
|
||||
tools:context=".games.papelito.PapelitoResultActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:title="Résultats"
|
||||
app:titleTextColor="@color/text_on_primary" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Winner Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/winnerCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/primary"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="VICTOIRE"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/winnerTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Les Civils gagnent !"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/winningTeamTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Tous les Undercover ont été éliminés"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="16sp"
|
||||
android:alpha="0.9"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Secret Words Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/secretWordsCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/accent"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="Mots secrets révélés"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/civilWordTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_gravity="center"
|
||||
android:text="Mot Civil: ???"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/undercoverWordTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_gravity="center"
|
||||
android:text="Mot Undercover: ???"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Players Roles Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/rolesCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Rôles des joueurs"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rolesRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
tools:listitem="@layout/item_papelito_result" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Statistics Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/statsCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Statistiques de la partie"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roundsTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Nombre de tours: 0"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalVotesTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Total des votes: 0"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/durationTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Durée de la partie: 0 min"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/newGameButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Nouvelle partie"
|
||||
android:textSize="14sp"
|
||||
app:icon="@android:drawable/ic_menu_rotate"
|
||||
app:cornerRadius="20dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/homeButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Retour"
|
||||
android:textSize="14sp"
|
||||
app:icon="@android:drawable/ic_menu_revert"
|
||||
app:cornerRadius="20dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,222 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/surface"
|
||||
tools:context=".games.papelito.PapelitoSetupActivity">
|
||||
|
||||
<!-- App Bar -->
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@android:drawable/ic_menu_revert"
|
||||
app:title="Papelito - Configuration"
|
||||
app:titleTextColor="@color/text_on_primary" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<!-- Main Content -->
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
style="@style/BoideloTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Papelito"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Game Rules Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Règles du jeu"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Chaque joueur reçoit un mot secret. Les Undercover doivent deviner le mot commun, tandis que les Civils doivent identifier les Undercover. Mr White doit deviner le mot exact !"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Players Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Joueurs"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playersContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/addPlayerButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Ajouter un joueur"
|
||||
app:icon="@android:drawable/ic_input_add" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Game Settings Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Paramètres de partie"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Number of Undercovers -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Nombre d'Undercover"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/undercoverSeekBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:max="3"
|
||||
android:min="1"
|
||||
android:progress="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/undercoverText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center"
|
||||
android:text="1 Undercover"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Mr White Toggle -->
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/mrWhiteSwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Activer Mr White"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
app:thumbTint="@color/primary"
|
||||
app:trackTint="@color/accent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Mr White ne connait aucun mot et doit le deviner pour gagner !"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Start Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/startGameButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="JOUER"
|
||||
android:textSize="18sp"
|
||||
app:icon="@android:drawable/ic_media_play" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Votez pour éliminer"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/voteInstructionTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Sélectionnez le joueur que vous soupçonnez être un Undercover"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- Players List for Voting -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/votePlayersRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:maxHeight="300dp"
|
||||
tools:listitem="@layout/item_papelito_player_vote" />
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/cancelVoteButton"
|
||||
style="@style/BoideloButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Annuler"
|
||||
app:backgroundTint="@color/surface_variant" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,148 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Title -->
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Passage du téléphone"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currentPlayerTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="À tour de Joueur 1"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:gravity="center" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Regardez votre mot secret en privé"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center" />
|
||||
|
||||
<!-- Word Reveal Area -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/wordCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:cardBackgroundColor="@color/primary"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp">
|
||||
|
||||
<!-- Hidden State -->
|
||||
<LinearLayout
|
||||
android:id="@+id/hiddenStateLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔒"
|
||||
android:textSize="64sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Mot caché"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Revealed State -->
|
||||
<LinearLayout
|
||||
android:id="@+id/revealedStateLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/secretWordTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="MOT SECRET"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold"
|
||||
android:letterSpacing="0.15" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roleTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Vous êtes: Civil"
|
||||
android:textColor="@color/text_on_primary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/showWordButton"
|
||||
style="@style/BoideloButton.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Voir le mot"
|
||||
android:textSize="14sp"
|
||||
app:cornerRadius="20dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/confirmButton"
|
||||
style="@style/BoideloButton.Success"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="J'ai vu"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone"
|
||||
app:cornerRadius="20dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,111 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:strokeColor="@color/card_stroke"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Game Icon Container -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:cardBackgroundColor="@color/accent"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/gameIcon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/game_icon"
|
||||
android:src="@drawable/ic_player_three"
|
||||
app:tint="@color/text_primary" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Game Info -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gameName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
tools:text="Boidelo Classic" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gameDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
tools:text="Le jeu original de questions et défis" />
|
||||
|
||||
<!-- Coming Soon Badge -->
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/statusText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/coming_soon"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone"
|
||||
app:chipBackgroundColor="@color/surface_variant"
|
||||
app:chipCornerRadius="4dp"
|
||||
app:chipMinHeight="24dp"
|
||||
style="@style/Widget.Material3.Chip.Assist.Elevated" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Play Indicator -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:cardBackgroundColor="@color/primary"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/play_game"
|
||||
android:src="@android:drawable/ic_media_play"
|
||||
app:tint="@color/text_on_primary" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- Player Avatar -->
|
||||
<TextView
|
||||
android:id="@+id/playerAvatarTextView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:gravity="center"
|
||||
android:text="A"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Player Info -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerNameTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Player Name"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerRoleTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Inconnu"
|
||||
android:textColor="@color/text_hint"
|
||||
android:textSize="12sp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Status Indicator -->
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerStatusTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="En vie"
|
||||
android:textColor="@color/success"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Vote Count -->
|
||||
<TextView
|
||||
android:id="@+id/voteCountTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="0 vote"
|
||||
android:textColor="@color/text_secondary"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp"
|
||||
app:strokeColor="@color/primary"
|
||||
app:strokeWidth="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- Player Avatar -->
|
||||
<TextView
|
||||
android:id="@+id/playerAvatarTextView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:gravity="center"
|
||||
android:text="A"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Player Name -->
|
||||
<TextView
|
||||
android:id="@+id/playerNameTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Player Name"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Vote Indicator -->
|
||||
<TextView
|
||||
android:id="@+id/votesTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:text="0 votes"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<!-- Player Avatar -->
|
||||
<TextView
|
||||
android:id="@+id/playerAvatarTextView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/bg_card"
|
||||
android:gravity="center"
|
||||
android:text="A"
|
||||
android:textColor="@color/primary"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Player Info -->
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerNameTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Player Name"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playerRoleTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Undercover"
|
||||
android:textColor="@color/error"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
tools:textColor="@color/error" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Status Badge -->
|
||||
<TextView
|
||||
android:id="@+id/playerStatusBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:text="Éliminé"
|
||||
android:textColor="@color/error"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Éliminé" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardBackgroundColor="@color/card_background"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp"
|
||||
app:strokeColor="@color/card_stroke"
|
||||
app:strokeWidth="1dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<!-- Player Number Indicator -->
|
||||
<TextView
|
||||
android:id="@+id/playerNumber"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@drawable/bg_button_secondary"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/text_primary"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:text="4" />
|
||||
|
||||
<!-- Player Name Input -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:hint="@string/player_name_hint"
|
||||
app:boxBackgroundColor="@color/white"
|
||||
app:boxStrokeColor="@color/primary"
|
||||
app:boxStrokeWidth="1dp"
|
||||
app:endIconMode="clear_text"
|
||||
app:hintTextColor="@color/text_hint"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/playerName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Remove Button with Material Design -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/removePlayerButton"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:contentDescription="@string/remove_player"
|
||||
app:icon="@android:drawable/ic_delete"
|
||||
app:iconTint="@color/error"
|
||||
app:backgroundTint="@color/surface"
|
||||
app:iconSize="20dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user