From b2571c191f2ea1a977ca4c5661f830188743a451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillaume=20T=C3=A2che?= Date: Tue, 2 Sep 2025 21:53:03 +0200 Subject: [PATCH] Exact picture seems to work --- .../main/java/ch/gtache/fro/BirdProvider.java | 1 - .../java/ch/gtache/fro/Configuration.java | 71 --- .../ch/gtache/fro/FetcherConfiguration.java | 35 ++ .../fro/practice/PracticeConfiguration.java | 66 +++ .../fro/practice/PracticeParameters.java | 17 +- .../gtache/fro/practice/PracticeQuestion.java | 7 + .../gtache/fro/practice/PracticeResult.java | 27 ++ .../ch/gtache/fro/practice/PracticeRun.java | 4 + .../fro/practice/PracticeTypeTranslator.java | 10 - .../{PracticeType.java => QuestionType.java} | 2 +- .../fro/practice/QuestionTypeTranslator.java | 10 + .../oiseaux/fr/ChantOiseauxFrFetcher.java | 4 +- core/pom.xml | 9 + .../ch/gtache/fro/impl/AbstractFetcher.java | 12 +- .../java/ch/gtache/fro/impl/BirdImpl.java | 23 + .../gtache/fro/impl/CommonBirdsProvider.java | 1 + .../fro/impl/CommonBirdsTranslatorImpl.java | 11 + .../ch/gtache/fro/impl/ConfigurationImpl.java | 157 ------- .../fro/impl/FetcherConfigurationImpl.java | 68 +++ .../fro/impl/PracticeConfigurationImpl.java | 213 +++++++++ .../impl/QuestionRatesMapTypeReference.java | 9 + .../SerializedBirdPracticeParameters.java | 15 + ...edBirdPracticeParametersTypeReference.java | 8 + .../gtache/fro/modules/impl/CoreModule.java | 13 +- .../practice/impl/CorePracticeModule.java | 14 +- ...ictureMultichoicePracticeQuestionImpl.java | 6 + .../impl/PicturePracticeQuestionImpl.java | 6 + .../practice/impl/PracticeParametersImpl.java | 50 +- .../impl/PracticeQuestionGeneratorImpl.java | 23 +- .../fro/practice/impl/PracticeRunnerImpl.java | 4 +- .../impl/PracticeTypeTranslatorImpl.java | 25 - .../impl/QuestionTypeTranslatorImpl.java | 25 + ...ultichoicePicturePracticeQuestionImpl.java | 6 + .../SoundMultichoicePracticeQuestionImpl.java | 6 + .../impl/SoundPracticeQuestionImpl.java | 6 + core/src/main/java/module-info.java | 8 +- .../ch/gtache/fro/impl/BirdBundle.properties | 428 ++++++++++++++++++ .../gtache/fro/impl/BirdBundle_fr.properties | 2 + .../impl/PracticeTypeBundle.properties | 5 - .../impl/PracticeTypeBundle_en.properties | 5 - .../impl/PracticeTypeBundle_fr.properties | 5 - .../impl/QuestionTypeBundle.properties | 5 + .../impl/QuestionTypeBundle_en.properties | 5 + .../impl/QuestionTypeBundle_fr.properties | 5 + .../ch/gtache/fro/gui/SettingsController.java | 10 + .../fro/practice/gui/PracticeExactModel.java | 6 +- .../practice/gui/PracticeGuessController.java | 5 + .../fro/practice/gui/PracticeModel.java | 13 + .../gui/PracticeMultichoiceModel.java | 2 +- .../practice/gui/PracticePictureModel.java | 5 +- .../practice/gui/PracticeQuestionModel.java | 35 ++ .../gui/PracticeResultController.java | 5 + .../fro/practice/gui/PracticeResultModel.java | 43 ++ .../practice/gui/PracticeSettingsModel.java | 48 +- .../fro/practice/gui/PracticeSoundModel.java | 5 +- .../PracticeSoundMultichoicePictureModel.java | 2 +- gui/core/src/main/java/module-info.java | 5 +- .../fro/gui/impl/FetchBundle_fr.properties | 4 +- .../fro/gui/impl/MainBundle_fr.properties | 6 +- .../fro/gui/impl/SettingsBundle_fr.properties | 2 +- .../PracticePictureExactBundle.properties | 3 +- .../PracticePictureExactBundle_en.properties | 1 + .../PracticePictureExactBundle_fr.properties | 1 + ...racticePictureMultichoiceBundle.properties | 3 +- ...ticePictureMultichoiceBundle_en.properties | 1 + ...ticePictureMultichoiceBundle_fr.properties | 1 + .../gui/impl/PracticeResultBundle.properties | 3 +- .../impl/PracticeResultBundle_en.properties | 1 + .../impl/PracticeResultBundle_fr.properties | 5 +- .../impl/PracticeSettingsBundle.properties | 4 +- .../impl/PracticeSettingsBundle_en.properties | 2 + .../impl/PracticeSettingsBundle_fr.properties | 6 +- .../impl/PracticeSoundExactBundle.properties | 3 +- .../PracticeSoundExactBundle_en.properties | 1 + .../PracticeSoundExactBundle_fr.properties | 1 + .../PracticeSoundMultichoiceBundle.properties | 3 +- ...acticeSoundMultichoiceBundle_en.properties | 1 + ...acticeSoundMultichoiceBundle_fr.properties | 1 + ...ceSoundMultichoicePictureBundle.properties | 3 +- ...oundMultichoicePictureBundle_en.properties | 1 + ...oundMultichoicePictureBundle_fr.properties | 1 + .../ch/gtache/fro/gui/fx/FXFetchModel.java | 16 +- .../fro/gui/fx/FXSettingsController.java | 16 +- .../ch/gtache/fro/gui/fx/FXSettingsModel.java | 6 + .../fx/AbstractFXPracticeQuestionModel.java | 56 +++ .../AbstractFXPraticeQuestionExactModel.java | 31 ++ ...ractFXPraticeQuestionMultichoiceModel.java | 44 ++ .../fro/practice/gui/fx/FXPracticeBinder.java | 50 ++ .../practice/gui/fx/FXPracticeController.java | 36 +- .../fro/practice/gui/fx/FXPracticeModel.java | 24 + .../fx/FXPracticePictureExactController.java | 94 +++- .../gui/fx/FXPracticePictureExactModel.java | 64 ++- ...XPracticePictureMultichoiceController.java | 36 +- .../fx/FXPracticePictureMultichoiceModel.java | 72 ++- .../gui/fx/FXPracticeResultController.java | 21 + .../gui/fx/FXPracticeResultModel.java | 75 ++- .../gui/fx/FXPracticeSettingsController.java | 41 +- .../gui/fx/FXPracticeSettingsModel.java | 94 +++- .../fx/FXPracticeSoundExactController.java | 37 +- .../gui/fx/FXPracticeSoundExactModel.java | 48 +- .../FXPracticeSoundMultichoiceController.java | 29 +- .../fx/FXPracticeSoundMultichoiceModel.java | 56 +-- ...ticeSoundMultichoicePictureController.java | 30 +- ...XPracticeSoundMultichoicePictureModel.java | 30 +- .../gui/fx/PracticeTypeConverter.java | 33 -- .../gui/fx/QuestionTypeConverter.java | 33 ++ .../practice/gui/fx/UserInputNormalizer.java | 51 +++ gui/fx/src/main/java/module-info.java | 11 +- .../gui/fx/practicePictureExactView.fxml | 40 +- .../fx/practicePictureMultichoiceView.fxml | 11 +- .../practice/gui/fx/practiceResultView.fxml | 5 + .../practice/gui/fx/practiceSettingsView.fxml | 37 +- .../gui/fx/practiceSoundExactView.fxml | 16 +- .../practiceSoundMultichoicePictureView.fxml | 29 +- .../gui/fx/practiceSoundMultichoiceView.fxml | 29 +- gui/run/pom.xml | 12 + .../ch/gtache/fro/gui/run/FroApplication.java | 3 + .../main/java/ch/gtache/fro/gui/run/Main.java | 5 + .../fro/gui/run/modules/FroComponent.java | 4 +- gui/run/src/main/java/module-info.java | 6 +- gui/run/src/main/resources/log4j2.xml | 15 + .../fro/jsoup/AbstractJSoupFetcher.java | 4 +- jsoup/src/main/java/module-info.java | 2 +- .../fro/modules/oiseaux/net/OiseauxNet.java | 16 + .../modules/oiseaux/net/OiseauxNetModule.java | 33 ++ .../oiseaux/net/BirdTranslationsFetcher.java | 44 +- .../fro/oiseaux/net/OiseauxNetFetcher.java | 77 ++-- .../java/ch/gtache/fro/oiseaux/net/Utils.java | 5 +- oiseaux-net/src/main/java/module-info.java | 9 +- .../oiseaux/net/TestOiseauxNetFetcher.java | 4 +- pom.xml | 15 + .../fro/selenium/AbstractSeleniumFetcher.java | 4 +- selenium/src/main/java/module-info.java | 2 +- .../fro/modules/vogelwarte/Vogelwarte.java | 16 + .../modules/vogelwarte/VogelwarteModule.java | 18 + .../fro/vogelwarte/VogelwarteFetcher.java | 5 +- vogelwarte/src/main/java/module-info.java | 10 +- 137 files changed, 2487 insertions(+), 797 deletions(-) delete mode 100644 api/src/main/java/ch/gtache/fro/Configuration.java create mode 100644 api/src/main/java/ch/gtache/fro/FetcherConfiguration.java create mode 100644 api/src/main/java/ch/gtache/fro/practice/PracticeConfiguration.java delete mode 100644 api/src/main/java/ch/gtache/fro/practice/PracticeTypeTranslator.java rename api/src/main/java/ch/gtache/fro/practice/{PracticeType.java => QuestionType.java} (95%) create mode 100644 api/src/main/java/ch/gtache/fro/practice/QuestionTypeTranslator.java create mode 100644 core/src/main/java/ch/gtache/fro/impl/BirdImpl.java delete mode 100644 core/src/main/java/ch/gtache/fro/impl/ConfigurationImpl.java create mode 100644 core/src/main/java/ch/gtache/fro/impl/FetcherConfigurationImpl.java create mode 100644 core/src/main/java/ch/gtache/fro/impl/PracticeConfigurationImpl.java create mode 100644 core/src/main/java/ch/gtache/fro/impl/QuestionRatesMapTypeReference.java create mode 100644 core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParameters.java create mode 100644 core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParametersTypeReference.java delete mode 100644 core/src/main/java/ch/gtache/fro/practice/impl/PracticeTypeTranslatorImpl.java create mode 100644 core/src/main/java/ch/gtache/fro/practice/impl/QuestionTypeTranslatorImpl.java create mode 100644 core/src/main/resources/ch/gtache/fro/impl/BirdBundle.properties delete mode 100644 core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle.properties delete mode 100644 core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_en.properties delete mode 100644 core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_fr.properties create mode 100644 core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle.properties create mode 100644 core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_en.properties create mode 100644 core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_fr.properties create mode 100644 gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeQuestionModel.java create mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPracticeQuestionModel.java create mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionExactModel.java create mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionMultichoiceModel.java create mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeBinder.java delete mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/PracticeTypeConverter.java create mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/QuestionTypeConverter.java create mode 100644 gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/UserInputNormalizer.java create mode 100644 gui/run/src/main/resources/log4j2.xml create mode 100644 oiseaux-net/src/main/java/ch/gtache/fro/modules/oiseaux/net/OiseauxNet.java create mode 100644 oiseaux-net/src/main/java/ch/gtache/fro/modules/oiseaux/net/OiseauxNetModule.java create mode 100644 vogelwarte/src/main/java/ch/gtache/fro/modules/vogelwarte/Vogelwarte.java diff --git a/api/src/main/java/ch/gtache/fro/BirdProvider.java b/api/src/main/java/ch/gtache/fro/BirdProvider.java index 8e9fd72..79f7879 100644 --- a/api/src/main/java/ch/gtache/fro/BirdProvider.java +++ b/api/src/main/java/ch/gtache/fro/BirdProvider.java @@ -4,5 +4,4 @@ package ch.gtache.fro; * {@link Provider} of {@link Bird} */ public interface BirdProvider extends Provider { - } diff --git a/api/src/main/java/ch/gtache/fro/Configuration.java b/api/src/main/java/ch/gtache/fro/Configuration.java deleted file mode 100644 index 40876a1..0000000 --- a/api/src/main/java/ch/gtache/fro/Configuration.java +++ /dev/null @@ -1,71 +0,0 @@ -package ch.gtache.fro; - -import ch.gtache.fro.practice.BirdPracticeParameters; - -import java.nio.file.Path; -import java.time.Duration; -import java.util.Collection; - -public interface Configuration { - - /** - * Returns the root folder where the data is stored - * - * @return The root folder - */ - Path rootFolder(); - - /** - * Sets the root folder where the data is stored - * - * @param rootFolder The root folder - */ - void setRootFolder(final Path rootFolder); - - /** - * Returns the duration to wait between two fetches - * - * @return The duration - */ - Duration waitBetweenFetch(); - - /** - * Sets the duration to wait between two fetches - * - * @param waitBetweenFetch The duration - */ - void setWaitBetweenFetch(final Duration waitBetweenFetch); - - /** - * Returns the bird practice parameters - * - * @return The parameters - */ - Collection birdPracticeParameters(); - - /** - * Returns the bird practice parameters for a specific bird - * - * @param bird The bird - * @return The parameters - */ - BirdPracticeParameters birdPracticeParameters(final Bird bird); - - /** - * Sets the bird practice parameters - * - * @param birdPracticeParameters The parameters - */ - default void setBirdPracticeParameters(final Collection birdPracticeParameters) { - for (final var birdPracticeParameter : birdPracticeParameters) { - setBirdPracticeParameters(birdPracticeParameter); - } - } - - /** - * Sets the bird practice parameters for a specific bird - * - * @param birdPracticeParameters The parameters - */ - void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters); -} diff --git a/api/src/main/java/ch/gtache/fro/FetcherConfiguration.java b/api/src/main/java/ch/gtache/fro/FetcherConfiguration.java new file mode 100644 index 0000000..cd3b4be --- /dev/null +++ b/api/src/main/java/ch/gtache/fro/FetcherConfiguration.java @@ -0,0 +1,35 @@ +package ch.gtache.fro; + +import java.nio.file.Path; +import java.time.Duration; + +public interface FetcherConfiguration { + + /** + * Returns the root folder where the data is stored + * + * @return The root folder + */ + Path rootFolder(); + + /** + * Sets the root folder where the data is stored + * + * @param rootFolder The root folder + */ + void setRootFolder(final Path rootFolder); + + /** + * Returns the duration to wait between two fetches + * + * @return The duration + */ + Duration waitBetweenFetch(); + + /** + * Sets the duration to wait between two fetches + * + * @param waitBetweenFetch The duration + */ + void setWaitBetweenFetch(final Duration waitBetweenFetch); +} diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeConfiguration.java b/api/src/main/java/ch/gtache/fro/practice/PracticeConfiguration.java new file mode 100644 index 0000000..7d115ac --- /dev/null +++ b/api/src/main/java/ch/gtache/fro/practice/PracticeConfiguration.java @@ -0,0 +1,66 @@ +package ch.gtache.fro.practice; + +import ch.gtache.fro.Bird; + +import java.util.Collection; +import java.util.Map; + +public interface PracticeConfiguration { + + /** + * Sets the number of unsuccessful guesses before a question is considered as failed + * + * @return The number of unsuccessful guesses + */ + int guessesNumber(); + + /** + * Sets the number of unsuccessful guesses before a question is considered as failed + * + * @param guessNumber The number of unsuccessful guesses + */ + void setGuessesNumber(final int guessNumber); + + /** + * Returns the saved question rates + * + * @return The question rates + */ + Map questionRates(); + + /** + * Sets the question rates + * + * @param questionRates The question rates + */ + void setQuestionRates(final Map questionRates); + + /** + * Returns the bird practice parameters + * + * @return The parameters + */ + Collection birdPracticeParameters(); + + /** + * Returns the bird practice parameters for a specific bird + * + * @param bird The bird + * @return The parameters + */ + BirdPracticeParameters birdPracticeParameters(final Bird bird); + + /** + * Sets the bird practice parameters + * + * @param birdPracticeParameters The parameters + */ + void setBirdPracticeParameters(final Collection birdPracticeParameters); + + /** + * Sets the bird practice parameters for a specific bird + * + * @param birdPracticeParameters The parameters + */ + void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters); +} diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeParameters.java b/api/src/main/java/ch/gtache/fro/practice/PracticeParameters.java index aaa0818..feb757c 100644 --- a/api/src/main/java/ch/gtache/fro/practice/PracticeParameters.java +++ b/api/src/main/java/ch/gtache/fro/practice/PracticeParameters.java @@ -17,23 +17,30 @@ public interface PracticeParameters { Map birdParameters(); /** - * Returns the practice type + * Returns the number of unsuccessful guesses before a question is considered as failed * - * @return The practice type + * @return The number of unsuccessful guesses */ - PracticeType practiceType(); + int guessesNumber(); + + /** + * Returns the question type rates (chance of each type) + * + * @return The question types + */ + Map questionTypeRates(); /** * Returns the number of questions in this practice * * @return The number of questions */ - int questionsCount(); + int questionsNumber(); /** * Returns the number of propositions for a multichoice type * * @return The number of propositions */ - int propositionsCount(); + int propositionsNumber(); } diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeQuestion.java b/api/src/main/java/ch/gtache/fro/practice/PracticeQuestion.java index 59a5c81..ee9386f 100644 --- a/api/src/main/java/ch/gtache/fro/practice/PracticeQuestion.java +++ b/api/src/main/java/ch/gtache/fro/practice/PracticeQuestion.java @@ -13,4 +13,11 @@ public interface PracticeQuestion { * @return The bird */ Bird bird(); + + /** + * Returns the type of this question + * + * @return The type + */ + QuestionType questionType(); } diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeResult.java b/api/src/main/java/ch/gtache/fro/practice/PracticeResult.java index 64b9da5..ce0c23f 100644 --- a/api/src/main/java/ch/gtache/fro/practice/PracticeResult.java +++ b/api/src/main/java/ch/gtache/fro/practice/PracticeResult.java @@ -14,10 +14,37 @@ public interface PracticeResult { */ List correctQuestions(); + /** + * Returns the total number of correct questions + * + * @return The total number of correct questions + */ + default int correctQuestionsCount() { + return correctQuestions().size(); + } + /** * Returns the failed questions * * @return The failed questions */ List failedQuestions(); + + /** + * Returns the total number of failed questions + * + * @return The total number of failed questions + */ + default int failedQuestionsCount() { + return failedQuestions().size(); + } + + /** + * Returns the total number of questions + * + * @return The total number of questions + */ + default int totalQuestions() { + return correctQuestions().size() + failedQuestions().size(); + } } diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeRun.java b/api/src/main/java/ch/gtache/fro/practice/PracticeRun.java index 137f97e..42161dc 100644 --- a/api/src/main/java/ch/gtache/fro/practice/PracticeRun.java +++ b/api/src/main/java/ch/gtache/fro/practice/PracticeRun.java @@ -41,4 +41,8 @@ public interface PracticeRun { * @return The failed questions */ List failedQuestions(); + + default boolean isFinished() { + return currentQuestionIndex() == parameters().questionsNumber(); + } } diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeTypeTranslator.java b/api/src/main/java/ch/gtache/fro/practice/PracticeTypeTranslator.java deleted file mode 100644 index dbda4d1..0000000 --- a/api/src/main/java/ch/gtache/fro/practice/PracticeTypeTranslator.java +++ /dev/null @@ -1,10 +0,0 @@ -package ch.gtache.fro.practice; - -import ch.gtache.fro.Translator; - -/** - * Translates a {@link PracticeType} to a string - */ -public interface PracticeTypeTranslator extends Translator { - -} diff --git a/api/src/main/java/ch/gtache/fro/practice/PracticeType.java b/api/src/main/java/ch/gtache/fro/practice/QuestionType.java similarity index 95% rename from api/src/main/java/ch/gtache/fro/practice/PracticeType.java rename to api/src/main/java/ch/gtache/fro/practice/QuestionType.java index 5a9120b..2970173 100644 --- a/api/src/main/java/ch/gtache/fro/practice/PracticeType.java +++ b/api/src/main/java/ch/gtache/fro/practice/QuestionType.java @@ -3,7 +3,7 @@ package ch.gtache.fro.practice; /** * Represents the type of practice */ -public enum PracticeType { +public enum QuestionType { /** * See an image, must write the bird name */ diff --git a/api/src/main/java/ch/gtache/fro/practice/QuestionTypeTranslator.java b/api/src/main/java/ch/gtache/fro/practice/QuestionTypeTranslator.java new file mode 100644 index 0000000..aeaefa1 --- /dev/null +++ b/api/src/main/java/ch/gtache/fro/practice/QuestionTypeTranslator.java @@ -0,0 +1,10 @@ +package ch.gtache.fro.practice; + +import ch.gtache.fro.Translator; + +/** + * Translates a {@link QuestionType} to a string + */ +public interface QuestionTypeTranslator extends Translator { + +} diff --git a/chant-oiseaux-fr/src/main/java/ch/gtache/fro/chants/oiseaux/fr/ChantOiseauxFrFetcher.java b/chant-oiseaux-fr/src/main/java/ch/gtache/fro/chants/oiseaux/fr/ChantOiseauxFrFetcher.java index 66a694b..78febd0 100644 --- a/chant-oiseaux-fr/src/main/java/ch/gtache/fro/chants/oiseaux/fr/ChantOiseauxFrFetcher.java +++ b/chant-oiseaux-fr/src/main/java/ch/gtache/fro/chants/oiseaux/fr/ChantOiseauxFrFetcher.java @@ -2,9 +2,9 @@ package ch.gtache.fro.chants.oiseaux.fr; import ch.gtache.fro.Bird; import ch.gtache.fro.BirdProvider; -import ch.gtache.fro.Configuration; import ch.gtache.fro.FetchException; import ch.gtache.fro.Fetcher; +import ch.gtache.fro.FetcherConfiguration; import ch.gtache.fro.selenium.AbstractSeleniumFetcher; import java.io.IOException; @@ -21,7 +21,7 @@ public class ChantOiseauxFrFetcher extends AbstractSeleniumFetcher { * @param configuration The configuration * @throws NullPointerException If any parameter is null */ - protected ChantOiseauxFrFetcher(final BirdProvider birdProvider, final Configuration configuration) { + protected ChantOiseauxFrFetcher(final BirdProvider birdProvider, final FetcherConfiguration configuration) { super(birdProvider, configuration); } diff --git a/core/pom.xml b/core/pom.xml index 0ca411e..51e948b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -11,6 +11,10 @@ fro-core + + 2.20.0 + + ch.gtache.fro @@ -20,6 +24,11 @@ org.apache.logging.log4j log4j-api + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + com.google.dagger dagger diff --git a/core/src/main/java/ch/gtache/fro/impl/AbstractFetcher.java b/core/src/main/java/ch/gtache/fro/impl/AbstractFetcher.java index 250d492..60cd4df 100644 --- a/core/src/main/java/ch/gtache/fro/impl/AbstractFetcher.java +++ b/core/src/main/java/ch/gtache/fro/impl/AbstractFetcher.java @@ -8,8 +8,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.regex.Pattern; /** @@ -22,7 +24,8 @@ public abstract class AbstractFetcher implements Fetcher { private static final Pattern FILENAME_SPLIT = Pattern.compile("__"); private final BirdProvider birdProvider; - private final Configuration configuration; + private final FetcherConfiguration configuration; + private final Set fetched; /** * Instantiates the fetcher @@ -31,9 +34,10 @@ public abstract class AbstractFetcher implements Fetcher { * @param configuration The configuration * @throws NullPointerException If any parameter is null */ - protected AbstractFetcher(final BirdProvider birdProvider, final Configuration configuration) { + protected AbstractFetcher(final BirdProvider birdProvider, final FetcherConfiguration configuration) { this.birdProvider = Objects.requireNonNull(birdProvider); this.configuration = Objects.requireNonNull(configuration); + this.fetched = new HashSet<>(); } @Override @@ -61,6 +65,7 @@ public abstract class AbstractFetcher implements Fetcher { @Override public FilledBird fetch(final Bird bird) throws FetchException { if (!isAlreadyFetched(bird)) { + fetched.add(bird); logger.info("Fetching {} ({})", bird, name()); download(bird); } @@ -169,6 +174,9 @@ public abstract class AbstractFetcher implements Fetcher { } private boolean isAlreadyFetched(final Bird bird) { + if (fetched.contains(bird)) { + return true; + } final var path = getCachePath(bird); if (Files.isDirectory(path)) { try (final var found = Files.find(path, Integer.MAX_VALUE, (p, bfa) -> bfa.isRegularFile())) { diff --git a/core/src/main/java/ch/gtache/fro/impl/BirdImpl.java b/core/src/main/java/ch/gtache/fro/impl/BirdImpl.java new file mode 100644 index 0000000..065ec8f --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/impl/BirdImpl.java @@ -0,0 +1,23 @@ +package ch.gtache.fro.impl; + +import ch.gtache.fro.Bird; + +import java.util.Objects; + +/** + * Implementation of {@link Bird} + * + * @param name The bird name + */ +public record BirdImpl(String name) implements Bird { + + /** + * Instantiates the bird + * + * @param name The bird name + * @throws NullPointerException If any parameter is null + */ + public BirdImpl { + Objects.requireNonNull(name); + } +} diff --git a/core/src/main/java/ch/gtache/fro/impl/CommonBirdsProvider.java b/core/src/main/java/ch/gtache/fro/impl/CommonBirdsProvider.java index ac46448..66a50f5 100644 --- a/core/src/main/java/ch/gtache/fro/impl/CommonBirdsProvider.java +++ b/core/src/main/java/ch/gtache/fro/impl/CommonBirdsProvider.java @@ -13,6 +13,7 @@ import java.util.Locale; */ public class CommonBirdsProvider implements BirdProvider { + /** * Instantiates the provider */ diff --git a/core/src/main/java/ch/gtache/fro/impl/CommonBirdsTranslatorImpl.java b/core/src/main/java/ch/gtache/fro/impl/CommonBirdsTranslatorImpl.java index b0b612e..e4af9f0 100644 --- a/core/src/main/java/ch/gtache/fro/impl/CommonBirdsTranslatorImpl.java +++ b/core/src/main/java/ch/gtache/fro/impl/CommonBirdsTranslatorImpl.java @@ -4,6 +4,8 @@ import ch.gtache.fro.Bird; import ch.gtache.fro.BirdTranslator; import jakarta.inject.Inject; +import java.util.Locale; + /** * Implementation of {@link BirdTranslator} for {@link CommonBirds} */ @@ -14,6 +16,15 @@ public class CommonBirdsTranslatorImpl extends AbstractBundleTranslator im super("ch.gtache.fro.impl.BirdBundle"); } + @Override + public String translate(final Bird object, final Locale locale) { + if (object instanceof CommonBirds) { + return super.translate(object, locale); + } else { + return object.name(); + } + } + @Override protected String getKey(final Bird object) { return "bird." + object.name() + ".label"; diff --git a/core/src/main/java/ch/gtache/fro/impl/ConfigurationImpl.java b/core/src/main/java/ch/gtache/fro/impl/ConfigurationImpl.java deleted file mode 100644 index 536a8bb..0000000 --- a/core/src/main/java/ch/gtache/fro/impl/ConfigurationImpl.java +++ /dev/null @@ -1,157 +0,0 @@ -package ch.gtache.fro.impl; - -import ch.gtache.fro.Bird; -import ch.gtache.fro.BirdProvider; -import ch.gtache.fro.Configuration; -import ch.gtache.fro.PictureType; -import ch.gtache.fro.ProvisionException; -import ch.gtache.fro.SoundType; -import ch.gtache.fro.practice.BirdPracticeParameters; -import ch.gtache.fro.practice.impl.BirdPracticeParametersImpl; -import jakarta.inject.Inject; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.nio.file.Path; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.prefs.Preferences; -import java.util.stream.Collectors; - -import static java.util.Objects.requireNonNull; - -/** - * Implementation of {@link Configuration} - */ -public class ConfigurationImpl implements Configuration { - - private static final Logger logger = LogManager.getLogger(ConfigurationImpl.class); - - private static final Path DEFAULT_ROOT_FOLDER = Path.of(System.getProperty("user.home"), ".fro", "birds"); - private static final Duration DEFAULT_WAIT_BETWEEN_FETCH = Duration.ofMillis(3000); - private static final String ROOT_FOLDER_KEY = "rootFolder"; - private static final String WAIT_BETWEEN_FETCH_KEY = "waitBetweenFetch"; - - - private final BirdProvider birdProvider; - private final Preferences preferences; - private Path rootFolder; - private Duration waitBetweenFetch; - private final Map birdPracticeParameters; - - @Inject - ConfigurationImpl(final BirdProvider birdProvider) { - this.birdProvider = requireNonNull(birdProvider); - this.rootFolder = DEFAULT_ROOT_FOLDER; - this.waitBetweenFetch = DEFAULT_WAIT_BETWEEN_FETCH; - this.preferences = Preferences.userNodeForPackage(this.getClass()); - this.birdPracticeParameters = new HashMap<>(); - load(); - } - - private void load() { - this.rootFolder = Path.of(preferences.get(ROOT_FOLDER_KEY, DEFAULT_ROOT_FOLDER.toString())); - this.waitBetweenFetch = Duration.ofMillis(preferences.getLong(WAIT_BETWEEN_FETCH_KEY, DEFAULT_WAIT_BETWEEN_FETCH.toMillis())); - try { - final var allBirds = birdProvider.getAllObjects(); - final var practiceParameters = allBirds.stream().map(this::loadPracticeParameters).collect(Collectors.toMap(BirdPracticeParameters::bird, e -> e)); - birdPracticeParameters.putAll(practiceParameters); - } catch (final ProvisionException e) { - logger.error("Failed to get all birds", e); - } - logger.info("Loaded configuration: rootFolder={}, waitBetweenFetch={}", rootFolder, waitBetweenFetch); - } - - @Override - public Path rootFolder() { - return rootFolder; - } - - @Override - public void setRootFolder(final Path rootFolder) { - logger.info("Setting root folder to {}", rootFolder); - this.rootFolder = requireNonNull(rootFolder); - preferences.put(ROOT_FOLDER_KEY, rootFolder.toString()); - } - - @Override - public Duration waitBetweenFetch() { - return waitBetweenFetch; - } - - @Override - public void setWaitBetweenFetch(final Duration waitBetweenFetch) { - logger.info("Setting wait between fetch to {}", waitBetweenFetch); - this.waitBetweenFetch = requireNonNull(waitBetweenFetch); - preferences.putLong(WAIT_BETWEEN_FETCH_KEY, waitBetweenFetch.toMillis()); - } - - @Override - public Collection birdPracticeParameters() { - return birdPracticeParameters.values(); - } - - @Override - public BirdPracticeParameters birdPracticeParameters(final Bird bird) { - return birdPracticeParameters.get(bird); - } - - private BirdPracticeParameters loadPracticeParameters(final Bird bird) { - final var parameters = getAllParameters(bird); - final var birdParameters = parameters.stream().collect(Collectors.toMap(e -> e, e -> preferences.get(e, getDefaultValue(e)))); - return convertParameters(bird, birdParameters); - } - - @Override - public void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters) { - this.birdPracticeParameters.put(birdPracticeParameters.bird(), birdPracticeParameters); - final var parameters = convertParameters(birdPracticeParameters); - parameters.forEach(preferences::put); - } - - private static Map convertParameters(final BirdPracticeParameters parameters) { - final var enabled = parameters.enabled(); - final var enabledFetchers = String.join(";", parameters.enabledFetchers()); - final var enabledPictureTypes = parameters.enabledPictureTypes().stream().map(PictureType::name).collect(Collectors.joining(";")); - final var enabledSoundTypes = parameters.enabledSoundTypes().stream().map(SoundType::name).collect(Collectors.joining(";")); - final var name = parameters.bird().name(); - return Map.of( - name + "_enabled", String.valueOf(enabled), - name + "_enabledFetchers", enabledFetchers, - name + "_enabledPictureTypes", enabledPictureTypes, - name + "_enabledSoundTypes", enabledSoundTypes - ); - - } - - private static BirdPracticeParameters convertParameters(final Bird bird, final Map parameters) { - final var enabled = Boolean.parseBoolean(parameters.get(bird.name() + "_enabled")); - final var enabledFetchers = Arrays.stream(parameters.get(bird.name() + "_enabledFetchers").split(";")).filter(e -> !e.isBlank()).collect(Collectors.toSet()); - final var enabledPictureTypes = Arrays.stream(parameters.get(bird.name() + "_enabledPictureTypes").split(";")).filter(e -> !e.isBlank()).map(PictureType::valueOf).collect(Collectors.toSet()); - final var enabledSoundTypes = Arrays.stream(parameters.get(bird.name() + "_enabledSoundTypes").split(";")).filter(e -> !e.isBlank()).map(SoundType::valueOf).collect(Collectors.toSet()); - return new BirdPracticeParametersImpl(bird, enabled, enabledFetchers, enabledPictureTypes, enabledSoundTypes); - } - - private static String getDefaultValue(final String parameter) { - final var type = parameter.split("_")[1]; - return switch (type) { - case "enabled" -> "true"; - case "enabledFetchers", "enabledPictureTypes", "enabledSoundTypes" -> "all"; - default -> ""; - }; - } - - private static Set getAllParameters(final Bird bird) { - final var name = bird.name(); - return Set.of( - name + "_enabled", - name + "_enabledFetchers", - name + "_enabledPictureTypes", - name + "_enabledSoundTypes" - ); - } -} diff --git a/core/src/main/java/ch/gtache/fro/impl/FetcherConfigurationImpl.java b/core/src/main/java/ch/gtache/fro/impl/FetcherConfigurationImpl.java new file mode 100644 index 0000000..1c84790 --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/impl/FetcherConfigurationImpl.java @@ -0,0 +1,68 @@ +package ch.gtache.fro.impl; + +import ch.gtache.fro.FetcherConfiguration; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.prefs.Preferences; + +import static java.util.Objects.requireNonNull; + +/** + * Implementation of {@link FetcherConfiguration} + */ +public class FetcherConfigurationImpl implements FetcherConfiguration { + private static final Logger logger = LogManager.getLogger(FetcherConfigurationImpl.class); + + private static final Path DEFAULT_ROOT_FOLDER = Path.of(System.getProperty("user.home"), ".fro", "birds"); + private static final Duration DEFAULT_WAIT_BETWEEN_FETCH = Duration.ofMillis(3000); + private static final String ROOT_FOLDER_KEY = "rootFolder"; + private static final String WAIT_BETWEEN_FETCH_KEY = "waitBetweenFetch"; + + private final Preferences preferences; + private Path rootFolder; + private Duration waitBetweenFetch; + + /** + * Instantiates the configuration + * + * @param fetcherName The name of the fetcher + * @throws NullPointerException If any parameter is null + */ + public FetcherConfigurationImpl(final String fetcherName) { + this.preferences = Preferences.userNodeForPackage(this.getClass()).node(fetcherName); + load(); + } + + private void load() { + this.rootFolder = Path.of(preferences.get(ROOT_FOLDER_KEY, DEFAULT_ROOT_FOLDER.toString())); + this.waitBetweenFetch = Duration.ofMillis(preferences.getLong(WAIT_BETWEEN_FETCH_KEY, DEFAULT_WAIT_BETWEEN_FETCH.toMillis())); + logger.info("Loaded configuration: rootFolder={}, waitBetweenFetch={}", rootFolder, waitBetweenFetch); + } + + @Override + public Path rootFolder() { + return rootFolder; + } + + @Override + public void setRootFolder(final Path rootFolder) { + logger.info("Setting root folder to {}", rootFolder); + this.rootFolder = requireNonNull(rootFolder); + preferences.put(ROOT_FOLDER_KEY, rootFolder.toString()); + } + + @Override + public Duration waitBetweenFetch() { + return waitBetweenFetch; + } + + @Override + public void setWaitBetweenFetch(final Duration waitBetweenFetch) { + logger.info("Setting wait between fetch to {}", waitBetweenFetch); + this.waitBetweenFetch = requireNonNull(waitBetweenFetch); + preferences.putLong(WAIT_BETWEEN_FETCH_KEY, waitBetweenFetch.toMillis()); + } +} diff --git a/core/src/main/java/ch/gtache/fro/impl/PracticeConfigurationImpl.java b/core/src/main/java/ch/gtache/fro/impl/PracticeConfigurationImpl.java new file mode 100644 index 0000000..ac723c9 --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/impl/PracticeConfigurationImpl.java @@ -0,0 +1,213 @@ +package ch.gtache.fro.impl; + +import ch.gtache.fro.Bird; +import ch.gtache.fro.BirdProvider; +import ch.gtache.fro.Fetcher; +import ch.gtache.fro.FetcherProvider; +import ch.gtache.fro.PictureType; +import ch.gtache.fro.ProvisionException; +import ch.gtache.fro.SoundType; +import ch.gtache.fro.practice.BirdPracticeParameters; +import ch.gtache.fro.practice.PracticeConfiguration; +import ch.gtache.fro.practice.QuestionType; +import ch.gtache.fro.practice.impl.BirdPracticeParametersImpl; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +/** + * Implementation of {@link PracticeConfiguration} + */ +public class PracticeConfigurationImpl implements PracticeConfiguration { + + private static final Logger logger = LogManager.getLogger(PracticeConfigurationImpl.class); + + private static final int DEFAULT_GUESS_NUMBER = 1; + + private static final String QUESTION_RATES_KEY = "questionRates"; + private static final String BIRD_PRACTICE_PARAMETERS_KEY = "birdPracticeParameters"; + private static final String GUESSES_NUMBER_KEY = "guessNumber"; + + private final BirdProvider birdProvider; + private final FetcherProvider fetcherProvider; + private final ObjectMapper objectMapper; + private final Preferences preferences; + private final Map birdPracticeParameters; + private final Map questionRates; + private int guessesNumber; + + @Inject + PracticeConfigurationImpl(final BirdProvider birdProvider, final FetcherProvider fetcherProvider, final ObjectMapper objectMapper) { + this.birdProvider = requireNonNull(birdProvider); + this.fetcherProvider = requireNonNull(fetcherProvider); + this.objectMapper = requireNonNull(objectMapper); + this.preferences = Preferences.userNodeForPackage(this.getClass()); + this.birdPracticeParameters = new HashMap<>(); + this.questionRates = new EnumMap<>(QuestionType.class); + this.guessesNumber = DEFAULT_GUESS_NUMBER; + load(); + } + + private void load() { + loadBirdPracticeParameters(); + loadQuestionRates(); + this.guessesNumber = preferences.getInt(GUESSES_NUMBER_KEY, DEFAULT_GUESS_NUMBER); + } + + private void loadQuestionRates() { + final var json = preferences.get(QUESTION_RATES_KEY, "{}"); + try { + final var deserializedQuestionRates = objectMapper.readValue(json, new QuestionRatesMapTypeReference()); + questionRates.putAll(deserializedQuestionRates); + } catch (final JsonProcessingException e) { + logger.error("Failed to deserialize question rates", e); + } + Arrays.stream(QuestionType.values()).forEach(qt -> { + if (!questionRates.containsKey(qt)) { + questionRates.put(qt, qt == QuestionType.PICTURE_EXACT ? 1.0 : 0.0); + } + }); + } + + private void loadBirdPracticeParameters() { + final var practiceParameters = preferences.get(BIRD_PRACTICE_PARAMETERS_KEY, "[]"); + try { + final var deserializedPracticeParameters = deserializeBirdPracticeParameters(objectMapper.readValue(practiceParameters, new SerializedBirdPracticeParametersTypeReference())); + birdPracticeParameters.putAll(deserializedPracticeParameters); + } catch (final JsonProcessingException e) { + logger.error("Failed to deserialize bird parameters", e); + } + try { + final var allBirds = birdProvider.getAllObjects(); + allBirds.forEach(b -> { + if (!birdPracticeParameters.containsKey(b)) { + birdPracticeParameters.put(b, getDefaultParameters(b)); + } + }); + } catch (final ProvisionException e) { + logger.error("Failed to get all birds", e); + } + } + + private Map deserializeBirdPracticeParameters(final List parameters) { + return parameters.stream().collect(Collectors.toMap(p -> { + try { + return birdProvider.getObject(p.bird()); + } catch (final ProvisionException e) { + logger.error("Failed to get bird {}", p.bird(), e); + return null; + } + }, this::deserializeBirdPracticeParameters)); + } + + private BirdPracticeParameters deserializeBirdPracticeParameters(final SerializedBirdPracticeParameters parameters) { + try { + final var bird = birdProvider.getObject(parameters.bird()); + return new BirdPracticeParametersImpl(bird, parameters.enabled(), parameters.enabledFetchers(), parameters.enabledPictureTypes(), parameters.enabledSoundTypes()); + } catch (final ProvisionException e) { + logger.error("Failed to get bird {}", parameters.bird(), e); + return null; + } + } + + private List serializeBirdPracticeParameters() { + return birdPracticeParameters.values().stream().map(PracticeConfigurationImpl::serializeBirdPracticeParameters).toList(); + } + + private static SerializedBirdPracticeParameters serializeBirdPracticeParameters(final BirdPracticeParameters parameters) { + return new SerializedBirdPracticeParameters(parameters.bird().name(), parameters.enabled(), parameters.enabledFetchers(), parameters.enabledPictureTypes(), parameters.enabledSoundTypes()); + } + + @Override + public int guessesNumber() { + return guessesNumber; + } + + @Override + public void setGuessesNumber(final int guessNumber) { + this.guessesNumber = guessNumber; + preferences.putInt(GUESSES_NUMBER_KEY, guessNumber); + } + + @Override + public Map questionRates() { + return new EnumMap<>(questionRates); + } + + @Override + public void setQuestionRates(final Map questionRates) { + this.questionRates.clear(); + this.questionRates.putAll(questionRates); + saveQuestionRates(); + } + + private void saveQuestionRates() { + final String json; + try { + json = objectMapper.writeValueAsString(questionRates); + preferences.put(QUESTION_RATES_KEY, json); + } catch (final JsonProcessingException e) { + logger.error("Failed to serialize question rates", e); + } + } + + @Override + public Collection birdPracticeParameters() { + return birdPracticeParameters.values(); + } + + @Override + public BirdPracticeParameters birdPracticeParameters(final Bird bird) { + return birdPracticeParameters.get(bird); + } + + @Override + public void setBirdPracticeParameters(final Collection birdPracticeParameters) { + this.birdPracticeParameters.clear(); + this.birdPracticeParameters.putAll(birdPracticeParameters.stream().collect(Collectors.toMap(BirdPracticeParameters::bird, Function.identity()))); + saveBirdPracticeParameters(); + } + + @Override + public void setBirdPracticeParameters(final BirdPracticeParameters birdPracticeParameters) { + this.birdPracticeParameters.put(birdPracticeParameters.bird(), birdPracticeParameters); + saveBirdPracticeParameters(); + } + + private void saveBirdPracticeParameters() { + final var parameters = serializeBirdPracticeParameters(); + try { + final var json = objectMapper.writeValueAsString(parameters); + preferences.put(BIRD_PRACTICE_PARAMETERS_KEY, json); + } catch (final JsonProcessingException e) { + logger.error("Failed to serialize bird parameters", e); + } + } + + private BirdPracticeParameters getDefaultParameters(final Bird bird) { + final var pictureTypes = Set.copyOf(Arrays.asList(PictureType.values())); + final var soundTypes = Set.copyOf(Arrays.asList(SoundType.values())); + try { + final var fetcherNames = fetcherProvider.getAllObjects().stream().map(Fetcher::name).collect(Collectors.toSet()); + return new BirdPracticeParametersImpl(bird, true, fetcherNames, pictureTypes, soundTypes); + } catch (final ProvisionException e) { + logger.error("Failed to get fetcher names", e); + return new BirdPracticeParametersImpl(bird, true, Set.of(), pictureTypes, soundTypes); + } + } +} diff --git a/core/src/main/java/ch/gtache/fro/impl/QuestionRatesMapTypeReference.java b/core/src/main/java/ch/gtache/fro/impl/QuestionRatesMapTypeReference.java new file mode 100644 index 0000000..940478c --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/impl/QuestionRatesMapTypeReference.java @@ -0,0 +1,9 @@ +package ch.gtache.fro.impl; + +import ch.gtache.fro.practice.QuestionType; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.Map; + +class QuestionRatesMapTypeReference extends TypeReference> { +} diff --git a/core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParameters.java b/core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParameters.java new file mode 100644 index 0000000..731d46a --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParameters.java @@ -0,0 +1,15 @@ +package ch.gtache.fro.impl; + +import ch.gtache.fro.PictureType; +import ch.gtache.fro.SoundType; + +import java.util.Set; + +record SerializedBirdPracticeParameters(String bird, boolean enabled, Set enabledFetchers, + Set enabledPictureTypes, + Set enabledSoundTypes) { + + SerializedBirdPracticeParameters(final String bird) { + this(bird, false, Set.of(), Set.of(), Set.of()); + } +} diff --git a/core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParametersTypeReference.java b/core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParametersTypeReference.java new file mode 100644 index 0000000..691d3af --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/impl/SerializedBirdPracticeParametersTypeReference.java @@ -0,0 +1,8 @@ +package ch.gtache.fro.impl; + +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.List; + +class SerializedBirdPracticeParametersTypeReference extends TypeReference> { +} diff --git a/core/src/main/java/ch/gtache/fro/modules/impl/CoreModule.java b/core/src/main/java/ch/gtache/fro/modules/impl/CoreModule.java index 9c1f4e9..9ab7c98 100644 --- a/core/src/main/java/ch/gtache/fro/modules/impl/CoreModule.java +++ b/core/src/main/java/ch/gtache/fro/modules/impl/CoreModule.java @@ -2,19 +2,21 @@ package ch.gtache.fro.modules.impl; import ch.gtache.fro.BirdProvider; import ch.gtache.fro.BirdTranslator; -import ch.gtache.fro.Configuration; import ch.gtache.fro.FetcherProvider; import ch.gtache.fro.PictureTypeTranslator; import ch.gtache.fro.SoundTypeTranslator; import ch.gtache.fro.impl.CommonBirdsProvider; import ch.gtache.fro.impl.CommonBirdsTranslatorImpl; -import ch.gtache.fro.impl.ConfigurationImpl; import ch.gtache.fro.impl.FetcherProviderImpl; import ch.gtache.fro.impl.PictureTypeTranslatorImpl; +import ch.gtache.fro.impl.PracticeConfigurationImpl; import ch.gtache.fro.impl.SoundTypeTranslatorImpl; import ch.gtache.fro.modules.practice.impl.CorePracticeModule; +import ch.gtache.fro.practice.PracticeConfiguration; +import com.fasterxml.jackson.databind.ObjectMapper; import dagger.Binds; import dagger.Module; +import dagger.Provides; /** * Dagger module for the core package @@ -30,7 +32,7 @@ public abstract class CoreModule { abstract BirdProvider bindsBirdProvider(final CommonBirdsProvider commonBirdsProvider); @Binds - abstract Configuration bindsConfiguration(final ConfigurationImpl configurationImpl); + abstract PracticeConfiguration bindsConfiguration(final PracticeConfigurationImpl configurationImpl); @Binds abstract FetcherProvider bindsFetcherProvider(final FetcherProviderImpl fetcherProviderImpl); @@ -43,4 +45,9 @@ public abstract class CoreModule { @Binds abstract SoundTypeTranslator bindsSoundTypeTranslator(final SoundTypeTranslatorImpl soundTypeTranslatorImpl); + + @Provides + static ObjectMapper providesObjectMapper() { + return new ObjectMapper(); + } } diff --git a/core/src/main/java/ch/gtache/fro/modules/practice/impl/CorePracticeModule.java b/core/src/main/java/ch/gtache/fro/modules/practice/impl/CorePracticeModule.java index 94120fe..6bc3d26 100644 --- a/core/src/main/java/ch/gtache/fro/modules/practice/impl/CorePracticeModule.java +++ b/core/src/main/java/ch/gtache/fro/modules/practice/impl/CorePracticeModule.java @@ -2,12 +2,15 @@ package ch.gtache.fro.modules.practice.impl; import ch.gtache.fro.practice.PracticeQuestionGenerator; import ch.gtache.fro.practice.PracticeRunner; -import ch.gtache.fro.practice.PracticeTypeTranslator; +import ch.gtache.fro.practice.QuestionTypeTranslator; import ch.gtache.fro.practice.impl.PracticeQuestionGeneratorImpl; import ch.gtache.fro.practice.impl.PracticeRunnerImpl; -import ch.gtache.fro.practice.impl.PracticeTypeTranslatorImpl; +import ch.gtache.fro.practice.impl.QuestionTypeTranslatorImpl; import dagger.Binds; import dagger.Module; +import dagger.Provides; + +import java.util.Random; /** * Dagger module for the core practice package @@ -26,5 +29,10 @@ public abstract class CorePracticeModule { abstract PracticeRunner bindsPracticeRunner(final PracticeRunnerImpl practiceRunnerImpl); @Binds - abstract PracticeTypeTranslator bindsPracticeTypeTranslator(final PracticeTypeTranslatorImpl practiceTypeTranslatorImpl); + abstract QuestionTypeTranslator bindsQuestionTypeTranslator(final QuestionTypeTranslatorImpl questionTypeTranslator); + + @Provides + static Random providesRandom() { + return new Random(); + } } diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/PictureMultichoicePracticeQuestionImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/PictureMultichoicePracticeQuestionImpl.java index 23838e6..55a9f51 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/PictureMultichoicePracticeQuestionImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/PictureMultichoicePracticeQuestionImpl.java @@ -3,6 +3,7 @@ package ch.gtache.fro.practice.impl; import ch.gtache.fro.Bird; import ch.gtache.fro.Picture; import ch.gtache.fro.practice.PictureMultichoicePracticeQuestion; +import ch.gtache.fro.practice.QuestionType; import java.util.List; @@ -28,4 +29,9 @@ public record PictureMultichoicePracticeQuestionImpl(List choices, choices = List.copyOf(choices); requireNonNull(picture); } + + @Override + public QuestionType questionType() { + return QuestionType.PICTURE_MULTICHOICE; + } } diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/PicturePracticeQuestionImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/PicturePracticeQuestionImpl.java index e966265..c6262c2 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/PicturePracticeQuestionImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/PicturePracticeQuestionImpl.java @@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl; import ch.gtache.fro.Picture; import ch.gtache.fro.practice.PicturePracticeQuestion; +import ch.gtache.fro.practice.QuestionType; import java.util.Objects; @@ -21,4 +22,9 @@ public record PicturePracticeQuestionImpl(Picture picture) implements PicturePra public PicturePracticeQuestionImpl { Objects.requireNonNull(picture); } + + @Override + public QuestionType questionType() { + return QuestionType.PICTURE_EXACT; + } } diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeParametersImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeParametersImpl.java index bff267c..47a9694 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeParametersImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeParametersImpl.java @@ -3,39 +3,44 @@ package ch.gtache.fro.practice.impl; import ch.gtache.fro.Bird; import ch.gtache.fro.practice.BirdPracticeParameters; import ch.gtache.fro.practice.PracticeParameters; -import ch.gtache.fro.practice.PracticeType; +import ch.gtache.fro.practice.QuestionType; import java.util.Collection; import java.util.Map; import java.util.stream.Collectors; -import static java.util.Objects.requireNonNull; - /** * Implementation of {@link PracticeParameters} * - * @param birdParameters The bird parameters - * @param practiceType The practice type - * @param questionsCount The number of questions - * @param propositionsCount The number of propositions + * @param birdParameters The bird parameters + * @param questionTypeRates The practice type rates + * @param guessesNumber The number of unsuccessful guesses before a question is considered as failed + * @param questionsNumber The number of questions + * @param propositionsNumber The number of propositions */ -public record PracticeParametersImpl(Map birdParameters, PracticeType practiceType, - int questionsCount, int propositionsCount) implements PracticeParameters { +public record PracticeParametersImpl(Map birdParameters, + Map questionTypeRates, + int guessesNumber, int questionsNumber, + int propositionsNumber) implements PracticeParameters { /** * Instantiates the parameters * - * @param birdParameters The bird parameters - * @param practiceType The practice type - * @param questionsCount The number of questions - * @param propositionsCount The number of propositions + * @param birdParameters The bird parameters + * @param questionTypeRates The practice type rates + * @param guessesNumber The number of unsuccessful guesses before a question is considered as failed + * @param questionsNumber The number of questions + * @param propositionsNumber The number of propositions * @throws NullPointerException If any parameter is null * @throws IllegalArgumentException If questionsCount is less than 1 */ public PracticeParametersImpl { birdParameters = Map.copyOf(birdParameters); - requireNonNull(practiceType); - if (questionsCount <= 0) { + questionTypeRates = Map.copyOf(questionTypeRates); + if (guessesNumber <= 0) { + throw new IllegalArgumentException("guessesNumber must be > 0"); + } + if (questionsNumber <= 0) { throw new IllegalArgumentException("questionsCount must be > 0"); } } @@ -43,17 +48,18 @@ public record PracticeParametersImpl(Map birdParam /** * Instantiates the parameters * - * @param birdParameters The bird parameters - * @param practiceType The practice type - * @param questionsCount The number of questions - * @param propositionsCount The number of propositions + * @param birdParameters The bird parameters + * @param questionTypeRates The practice type rates + * @param guessesNumber The number of unsuccessful guesses before a question is considered as failed + * @param questionsNumber The number of questions + * @param propositionsNumber The number of propositions * @throws NullPointerException If any parameter is null * @throws IllegalArgumentException If questionsCount is less than 1 */ public PracticeParametersImpl(final Collection birdParameters, - final PracticeType practiceType, final int questionsCount, - final int propositionsCount) { + final Map questionTypeRates, final int guessesNumber, + final int questionsNumber, final int propositionsNumber) { this(birdParameters.stream().collect(Collectors.toMap(BirdPracticeParameters::bird, e -> e)), - practiceType, questionsCount, propositionsCount); + questionTypeRates, guessesNumber, questionsNumber, propositionsNumber); } } diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeQuestionGeneratorImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeQuestionGeneratorImpl.java index ec29eaf..1388e68 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeQuestionGeneratorImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeQuestionGeneratorImpl.java @@ -10,6 +10,7 @@ import ch.gtache.fro.practice.BirdPracticeParameters; import ch.gtache.fro.practice.PracticeParameters; import ch.gtache.fro.practice.PracticeQuestion; import ch.gtache.fro.practice.PracticeQuestionGenerator; +import ch.gtache.fro.practice.QuestionType; import jakarta.inject.Inject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,6 +18,7 @@ import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.stream.Stream; @@ -48,15 +50,15 @@ public class PracticeQuestionGeneratorImpl implements PracticeQuestionGenerator public PracticeQuestion generate(final PracticeParameters parameters) { final var enabledBirds = parameters.birdParameters().values().stream() .filter(p -> p.enabled() && !p.enabledFetchers().isEmpty()).toList(); - final var practiceType = parameters.practiceType(); - return switch (practiceType) { + final var questionType = getQuestionType(parameters.questionTypeRates()); + return switch (questionType) { case PICTURE_EXACT -> { final var image = getPicture(enabledBirds); yield new PicturePracticeQuestionImpl(image); } case PICTURE_MULTICHOICE -> { final var image = getPicture(enabledBirds); - final var propositions = getPropositions(enabledBirds, parameters.propositionsCount()); + final var propositions = getPropositions(enabledBirds, parameters.propositionsNumber()); yield new PictureMultichoicePracticeQuestionImpl(propositions, image); } case SOUND_EXACT -> { @@ -65,13 +67,26 @@ public class PracticeQuestionGeneratorImpl implements PracticeQuestionGenerator } case SOUND_MULTICHOICE -> { final var sound = getSound(enabledBirds); - final var propositions = getPropositions(enabledBirds, parameters.propositionsCount()); + final var propositions = getPropositions(enabledBirds, parameters.propositionsNumber()); yield new SoundMultichoicePracticeQuestionImpl(propositions, sound); } case SOUND_MULTICHOICE_PICTURE -> throw new UnsupportedOperationException("Not implemented yet"); }; } + private QuestionType getQuestionType(final Map map) { + final var total = map.values().stream().mapToDouble(Double::doubleValue).sum(); + final var randomCount = random.nextDouble() * total; + var sum = 0.0; + for (final var entry : map.entrySet()) { + sum += entry.getValue(); + if (sum >= randomCount) { + return entry.getKey(); + } + } + throw new IllegalStateException("Should never happen"); + } + private Sound getSound(final Collection enabledBirds) { final var sounds = enabledBirds.stream().flatMap(p -> p.enabledFetchers().stream().flatMap(f -> { diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeRunnerImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeRunnerImpl.java index 46ce86f..94a9811 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeRunnerImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeRunnerImpl.java @@ -38,7 +38,7 @@ public class PracticeRunnerImpl implements PracticeRunner { @Override public PracticeRun step(final PracticeRun run, final Bird answer) { - if (run.currentQuestionIndex() < run.parameters().questionsCount()) { + if (run.currentQuestionIndex() < run.parameters().questionsNumber()) { final var question = questionGenerator.generate(run.parameters()); final var currentQuestion = run.currentQuestion(); if (currentQuestion.bird() == answer) { @@ -57,7 +57,7 @@ public class PracticeRunnerImpl implements PracticeRunner { @Override public PracticeResult finish(final PracticeRun run) { - if (run.currentQuestionIndex() == run.parameters().questionsCount()) { + if (run.currentQuestionIndex() == run.parameters().questionsNumber()) { return new PracticeResultImpl(run.correctQuestions(), run.failedQuestions()); } else { throw new IllegalStateException("The practice run " + run + " is not finished"); diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeTypeTranslatorImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/PracticeTypeTranslatorImpl.java deleted file mode 100644 index ac7f351..0000000 --- a/core/src/main/java/ch/gtache/fro/practice/impl/PracticeTypeTranslatorImpl.java +++ /dev/null @@ -1,25 +0,0 @@ -package ch.gtache.fro.practice.impl; - -import ch.gtache.fro.impl.AbstractBundleTranslator; -import ch.gtache.fro.practice.PracticeType; -import ch.gtache.fro.practice.PracticeTypeTranslator; -import jakarta.inject.Inject; - -import java.util.Locale; - -/** - * Implementation of {@link PracticeTypeTranslator} - */ -public class PracticeTypeTranslatorImpl extends AbstractBundleTranslator - implements PracticeTypeTranslator { - - @Inject - PracticeTypeTranslatorImpl() { - super("ch.gtache.fro.practice.impl.PracticeTypeBundle"); - } - - @Override - protected String getKey(final PracticeType object) { - return "practice.type." + object.name().toLowerCase(Locale.ROOT) + ".label"; - } -} diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/QuestionTypeTranslatorImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/QuestionTypeTranslatorImpl.java new file mode 100644 index 0000000..5c12c77 --- /dev/null +++ b/core/src/main/java/ch/gtache/fro/practice/impl/QuestionTypeTranslatorImpl.java @@ -0,0 +1,25 @@ +package ch.gtache.fro.practice.impl; + +import ch.gtache.fro.impl.AbstractBundleTranslator; +import ch.gtache.fro.practice.QuestionType; +import ch.gtache.fro.practice.QuestionTypeTranslator; +import jakarta.inject.Inject; + +import java.util.Locale; + +/** + * Implementation of {@link QuestionTypeTranslator} + */ +public class QuestionTypeTranslatorImpl extends AbstractBundleTranslator + implements QuestionTypeTranslator { + + @Inject + QuestionTypeTranslatorImpl() { + super("ch.gtache.fro.practice.impl.QuestionTypeBundle"); + } + + @Override + protected String getKey(final QuestionType object) { + return "question.type." + object.name().toLowerCase(Locale.ROOT) + ".label"; + } +} diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePicturePracticeQuestionImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePicturePracticeQuestionImpl.java index 0290037..64a32e3 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePicturePracticeQuestionImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePicturePracticeQuestionImpl.java @@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl; import ch.gtache.fro.Picture; import ch.gtache.fro.Sound; +import ch.gtache.fro.practice.QuestionType; import ch.gtache.fro.practice.SoundMultichoicePicturePracticeQuestion; import java.util.List; @@ -28,4 +29,9 @@ public record SoundMultichoicePicturePracticeQuestionImpl(List pictureC pictureChoices = List.copyOf(pictureChoices); requireNonNull(sound); } + + @Override + public QuestionType questionType() { + return QuestionType.SOUND_MULTICHOICE_PICTURE; + } } diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePracticeQuestionImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePracticeQuestionImpl.java index f8607f1..fb8548c 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePracticeQuestionImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/SoundMultichoicePracticeQuestionImpl.java @@ -2,6 +2,7 @@ package ch.gtache.fro.practice.impl; import ch.gtache.fro.Bird; import ch.gtache.fro.Sound; +import ch.gtache.fro.practice.QuestionType; import ch.gtache.fro.practice.SoundMultichoicePracticeQuestion; import java.util.List; @@ -28,4 +29,9 @@ public record SoundMultichoicePracticeQuestionImpl(List choices, choices = List.copyOf(choices); requireNonNull(sound); } + + @Override + public QuestionType questionType() { + return QuestionType.SOUND_MULTICHOICE; + } } diff --git a/core/src/main/java/ch/gtache/fro/practice/impl/SoundPracticeQuestionImpl.java b/core/src/main/java/ch/gtache/fro/practice/impl/SoundPracticeQuestionImpl.java index 3b749ad..f78710f 100644 --- a/core/src/main/java/ch/gtache/fro/practice/impl/SoundPracticeQuestionImpl.java +++ b/core/src/main/java/ch/gtache/fro/practice/impl/SoundPracticeQuestionImpl.java @@ -1,6 +1,7 @@ package ch.gtache.fro.practice.impl; import ch.gtache.fro.Sound; +import ch.gtache.fro.practice.QuestionType; import ch.gtache.fro.practice.SoundPracticeQuestion; import java.util.Objects; @@ -21,4 +22,9 @@ public record SoundPracticeQuestionImpl(Sound sound) implements SoundPracticeQue public SoundPracticeQuestionImpl { Objects.requireNonNull(sound); } + + @Override + public QuestionType questionType() { + return QuestionType.SOUND_EXACT; + } } diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 35f4190..a34e101 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -3,14 +3,18 @@ */ module ch.gtache.fro.core { requires transitive ch.gtache.fro.api; - requires java.prefs; + requires transitive com.fasterxml.jackson.core; + requires transitive dagger; requires org.apache.logging.log4j; - requires dagger; + requires java.prefs; requires jakarta.inject; + requires com.fasterxml.jackson.databind; exports ch.gtache.fro.impl; exports ch.gtache.fro.practice.impl; exports ch.gtache.fro.modules.impl; exports ch.gtache.fro.modules.practice.impl; + + opens ch.gtache.fro.impl to com.fasterxml.jackson.databind; } \ No newline at end of file diff --git a/core/src/main/resources/ch/gtache/fro/impl/BirdBundle.properties b/core/src/main/resources/ch/gtache/fro/impl/BirdBundle.properties new file mode 100644 index 0000000..93cabd5 --- /dev/null +++ b/core/src/main/resources/ch/gtache/fro/impl/BirdBundle.properties @@ -0,0 +1,428 @@ +bird.ACCENTEUR_ALPIN.label=Alpine Accentor +bird.ACCENTEUR_MOUCHET.label=Dunnock +bird.AGROBATE_ROUX.label=Rufous-tailed Scrub Robin +bird.AIGLE_BOTTE.label=Booted Eagle +bird.AIGLE_CRIARD.label=Greater Spotted Eagle +bird.AIGLE_POMARIN.label=Lesser Spotted Eagle +bird.AIGLE_ROYAL.label=Golden Eagle +bird.AIGRETTE_GARZETTE.label=Little Egret +bird.ALOUETTE_CALANDRE.label=Calandra Lark +bird.ALOUETTE_CALANDRELLE.label=Greater Short-toed Lark +bird.ALOUETTE_DES_CHAMPS.label=Eurasian Skylark +bird.ALOUETTE_HAUSSECOL.label=Horned Lark +bird.ALOUETTE_LEUCOPTERE.label=White-winged Lark +bird.ALOUETTE_LULU.label=Woodlark +bird.ALOUETTE_MONTICOLE.label=Bimaculated Lark +bird.ALOUETTE_PISPOLETTE.label=Mediterranean Short-toed Lark +bird.AUTOUR_DES_PALOMBES.label=Eurasian Goshawk +bird.AVOCETTE_ELEGANTE.label=Pied Avocet +bird.BALBUZARD_PECHEUR.label=Osprey +bird.BARGE_A_QUEUE_NOIRE.label=Black-tailed Godwit +bird.BARGE_ROUSSE.label=Bar-tailed Godwit +bird.BEC_CROISE_DES_SAPINS.label=Red Crossbill +bird.BECASSE_DES_BOIS.label=Eurasian Woodcock +bird.BECASSEAU_COCORLI.label=Curlew Sandpiper +bird.BECASSEAU_DE_BONAPARTE.label=White-rumped Sandpiper +bird.BECASSEAU_DE_TEMMINCK.label=Temminck's Stint +bird.BECASSEAU_FALCINELLE.label=Broad-billed Sandpiper +bird.BECASSEAU_MAUBECHE.label=Red Knot +bird.BECASSEAU_MINUTE.label=Little Stint +bird.BECASSEAU_ROUSSET.label=Buff-breasted Sandpiper +bird.BECASSEAU_SANDERLING.label=Sanderling +bird.BECASSEAU_TACHETE.label=Pectoral Sandpiper +bird.BECASSEAU_VARIABLE.label=Dunlin +bird.BECASSEAU_VIOLET.label=Purple Sandpiper +bird.BECASSINE_DES_MARAIS.label=Common Snipe +bird.BECASSINE_DOUBLE.label=Great Snipe +bird.BECASSINE_SOURDE.label=Jack Snipe +bird.BERGERONNETTE_CITRINE.label=Citrine Wagtail +bird.BERGERONNETTE_DES_RUISSEAUX.label=Grey Wagtail +bird.BERGERONNETTE_GRISE.label=White Wagtail +bird.BERGERONNETTE_PRINTANIERE.label=Western Yellow Wagtail +bird.BERNACHE_A_COU_ROUX.label=Red-breasted Goose +bird.BERNACHE_CRAVANT.label=Brant Goose +bird.BERNACHE_DU_CANADA.label=Canada Goose +bird.BERNACHE_NONNETTE.label=Barnacle Goose +bird.BIHOREAU_GRIS.label=Black-crowned Night Heron +bird.BLONGIOS_NAIN.label=Little Bittern +bird.BONDREE_APIVORE.label=European Honey Buzzard +bird.BOUSCARLE_DE_CETTI.label=Cetti's Warbler +bird.BOUVREUIL_PIVOINE.label=Eurasian Bullfinch +bird.BRUANT_A_CALOTTE_BLANCHE.label=Pine Bunting +bird.BRUANT_CHANTEUR.label=Song Sparrow +bird.BRUANT_DES_NEIGES.label=Snow Bunting +bird.BRUANT_DES_ROSEAUX.label=Common Reed Bunting +bird.BRUANT_FOU.label=Rock Bunting +bird.BRUANT_JAUNE.label=Yellowhammer +bird.BRUANT_LAPON.label=Lapland Longspur +bird.BRUANT_MELANOCEPHALE.label=Black-headed Bunting +bird.BRUANT_NAIN.label=Little Bunting +bird.BRUANT_ORTOLAN.label=Ortolan Bunting +bird.BRUANT_PROYER.label=Corn Bunting +bird.BRUANT_RUSTIQUE.label=Rustic Bunting +bird.BRUANT_ZIZI.label=Cirl Bunting +bird.BUSARD_CENDRE.label=Montagu's Harrier +bird.BUSARD_DES_ROSEAUX.label=Western Marsh Harrier +bird.BUSARD_PALE.label=Pallid Harrier +bird.BUSARD_SAINT_MARTIN.label=Hen Harrier +bird.BUSE_FEROCE.label=Long-legged Buzzard +bird.BUSE_PATTUE.label=Rough-legged Buzzard +bird.BUSE_VARIABLE.label=Common Buzzard +bird.BUTOR_ETOILE.label=Eurasian Bittern +bird.CAILLE_DES_BLES.label=Common Quail +bird.CANARD_CAROLIN.label=Wood Duck +bird.CANARD_CHIPEAU.label=Gadwall +bird.CANARD_COLVERT.label=Mallard +bird.CANARD_MANDARIN.label=Mandarin Duck +bird.CANARD_PILET.label=Northern Pintail +bird.CANARD_SIFFLEUR.label=Eurasian Wigeon +bird.CANARD_SOUCHET.label=Northern Shoveler +bird.CASSENOIX_MOUCHETE.label=Northern Nutcracker +bird.CHARDONNERET_ELEGANT.label=European Goldfinch +bird.CHEVALIER_ABOYEUR.label=Common Greenshank +bird.CHEVALIER_ARLEQUIN.label=Spotted Redshank +bird.CHEVALIER_BARGETTE.label=Terek Sandpiper +bird.CHEVALIER_CULBLANC.label=Green Sandpiper +bird.CHEVALIER_GAMBETTE.label=Common Redshank +bird.CHEVALIER_GRIVELE.label=Spotted Sandpiper +bird.CHEVALIER_GUIGNETTE.label=Common Sandpiper +bird.CHEVALIER_STAGNATILE.label=Marsh Sandpiper +bird.CHEVALIER_SYLVAIN.label=Wood Sandpiper +bird.CHEVECHE_D_ATHENA.label=Little Owl +bird.CHEVECHETTE_D_EUROPE.label=Eurasian Pygmy Owl +bird.CHOCARD_A_BEC_JAUNE.label=Alpine Chough +bird.CHOUCAS_DES_TOURS.label=Western Jackdaw +bird.CHOUETTE_DE_TENGMALM.label=Boreal Owl +bird.CHOUETTE_EPERVIERE.label=Northern Hawk-Owl +bird.CHOUETTE_HULOTTE.label=Tawny Owl +bird.CIGOGNE_BLANCHE.label=White Stork +bird.CIGOGNE_NOIRE.label=Black Stork +bird.CINCLE_PLONGEUR.label=White-throated Dipper +bird.CIRCAETE_JEAN_LE_BLANC.label=Short-toed Snake Eagle +bird.CISTICOLE_DES_JONCS.label=Zitting Cisticola +bird.COCHEVIS_HUPPE.label=Crested Lark +bird.COMBATTANT_VARIE.label=Ruff +bird.CORBEAU_FREUX.label=Rook +bird.CORMORAN_HUPPE.label=European Shag +bird.CORMORAN_PYGMEE.label=Pygmy Cormorant +bird.CORNEILLE_MANTELEE.label=Hooded Crow +bird.CORNEILLE_NOIRE.label=Carrion Crow +bird.COUCOU_GEAI.label=Great Spotted Cuckoo +bird.COUCOU_GRIS.label=Common Cuckoo +bird.COURLIS_A_BEC_GRELE.label=Slender-billed Curlew +bird.COURLIS_CENDRE.label=Eurasian Curlew +bird.COURLIS_CORLIEU.label=Eurasian Whimbrel +bird.COURVITE_ISABELLE.label=Cream-colored Courser +bird.CRABIER_CHEVELU.label=Squacco Heron +bird.CRAVE_A_BEC_ROUGE.label=Red-billed Chough +bird.CYGNE_CHANTEUR.label=Whooper Swan +bird.CYGNE_DE_BEWICK.label=Tundra Swan +bird.CYGNE_TUBERCULE.label=Mute Swan +bird.DURBEC_DES_SAPINS.label=Pine Grosbeak +bird.ECHASSE_BLANCHE.label=Black-winged Stilt +bird.EFFRAIE_DES_CLOCHERS.label=Western Barn Owl +bird.EIDER_A_DUVET.label=Common Eider +bird.ELANION_BLANC.label=Black-winged Kite +bird.ENGOULEVENT_D_EUROPE.label=European Nightjar +bird.EPERVIER_D_EUROPE.label=Eurasian Sparrowhawk +bird.ERISMATURE_A_TETE_BLANCHE.label=White-headed Duck +bird.ERISMATURE_ROUSSE.label=Ruddy Duck +bird.ETOURNEAU_ROSELIN.label=Rosy Starling +bird.ETOURNEAU_SANSONNET.label=Common Starling +bird.ETOURNEAU_UNICOLORE.label=Spotless Starling +bird.FAISAN_DE_COLCHIDE.label=Common Pheasant +bird.FAUCON_CRECERELLE.label=Common Kestrel +bird.FAUCON_CRECERELLETTE.label=Lesser Kestrel +bird.FAUCON_D_ELEONORE.label=Eleonora's Falcon +bird.FAUCON_EMERILLON.label=Merlin +bird.FAUCON_GERFAUT.label=Gyrfalcon +bird.FAUCON_HOBEREAU.label=Eurasian Hobby +bird.FAUCON_KOBEZ.label=Red-footed Falcon +bird.FAUCON_PELERIN.label=Peregrine Falcon +bird.FAUCON_SACRE.label=Saker Falcon +bird.FAUVETTE_A_LUNETTES.label=Spectacled Warbler +bird.FAUVETTE_A_TETE_NOIRE.label=Eurasian Blackcap +bird.FAUVETTE_BABILLARDE.label=Lesser Whitethroat +bird.FAUVETTE_DES_JARDINS.label=Garden Warbler +bird.FAUVETTE_EPERVIERE.label=Barred Warbler +bird.FAUVETTE_GRISETTE.label=Common Whitethroat +bird.FAUVETTE_MELANOCEPHALE.label=Sardinian Warbler +bird.FAUVETTE_ORPHEE.label=Western Orphean Warbler +bird.FAUVETTE_PASSERINETTE.label=Western Subalpine Warbler +bird.FAUVETTE_PITCHOU.label=Dartford Warbler +bird.FLAMANT_ROSE.label=Greater Flamingo +bird.FOU_DE_BASSAN.label=Northern Gannet +bird.FOULQUE_MACROULE.label=Eurasian Coot +bird.FULIGULE_A_BEC_CERCLE.label=Ring-necked Duck +bird.FULIGULE_A_TETE_NOIRE.label=Lesser Scaup +bird.FULIGULE_MILOUIN.label=Common Pochard +bird.FULIGULE_MILOUINAN.label=Greater Scaup +bird.FULIGULE_MORILLON.label=Tufted Duck +bird.FULIGULE_NYROCA.label=Ferruginous Duck +bird.GALLINULE_POULE_D_EAU.label=Common Moorhen +bird.GARROT_A_OEIL_D_OR.label=Common Goldeneye +bird.GEAI_DES_CHENES.label=Eurasian Jay +bird.GELINOTTE_DES_BOIS.label=Hazel Grouse +bird.GLAREOLE_A_AILES_NOIRES.label=Black-winged Pratincole +bird.GLAREOLE_A_COLLIER.label=Collared Pratincole +bird.GOBEMOUCHE_A_COLLIER.label=Collared Flycatcher +bird.GOBEMOUCHE_A_DEMI_COLLIER.label=Semicollared Flycatcher +bird.GOBEMOUCHE_GRIS.label=Spotted Flycatcher +bird.GOBEMOUCHE_NAIN.label=Red-breasted Flycatcher +bird.GOBEMOUCHE_NOIR.label=European Pied Flycatcher +bird.GOELAND_A_AILES_BLANCHES.label=Iceland Gull +bird.GOELAND_ARGENTE.label=European Herring Gull +bird.GOELAND_BOURGMESTRE.label=Glaucous Gull +bird.GOELAND_BRUN.label=Lesser Black-backed Gull +bird.GOELAND_CENDRE.label=Common Gull +bird.GOELAND_D_AUDOUIN.label=Audouin's Gull +bird.GOELAND_LEUCOPHEE.label=Yellow-legged Gull +bird.GOELAND_MARIN.label=Great Black-backed Gull +bird.GOELAND_PONTIQUE.label=Caspian Gull +bird.GOELAND_RAILLEUR.label=Slender-billed Gull +bird.GORGEBLEUE_A_MIROIR.label=Bluethroat +bird.GRAND_CORBEAU.label=Northern Raven +bird.GRAND_CORMORAN.label=Great Cormorant +bird.GRAND_GRAVELOT.label=Common Ringed Plover +bird.GRAND_LABBE.label=Great Skua +bird.GRAND_TETRAS.label=Western Capercaillie +bird.GRAND_DUC_D_EUROPE.label=Eurasian Eagle-Owl +bird.GRANDE_AIGRETTE.label=Great Egret +bird.GRAVELOT_A_COLLIER_INTERROMPU.label=Kentish Plover +bird.GRAVELOT_KILDIR.label=Killdeer +bird.GREBE_A_COU_NOIR.label=Black-necked Grebe +bird.GREBE_CASTAGNEUX.label=Little Grebe +bird.GREBE_ESCLAVON.label=Horned Grebe +bird.GREBE_HUPPE.label=Great Crested Grebe +bird.GREBE_JOUGRIS.label=Red-necked Grebe +bird.GRIMPEREAU_DES_BOIS.label=Eurasian Treecreeper +bird.GRIMPEREAU_DES_JARDINS.label=Short-toed Treecreeper +bird.GRIVE_DE_SIBERIE.label=Siberian Thrush +bird.GRIVE_DRAINE.label=Mistle Thrush +bird.GRIVE_LITORNE.label=Fieldfare +bird.GRIVE_MAUVIS.label=Redwing +bird.GRIVE_MUSICIENNE.label=Song Thrush +bird.GROSBEC_CASSE_NOYAUX.label=Hawfinch +bird.GRUE_CENDREE.label=Common Crane +bird.GUEPIER_D_EUROPE.label=European Bee-eater +bird.GUEPIER_DE_PERSE.label=Blue-cheeked Bee-eater +bird.GUIFETTE_LEUCOPTERE.label=White-winged Tern +bird.GUIFETTE_MOUSTAC.label=Whiskered Tern +bird.GUIFETTE_NOIRE.label=Black Tern +bird.GUILLEMOT_A_LONG_BEC.label=Long-billed Murrelet +bird.GUILLEMOT_DE_TROIL.label=Common Murre +bird.GYPAETE_BARBU.label=Bearded Vulture +bird.HARELDE_BOREALE.label=Long-tailed Duck +bird.HARLE_BIEVRE.label=Common Merganser +bird.HARLE_HUPPE.label=Red-breasted Merganser +bird.HARLE_PIETTE.label=Smew +bird.HERON_CENDRE.label=Grey Heron +bird.HERON_GARDE_BOEUFS.label=Western Cattle Egret +bird.HERON_POURPRE.label=Purple Heron +bird.HIBOU_DES_MARAIS.label=Short-eared Owl +bird.HIBOU_MOYEN_DUC.label=Long-eared Owl +bird.HIRONDELLE_DE_FENETRE.label=Western House Martin +bird.HIRONDELLE_DE_RIVAGE.label=Sand Martin +bird.HIRONDELLE_DE_ROCHERS.label=Eurasian Crag Martin +bird.HIRONDELLE_ROUSSELINE.label=European Red-rumped Swallow +bird.HIRONDELLE_RUSTIQUE.label=Barn Swallow +bird.HUITRIER_PIE.label=Eurasian Oystercatcher +bird.HUPPE_FASCIEE.label=Eurasian Hoopoe +bird.HYPOLAIS_BOTTEE.label=Booted Warbler +bird.HYPOLAIS_ICTERINE.label=Icterine Warbler +bird.HYPOLAIS_POLYGLOTTE.label=Melodious Warbler +bird.IBIS_CHAUVE.label=Northern Bald Ibis +bird.IBIS_FALCINELLE.label=Glossy Ibis +bird.IBIS_SACRE.label=African Sacred Ibis +bird.IRANIE_A_GORGE_BLANCHE.label=White-throated Robin +bird.JASEUR_BOREAL.label=Bohemian Waxwing +bird.LABBE_A_LONGUE_QUEUE.label=Long-tailed Jaeger +bird.LABBE_PARASITE.label=Parasitic Jaeger +bird.LABBE_POMARIN.label=Pomarine Jaeger +bird.LAGOPEDE_ALPIN.label=Rock Ptarmigan +bird.LINOTTE_A_BEC_JAUNE.label=Twite +bird.LINOTTE_MELODIEUSE.label=Common Linnet +bird.LOCUSTELLE_FLUVIATILE.label=River Warbler +bird.LOCUSTELLE_LUSCINIOIDE.label=Savi's Warbler +bird.LOCUSTELLE_TACHETEE.label=Common Grasshopper Warbler +bird.LORIOT_D_EUROPE.label=Eurasian Golden Oriole +bird.LUSCINIOLE_A_MOUSTACHES.label=Moustached Warbler +bird.MACREUSE_BRUNE.label=Velvet Scoter +bird.MACREUSE_NOIRE.label=Common Scoter +bird.MAROUETTE_DE_BAILLON.label=Baillon's Crake +bird.MAROUETTE_PONCTUEE.label=Spotted Crake +bird.MAROUETTE_POUSSIN.label=Little Crake +bird.MARTIN_PECHEUR_D_EUROPE.label=Common Kingfisher +bird.MARTINET_A_VENTRE_BLANC.label=Alpine Swift +bird.MARTINET_NOIR.label=Common Swift +bird.MARTINET_PALE.label=Pallid Swift +bird.MERLE_A_PLASTRON.label=Ring Ouzel +bird.MERLE_NOIR.label=Common Blackbird +bird.MESANGE_ALPESTRE.label=??? +bird.MESANGE_A_LONGUE_QUEUE.label=Long-tailed Tit +bird.MESANGE_BLEUE.label=Eurasian Blue Tit +bird.MESANGE_BOREALE.label=Willow Tit +bird.MESANGE_CHARBONNIERE.label=Great Tit +bird.MESANGE_DES_SAULES.label=??? +bird.MESANGE_HUPPEE.label=Crested Tit +bird.MESANGE_NOIRE.label=Coal Tit +bird.MESANGE_NONNETTE.label=Marsh Tit +bird.MILAN_NOIR.label=Black Kite +bird.MILAN_ROYAL.label=Red Kite +bird.MOINEAU_CISALPIN.label=Italian Sparrow +bird.MOINEAU_DOMESTIQUE.label=House Sparrow +bird.MOINEAU_ESPAGNOL.label=Spanish Sparrow +bird.MOINEAU_FRIQUET.label=Eurasian Tree Sparrow +bird.MOINEAU_SOULCIE.label=Rock Sparrow +bird.MONTICOLE_BLEU.label=Blue Rock Thrush +bird.MONTICOLE_DE_ROCHE.label=Common Rock Thrush +bird.MOUETTE_ATRICILLE.label=Laughing Gull +bird.MOUETTE_BLANCHE.label=Ivory Gull +bird.MOUETTE_DE_FRANKLIN.label=Franklin's Gull +bird.MOUETTE_DE_SABINE.label=Sabine's Gull +bird.MOUETTE_MELANOCEPHALE.label=Mediterranean Gull +bird.MOUETTE_PYGMEE.label=Little Gull +bird.MOUETTE_RIEUSE.label=Black-headed Gull +bird.MOUETTE_TRIDACTYLE.label=Black-legged Kittiwake +bird.NETTE_ROUSSE.label=Red-crested Pochard +bird.NIVEROLLE_ALPINE.label=White-winged Snowfinch +bird.OCEANITE_CULBLANC.label=Leach's Storm Petrel +bird.OCEANITE_DE_CASTRO.label=Band-rumped Storm Petrel +bird.OCEANITE_TEMPETE.label=European Storm Petrel +bird.OEDICNEME_CRIARD.label=Eurasian Stone-curlew +bird.OIE_A_BEC_COURT.label=Pink-footed Goose +bird.OIE_CENDREE.label=Greylag Goose +bird.OIE_DES_MOISSONS.label=Taiga Bean Goose +bird.OIE_DES_NEIGES.label=Snow Goose +bird.OIE_NAINE.label=Lesser White-fronted Goose +bird.OIE_RIEUSE.label=Greater White-fronted Goose +bird.OUETTE_D_EGYPTE.label=Egyptian Goose +bird.OUTARDE_BARBUE.label=Great Bustard +bird.OUTARDE_CANEPETIERE.label=Little Bustard +bird.OUTARDE_DE_MACQUEEN.label=Asian Houbara +bird.OUTARDE_HOUBARA.label=African Houbara +bird.PANURE_A_MOUSTACHES.label=Bearded Reedling +bird.PELICAN_BLANC.label=Great White Pelican +bird.PELICAN_FRISE.label=Dalmatian Pelican +bird.PELICAN_GRIS.label=Pink-backed Pelican +bird.PERDRIX_BARTAVELLE.label=Rock Partridge +bird.PERDRIX_GRISE.label=Grey Partridge +bird.PERDRIX_ROUGE.label=Red-legged Partridge +bird.PETIT_GRAVELOT.label=Little Ringed Plover +bird.PETIT_DUC_SCOPS.label=Eurasian Scops Owl +bird.PHALAROPE_A_BEC_ETROIT.label=Red-necked Phalarope +bird.PHALAROPE_A_BEC_LARGE.label=Red Phalarope +bird.PHRAGMITE_AQUATIQUE.label=Aquatic Warbler +bird.PHRAGMITE_DES_JONCS.label=Sedge Warbler +bird.PIC_A_DOS_BLANC.label=White-backed Woodpecker +bird.PIC_CENDRE.label=Grey-headed Woodpecker +bird.PIC_EPEICHE.label=Great Spotted Woodpecker +bird.PIC_EPEICHETTE.label=Lesser Spotted Woodpecker +bird.PIC_MAR.label=Middle Spotted Woodpecker +bird.PIC_NOIR.label=Black Woodpecker +bird.PIC_TRIDACTYLE.label=Eurasian Three-toed Woodpecker +bird.PIC_VERT.label=European Green Woodpecker +bird.PIE_BAVARDE.label=Eurasian Magpie +bird.PIE_GRIECHE_A_POITRINE_ROSE.label=Lesser Grey Shrike +bird.PIE_GRIECHE_A_TETE_ROUSSE.label=Woodchat Shrike +bird.PIE_GRIECHE_ECORCHEUR.label=Red-backed Shrike +bird.PIE_GRIECHE_GRISE.label=Great Grey Shrike +bird.PIE_GRIECHE_ISABELLE.label=Isabelline Shrike +bird.PIGEON_BISET_DOMESTIQUE.label=Rock Dove +bird.PIGEON_COLOMBIN.label=Stock Dove +bird.PIGEON_RAMIER.label=Common Wood Pigeon +bird.PINSON_DES_ARBRES.label=Eurasian Chaffinch +bird.PINSON_DU_NORD.label=Brambling +bird.PIPIT_A_DOS_OLIVE.label=Olive-backed Pipit +bird.PIPIT_A_GORGE_ROUSSE.label=Red-throated Pipit +bird.PIPIT_DE_RICHARD.label=Richard's Pipit +bird.PIPIT_DES_ARBRES.label=Tree Pipit +bird.PIPIT_FARLOUSE.label=Meadow Pipit +bird.PIPIT_ROUSSELINE.label=Tawny Pipit +bird.PIPIT_SPIONCELLE.label=Water Pipit +bird.PLONGEON_A_BEC_BLANC.label=Yellow-billed Loon +bird.PLONGEON_ARCTIQUE.label=Black-throated Loon +bird.PLONGEON_CATMARIN.label=Red-throated Loon +bird.PLONGEON_DU_PACIFIQUE.label=Pacific Loon +bird.PLONGEON_IMBRIN.label=Common Loon +bird.PLUVIER_ARGENTE.label=Grey Plover +bird.PLUVIER_DORE.label=European Golden Plover +bird.PLUVIER_FAUVE.label=Pacific Golden Plover +bird.PLUVIER_GUIGNARD.label=Eurasian Dotterel +bird.POUILLOT_A_GRANDS_SOURCILS.label=Yellow-browed Warbler +bird.POUILLOT_BRUN.label=Dusky Warbler +bird.POUILLOT_DE_BONELLI.label=Western Bonelli's Warbler +bird.POUILLOT_DE_HUME.label=Hume's Leaf Warbler +bird.POUILLOT_DE_PALLAS.label=Pallas's Leaf Warbler +bird.POUILLOT_DE_SCHWARZ.label=Radde's Warbler +bird.POUILLOT_DE_SIBERIE.label=Common Chiffchaff (tristis) +bird.POUILLOT_FITIS.label=Willow Warbler +bird.POUILLOT_IBERIQUE.label=Iberian Chiffchaff +bird.POUILLOT_SIFFLEUR.label=Wood Warbler +bird.POUILLOT_VELOCE.label=Common Chiffchaff +bird.POUILLOT_VERDATRE.label=Greenish Warbler +bird.PUFFIN_CENDRE.label=Cory's Shearwater +bird.PUFFIN_DE_SCOPOLI.label=Scopoli's Shearwater +bird.PUFFIN_DES_ANGLAIS.label=Manx Shearwater +bird.PUFFIN_FULIGINEUX.label=Sooty Shearwater +bird.PUFFIN_YELKOUAN.label=Yelkouan Shearwater +bird.PYGARGUE_A_QUEUE_BLANCHE.label=White-tailed Eagle +bird.RALE_D_EAU.label=Water Rail +bird.RALE_DES_GENETS.label=Corn Crake +bird.REMIZ_PENDULINE.label=Eurasian Penduline Tit +bird.ROITELET_A_TRIPLE_BANDEAU.label=Common Firecrest +bird.ROITELET_HUPPE.label=Goldcrest +bird.ROLLIER_D_EUROPE.label=European Roller +bird.ROSELIN_CRAMOISI.label=Common Rosefinch +bird.ROSELIN_GITHAGINE.label=Trumpeter Finch +bird.ROSSIGNOL_PHILOMELE.label=Common Nightingale +bird.ROSSIGNOL_PROGNE.label=Thrush Nightingale +bird.ROUGEGORGE_FAMILIER.label=European Robin +bird.ROUGEQUEUE_A_FRONT_BLANC.label=Common Redstart +bird.ROUGEQUEUE_NOIR.label=Black Redstart +bird.ROUSSEROLLE_DES_BUISSONS.label=Blyth's Reed Warbler +bird.ROUSSEROLLE_EFFARVATTE.label=Common Reed Warbler +bird.ROUSSEROLLE_ISABELLE.label=Paddyfield Warbler +bird.ROUSSEROLLE_TURDOIDE.label=Great Reed Warbler +bird.ROUSSEROLLE_VERDEROLLE.label=Marsh Warbler +bird.SARCELLE_A_AILES_BLEUES.label=Blue-winged Teal +bird.SARCELLE_D_ETE.label=Garganey +bird.SARCELLE_D_HIVER.label=Eurasian Teal +bird.SARCELLE_MARBREE.label=Marbled Duck +bird.SERIN_CINI.label=European Serin +bird.SITTELLE_TORCHEPOT.label=Eurasian Nuthatch +bird.SIZERIN_FLAMME.label=Redpoll +bird.SPATULE_BLANCHE.label=Eurasian Spoonbill +bird.STERNE_ARCTIQUE.label=Arctic Tern +bird.STERNE_CASPIENNE.label=Caspian Tern +bird.STERNE_CAUGEK.label=Sandwich Tern +bird.STERNE_DE_DOUGALL.label=Roseate Tern +bird.STERNE_HANSEL.label=Gull-billed Tern +bird.STERNE_NAINE.label=Little Tern +bird.STERNE_PIERREGARIN.label=Common Tern +bird.STERNE_VOYAGEUSE.label=Lesser Crested Tern +bird.SYRRHAPTE_PARADOXAL.label=Pallas's Sandgrouse +bird.TADORNE_CASARCA.label=Ruddy Shelduck +bird.TADORNE_DE_BELON.label=Common Shelduck +bird.TALEVE_SULTANE.label=Western Swamphen +bird.TALEVE_VIOLACEE.label=Purple Gallinule +bird.TARIER_DES_PRES.label=Whinchat +bird.TARIER_PATRE.label=European Stonechat +bird.TARIN_DES_AULNES.label=Eurasian Siskin +bird.TETRAS_LYRE.label=Black Grouse +bird.TICHODROME_ECHELETTE.label=Wallcreeper +bird.TORCOL_FOURMILIER.label=Eurasian Wryneck +bird.TOURNEPIERRE_A_COLLIER.label=Ruddy Turnstone +bird.TOURTERELLE_DES_BOIS.label=European Turtle Dove +bird.TOURTERELLE_TURQUE.label=Eurasian Collared Dove +bird.TRAQUET_DU_DESERT.label=Desert Wheatear +bird.TRAQUET_MOTTEUX.label=Northern Wheatear +bird.TRAQUET_OREILLARD.label=Western Black-eared Wheatear +bird.TROGLODYTE_MIGNON.label=Eurasian Wren +bird.VANNEAU_HUPPE.label=Northern Lapwing +bird.VANNEAU_SOCIABLE.label=Sociable Lapwing +bird.VAUTOUR_FAUVE.label=Griffon Vulture +bird.VAUTOUR_MOINE.label=Cinereous Vulture +bird.VAUTOUR_PERCNOPTERE.label=Egyptian Vulture +bird.VENTURON_MONTAGNARD.label=Citril Finch +bird.VERDIER_D_EUROPE.label=European Greenfinch diff --git a/core/src/main/resources/ch/gtache/fro/impl/BirdBundle_fr.properties b/core/src/main/resources/ch/gtache/fro/impl/BirdBundle_fr.properties index 7757d37..6c54362 100644 --- a/core/src/main/resources/ch/gtache/fro/impl/BirdBundle_fr.properties +++ b/core/src/main/resources/ch/gtache/fro/impl/BirdBundle_fr.properties @@ -424,3 +424,5 @@ bird.VAUTOUR_MOINE.label=Vautour moine bird.VAUTOUR_PERCNOPTERE.label=Percnoptère d'Égypte bird.VENTURON_MONTAGNARD.label=Venturon montagnard bird.VERDIER_D_EUROPE.label=Verdier d'Europe +bird.MESANGE_DES_SAULES.label=Mésange des saules +bird.MESANGE_ALPESTRE.label=Mésange alpestre diff --git a/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle.properties b/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle.properties deleted file mode 100644 index 58d3efe..0000000 --- a/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle.properties +++ /dev/null @@ -1,5 +0,0 @@ -practice.type.picture_exact.label=Image -practice.type.picture_multichoice.label=Image (multiple choice) -practice.type.sound_exact.label=Sound -practice.type.sound_multichoice.label=Sound (multiple choice) -practice.type.sound_multichoice_picture.label=Sound (multiple picture choice) diff --git a/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_en.properties b/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_en.properties deleted file mode 100644 index 4548c68..0000000 --- a/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_en.properties +++ /dev/null @@ -1,5 +0,0 @@ -practice.type.picture_exact.label=Image -practice.type.picture_multichoice.label=Image (multiple choice) -practice.type.sound_exact.label=Sound -practice.type.sound_multichoice.label=Sound (multiple choice) -practice.type.sound_multichoice_picture.label=Sound (multiple picture choice) \ No newline at end of file diff --git a/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_fr.properties b/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_fr.properties deleted file mode 100644 index 8204708..0000000 --- a/core/src/main/resources/ch/gtache/fro/practice/impl/PracticeTypeBundle_fr.properties +++ /dev/null @@ -1,5 +0,0 @@ -practice.type.picture_exact.label=Image -practice.type.picture_multichoice.label=Image (choix multiples) -practice.type.sound_exact.label=Son -practice.type.sound_multichoice.label=Son (choix multiples) -practice.type.sound_multichoice_picture.label=Son (choix multiple d'images) \ No newline at end of file diff --git a/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle.properties b/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle.properties new file mode 100644 index 0000000..d5d0871 --- /dev/null +++ b/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle.properties @@ -0,0 +1,5 @@ +question.type.picture_exact.label=Image +question.type.picture_multichoice.label=Image (multiple choice) +question.type.sound_exact.label=Sound +question.type.sound_multichoice.label=Sound (multiple choice) +question.type.sound_multichoice_picture.label=Sound (multiple picture choice) diff --git a/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_en.properties b/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_en.properties new file mode 100644 index 0000000..cfabdc0 --- /dev/null +++ b/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_en.properties @@ -0,0 +1,5 @@ +question.type.picture_exact.label=Image +question.type.picture_multichoice.label=Image (multiple choice) +question.type.sound_exact.label=Sound +question.type.sound_multichoice.label=Sound (multiple choice) +question.type.sound_multichoice_picture.label=Sound (multiple picture choice) \ No newline at end of file diff --git a/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_fr.properties b/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_fr.properties new file mode 100644 index 0000000..da3d8e2 --- /dev/null +++ b/core/src/main/resources/ch/gtache/fro/practice/impl/QuestionTypeBundle_fr.properties @@ -0,0 +1,5 @@ +question.type.picture_exact.label=Image +question.type.picture_multichoice.label=Image (choix multiples) +question.type.sound_exact.label=Son +question.type.sound_multichoice.label=Son (choix multiples) +question.type.sound_multichoice_picture.label=Son (choix multiple d'images) \ No newline at end of file diff --git a/gui/api/src/main/java/ch/gtache/fro/gui/SettingsController.java b/gui/api/src/main/java/ch/gtache/fro/gui/SettingsController.java index b3e156d..ba4a1ce 100644 --- a/gui/api/src/main/java/ch/gtache/fro/gui/SettingsController.java +++ b/gui/api/src/main/java/ch/gtache/fro/gui/SettingsController.java @@ -5,6 +5,16 @@ package ch.gtache.fro.gui; */ public interface SettingsController extends Controller { + /** + * Imports settings from a file + */ + void importSettings(); + + /** + * Exports settings to a file + */ + void exportSettings(); + @Override SettingsModel model(); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeExactModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeExactModel.java index 0100ee9..3f79e68 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeExactModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeExactModel.java @@ -1,11 +1,9 @@ package ch.gtache.fro.practice.gui; -import ch.gtache.fro.gui.Model; - /** * Model for the practice view - exact guess */ -public interface PracticeExactModel extends Model { +public interface PracticeExactModel extends PracticeQuestionModel { /** * Returns the user's current guess @@ -19,5 +17,5 @@ public interface PracticeExactModel extends Model { * * @param guess The guess */ - void guess(String guess); + void setGuess(String guess); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeGuessController.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeGuessController.java index 9183c14..44c367e 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeGuessController.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeGuessController.java @@ -11,4 +11,9 @@ public interface PracticeGuessController extends Controller { * Confirms the current guess */ void confirm(); + + /** + * Proceeds to the next question/show the result page + */ + void next(); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeModel.java index da5ed5b..7552420 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeModel.java @@ -1,11 +1,24 @@ package ch.gtache.fro.practice.gui; import ch.gtache.fro.gui.Model; +import ch.gtache.fro.practice.PracticeRun; /** * Model for the practice view */ public interface PracticeModel extends Model { + /** + * Returns the current practice run + * + * @return The practice run + */ + PracticeRun practiceRun(); + /** + * Sets the practice run + * + * @param practiceRun The practice run + */ + void setPracticeRun(PracticeRun practiceRun); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeMultichoiceModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeMultichoiceModel.java index f8f9602..44c56ac 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeMultichoiceModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeMultichoiceModel.java @@ -29,5 +29,5 @@ public interface PracticeMultichoiceModel extends Model { * * @param selected The selected bird */ - void selected(Bird selected); + void setSelected(Bird selected); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticePictureModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticePictureModel.java index c1cb7b8..2eca784 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticePictureModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticePictureModel.java @@ -1,12 +1,11 @@ package ch.gtache.fro.practice.gui; import ch.gtache.fro.Picture; -import ch.gtache.fro.gui.Model; /** * Represents a model for a practice view with a picture */ -public interface PracticePictureModel extends Model { +public interface PracticePictureModel extends PracticeQuestionModel { /** * Returns the picture to guess @@ -20,5 +19,5 @@ public interface PracticePictureModel extends Model { * * @param picture The picture */ - void picture(Picture picture); + void setPicture(Picture picture); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeQuestionModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeQuestionModel.java new file mode 100644 index 0000000..90c6e95 --- /dev/null +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeQuestionModel.java @@ -0,0 +1,35 @@ +package ch.gtache.fro.practice.gui; + +import ch.gtache.fro.Bird; +import ch.gtache.fro.gui.Model; + +import java.util.List; + +/** + * Model for the practice questions views + */ +public interface PracticeQuestionModel extends Model { + + /** + * Returns whether the view is currently showing the question result + * + * @return The result + */ + boolean isShowingResult(); + + /** + * Returns the list of guesses made + * + * @return The guesses + */ + List guesses(); + + /** + * Returns the number of guesses made + * + * @return The guess count + */ + default int guessesCount() { + return guesses().size(); + } +} diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultController.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultController.java index 01e10b7..8761f39 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultController.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultController.java @@ -7,6 +7,11 @@ import ch.gtache.fro.gui.Controller; */ public interface PracticeResultController extends Controller { + /** + * Closes the practice result view + */ + void confirm(); + @Override PracticeResultModel model(); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultModel.java index 598bb7d..1c56f6b 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeResultModel.java @@ -1,9 +1,52 @@ package ch.gtache.fro.practice.gui; import ch.gtache.fro.gui.Model; +import ch.gtache.fro.practice.PracticeResult; /** * Model for the practice result view */ public interface PracticeResultModel extends Model { + + /** + * Gets the practice result + * + * @return the practice result + */ + PracticeResult result(); + + /** + * Sets the practice result + * + * @param result the practice result + */ + void setResult(PracticeResult result); + + /** + * The text for the success label + * + * @return The text + */ + String successLabel(); + + /** + * The text for the failure label + * + * @return The text + */ + String failureLabel(); + + /** + * The text for the label of successful guesses + * + * @return The text + */ + String successListLabel(); + + /** + * The text for the label of failed guesses + * + * @return The text + */ + String failureListLabel(); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSettingsModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSettingsModel.java index 683502f..a26d592 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSettingsModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSettingsModel.java @@ -1,7 +1,8 @@ package ch.gtache.fro.practice.gui; import ch.gtache.fro.gui.Model; -import ch.gtache.fro.practice.PracticeType; +import ch.gtache.fro.practice.PracticeParameters; +import ch.gtache.fro.practice.QuestionType; import java.util.List; @@ -15,21 +16,49 @@ public interface PracticeSettingsModel extends Model { * * @return The list of practice types */ - List practiceTypes(); + List questionTypes(); /** * Returns the currently selected practice type * * @return The practice type */ - PracticeType practiceType(); + QuestionType questionType(); /** * Sets the practice type * - * @param practiceType The practice type + * @param questionType The practice type */ - void practiceType(PracticeType practiceType); + void setQuestionType(QuestionType questionType); + + /** + * Returns the number of unsuccessful guesses before a question is considered as failed + * + * @return The number of unsuccessful guesses + */ + int guessesNumber(); + + /** + * Sets the number of unsuccessful guesses before a question is considered as failed + * + * @param guessesNumber The number of unsuccessful guesses + */ + void setGuessesNumber(int guessesNumber); + + /** + * Returns the number of questions + * + * @return The number of questions + */ + int questionsNumber(); + + /** + * Sets the number of questions + * + * @param questionsNumber The number of questions + */ + void setQuestionsNumber(int questionsNumber); /** * Returns true if the practice type has suggestions @@ -50,5 +79,12 @@ public interface PracticeSettingsModel extends Model { * * @param suggestionsNumber The number of suggestions */ - void suggestionsNumber(int suggestionsNumber); + void setSuggestionsNumber(int suggestionsNumber); + + /** + * Returns the current practice parameters + * + * @return The practice parameters + */ + PracticeParameters practiceParameters(); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundModel.java index 7db4e8b..5b215c1 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundModel.java @@ -1,12 +1,11 @@ package ch.gtache.fro.practice.gui; import ch.gtache.fro.Sound; -import ch.gtache.fro.gui.Model; /** * Represents a model for a practice view with a sound */ -public interface PracticeSoundModel extends Model { +public interface PracticeSoundModel extends PracticeQuestionModel { /** * Returns the sound to guess @@ -20,5 +19,5 @@ public interface PracticeSoundModel extends Model { * * @param sound The sound */ - void sound(Sound sound); + void setSound(Sound sound); } diff --git a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundMultichoicePictureModel.java b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundMultichoicePictureModel.java index bda567b..013f9ce 100644 --- a/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundMultichoicePictureModel.java +++ b/gui/api/src/main/java/ch/gtache/fro/practice/gui/PracticeSoundMultichoicePictureModel.java @@ -28,5 +28,5 @@ public interface PracticeSoundMultichoicePictureModel extends PracticeSoundModel * * @param selected The selected picture */ - void selected(Picture selected); + void setSelected(Picture selected); } \ No newline at end of file diff --git a/gui/core/src/main/java/module-info.java b/gui/core/src/main/java/module-info.java index 7e9c759..1ba2cb7 100644 --- a/gui/core/src/main/java/module-info.java +++ b/gui/core/src/main/java/module-info.java @@ -3,9 +3,8 @@ */ module ch.gtache.fro.gui.core { requires transitive ch.gtache.fro.gui.api; - requires ch.gtache.fro.core; - requires jakarta.inject; - requires dagger; + requires transitive jakarta.inject; + requires transitive ch.gtache.fro.core; exports ch.gtache.fro.gui.impl; exports ch.gtache.fro.modules.gui.impl; diff --git a/gui/core/src/main/resources/ch/gtache/fro/gui/impl/FetchBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/gui/impl/FetchBundle_fr.properties index 8d5212b..10c02a1 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/gui/impl/FetchBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/gui/impl/FetchBundle_fr.properties @@ -1,3 +1,3 @@ -fetch.button.all.label=Télécharger tous -fetch.button.fetch.label=Télécharger +fetch.button.all.label=Télécharger tous +fetch.button.fetch.label=Télécharger fetch.providers.label=Sources \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/gui/impl/MainBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/gui/impl/MainBundle_fr.properties index 1357306..ab59704 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/gui/impl/MainBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/gui/impl/MainBundle_fr.properties @@ -1,3 +1,3 @@ -main.tab.fetch.label=Téléchargement -main.tab.practice.label=Entraînement -main.tab.settings.label=Paramètres \ No newline at end of file +main.tab.fetch.label=Téléchargement +main.tab.practice.label=Entraînement +main.tab.settings.label=Paramètres \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/gui/impl/SettingsBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/gui/impl/SettingsBundle_fr.properties index 4788c73..6971f19 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/gui/impl/SettingsBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/gui/impl/SettingsBundle_fr.properties @@ -1,5 +1,5 @@ settings.table.column.bird=Oiseau -settings.table.column.enabled=Activé +settings.table.column.enabled=Activé settings.table.column.fetchers=Fournisseurs settings.table.column.pictures=Images settings.table.column.sounds=Sons \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle.properties index c3c1edf..589a86b 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle.properties @@ -1 +1,2 @@ -practice.picture.exact.validate.button.label=Confirm \ No newline at end of file +practice.picture.exact.next.button.label=Next +practice.picture.exact.validate.button.label=Confirm diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_en.properties index c3c1edf..4b72cb0 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_en.properties @@ -1 +1,2 @@ +practice.picture.exact.next.button.label=Next practice.picture.exact.validate.button.label=Confirm \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_fr.properties index b7e28d8..ce1ff9f 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureExactBundle_fr.properties @@ -1 +1,2 @@ +practice.picture.exact.next.button.label=Suivant practice.picture.exact.validate.button.label=Confirmer \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle.properties index 330c7f9..aa8f246 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle.properties @@ -1 +1,2 @@ -practice.picture.multichoice.validate.button.label=Confirm \ No newline at end of file +practice.picture.multichoice.next.button.label=Next +practice.picture.multichoice.validate.button.label=Confirm diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_en.properties index 330c7f9..a048a10 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_en.properties @@ -1 +1,2 @@ +practice.picture.multichoice.next.button.label=Next practice.picture.multichoice.validate.button.label=Confirm \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_fr.properties index 8cf8396..bce96dc 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticePictureMultichoiceBundle_fr.properties @@ -1 +1,2 @@ +practice.picture.multichoice.next.button.label=Suivant practice.picture.multichoice.validate.button.label=Confirmer \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle.properties index 4be88ee..91540bb 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle.properties @@ -1,3 +1,4 @@ +practice.result.confirm.button.label=Confirm practice.result.failure.label=Failed guesses practice.result.label=Practice result -practice.result.success.label=Correct guesses \ No newline at end of file +practice.result.success.label=Correct guesses diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_en.properties index 4be88ee..ea72d16 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_en.properties @@ -1,3 +1,4 @@ +practice.result.confirm.button.label=Confirm practice.result.failure.label=Failed guesses practice.result.label=Practice result practice.result.success.label=Correct guesses \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_fr.properties index c610509..8dbf884 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeResultBundle_fr.properties @@ -1,3 +1,4 @@ -practice.result.failure.label=Choix incorrects +practice.result.confirm.button.label=Confirmer +practice.result.failure.label=Réponses incorrectes practice.result.label=Résultat -practice.result.success.label=Choix corrects \ No newline at end of file +practice.result.success.label=Réponses correctes \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle.properties index 4ac8cad..2cde4b9 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle.properties @@ -1,3 +1,5 @@ +practice.settings.guesses.number.label=Number of available guesses +practice.settings.questions.number.label=Number of questions practice.settings.start.button.label=Start practice.settings.suggestions.number.label=Number of suggestions -practice.settings.type.label=Practice type +practice.settings.type.label=Practice type \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_en.properties index 45fcfdf..2cde4b9 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_en.properties @@ -1,3 +1,5 @@ +practice.settings.guesses.number.label=Number of available guesses +practice.settings.questions.number.label=Number of questions practice.settings.start.button.label=Start practice.settings.suggestions.number.label=Number of suggestions practice.settings.type.label=Practice type \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_fr.properties index d871026..d3d7ca0 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSettingsBundle_fr.properties @@ -1,3 +1,5 @@ -practice.settings.start.button.label=Démarrer +practice.settings.guesses.number.label=Nombre d'essais disponibles +practice.settings.questions.number.label=Nombre de questions +practice.settings.start.button.label=Démarrer practice.settings.suggestions.number.label=Nombre de suggestions -practice.settings.type.label=Type d'entraînement \ No newline at end of file +practice.settings.type.label=Type d'entraînement \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle.properties index 34813eb..2c4fdd7 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle.properties @@ -1 +1,2 @@ -practice.sound.exact.validate.button.label=Confirm \ No newline at end of file +practice.sound.exact.next.button.label=Next +practice.sound.exact.validate.button.label=Confirm diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_en.properties index 34813eb..27d28a9 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_en.properties @@ -1 +1,2 @@ +practice.sound.exact.next.button.label=Next practice.sound.exact.validate.button.label=Confirm \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_fr.properties index bff1c0e..d68c237 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundExactBundle_fr.properties @@ -1 +1,2 @@ +practice.sound.exact.next.button.label=Suivant practice.sound.exact.validate.button.label=Confirmer \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle.properties index 42f8630..dfb6960 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle.properties @@ -1 +1,2 @@ -practice.sound.multichoice.validate.button.label=Confirm \ No newline at end of file +practice.sound.multichoice.next.button.label=Next +practice.sound.multichoice.validate.button.label=Confirm diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_en.properties index 42f8630..7f13970 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_en.properties @@ -1 +1,2 @@ +practice.sound.multichoice.next.button.label=Next practice.sound.multichoice.validate.button.label=Confirm \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_fr.properties index e5899b2..51917bf 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoiceBundle_fr.properties @@ -1 +1,2 @@ +practice.sound.multichoice.next.button.label=Suivant practice.sound.multichoice.validate.button.label=Confirmer \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle.properties index cd7846c..cc73682 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle.properties @@ -1 +1,2 @@ -practice.sound.multichoice.picture.validate.button.label=Confirm \ No newline at end of file +practice.sound.multichoice.picture.next.button.label=Next +practice.sound.multichoice.picture.validate.button.label=Confirm diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_en.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_en.properties index cd7846c..2c1b141 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_en.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_en.properties @@ -1 +1,2 @@ +practice.sound.multichoice.picture.next.button.label=Next practice.sound.multichoice.picture.validate.button.label=Confirm \ No newline at end of file diff --git a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_fr.properties b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_fr.properties index 745b89d..7d4abd6 100644 --- a/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_fr.properties +++ b/gui/core/src/main/resources/ch/gtache/fro/practice/gui/impl/PracticeSoundMultichoicePictureBundle_fr.properties @@ -1 +1,2 @@ +practice.sound.multichoice.picture.next.button.label=Suivant practice.sound.multichoice.picture.validate.button.label=Confirmer \ No newline at end of file diff --git a/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXFetchModel.java b/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXFetchModel.java index e877a67..30cfcc9 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXFetchModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXFetchModel.java @@ -1,8 +1,14 @@ package ch.gtache.fro.gui.fx; +import ch.gtache.fro.Bird; +import ch.gtache.fro.Fetcher; import ch.gtache.fro.gui.FetchModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; /** * FX implementation of {@link FetchModel} @@ -10,8 +16,16 @@ import jakarta.inject.Singleton; @Singleton public final class FXFetchModel implements FetchModel { + private final ObservableList birds; + private final ObjectProperty selectedBird; + private final ObservableList fetchers; + private final ObjectProperty selectedFetcher; + @Inject FXFetchModel() { - + this.birds = FXCollections.observableArrayList(); + this.selectedBird = new SimpleObjectProperty<>(); + this.fetchers = FXCollections.observableArrayList(); + this.selectedFetcher = new SimpleObjectProperty<>(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsController.java b/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsController.java index 1659ded..c159c27 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsController.java @@ -1,7 +1,7 @@ package ch.gtache.fro.gui.fx; -import ch.gtache.fro.Bird; import ch.gtache.fro.gui.SettingsController; +import ch.gtache.fro.practice.BirdPracticeParameters; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; @@ -16,15 +16,25 @@ import java.util.Objects; public final class FXSettingsController implements SettingsController { private final FXSettingsModel model; - + @FXML - private TableView table; + private TableView table; @Inject FXSettingsController(final FXSettingsModel model) { this.model = Objects.requireNonNull(model); } + @Override + public void importSettings() { + throw new UnsupportedOperationException(); + } + + @Override + public void exportSettings() { + throw new UnsupportedOperationException(); + } + @Override public FXSettingsModel model() { return model; diff --git a/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsModel.java b/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsModel.java index d27ba83..2393b8a 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/gui/fx/FXSettingsModel.java @@ -1,8 +1,11 @@ package ch.gtache.fro.gui.fx; import ch.gtache.fro.gui.SettingsModel; +import ch.gtache.fro.practice.BirdPracticeParameters; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; /** * FX implementation of {@link SettingsModel} @@ -10,7 +13,10 @@ import jakarta.inject.Singleton; @Singleton public final class FXSettingsModel implements SettingsModel { + private final ObservableList birdParameters; + @Inject FXSettingsModel() { + this.birdParameters = FXCollections.observableArrayList(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPracticeQuestionModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPracticeQuestionModel.java new file mode 100644 index 0000000..42046f1 --- /dev/null +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPracticeQuestionModel.java @@ -0,0 +1,56 @@ +package ch.gtache.fro.practice.gui.fx; + +import ch.gtache.fro.Bird; +import ch.gtache.fro.practice.PracticeRun; +import ch.gtache.fro.practice.gui.PracticeQuestionModel; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * Base FX model for practice question views + */ +public abstract class AbstractFXPracticeQuestionModel implements PracticeQuestionModel { + private final ObjectProperty practiceRun; + private final ReadOnlyStringWrapper progress; + private final BooleanProperty showingResult; + private final ObservableList guesses; + + /** + * Instantiates the model + */ + protected AbstractFXPracticeQuestionModel() { + this.practiceRun = new SimpleObjectProperty<>(); + this.progress = new ReadOnlyStringWrapper(); + this.showingResult = new SimpleBooleanProperty(false); + this.guesses = FXCollections.observableArrayList(); + this.progress.bind(practiceRun.map(r -> r.currentQuestionIndex() + 1 + "/" + r.parameters().questionsNumber())); + } + + ObjectProperty practiceRunProperty() { + return practiceRun; + } + + ReadOnlyStringProperty progressProperty() { + return progress.getReadOnlyProperty(); + } + + @Override + public boolean isShowingResult() { + return showingResult.get(); + } + + BooleanProperty showingResultProperty() { + return showingResult; + } + + @Override + public ObservableList guesses() { + return guesses; + } +} diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionExactModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionExactModel.java new file mode 100644 index 0000000..dcde6b6 --- /dev/null +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionExactModel.java @@ -0,0 +1,31 @@ +package ch.gtache.fro.practice.gui.fx; + +import ch.gtache.fro.practice.gui.PracticeExactModel; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/** + * Base FX model for practice question views with exact answer + */ +public abstract class AbstractFXPraticeQuestionExactModel extends AbstractFXPracticeQuestionModel implements PracticeExactModel { + + private final StringProperty guess; + + protected AbstractFXPraticeQuestionExactModel() { + this.guess = new SimpleStringProperty(); + } + + @Override + public String guess() { + return guess.get(); + } + + @Override + public void setGuess(String guess) { + this.guess.set(guess); + } + + StringProperty guessProperty() { + return guess; + } +} diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionMultichoiceModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionMultichoiceModel.java new file mode 100644 index 0000000..f119bc7 --- /dev/null +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/AbstractFXPraticeQuestionMultichoiceModel.java @@ -0,0 +1,44 @@ +package ch.gtache.fro.practice.gui.fx; + +import ch.gtache.fro.Bird; +import ch.gtache.fro.practice.gui.PracticeMultichoiceModel; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * Base FX model for practice question views with multichoice answers + */ +public abstract class AbstractFXPraticeQuestionMultichoiceModel extends AbstractFXPracticeQuestionModel implements PracticeMultichoiceModel { + + private final ObservableList suggestions; + private final ObjectProperty selected; + + /** + * Instantiates the model + */ + protected AbstractFXPraticeQuestionMultichoiceModel() { + this.suggestions = FXCollections.observableArrayList(); + this.selected = new SimpleObjectProperty<>(); + } + + @Override + public ObservableList suggestions() { + return suggestions; + } + + @Override + public Bird selected() { + return selected.get(); + } + + @Override + public void setSelected(final Bird selected) { + this.selected.set(selected); + } + + ObjectProperty selectedProperty() { + return selected; + } +} diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeBinder.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeBinder.java new file mode 100644 index 0000000..8cd5359 --- /dev/null +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeBinder.java @@ -0,0 +1,50 @@ +package ch.gtache.fro.practice.gui.fx; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import java.util.Objects; + +/** + * Binds the models. + */ +@Singleton +public class FXPracticeBinder { + + private final FXPracticeModel model; + private final FXPracticePictureExactModel pictureExactModel; + private final FXPracticePictureMultichoiceModel practicePictureMultichoiceModel; + private final FXPracticeSoundExactModel soundExactModel; + private final FXPracticeSoundMultichoiceModel soundMultichoiceModel; + private final FXPracticeSoundMultichoicePictureModel soundMultichoicePictureModel; + private final FXPracticeSettingsModel settingsModel; + private final FXPracticeResultModel resultModel; + + @Inject + FXPracticeBinder(final FXPracticeModel model, final FXPracticePictureExactModel pictureExactModel, + final FXPracticePictureMultichoiceModel practicePictureMultichoiceModel, + final FXPracticeSoundExactModel soundExactModel, + final FXPracticeSoundMultichoiceModel soundMultichoiceModel, + final FXPracticeSoundMultichoicePictureModel soundMultichoicePictureModel, + final FXPracticeSettingsModel settingsModel, + final FXPracticeResultModel resultModel) { + this.model = Objects.requireNonNull(model); + this.pictureExactModel = Objects.requireNonNull(pictureExactModel); + this.practicePictureMultichoiceModel = Objects.requireNonNull(practicePictureMultichoiceModel); + this.soundExactModel = Objects.requireNonNull(soundExactModel); + this.soundMultichoiceModel = Objects.requireNonNull(soundMultichoiceModel); + this.soundMultichoicePictureModel = Objects.requireNonNull(soundMultichoicePictureModel); + this.settingsModel = Objects.requireNonNull(settingsModel); + this.resultModel = Objects.requireNonNull(resultModel); + } + + void createBindings() { + model.practiceRunProperty().bindBidirectional(pictureExactModel.practiceRunProperty()); + model.practiceRunProperty().bindBidirectional(practicePictureMultichoiceModel.practiceRunProperty()); + model.practiceRunProperty().bindBidirectional(soundExactModel.practiceRunProperty()); + model.practiceRunProperty().bindBidirectional(soundMultichoiceModel.practiceRunProperty()); + model.practiceRunProperty().bindBidirectional(soundMultichoicePictureModel.practiceRunProperty()); + model.practiceRunProperty().bindBidirectional(settingsModel.practiceRunProperty()); + model.practiceResultProperty().bindBidirectional(resultModel.resultProperty()); + } +} diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeController.java index 3a44d12..1a74dcf 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeController.java @@ -1,12 +1,13 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.practice.PracticeRunner; import ch.gtache.fro.practice.gui.PracticeController; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; import javafx.scene.layout.Pane; -import java.util.Objects; +import static java.util.Objects.requireNonNull; /** * FX implementation of {@link PracticeController} @@ -30,10 +31,14 @@ public final class FXPracticeController implements PracticeController { private Pane practiceSoundMultichoicePicture; private final FXPracticeModel model; + private final FXPracticeBinder binder; + private final PracticeRunner runner; @Inject - FXPracticeController(final FXPracticeModel model) { - this.model = Objects.requireNonNull(model); + FXPracticeController(final FXPracticeModel model, final FXPracticeBinder binder, final PracticeRunner runner) { + this.model = requireNonNull(model); + this.binder = requireNonNull(binder); + this.runner = requireNonNull(runner); } @FXML @@ -47,6 +52,31 @@ public final class FXPracticeController implements PracticeController { } }); model.setSelectedPane(practiceSettings); + binder.createBindings(); + + model.practiceRunProperty().addListener((_, _, newValue) -> { + if (newValue == null) { + model.setSelectedPane(practiceSettings); + } else if (newValue.isFinished()) { + final var result = runner.finish(newValue); + model.practiceResultProperty().set(result); + model.setSelectedPane(practiceResult); + } else { + final var pane = switch (newValue.currentQuestion().questionType()) { + case PICTURE_EXACT -> practicePictureExact; + case PICTURE_MULTICHOICE -> practicePictureMultichoice; + case SOUND_EXACT -> practiceSoundExact; + case SOUND_MULTICHOICE -> practiceSoundMultichoice; + case SOUND_MULTICHOICE_PICTURE -> practiceSoundMultichoicePicture; + }; + model.setSelectedPane(pane); + } + }); + model.practiceResultProperty().addListener((_, _, newValue) -> { + if (newValue == null) { + model.setPracticeRun(null); + } + }); } @Override diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeModel.java index c5b0ed9..c8f80af 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeModel.java @@ -1,5 +1,7 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.practice.PracticeResult; +import ch.gtache.fro.practice.PracticeRun; import ch.gtache.fro.practice.gui.PracticeModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; @@ -17,11 +19,15 @@ public final class FXPracticeModel implements PracticeModel { private final ObservableList panes; private final ObjectProperty selectedPane; + private final ObjectProperty practiceRun; + private final ObjectProperty practiceResult; @Inject FXPracticeModel() { this.panes = FXCollections.observableArrayList(); this.selectedPane = new SimpleObjectProperty<>(); + this.practiceRun = new SimpleObjectProperty<>(); + this.practiceResult = new SimpleObjectProperty<>(); } ObservableList panes() { @@ -39,4 +45,22 @@ public final class FXPracticeModel implements PracticeModel { ObjectProperty selectedPaneProperty() { return selectedPane; } + + @Override + public PracticeRun practiceRun() { + return practiceRun.get(); + } + + @Override + public void setPracticeRun(final PracticeRun practiceRun) { + this.practiceRun.set(practiceRun); + } + + ObjectProperty practiceRunProperty() { + return practiceRun; + } + + ObjectProperty practiceResultProperty() { + return practiceResult; + } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactController.java index f0551bd..9df7914 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactController.java @@ -1,16 +1,25 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.Bird; +import ch.gtache.fro.BirdTranslator; import ch.gtache.fro.PictureType; +import ch.gtache.fro.practice.PracticeRunner; import ch.gtache.fro.practice.gui.PracticePictureExactController; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.controlsfx.control.PrefixSelectionComboBox; import org.controlsfx.control.textfield.CustomTextField; -import java.util.Objects; +import static java.util.Objects.requireNonNull; /** * FX implementation of {@link PracticePictureExactController} @@ -18,25 +27,84 @@ import java.util.Objects; @Singleton public final class FXPracticePictureExactController implements PracticePictureExactController { + private static final Logger logger = LogManager.getLogger(FXPracticePictureExactController.class); + private final FXPracticePictureExactModel model; + private final PracticeRunner runner; + private final UserInputNormalizer normalizer; + private final BirdTranslator translator; @FXML private ImageView pictureView; @FXML - private CustomTextField nameField; + private CustomTextField inputField; @FXML private PrefixSelectionComboBox typeCombobox; @FXML - private Button validateButton; + private Button confirmButton; + @FXML + private Label progressLabel; + @FXML + private VBox guessesBox; + @FXML + private Label answerLabel; @Inject - FXPracticePictureExactController(final FXPracticePictureExactModel model) { - this.model = Objects.requireNonNull(model); + FXPracticePictureExactController(final FXPracticePictureExactModel model, final PracticeRunner runner, + final UserInputNormalizer normalizer, final BirdTranslator translator) { + this.model = requireNonNull(model); + this.runner = requireNonNull(runner); + this.normalizer = requireNonNull(normalizer); + this.translator = requireNonNull(translator); + } + + @FXML + private void initialize() { + inputField.textProperty().bindBidirectional(model.guessProperty()); + inputField.disableProperty().bind(model.showingResultProperty()); + confirmButton.disableProperty().bind(model.guessProperty().isEmpty().and(model.showingResultProperty().not())); + answerLabel.visibleProperty().bind(model.showingResultProperty()); + answerLabel.textProperty().bind(model.pictureProperty().map(p -> translator.translateAsync(p.bird()).join())); + progressLabel.textProperty().bind(model.progressProperty()); + pictureView.imageProperty().bind(model.imageProperty()); + pictureView.fitHeightProperty().bind(model.imageProperty().map(Image::getHeight)); + pictureView.fitWidthProperty().bind(model.imageProperty().map(Image::getWidth)); + model.guesses().addListener((ListChangeListener) _ -> buildGuessesBox()); + } + + private void buildGuessesBox() { + final var labels = model.guesses().stream().map(this::getLabel).toList(); + guessesBox.getChildren().setAll(labels); + } + + private Label getLabel(final Bird bird) { + final String text; + final var birdName = translator.translateAsync(bird).join(); + if (bird.equals(model.picture().bird())) { + text = "✓ " + birdName; + } else { + text = "✗ " + birdName; + } + return new Label(text); } @Override public void confirm() { - throw new UnsupportedOperationException(); + final var bird = normalizer.getBird(model.guess()); + model.setGuess(""); + model.guesses().add(bird); + if (bird.equals(model.picture().bird()) || + model.guessesCount() >= model.practiceRunProperty().get().parameters().guessesNumber()) { + model.showingResultProperty().set(true); + } + } + + @Override + public void next() { + final var newRun = runner.step(model.practiceRunProperty().get(), model.guesses().getLast()); + model.guesses().clear(); + model.showingResultProperty().set(false); + model.practiceRunProperty().set(newRun); } @Override @@ -46,11 +114,19 @@ public final class FXPracticePictureExactController implements PracticePictureEx @FXML private void enterPressed() { - confirm(); + confirmOrNext(); } @FXML - private void validatePressed() { - confirm(); + private void confirmPressed() { + confirmOrNext(); + } + + private void confirmOrNext() { + if (model.isShowingResult()) { + next(); + } else { + confirm(); + } } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactModel.java index 8684d3e..232269d 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureExactModel.java @@ -1,47 +1,38 @@ package ch.gtache.fro.practice.gui.fx; import ch.gtache.fro.Picture; +import ch.gtache.fro.practice.PicturePracticeQuestion; import ch.gtache.fro.practice.gui.PracticePictureExactModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.scene.image.Image; + +import java.net.MalformedURLException; /** * FX implementation of {@link PracticePictureExactModel} */ @Singleton -public final class FXPracticePictureExactModel implements PracticePictureExactModel { +public final class FXPracticePictureExactModel extends AbstractFXPraticeQuestionExactModel implements PracticePictureExactModel { - private final StringProperty guess; - private final ObjectProperty picture; + private final ReadOnlyObjectWrapper picture; + private final ReadOnlyObjectWrapper image; @Inject FXPracticePictureExactModel() { - this.guess = new SimpleStringProperty(); - this.picture = new SimpleObjectProperty<>(); - } - - @Override - public String guess() { - return guess.get(); - } - - @Override - public void guess(final String guess) { - this.guess.set(guess); - } - - /** - * Returns the property for the user guess - * - * @return the property for the user guess - */ - StringProperty guessProperty() { - return guess; + this.picture = new ReadOnlyObjectWrapper<>(); + this.image = new ReadOnlyObjectWrapper<>(); + this.picture.bind(practiceRunProperty().map(r -> ((PicturePracticeQuestion) r.currentQuestion()).picture())); + this.image.bind(picture.map(p -> { + try { + return new Image(p.path().toUri().toURL().toString(), 800, 600, true, true); + } catch (final MalformedURLException e) { + throw new IllegalArgumentException(e); + } + })); } @Override @@ -50,16 +41,15 @@ public final class FXPracticePictureExactModel implements PracticePictureExactMo } @Override - public void picture(final Picture picture) { - this.picture.set(picture); + public void setPicture(final Picture picture) { + throw new IllegalArgumentException("Picture cannot be set"); } - /** - * Returns the property for the picture - * - * @return the property for the picture - */ - ObjectProperty pictureProperty() { - return picture; + ReadOnlyObjectProperty pictureProperty() { + return picture.getReadOnlyProperty(); + } + + ReadOnlyObjectProperty imageProperty() { + return image.getReadOnlyProperty(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceController.java index acc1556..cb6adfd 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceController.java @@ -1,13 +1,17 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.practice.PracticeRunner; import ch.gtache.fro.practice.gui.PracticePictureMultichoiceController; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; -import java.util.Objects; +import static java.util.Objects.requireNonNull; /** @@ -17,15 +21,30 @@ import java.util.Objects; public final class FXPracticePictureMultichoiceController implements PracticePictureMultichoiceController { private final FXPracticePictureMultichoiceModel model; + private final PracticeRunner runner; + @FXML + private ImageView pictureView; @FXML private GridPane grid; @FXML - private Button validateButton; + private Button confirmButton; + @FXML + private Label progressLabel; @Inject - FXPracticePictureMultichoiceController(final FXPracticePictureMultichoiceModel model) { - this.model = Objects.requireNonNull(model); + FXPracticePictureMultichoiceController(final FXPracticePictureMultichoiceModel model, final PracticeRunner runner) { + this.model = requireNonNull(model); + this.runner = requireNonNull(runner); + } + + @FXML + private void initialize() { + confirmButton.disableProperty().bind(model.selectedProperty().isNull()); + progressLabel.textProperty().bind(model.progressProperty()); + pictureView.imageProperty().bind(model.imageProperty()); + pictureView.fitHeightProperty().bind(model.imageProperty().map(Image::getHeight)); + pictureView.fitWidthProperty().bind(model.imageProperty().map(Image::getWidth)); } @Override @@ -35,11 +54,18 @@ public final class FXPracticePictureMultichoiceController implements PracticePic @Override public void confirm() { + final var newRun = runner.step(model.practiceRunProperty().get(), model.selected()); + model.setSelected(null); + model.practiceRunProperty().set(newRun); + } + + @Override + public void next() { throw new UnsupportedOperationException(); } @FXML - private void validatePressed() { + private void confirmPressed() { confirm(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceModel.java index 77d5b02..3bb19a3 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticePictureMultichoiceModel.java @@ -1,55 +1,38 @@ package ch.gtache.fro.practice.gui.fx; -import ch.gtache.fro.Bird; import ch.gtache.fro.Picture; +import ch.gtache.fro.practice.PicturePracticeQuestion; import ch.gtache.fro.practice.gui.PracticePictureMultichoiceModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.scene.image.Image; + +import java.net.MalformedURLException; /** * FX implementation of {@link PracticePictureMultichoiceModel} */ @Singleton -public final class FXPracticePictureMultichoiceModel implements PracticePictureMultichoiceModel { +public final class FXPracticePictureMultichoiceModel extends AbstractFXPraticeQuestionMultichoiceModel implements PracticePictureMultichoiceModel { - private final ObjectProperty picture; - private final ObservableList suggestions; - private final ObjectProperty selected; + private final ReadOnlyObjectWrapper picture; + private final ReadOnlyObjectWrapper image; @Inject FXPracticePictureMultichoiceModel() { - this.picture = new SimpleObjectProperty<>(); - this.suggestions = FXCollections.observableArrayList(); - this.selected = new SimpleObjectProperty<>(); - } - - @Override - public ObservableList suggestions() { - return suggestions; - } - - @Override - public Bird selected() { - return selected.get(); - } - - @Override - public void selected(final Bird selected) { - this.selected.set(selected); - } - - /** - * Returns the property for the selected bird - * - * @return the property for the selected bird - */ - ObjectProperty selectedProperty() { - return selected; + this.picture = new ReadOnlyObjectWrapper<>(); + this.image = new ReadOnlyObjectWrapper<>(); + this.picture.bind(practiceRunProperty().map(r -> ((PicturePracticeQuestion) r.currentQuestion()).picture())); + this.image.bind(picture.map(p -> { + try { + return new Image(p.path().toUri().toURL().toString(), 800, 600, true, true); + } catch (final MalformedURLException e) { + throw new IllegalArgumentException(e); + } + })); } @Override @@ -58,16 +41,15 @@ public final class FXPracticePictureMultichoiceModel implements PracticePictureM } @Override - public void picture(final Picture picture) { - this.picture.set(picture); + public void setPicture(final Picture picture) { + throw new IllegalArgumentException("Picture cannot be set"); } - /** - * Returns the property for the picture - * - * @return the property for the picture - */ - ObjectProperty pictureProperty() { - return picture; + ReadOnlyObjectProperty pictureProperty() { + return picture.getReadOnlyProperty(); + } + + ReadOnlyObjectProperty imageProperty() { + return image.getReadOnlyProperty(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultController.java index 72d930b..7bb2f66 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultController.java @@ -4,6 +4,7 @@ import ch.gtache.fro.practice.gui.PracticeResultController; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; +import javafx.scene.control.Button; import javafx.scene.control.Label; import java.util.Objects; @@ -23,12 +24,32 @@ public final class FXPracticeResultController implements PracticeResultControlle private Label successListLabel; @FXML private Label failureListLabel; + @FXML + private Button confirmButton; @Inject FXPracticeResultController(final FXPracticeResultModel model) { this.model = Objects.requireNonNull(model); } + @FXML + private void initialize() { + successNumberLabel.textProperty().bind(model.successLabelProperty()); + failureNumberLabel.textProperty().bind(model.failureLabelProperty()); + successListLabel.textProperty().bind(model.successListLabelProperty()); + failureListLabel.textProperty().bind(model.failureListLabelProperty()); + } + + @FXML + private void confirmPressed() { + confirm(); + } + + @Override + public void confirm() { + model.setResult(null); + } + @Override public FXPracticeResultModel model() { return model; diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultModel.java index 344c61c..e19527b 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeResultModel.java @@ -1,8 +1,16 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.BirdTranslator; +import ch.gtache.fro.practice.PracticeResult; import ch.gtache.fro.practice.gui.PracticeResultModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.stream.Collectors; /** * FX implementation of {@link PracticeResultModel} @@ -10,9 +18,72 @@ import jakarta.inject.Singleton; @Singleton public final class FXPracticeResultModel implements PracticeResultModel { + private final ObjectProperty practiceResult; + private final ReadOnlyStringWrapper successLabel; + private final ReadOnlyStringWrapper failureLabel; + private final ReadOnlyStringWrapper successListLabel; + private final ReadOnlyStringWrapper failureListLabel; + @Inject - FXPracticeResultModel() { - + FXPracticeResultModel(final BirdTranslator translator) { + this.practiceResult = new SimpleObjectProperty<>(); + this.successLabel = new ReadOnlyStringWrapper(); + this.failureLabel = new ReadOnlyStringWrapper(); + this.successListLabel = new ReadOnlyStringWrapper(); + this.failureListLabel = new ReadOnlyStringWrapper(); + successLabel.bind(resultProperty().map(r -> r.correctQuestionsCount() + "/" + r.totalQuestions())); + failureLabel.bind(resultProperty().map(r -> r.failedQuestionsCount() + "/" + r.totalQuestions())); + successListLabel.bind(resultProperty().map(r -> r.correctQuestions().stream().map(pq -> translator.translateAsync(pq.bird()).join()).collect(Collectors.joining(", ")))); + failureListLabel.bind(resultProperty().map(r -> r.failedQuestions().stream().map(pq -> translator.translateAsync(pq.bird()).join()).collect(Collectors.joining(", ")))); } + @Override + public PracticeResult result() { + return practiceResult.get(); + } + + @Override + public void setResult(final PracticeResult result) { + this.practiceResult.set(result); + } + + ObjectProperty resultProperty() { + return practiceResult; + } + + @Override + public String successLabel() { + return successLabel.get(); + } + + ReadOnlyStringProperty successLabelProperty() { + return successLabel.getReadOnlyProperty(); + } + + @Override + public String failureLabel() { + return failureLabel.get(); + } + + ReadOnlyStringProperty failureLabelProperty() { + return failureLabel.getReadOnlyProperty(); + } + + @Override + public String successListLabel() { + return successListLabel.get(); + } + + ReadOnlyStringProperty successListLabelProperty() { + return successListLabel.getReadOnlyProperty(); + } + + @Override + public String failureListLabel() { + return failureListLabel.get(); + } + + ReadOnlyStringProperty failureListLabelProperty() { + return failureListLabel.getReadOnlyProperty(); + } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsController.java index 80c9a12..b8b80ba 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsController.java @@ -1,9 +1,11 @@ package ch.gtache.fro.practice.gui.fx; -import ch.gtache.fro.practice.PracticeType; +import ch.gtache.fro.practice.PracticeRunner; +import ch.gtache.fro.practice.QuestionType; import ch.gtache.fro.practice.gui.PracticeSettingsController; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import javafx.beans.property.ObjectProperty; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.Spinner; @@ -19,34 +21,53 @@ import static java.util.Objects.requireNonNull; public final class FXPracticeSettingsController implements PracticeSettingsController { private final FXPracticeSettingsModel model; - private final PracticeTypeConverter practiceTypeConverter; + private final QuestionTypeConverter questionTypeConverter; + private final PracticeRunner runner; @FXML - private PrefixSelectionComboBox practiceTypeCombobox; + private PrefixSelectionComboBox questionTypeCombobox; + @FXML + private Spinner questionsNumberSpinner; @FXML private Label suggestionsNumberLabel; @FXML private Spinner suggestionsNumberSpinner; + @FXML + private Spinner guessesNumberSpinner; + + private final ObjectProperty guessesNumberProperty; + private final ObjectProperty questionsNumberProperty; + private final ObjectProperty suggestionsNumberProperty; @Inject - FXPracticeSettingsController(final FXPracticeSettingsModel model, final PracticeTypeConverter practiceTypeConverter) { + FXPracticeSettingsController(final FXPracticeSettingsModel model, final QuestionTypeConverter questionTypeConverter, + final PracticeRunner runner) { this.model = requireNonNull(model); - this.practiceTypeConverter = requireNonNull(practiceTypeConverter); + this.questionTypeConverter = requireNonNull(questionTypeConverter); + this.runner = requireNonNull(runner); + this.guessesNumberProperty = model.guessesNumberProperty().asObject(); + this.questionsNumberProperty = model.questionsNumberProperty().asObject(); + this.suggestionsNumberProperty = model.suggestionsNumberProperty().asObject(); } @FXML private void initialize() { - practiceTypeCombobox.setItems(model.practiceTypes()); - practiceTypeCombobox.valueProperty().bindBidirectional(model.practiceTypeProperty()); - practiceTypeCombobox.setConverter(practiceTypeConverter); + questionTypeCombobox.setItems(model.questionTypes()); + questionTypeCombobox.valueProperty().bindBidirectional(model.questionTypeProperty()); + questionTypeCombobox.setConverter(questionTypeConverter); + guessesNumberSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, model.guessesNumber())); + guessesNumberSpinner.getValueFactory().valueProperty().bindBidirectional(guessesNumberProperty); + questionsNumberSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, model.suggestionsNumber())); + questionsNumberSpinner.getValueFactory().valueProperty().bindBidirectional(questionsNumberProperty); suggestionsNumberLabel.visibleProperty().bind(model.hasSuggestionsProperty()); suggestionsNumberSpinner.visibleProperty().bind(model.hasSuggestionsProperty()); suggestionsNumberSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 8, model.suggestionsNumber())); - suggestionsNumberSpinner.getValueFactory().valueProperty().bindBidirectional(model.suggestionsNumberProperty().asObject()); + suggestionsNumberSpinner.getValueFactory().valueProperty().bindBidirectional(suggestionsNumberProperty); } @Override public void startPractice() { - throw new UnsupportedOperationException(); + final var run = runner.start(model.practiceParameters()); + model.practiceRunProperty().set(run); } @FXML diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsModel.java index e2bb484..679d621 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSettingsModel.java @@ -1,53 +1,88 @@ package ch.gtache.fro.practice.gui.fx; -import ch.gtache.fro.practice.PracticeType; +import ch.gtache.fro.practice.PracticeConfiguration; +import ch.gtache.fro.practice.PracticeParameters; +import ch.gtache.fro.practice.PracticeRun; +import ch.gtache.fro.practice.QuestionType; import ch.gtache.fro.practice.gui.PracticeSettingsModel; +import ch.gtache.fro.practice.impl.PracticeParametersImpl; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import java.util.Map; + /** * FX implementation of {@link PracticeSettingsModel} */ @Singleton public final class FXPracticeSettingsModel implements PracticeSettingsModel { + private static final int DEFAULT_QUESTIONS_NUMBER = 20; private static final int DEFAULT_SUGGESTIONS_NUMBER = 4; - private final ObservableList practiceTypes; - private final ObjectProperty practiceType; + private final ObservableList questionTypes; + private final ObjectProperty questionType; private final ReadOnlyBooleanWrapper hasSuggestions; + private final IntegerProperty guessesNumber; + private final IntegerProperty questionsNumber; private final IntegerProperty suggestionsNumber; + private final ReadOnlyObjectWrapper practiceParameters; + private final ObjectProperty practiceRun; @Inject - FXPracticeSettingsModel() { - this.practiceTypes = FXCollections.observableArrayList(PracticeType.values()); - this.practiceType = new SimpleObjectProperty<>(PracticeType.PICTURE_EXACT); + FXPracticeSettingsModel(final PracticeConfiguration configuration) { + this.questionTypes = FXCollections.observableArrayList(QuestionType.values()); + this.questionType = new SimpleObjectProperty<>(QuestionType.PICTURE_EXACT); this.hasSuggestions = new ReadOnlyBooleanWrapper(true); - hasSuggestions.bind(practiceType.isEqualTo(PracticeType.PICTURE_EXACT).or(practiceType.isEqualTo(PracticeType.SOUND_EXACT))); + this.guessesNumber = new SimpleIntegerProperty(configuration.guessesNumber()); + this.questionsNumber = new SimpleIntegerProperty(DEFAULT_QUESTIONS_NUMBER); this.suggestionsNumber = new SimpleIntegerProperty(DEFAULT_SUGGESTIONS_NUMBER); + this.practiceParameters = new ReadOnlyObjectWrapper<>(); + this.practiceRun = new SimpleObjectProperty<>(); + practiceParameters.bind(Bindings.createObjectBinding(() -> new PracticeParametersImpl(configuration.birdPracticeParameters(), Map.of(questionType(), 1.0), + guessesNumber(), questionsNumber(), suggestionsNumber()), questionType, guessesNumber, questionsNumber, suggestionsNumber)); + hasSuggestions.bind(questionType.isNotEqualTo(QuestionType.PICTURE_EXACT).and(questionType.isNotEqualTo(QuestionType.SOUND_EXACT))); + guessesNumber.addListener((_, _, newValue) -> configuration.setGuessesNumber(newValue.intValue())); } @Override - public ObservableList practiceTypes() { - return practiceTypes; + public ObservableList questionTypes() { + return questionTypes; } @Override - public PracticeType practiceType() { - return practiceType.get(); + public QuestionType questionType() { + return questionType.get(); } @Override - public void practiceType(final PracticeType practiceType) { - this.practiceType.set(practiceType); + public void setQuestionType(final QuestionType questionType) { + this.questionType.set(questionType); + } + + @Override + public int guessesNumber() { + return guessesNumber.get(); + } + + @Override + public void setGuessesNumber(final int guessesNumber) { + this.guessesNumber.set(guessesNumber); + } + + IntegerProperty guessesNumberProperty() { + return guessesNumber; } /** @@ -55,8 +90,8 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel { * * @return The practice type property */ - ObjectProperty practiceTypeProperty() { - return practiceType; + ObjectProperty questionTypeProperty() { + return questionType; } @Override @@ -73,16 +108,39 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel { return hasSuggestions.getReadOnlyProperty(); } + @Override + public int questionsNumber() { + return questionsNumber.get(); + } + + @Override + public void setQuestionsNumber(final int questionsNumber) { + this.questionsNumber.set(questionsNumber); + } + + IntegerProperty questionsNumberProperty() { + return questionsNumber; + } + @Override public int suggestionsNumber() { return suggestionsNumber.get(); } @Override - public void suggestionsNumber(final int suggestionsNumber) { + public void setSuggestionsNumber(final int suggestionsNumber) { this.suggestionsNumber.set(suggestionsNumber); } + @Override + public PracticeParameters practiceParameters() { + return practiceParameters.get(); + } + + ReadOnlyObjectProperty practiceParametersProperty() { + return practiceParameters.getReadOnlyProperty(); + } + /** * Returns the suggestions number property * @@ -91,4 +149,8 @@ public final class FXPracticeSettingsModel implements PracticeSettingsModel { IntegerProperty suggestionsNumberProperty() { return suggestionsNumber; } + + ObjectProperty practiceRunProperty() { + return practiceRun; + } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactController.java index 5ea2994..785955d 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactController.java @@ -1,16 +1,19 @@ package ch.gtache.fro.practice.gui.fx; import ch.gtache.fro.SoundType; +import ch.gtache.fro.practice.PracticeRunner; import ch.gtache.fro.practice.gui.PracticeSoundExactController; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; import javafx.scene.media.MediaView; import org.controlsfx.control.PrefixSelectionComboBox; -import java.util.Objects; +import static java.util.Objects.requireNonNull; /** @@ -20,19 +23,35 @@ import java.util.Objects; public final class FXPracticeSoundExactController implements PracticeSoundExactController { private final FXPracticeSoundExactModel model; + private final UserInputNormalizer normalizer; + private final PracticeRunner runner; @FXML - private Button validateButton; + private Button confirmButton; @FXML private TextField inputField; @FXML private MediaView mediaView; @FXML private PrefixSelectionComboBox typeCombobox; + @FXML + private Label progressLabel; + @FXML + private VBox guessesBox; @Inject - FXPracticeSoundExactController(final FXPracticeSoundExactModel model) { - this.model = Objects.requireNonNull(model); + FXPracticeSoundExactController(final FXPracticeSoundExactModel model, final PracticeRunner runner, + final UserInputNormalizer normalizer) { + this.model = requireNonNull(model); + this.normalizer = requireNonNull(normalizer); + this.runner = requireNonNull(runner); + } + + @FXML + private void initialize() { + inputField.textProperty().bindBidirectional(model.guessProperty()); + confirmButton.disableProperty().bind(model.guessProperty().isEmpty()); + progressLabel.textProperty().bind(model.progressProperty()); } @Override @@ -42,11 +61,19 @@ public final class FXPracticeSoundExactController implements PracticeSoundExactC @Override public void confirm() { + final var bird = normalizer.getBird(model.guess()); + model.setGuess(""); + final var newRun = runner.step(model.practiceRunProperty().get(), bird); + model.practiceRunProperty().set(newRun); + } + + @Override + public void next() { throw new UnsupportedOperationException(); } @FXML - private void validatePressed() { + private void confirmButton() { confirm(); } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactModel.java index 61b819d..4dc6d1e 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundExactModel.java @@ -1,47 +1,26 @@ package ch.gtache.fro.practice.gui.fx; import ch.gtache.fro.Sound; +import ch.gtache.fro.practice.SoundPracticeQuestion; import ch.gtache.fro.practice.gui.PracticeSoundExactModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; /** * FX implementation of {@link PracticeSoundExactModel} */ @Singleton -public final class FXPracticeSoundExactModel implements PracticeSoundExactModel { +public final class FXPracticeSoundExactModel extends AbstractFXPraticeQuestionExactModel implements PracticeSoundExactModel { - private final StringProperty guess; - private final ObjectProperty sound; + private final ReadOnlyObjectWrapper sound; @Inject FXPracticeSoundExactModel() { - this.guess = new SimpleStringProperty(); - this.sound = new SimpleObjectProperty<>(); - } - - @Override - public String guess() { - return guess.get(); - } - - @Override - public void guess(final String guess) { - this.guess.set(guess); - } - - /** - * Returns the property for the user guess - * - * @return the property for the user guess - */ - StringProperty guessProperty() { - return guess; + this.sound = new ReadOnlyObjectWrapper<>(); + this.sound.bind(practiceRunProperty().map(r -> ((SoundPracticeQuestion) r.currentQuestion()).sound())); } @Override @@ -50,16 +29,11 @@ public final class FXPracticeSoundExactModel implements PracticeSoundExactModel } @Override - public void sound(final Sound sound) { - this.sound.set(sound); + public void setSound(final Sound sound) { + throw new IllegalArgumentException("Sound cannot be set"); } - /** - * Returns the property for the sound - * - * @return the property for the sound - */ - ObjectProperty soundProperty() { - return sound; + ReadOnlyObjectProperty soundProperty() { + return sound.getReadOnlyProperty(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceController.java index 6d7d73d..3da3488 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceController.java @@ -1,14 +1,16 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.practice.PracticeRunner; import ch.gtache.fro.practice.gui.PracticeSoundMultichoiceController; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.media.MediaView; -import java.util.Objects; +import static java.util.Objects.requireNonNull; /** @@ -18,17 +20,27 @@ import java.util.Objects; public final class FXPracticeSoundMultichoiceController implements PracticeSoundMultichoiceController { private final FXPracticeSoundMultichoiceModel model; + private final PracticeRunner runner; @FXML private GridPane grid; @FXML private MediaView mediaView; @FXML - private Button validateButton; + private Button confirmButton; + @FXML + private Label progressLabel; @Inject - FXPracticeSoundMultichoiceController(final FXPracticeSoundMultichoiceModel model) { - this.model = Objects.requireNonNull(model); + FXPracticeSoundMultichoiceController(final FXPracticeSoundMultichoiceModel model, final PracticeRunner runner) { + this.model = requireNonNull(model); + this.runner = requireNonNull(runner); + } + + @FXML + private void initialize() { + confirmButton.disableProperty().bind(model.selectedProperty().isNull()); + progressLabel.textProperty().bind(model.progressProperty()); } @Override @@ -38,11 +50,18 @@ public final class FXPracticeSoundMultichoiceController implements PracticeSound @Override public void confirm() { + final var newRun = runner.step(model.practiceRunProperty().get(), model.selected()); + model.setSelected(null); + model.practiceRunProperty().set(newRun); + } + + @Override + public void next() { throw new UnsupportedOperationException(); } @FXML - private void validatePressed() { + private void confirmPressed() { confirm(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceModel.java index b4ab8a0..9e6f513 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoiceModel.java @@ -1,55 +1,26 @@ package ch.gtache.fro.practice.gui.fx; -import ch.gtache.fro.Bird; import ch.gtache.fro.Sound; +import ch.gtache.fro.practice.SoundPracticeQuestion; import ch.gtache.fro.practice.gui.PracticeSoundMultichoiceModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; /** * FX implementation of {@link PracticeSoundMultichoiceModel} */ @Singleton -public final class FXPracticeSoundMultichoiceModel implements PracticeSoundMultichoiceModel { +public final class FXPracticeSoundMultichoiceModel extends AbstractFXPraticeQuestionMultichoiceModel implements PracticeSoundMultichoiceModel { - private final ObservableList suggestions; - private final ObjectProperty selected; - private final ObjectProperty sound; + private final ReadOnlyObjectWrapper sound; @Inject FXPracticeSoundMultichoiceModel() { - this.suggestions = FXCollections.observableArrayList(); - this.selected = new SimpleObjectProperty<>(); - this.sound = new SimpleObjectProperty<>(); - } - - @Override - public ObservableList suggestions() { - return suggestions; - } - - @Override - public Bird selected() { - return selected.get(); - } - - @Override - public void selected(final Bird selected) { - this.selected.set(selected); - } - - /** - * Returns the property for the selected bird - * - * @return the property for the selected bird - */ - ObjectProperty selectedProperty() { - return selected; + this.sound = new ReadOnlyObjectWrapper<>(); + this.sound.bind(practiceRunProperty().map(r -> ((SoundPracticeQuestion) r.currentQuestion()).sound())); } @Override @@ -58,16 +29,11 @@ public final class FXPracticeSoundMultichoiceModel implements PracticeSoundMulti } @Override - public void sound(final Sound sound) { - this.sound.set(sound); + public void setSound(final Sound sound) { + throw new IllegalArgumentException("Sound cannot be set"); } - /** - * Returns the property for the sound - * - * @return the property for the sound - */ - ObjectProperty soundProperty() { - return sound; + ReadOnlyObjectProperty soundProperty() { + return sound.getReadOnlyProperty(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureController.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureController.java index 3944850..8e95989 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureController.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureController.java @@ -1,14 +1,16 @@ package ch.gtache.fro.practice.gui.fx; +import ch.gtache.fro.practice.PracticeRunner; import ch.gtache.fro.practice.gui.PracticeSoundMultichoicePictureController; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.fxml.FXML; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.media.MediaView; -import java.util.Objects; +import static java.util.Objects.requireNonNull; /** @@ -18,17 +20,28 @@ import java.util.Objects; public final class FXPracticeSoundMultichoicePictureController implements PracticeSoundMultichoicePictureController { private final FXPracticeSoundMultichoicePictureModel model; + private final PracticeRunner runner; @FXML private GridPane grid; @FXML private MediaView mediaView; @FXML - private Button validateButton; + private Button confirmButton; + @FXML + private Label progressLabel; @Inject - FXPracticeSoundMultichoicePictureController(final FXPracticeSoundMultichoicePictureModel model) { - this.model = Objects.requireNonNull(model); + FXPracticeSoundMultichoicePictureController(final FXPracticeSoundMultichoicePictureModel model, + final PracticeRunner runner) { + this.model = requireNonNull(model); + this.runner = requireNonNull(runner); + } + + @FXML + private void initialize() { + confirmButton.disableProperty().bind(model.selectedProperty().isNull()); + progressLabel.textProperty().bind(model.progressProperty()); } @Override @@ -38,11 +51,18 @@ public final class FXPracticeSoundMultichoicePictureController implements Practi @Override public void confirm() { + final var newRun = runner.step(model.practiceRunProperty().get(), model.selected().bird()); + model.setSelected(null); + model.practiceRunProperty().set(newRun); + } + + @Override + public void next() { throw new UnsupportedOperationException(); } @FXML - private void validatePressed() { + private void confirmPressed() { confirm(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureModel.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureModel.java index c1581f9..79c7a55 100644 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureModel.java +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/FXPracticeSoundMultichoicePictureModel.java @@ -2,10 +2,13 @@ package ch.gtache.fro.practice.gui.fx; import ch.gtache.fro.Picture; import ch.gtache.fro.Sound; +import ch.gtache.fro.practice.SoundPracticeQuestion; import ch.gtache.fro.practice.gui.PracticeSoundMultichoicePictureModel; import jakarta.inject.Inject; import jakarta.inject.Singleton; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -15,17 +18,18 @@ import javafx.collections.ObservableList; * FX implementation of {@link PracticeSoundMultichoicePictureModel} */ @Singleton -public final class FXPracticeSoundMultichoicePictureModel implements PracticeSoundMultichoicePictureModel { +public final class FXPracticeSoundMultichoicePictureModel extends AbstractFXPracticeQuestionModel implements PracticeSoundMultichoicePictureModel { private final ObservableList suggestions; private final ObjectProperty selected; - private final ObjectProperty sound; + private final ReadOnlyObjectWrapper sound; @Inject FXPracticeSoundMultichoicePictureModel() { this.suggestions = FXCollections.observableArrayList(); this.selected = new SimpleObjectProperty<>(); - this.sound = new SimpleObjectProperty<>(); + this.sound = new ReadOnlyObjectWrapper<>(); + this.sound.bind(practiceRunProperty().map(r -> ((SoundPracticeQuestion) r.currentQuestion()).sound())); } @Override @@ -39,15 +43,10 @@ public final class FXPracticeSoundMultichoicePictureModel implements PracticeSou } @Override - public void selected(final Picture selected) { + public void setSelected(final Picture selected) { this.selected.set(selected); } - /** - * Returns the property for the selected picture - * - * @return the property for the selected picture - */ ObjectProperty selectedProperty() { return selected; } @@ -58,16 +57,11 @@ public final class FXPracticeSoundMultichoicePictureModel implements PracticeSou } @Override - public void sound(final Sound sound) { - this.sound.set(sound); + public void setSound(final Sound sound) { + throw new IllegalArgumentException("Sound cannot be set"); } - /** - * Returns the property for the sound - * - * @return the property for the sound - */ - ObjectProperty soundProperty() { - return sound; + ReadOnlyObjectProperty soundProperty() { + return sound.getReadOnlyProperty(); } } diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/PracticeTypeConverter.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/PracticeTypeConverter.java deleted file mode 100644 index 657766e..0000000 --- a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/PracticeTypeConverter.java +++ /dev/null @@ -1,33 +0,0 @@ -package ch.gtache.fro.practice.gui.fx; - -import ch.gtache.fro.TranslationException; -import ch.gtache.fro.practice.PracticeType; -import ch.gtache.fro.practice.PracticeTypeTranslator; -import jakarta.inject.Inject; -import javafx.util.StringConverter; - -import java.util.Objects; - -public class PracticeTypeConverter extends StringConverter { - - private final PracticeTypeTranslator translator; - - @Inject - PracticeTypeConverter(final PracticeTypeTranslator translator) { - this.translator = Objects.requireNonNull(translator); - } - - @Override - public String toString(final PracticeType object) { - try { - return translator.translate(object); - } catch (final TranslationException e) { - return object.name(); - } - } - - @Override - public PracticeType fromString(final String string) { - return null; - } -} diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/QuestionTypeConverter.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/QuestionTypeConverter.java new file mode 100644 index 0000000..f6dc6f8 --- /dev/null +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/QuestionTypeConverter.java @@ -0,0 +1,33 @@ +package ch.gtache.fro.practice.gui.fx; + +import ch.gtache.fro.TranslationException; +import ch.gtache.fro.practice.QuestionType; +import ch.gtache.fro.practice.QuestionTypeTranslator; +import jakarta.inject.Inject; +import javafx.util.StringConverter; + +import java.util.Objects; + +public class QuestionTypeConverter extends StringConverter { + + private final QuestionTypeTranslator translator; + + @Inject + QuestionTypeConverter(final QuestionTypeTranslator translator) { + this.translator = Objects.requireNonNull(translator); + } + + @Override + public String toString(final QuestionType object) { + try { + return translator.translate(object); + } catch (final TranslationException _) { + return object.name(); + } + } + + @Override + public QuestionType fromString(final String string) { + return null; + } +} diff --git a/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/UserInputNormalizer.java b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/UserInputNormalizer.java new file mode 100644 index 0000000..c8646eb --- /dev/null +++ b/gui/fx/src/main/java/ch/gtache/fro/practice/gui/fx/UserInputNormalizer.java @@ -0,0 +1,51 @@ +package ch.gtache.fro.practice.gui.fx; + +import ch.gtache.fro.Bird; +import ch.gtache.fro.BirdProvider; +import ch.gtache.fro.BirdTranslator; +import ch.gtache.fro.ProvisionException; +import ch.gtache.fro.TranslationException; +import ch.gtache.fro.impl.BirdImpl; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import java.text.Normalizer; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Normalizes user input to match bird names + */ +@Singleton +public class UserInputNormalizer { + + private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}"); + + private final Map birdMap; + + @Inject + UserInputNormalizer(final BirdProvider provider, final BirdTranslator translator) { + this.birdMap = new HashMap<>(); + try { + for (final var object : provider.getAllObjects()) { + birdMap.put(normalize(object.name()), object); + birdMap.put(normalize(translator.translate(object)), object); + } + } catch (final ProvisionException e) { + throw new IllegalStateException("Error getting all birds", e); + } catch (final TranslationException e) { + throw new IllegalStateException("Error translating bird name", e); + } + } + + Bird getBird(final String input) { + final var normalized = normalize(input); + return birdMap.getOrDefault(normalized, new BirdImpl(input)); + } + + private static String normalize(final CharSequence input) { + return DIACRITICS_PATTERN.matcher(Normalizer.normalize(input, Normalizer.Form.NFKD)).replaceAll("").toUpperCase(Locale.ROOT); + } +} diff --git a/gui/fx/src/main/java/module-info.java b/gui/fx/src/main/java/module-info.java index 167e024..4c7fb99 100644 --- a/gui/fx/src/main/java/module-info.java +++ b/gui/fx/src/main/java/module-info.java @@ -3,17 +3,18 @@ */ module ch.gtache.fro.gui.fx { requires transitive ch.gtache.fro.gui.api; - requires ch.gtache.fro.gui.core; - requires dagger; - requires jakarta.inject; - requires javafx.fxml; + requires transitive ch.gtache.fro.gui.core; + requires transitive javafx.fxml; requires javafx.graphics; requires javafx.media; requires org.controlsfx.controls; + requires javafx.base; + requires ch.gtache.fro.api; + requires org.apache.logging.log4j; + requires javafx.controls; exports ch.gtache.fro.gui.fx; exports ch.gtache.fro.practice.gui.fx; - exports ch.gtache.fro.modules.gui.fx; exports ch.gtache.fro.modules.practice.gui.fx; diff --git a/gui/fx/src/main/resources/ch/gtache/fro/practice/gui/fx/practicePictureExactView.fxml b/gui/fx/src/main/resources/ch/gtache/fro/practice/gui/fx/practicePictureExactView.fxml index cbdc461..14376d0 100644 --- a/gui/fx/src/main/resources/ch/gtache/fro/practice/gui/fx/practicePictureExactView.fxml +++ b/gui/fx/src/main/resources/ch/gtache/fro/practice/gui/fx/practicePictureExactView.fxml @@ -2,32 +2,38 @@ + - + + + + - + + - - + + - - - + + + + + + - - - -