From 049268fd1d345e908cc75fabf03d8941141b31a9 Mon Sep 17 00:00:00 2001 From: Neomer Date: Sun, 14 Apr 2019 13:23:22 +0400 Subject: [PATCH] =?UTF-8?q?#31-state-pattern=20!=D0=97=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=D0=B8=20=D1=81=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/codeStyles/Project.xml | 29 -- .idea/dictionaries/Kir.xml | 7 + .idea/encodings.xml | 4 + .idea/misc.xml | 5 + app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 15 +- .../GameModeSelectionActivity.java | 50 +++ .../my/neomer/sixtyseconds/MainActivity.java | 403 ++---------------- .../java/my/neomer/sixtyseconds/MyApp.java | 2 + .../neomer/sixtyseconds/QuestionFragment.java | 29 +- .../QuestionFragmentViewModel.java | 72 ---- .../sixtyseconds/ads/AdMobAdProvider.java | 85 ++++ .../neomer/sixtyseconds/ads/IAdsProvider.java | 13 + .../ads/IRewardedAdResultListener.java | 11 + .../sixtyseconds/core/ForwardPipeline.java | 97 +++++ .../sixtyseconds/core/InfinitePipeline.java | 65 +++ .../my/neomer/sixtyseconds/core/Pipeline.java | 14 + .../neomer/sixtyseconds/dao/QuestionDTO.java | 10 +- .../gamemodes/BaseGameContext.java | 92 ++++ .../sixtyseconds/gamemodes/BaseGameMode.java | 89 ++++ .../sixtyseconds/gamemodes/IGameMode.java | 28 ++ .../gamemodes/SinglePlayerContext.java | 5 + .../gamemodes/SinglePlayerGameMode.java | 54 +++ .../SinglePlayerWithRatesContext.java | 14 + .../SinglePlayerWithRatesGameMode.java | 58 +++ .../sixtyseconds/gamemodes/StatePipeline.java | 2 + .../{ => helpers}/ActivityHelper.java | 11 +- .../{ => helpers}/AppMetricaHelper.java | 2 +- .../helpers/ApplicationResources.java | 95 +++++ .../helpers/DifficultyHelper.java | 32 ++ .../states/AdsDisplayingState.java | 78 ++++ .../states/AnswerReceivingState.java | 115 +++++ .../sixtyseconds/states/AnswerState.java | 95 +++++ .../states/BaseEscalationState.java | 92 ++++ .../neomer/sixtyseconds/states/BaseState.java | 53 +++ .../sixtyseconds/states/EscalationTimer.java | 81 ++++ .../sixtyseconds/states/GuessInputState.java | 108 +++++ .../my/neomer/sixtyseconds/states/IState.java | 40 ++ .../states/IStateFinishListener.java | 9 + .../states/QuestionReceivingState.java | 116 +++++ .../states/QuestionSettingsState.java | 83 ++++ .../sixtyseconds/states/QuestionState.java | 117 +++++ .../sixtyseconds/transport/AskMeAPI.java | 5 +- .../transport/FakeQuestionProvider.java | 15 +- .../transport/HttpQuestionProvider.java | 6 +- .../transport/IQuestionProvider.java | 2 +- .../layout/activity_game_mode_selection.xml | 70 +++ app/src/main/res/layout/activity_main.xml | 55 ++- app/src/main/res/values-ru-rRU/strings.xml | 6 + app/src/main/res/values/colors.xml | 4 +- app/src/main/res/values/strings.xml | 14 + build.gradle | 2 +- gradle.properties | 2 - gradle/wrapper/gradle-wrapper.properties | 4 +- 54 files changed, 2042 insertions(+), 537 deletions(-) delete mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/dictionaries/Kir.xml create mode 100644 .idea/encodings.xml create mode 100644 app/src/main/java/my/neomer/sixtyseconds/GameModeSelectionActivity.java delete mode 100644 app/src/main/java/my/neomer/sixtyseconds/QuestionFragmentViewModel.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/ads/AdMobAdProvider.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/ads/IAdsProvider.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/ads/IRewardedAdResultListener.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/core/ForwardPipeline.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/core/InfinitePipeline.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/core/Pipeline.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameContext.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameMode.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/IGameMode.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerContext.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerGameMode.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesContext.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesGameMode.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/gamemodes/StatePipeline.java rename app/src/main/java/my/neomer/sixtyseconds/{ => helpers}/ActivityHelper.java (56%) rename app/src/main/java/my/neomer/sixtyseconds/{ => helpers}/AppMetricaHelper.java (75%) create mode 100644 app/src/main/java/my/neomer/sixtyseconds/helpers/ApplicationResources.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/helpers/DifficultyHelper.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/AdsDisplayingState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/AnswerReceivingState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/AnswerState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/BaseEscalationState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/BaseState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/EscalationTimer.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/GuessInputState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/IState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/IStateFinishListener.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/QuestionReceivingState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/QuestionSettingsState.java create mode 100644 app/src/main/java/my/neomer/sixtyseconds/states/QuestionState.java create mode 100644 app/src/main/res/layout/activity_game_mode_selection.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 30aa626..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/dictionaries/Kir.xml b/.idea/dictionaries/Kir.xml new file mode 100644 index 0000000..2c9786a --- /dev/null +++ b/.idea/dictionaries/Kir.xml @@ -0,0 +1,7 @@ + + + + gamemode + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index af0bbdd..88fca0b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,6 +5,11 @@ + + + + + diff --git a/app/build.gradle b/app/build.gradle index 1282c07..fcaa9d2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,18 +21,22 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:support-v4:28.0.0' - implementation "com.android.support:customtabs:28.0.0" - implementation 'android.arch.lifecycle:extensions:1.1.1' + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:customtabs:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' - implementation 'com.google.android.gms:play-services-ads:17.2.0' implementation 'com.yandex.android:mobmetricalib:3.5.3' implementation 'com.android.installreferrer:installreferrer:1.0' - implementation 'com.android.support:cardview-v7:28.0.0' + implementation 'android.arch.lifecycle:extensions:1.1.1' + + // ButterKnife + implementation 'com.jakewharton:butterknife:8.8.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a0fad9..0c1b3de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -12,19 +13,21 @@ android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:ignore="UnusedAttribute"> + + + + + + - - - - - diff --git a/app/src/main/java/my/neomer/sixtyseconds/GameModeSelectionActivity.java b/app/src/main/java/my/neomer/sixtyseconds/GameModeSelectionActivity.java new file mode 100644 index 0000000..4f4502b --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/GameModeSelectionActivity.java @@ -0,0 +1,50 @@ +package my.neomer.sixtyseconds; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.widget.Button; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import my.neomer.sixtyseconds.gamemodes.IGameMode; +import my.neomer.sixtyseconds.gamemodes.SinglePlayerGameMode; +import my.neomer.sixtyseconds.gamemodes.SinglePlayerWithRatesGameMode; +import my.neomer.sixtyseconds.helpers.ApplicationResources; + +public class GameModeSelectionActivity extends AppCompatActivity { + + public static final String GAMEMODE_TAG = "GameModeTag"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_game_mode_selection); + + ApplicationResources.getInstance().setDebug(0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)); + + ApplicationResources.getInstance().loadSounds(this, null); + ButterKnife.bind(this); + } + + @OnClick(R.id.btnSinglePlayerWithRatesGameMode) + void SinglePlayerWithRatesGameMode() { + ApplicationResources.getInstance().playClickSound(); + runGame(new SinglePlayerWithRatesGameMode()); + } + + @OnClick(R.id.btnSinglePlayerGameMode) + void SinglePlayerGameMode() { + ApplicationResources.getInstance().playClickSound(); + runGame(new SinglePlayerGameMode()); + } + + private void runGame(IGameMode gameMode) { + Intent intent = new Intent(this, MainActivity.class); + intent.putExtra(GAMEMODE_TAG, gameMode); + startActivity(intent); + } + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/MainActivity.java b/app/src/main/java/my/neomer/sixtyseconds/MainActivity.java index 41a4fbc..2c994da 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/MainActivity.java +++ b/app/src/main/java/my/neomer/sixtyseconds/MainActivity.java @@ -1,29 +1,19 @@ package my.neomer.sixtyseconds; -import android.arch.lifecycle.Observer; -import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.content.SharedPreferences; -import android.media.AudioAttributes; -import android.media.SoundPool; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.Nullable; import android.support.constraint.ConstraintLayout; import android.support.constraint.ConstraintSet; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.CardView; import android.util.Log; -import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.widget.Button; -import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.TextView; import com.google.android.gms.ads.AdListener; @@ -35,66 +25,34 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; -import my.neomer.sixtyseconds.model.Answer; -import my.neomer.sixtyseconds.model.Question; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.gamemodes.IGameMode; +import my.neomer.sixtyseconds.helpers.AppMetricaHelper; +import my.neomer.sixtyseconds.helpers.ApplicationResources; import my.neomer.sixtyseconds.transport.TransportConfiguration; public class MainActivity - extends AppCompatActivity - implements ICountdownListener, View.OnClickListener, Observer, SoundPool.OnLoadCompleteListener { + extends AppCompatActivity { - private static final int MAX_STREAMS = 2; private static final String TAG = "MainActivity"; private static final int SKIP_ADS_COUNT = 5; // Сколько вопросов показывать без рекламы - private static final int GUESS_MAX_TIME = 20; // Configuration keys private static final String USER_UUID_KEY = "USER_UUID"; private static final String DIFFICULTY_KEY = "DIFFICULTY"; private static final String ADS_COUNTER_KEY = "ADS"; - private TextView txtCountdown; - private TextView txtGuess; - private Button btnStart, btnSendGuess; - private AsyncTask mainCountdown, guessCountdown; private ConstraintLayout voteLayout, mainLayout; - private ImageButton btnLike, btnDislike; private CardView cardGuess, cardCorrect; - private ProgressBar progressBarGuess; private ImageView imgCorrect; private TextView txtCorrect; - private QuestionFragmentViewModel mViewModel; - private QuestionFragment questionFragment; private InterstitialAd mInterstitialAd; private int ad_skip = 0; - private SoundPool soundPool; - private int timeIsUpSoundId; - private int clickSoundId; - private int countDownSoundId; - - @Override - public void onChanged(@Nullable Question question) { - state = GameState.Idle; - btnStart.setText(getResources().getString(R.string.start_countdown_text)); - txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.title_font_size)); - txtCountdown.setText(R.string.press_start_message); - btnStart.callOnClick(); - } - - @Override - public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { - - } + private IGameMode gameMode; private enum GameState { - Updating, - Idle, - Counting, - TypingGuess, - Result, DisplayAds } private GameState state; @@ -140,53 +98,31 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - loadSounds(); - loadAds(); - - txtCountdown = findViewById(R.id.txtTime); - - btnStart = findViewById(R.id.btnStart); - btnStart.setOnClickListener(this); - mainLayout = findViewById(R.id.mainLayout); voteLayout = findViewById(R.id.layoutVote); - btnLike = findViewById(R.id.btnLike); - btnLike.setOnClickListener(this); - - btnDislike = findViewById(R.id.btnDislike); - btnDislike.setOnClickListener(this); - - cardGuess = findViewById(R.id.cardGuess); - progressBarGuess = findViewById(R.id.progressBarGuess); - progressBarGuess.setMax(GUESS_MAX_TIME); - btnSendGuess = findViewById(R.id.btnSendGuess); - btnSendGuess.setOnClickListener(this); - txtGuess = findViewById(R.id.txtGuess); - cardCorrect = findViewById(R.id.cardCorrect); imgCorrect = findViewById(R.id.imgCorrect); txtCorrect = findViewById(R.id.txtCorrect); - mViewModel = ViewModelProviders.of(this).get(QuestionFragmentViewModel.class); - mViewModel.getQuestion().observe(this, this); - mViewModel.getAnswer().observe(this, new Observer() { - @Override - public void onChanged(@Nullable Answer answer) { - showCardCorrect(answer.isCorrect()); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - hideCardCorrect(); - } - }, 3000); - } - }); + loadGameMode(); + loadAds(); + loadPreferences(); + } - questionFragment = (QuestionFragment)getSupportFragmentManager().findFragmentById(R.id.fragment); + @Override + protected void onStart() { + super.onStart(); + gameMode.run(); + } - loadPreferences(); - updateQuestion(); + private void loadGameMode() { + gameMode = getIntent().getParcelableExtra(GameModeSelectionActivity.GAMEMODE_TAG); + if (gameMode == null) { + Log.e(TAG, "GameMode not set!"); + finish(); + } + gameMode.getGameContext().setActivity(this); } private void showCardCorrect(boolean correct) { @@ -208,35 +144,7 @@ private void hideCardCorrect() { * Подготовить систему отображения рекламы */ private void loadAds() { - MobileAds.initialize(this, "ca-app-pub-5078878060587689~8320307873"); - mInterstitialAd = new InterstitialAd(this); - mInterstitialAd.setAdUnitId("ca-app-pub-5078878060587689/9345738647"); - mInterstitialAd.loadAd(new AdRequest.Builder().build()); - mInterstitialAd.setAdListener(new AdListener() { - @Override - public void onAdClosed() { - mInterstitialAd.loadAd(new AdRequest.Builder().build()); - } - }); - } - - /** - * Подготовтиь библиотеку звуков - */ - private void loadSounds() { - soundPool = new SoundPool.Builder() - .setMaxStreams(MAX_STREAMS) - .setAudioAttributes( - new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .build()) - .build(); - soundPool.setOnLoadCompleteListener(this); - - timeIsUpSoundId = soundPool.load(this, R.raw.time_is_up, 1); - clickSoundId = soundPool.load(this, R.raw.click, 1); - countDownSoundId = soundPool.load(this, R.raw.countdown, 1); + ApplicationResources.getInstance().loadAds(this); } /** @@ -247,7 +155,7 @@ private void loadPreferences() { String user = pref.getString(USER_UUID_KEY, null); int difficulty = pref.getInt(DIFFICULTY_KEY, 5); - ad_skip = pref.getInt(ADS_COUNTER_KEY, 0); + gameMode.getGameContext().setAdsSkipped(pref.getInt(ADS_COUNTER_KEY, 0)); if (user == null) { user = UUID.randomUUID().toString(); @@ -255,174 +163,12 @@ private void loadPreferences() { ed.putString(USER_UUID_KEY, user); ed.apply(); } - mViewModel.getProvider().setConfiguration(new TransportConfiguration(user, difficulty)); - } - - //region States processing - - /** - * Получить новый вопрос от сервера - */ - private void updateQuestion() { - state = GameState.Updating; - btnStart.setText(R.string.wait_countdown_text); - txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.title_font_size)); - txtCountdown.setText(R.string.updating_message); - questionFragment.clear(); - mViewModel.update(); - } - - /** - * Обновить счетчик времени - * @param value Время - */ - @Override - public void updateTime(AsyncTask timer, int value) { - if (timer instanceof Countdown) { - txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.clock_font_size)); - txtCountdown.setText(String.valueOf(value)); - if (value == 5) { - soundPool.play(countDownSoundId, 1, 1, 0 ,0, 1); - } - } else if (timer instanceof GuessTypingCountdown) { - progressBarGuess.setProgress(value); - } - } - - /** - * Отсчет закончился. Время вышло. - */ - @Override - public void countFinish(AsyncTask timer) { - if (timer instanceof Countdown) { - displayGuessCard(); - txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.title_font_size)); - txtCountdown.setText(R.string.finish_message); - } else if (timer instanceof GuessTypingCountdown) { - displayAnswer(); - } - } - - /** - * Отсчет закончился. Нажали "Есть ответ". - */ - @Override - public void countCancel(AsyncTask timer) { - if (timer instanceof Countdown) { - displayGuessCard(); - soundPool.play(timeIsUpSoundId, 1, 1, 0, 0, 1); - txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, - getResources().getDimension(R.dimen.title_font_size)); - txtCountdown.setText(R.string.cancel_message); - } else if (timer instanceof GuessTypingCountdown) { - displayAnswer(); - } - } - - /** - * Отобразить окно ввода ответа - */ - private void displayGuessCard() { - state = GameState.TypingGuess; - btnStart.setEnabled(false); - cardGuess.setVisibility(View.VISIBLE); - guessCountdown = new GuessTypingCountdown(this); - guessCountdown.execute(); - } - - private void hideGuessCard() { - ActivityHelper.hideKeyboard(this); - btnStart.setEnabled(true); - cardGuess.setVisibility(View.INVISIBLE); - txtGuess.setText(""); + ApplicationResources.getInstance() + .getQuestionProvider() + .setConfiguration(new TransportConfiguration(user, difficulty)); } - /** - * Отобразить правильный ответ - */ - private void displayAnswer() { - String guess = txtGuess.getText().toString(); - hideGuessCard(); - showVoting(); - state = GameState.Result; - btnStart.setText(getResources().getString(R.string.next_question_message)); - questionFragment.displayAnswer(guess); - } - - /** - * Начать отсчет времени - */ - private void startTimer() { - state = GameState.Counting; - btnStart.setText(getResources().getString(R.string.cancel_countdown_text)); - mainCountdown = new Countdown(this); - mainCountdown.execute(); - } - - /** - * Отобразить рекламу - */ - private void displayAds() { - hideGuessCard(); - hideCardCorrect(); - voteLayout.setVisibility(View.INVISIBLE); - state = GameState.DisplayAds; - if (mInterstitialAd.isLoaded() && ++ad_skip >= SKIP_ADS_COUNT) { - ad_skip = 0; - mInterstitialAd.show(); - } else { - Log.d(TAG, "Ad is not ready!"); - updateQuestion(); - } - } - - //endregion - - @Override - public void onClick(View v) { - soundPool.play(clickSoundId, 1, 1,0,0,1); - if (v == btnStart) { - - switch (state) { - case Updating: - break; - - case Idle: - if (mViewModel != null && mViewModel.hasValue()) { - startTimer(); - } - break; - - case Counting: - mainCountdown.cancel(true); - break; - - case TypingGuess: - break; - - case Result: - displayAds(); - break; - - case DisplayAds: - updateQuestion(); - break; - } - } else if (v == btnLike) { - mViewModel.like(); - hideVoting(); - } else if (v == btnDislike) { - mViewModel.dislike(); - hideVoting(); - } else if (v == btnSendGuess) { - guessCountdown.cancel(true); - } - } - - @Override + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_privacypolicy: @@ -460,97 +206,4 @@ private void showVoting() { constraintSet.connect(R.id.fragment, ConstraintSet.BOTTOM, R.id.layoutVote, ConstraintSet.TOP,0); constraintSet.applyTo(mainLayout); } - - - private static class Countdown extends AsyncTask { - - private ICountdownListener updateListener; - - Countdown(ICountdownListener updateListener) { - this.updateListener = updateListener; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected Void doInBackground(Void... voids) { - for (int time = 60; time > 0; --time) { - try { - publishProgress(time); - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - return null; - } - } - return null; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - updateListener.updateTime(this, values[0]); - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - updateListener.countFinish(this); - } - - @Override - protected void onCancelled() { - super.onCancelled(); - updateListener.countCancel(this); - } - } - - private static class GuessTypingCountdown extends AsyncTask - { - - private final ICountdownListener updateListener; - - GuessTypingCountdown(ICountdownListener updateListener) { - this.updateListener = updateListener; - } - - @Override - protected Void doInBackground(Void... voids) { - for (int time = GUESS_MAX_TIME; time > 0; --time) { - try { - publishProgress(time); - TimeUnit.SECONDS.sleep(1); - } catch (InterruptedException e) { - return null; - } - } - return null; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - this.updateListener.countFinish(this); - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - this.updateListener.updateTime(this, values[0]); - } - - @Override - protected void onCancelled() { - super.onCancelled(); - updateListener.countCancel(this); - } - } - } diff --git a/app/src/main/java/my/neomer/sixtyseconds/MyApp.java b/app/src/main/java/my/neomer/sixtyseconds/MyApp.java index ec10aa0..d54eff7 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/MyApp.java +++ b/app/src/main/java/my/neomer/sixtyseconds/MyApp.java @@ -5,6 +5,8 @@ import com.yandex.metrica.YandexMetrica; import com.yandex.metrica.YandexMetricaConfig; +import my.neomer.sixtyseconds.helpers.AppMetricaHelper; + public class MyApp extends Application { public static final String Version = BuildConfig.VERSION_NAME; diff --git a/app/src/main/java/my/neomer/sixtyseconds/QuestionFragment.java b/app/src/main/java/my/neomer/sixtyseconds/QuestionFragment.java index 6c68d33..eaf9cc7 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/QuestionFragment.java +++ b/app/src/main/java/my/neomer/sixtyseconds/QuestionFragment.java @@ -22,12 +22,13 @@ import java.security.PrivateKey; import java.util.Observable; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; import my.neomer.sixtyseconds.model.Answer; import my.neomer.sixtyseconds.model.Question; public class QuestionFragment extends Fragment { - private QuestionFragmentViewModel mViewModel; + private BaseGameContext mViewModel; private TextView txtQuestion; private ProgressBar progressBar; @@ -50,20 +51,7 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { progressBar = getView().findViewById(R.id.updateProgressBar); - mViewModel = ViewModelProviders.of(getActivity()).get(QuestionFragmentViewModel.class); - mViewModel.getQuestion().observe(this, new Observer() { - @Override - public void onChanged(@Nullable Question question) { - update(question); - } - }); - mViewModel.getAnswer().observe(this, new Observer() { - @Override - public void onChanged(@Nullable Answer answer) { - updateAnswer(answer); - } - }); - + mViewModel = ViewModelProviders.of(getActivity()).get(BaseGameContext.class); } private String translatedDifficulty(Question.Difficulty difficulty) { @@ -88,7 +76,7 @@ private CharSequence getText(int id, Object... args) { return Html.fromHtml(s); } - public void update(Question question) { + public void displayQuestion(Question question) { txtQuestion.scrollTo(0, 0); if (progressBar != null) { progressBar.setVisibility(View.INVISIBLE); @@ -101,8 +89,11 @@ public void update(Question question) { txtQuestion.setText(Html.fromHtml(txt)); } - private void updateAnswer(Answer answer) { + public void displayAnswer(Answer answer) { txtQuestion.scrollTo(0, 0); + if (progressBar != null) { + progressBar.setVisibility(View.INVISIBLE); + } if (answer.getComment() != null && !answer.getComment().isEmpty()) { txtQuestion.setText(Html.fromHtml( getResources().getString(R.string.answer_and_comment_label, @@ -123,9 +114,5 @@ public void clear() { txtQuestion.setText(""); } } - - public void displayAnswer(String guess) { - mViewModel.checkAnswer(guess); - } } diff --git a/app/src/main/java/my/neomer/sixtyseconds/QuestionFragmentViewModel.java b/app/src/main/java/my/neomer/sixtyseconds/QuestionFragmentViewModel.java deleted file mode 100644 index dea4cc7..0000000 --- a/app/src/main/java/my/neomer/sixtyseconds/QuestionFragmentViewModel.java +++ /dev/null @@ -1,72 +0,0 @@ -package my.neomer.sixtyseconds; - -import android.arch.lifecycle.LiveData; -import android.arch.lifecycle.MutableLiveData; -import android.arch.lifecycle.ViewModel; -import android.util.Log; -import android.widget.Toast; - -import my.neomer.sixtyseconds.model.Answer; -import my.neomer.sixtyseconds.model.Question; -import my.neomer.sixtyseconds.transport.Callback; -import my.neomer.sixtyseconds.transport.FakeQuestionProvider; -import my.neomer.sixtyseconds.transport.HttpQuestionProvider; -import my.neomer.sixtyseconds.transport.IQuestionProvider; - -public class QuestionFragmentViewModel extends ViewModel { - - private static final String TAG = "QuestionFragmentVM"; - private IQuestionProvider provider = new HttpQuestionProvider(); - private final MutableLiveData question = new MutableLiveData<>(); - private final MutableLiveData answer = new MutableLiveData<>(); - - boolean hasValue() { return question.getValue() != null; } - - public LiveData getQuestion() { - return question; - } - - public LiveData getAnswer() { return answer; } - - IQuestionProvider getProvider() { return provider; } - - void like() { - if (hasValue()) { - provider.like(question.getValue()); - } - } - - void dislike() { - if (hasValue()) { - provider.dislike(question.getValue()); - } - } - - void checkAnswer(final String guess) { - provider.getAnswer(question.getValue(), guess, new Callback() { - @Override - public void onReady(Answer data) { - answer.setValue(data); - } - - @Override - public void onFailure(Throwable t) { - provider.getAnswer(getQuestion().getValue(), guess, this); - } - }); - } - - void update() { - provider.getNextQuestion(new Callback() { - @Override - public void onReady(Question data) { - question.setValue(data); - } - - @Override - public void onFailure(Throwable t) { - provider.getNextQuestion(this); - } - }); - } -} diff --git a/app/src/main/java/my/neomer/sixtyseconds/ads/AdMobAdProvider.java b/app/src/main/java/my/neomer/sixtyseconds/ads/AdMobAdProvider.java new file mode 100644 index 0000000..3e2fe5f --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/ads/AdMobAdProvider.java @@ -0,0 +1,85 @@ +package my.neomer.sixtyseconds.ads; + +import android.content.Context; +import android.net.sip.SipSession; +import android.util.Log; + +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.InterstitialAd; +import com.google.android.gms.ads.MobileAds; + +import my.neomer.sixtyseconds.helpers.ApplicationResources; + +public class AdMobAdProvider extends AdListener implements IAdsProvider { + private static final String LOG_TAG = "AdMobAdProvider"; + + private static final String ADMOB_APP_KEY = "ca-app-pub-5078878060587689~8320307873"; + private static final String ADMOB_APP_KEY_DEBUG = "ca-app-pub-3940256099942544~3347511713"; // Sample adMob app + private static final String ADMOB_BETWEEN_ACTIVITY_KEY = "ca-app-pub-5078878060587689/9345738647"; + private static final String ADMOB_BETWEEN_ACTIVITY_KEY_DEBUG = "ca-app-pub-3940256099942544/1033173712"; // Sample InterstitialAd + + private InterstitialAd interstitialAd; + private IRewardedAdResultListener resultListener; + + @Override + public void initialize(Context context) { + MobileAds.initialize(context, (ApplicationResources.getInstance().isDebug() ? ADMOB_APP_KEY_DEBUG : ADMOB_APP_KEY)); + interstitialAd = new InterstitialAd(context); + interstitialAd.setAdUnitId((ApplicationResources.getInstance().isDebug() ? ADMOB_BETWEEN_ACTIVITY_KEY_DEBUG : ADMOB_BETWEEN_ACTIVITY_KEY)); + interstitialAd.loadAd(new AdRequest.Builder().build()); + interstitialAd.setAdListener(this); + } + + @Override + public void onAdClosed() { + Log.d(LOG_TAG, "Ad closed. Reload..."); + interstitialAd.loadAd(new AdRequest.Builder().build()); + if (resultListener != null) { + resultListener.onAdComplete(); + } + } + + @Override + public void onAdFailedToLoad(int i) { + Log.e(LOG_TAG, "Failed to load ad! Error: " + i); + interstitialAd.loadAd(new AdRequest.Builder().build()); + if (resultListener != null) { + resultListener.onAdLoadFailed(); + } + } + + @Override + public void onAdClicked() { + Log.d(LOG_TAG, "Ad clicked!"); + interstitialAd.loadAd(new AdRequest.Builder().build()); + if (resultListener != null) { + resultListener.onAdClick(); + } + } + + @Override + public void onAdImpression() { + Log.d(LOG_TAG, "Ad impressed!"); + interstitialAd.loadAd(new AdRequest.Builder().build()); + if (resultListener != null) { + resultListener.onAdClick(); + } + } + + @Override + public boolean showInterstitialAd(IRewardedAdResultListener listener) { + resultListener = listener; + if (interstitialAd.isLoaded()) { + interstitialAd.show(); + return true; + } + return false; + } + + @Override + public boolean showRewardedAd(IRewardedAdResultListener listener) { + resultListener = listener; + return false; + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/ads/IAdsProvider.java b/app/src/main/java/my/neomer/sixtyseconds/ads/IAdsProvider.java new file mode 100644 index 0000000..2e8f6f3 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/ads/IAdsProvider.java @@ -0,0 +1,13 @@ +package my.neomer.sixtyseconds.ads; + +import android.content.Context; + +public interface IAdsProvider { + + void initialize(Context context); + + boolean showInterstitialAd(IRewardedAdResultListener listener); + + boolean showRewardedAd(IRewardedAdResultListener listener); + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/ads/IRewardedAdResultListener.java b/app/src/main/java/my/neomer/sixtyseconds/ads/IRewardedAdResultListener.java new file mode 100644 index 0000000..c045da7 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/ads/IRewardedAdResultListener.java @@ -0,0 +1,11 @@ +package my.neomer.sixtyseconds.ads; + +public interface IRewardedAdResultListener { + + void onAdClick(); + + void onAdComplete(); + + void onAdLoadFailed(); + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/core/ForwardPipeline.java b/app/src/main/java/my/neomer/sixtyseconds/core/ForwardPipeline.java new file mode 100644 index 0000000..6b19d32 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/core/ForwardPipeline.java @@ -0,0 +1,97 @@ +package my.neomer.sixtyseconds.core; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ForwardPipeline implements Pipeline { + + private static final String TAG = "ForwardPipeline"; + + //region Parcelable + + ForwardPipeline(Parcel in) { + int listCount = in.readInt(); + for (int i = 0; i < listCount; ++i) { + Class elementClass = (Class) in.readSerializable(); + if (elementClass != null) { + ClassLoader classLoader = elementClass.getClassLoader(); + if (classLoader != null) { + T item = in.readParcelable(classLoader); + if (item != null) { + list.add(item); + } + } else { + Log.e(TAG, "Element class has no ClassLoader!"); + } + } else { + Log.e(TAG, "elementClass is null!"); + } + } + + idx = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(list.size()); + for (T item : list) { + Class itemClass = item.getClass(); + dest.writeSerializable(itemClass); + dest.writeParcelable((Parcelable) item, 0); + } + dest.writeInt(idx); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ForwardPipeline createFromParcel(Parcel in) { + return new ForwardPipeline(in); + } + + public ForwardPipeline[] newArray(int size) { + return new ForwardPipeline[size]; + } + }; + + //endregion + + List list = new ArrayList<>(); + int idx = 0; + + public ForwardPipeline() { + + } + + @SafeVarargs + public ForwardPipeline(T ...values) + { + Collections.addAll(list, values); + } + + @Nullable + @Override + public T current() { + return list != null ? list.get(idx) : null; + } + + @Nullable + @Override + public T next() { + return list != null ? list.get(++idx) : null; + } + + @Override + public boolean isEnd() { + return idx >= list.size(); + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/core/InfinitePipeline.java b/app/src/main/java/my/neomer/sixtyseconds/core/InfinitePipeline.java new file mode 100644 index 0000000..f50a52e --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/core/InfinitePipeline.java @@ -0,0 +1,65 @@ +package my.neomer.sixtyseconds.core; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +public class InfinitePipeline extends ForwardPipeline { + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public InfinitePipeline createFromParcel(Parcel in) { + return new InfinitePipeline(in); + } + + public InfinitePipeline[] newArray(int size) { + return new InfinitePipeline[size]; + } + }; + + private InfinitePipeline(Parcel in) { + super(in); + returnIndex = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(returnIndex); + } + + //endregion + + private int returnIndex; + + public InfinitePipeline() { + } + + @SafeVarargs + public InfinitePipeline(T... values) { + super(values); + this.returnIndex = -1; + } + + + @SafeVarargs + public InfinitePipeline(int returnIndex, T... values) { + super(values); + this.returnIndex = returnIndex - 1; + } + + @Nullable + @Override + public T next() { + if (idx == list.size() - 1) { + idx = this.returnIndex; + } + return super.next(); + } + + @Override + public boolean isEnd() { + return false; + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/core/Pipeline.java b/app/src/main/java/my/neomer/sixtyseconds/core/Pipeline.java new file mode 100644 index 0000000..2ddcaad --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/core/Pipeline.java @@ -0,0 +1,14 @@ +package my.neomer.sixtyseconds.core; + +import android.os.Parcelable; +import android.support.annotation.Nullable; + +public interface Pipeline extends Parcelable { + + @Nullable T current(); + + @Nullable T next(); + + boolean isEnd(); + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/dao/QuestionDTO.java b/app/src/main/java/my/neomer/sixtyseconds/dao/QuestionDTO.java index 20f479d..7c75d06 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/dao/QuestionDTO.java +++ b/app/src/main/java/my/neomer/sixtyseconds/dao/QuestionDTO.java @@ -3,6 +3,7 @@ import android.os.Parcel; import android.os.Parcelable; +import my.neomer.sixtyseconds.helpers.DifficultyHelper; import my.neomer.sixtyseconds.model.Question; public class QuestionDTO implements Parcelable { @@ -17,14 +18,7 @@ public Question toQuestion() { question.setId(id); question.setText(text); question.setVote(vote); - switch (level) { - case 0: question.setDifficulty(Question.Difficulty.Easiest); break; - case 1: question.setDifficulty(Question.Difficulty.Normal); break; - case 2: question.setDifficulty(Question.Difficulty.Moderate); break; - case 3: question.setDifficulty(Question.Difficulty.Professional); break; - case 4: question.setDifficulty(Question.Difficulty.Hardest); break; - default: question.setDifficulty(Question.Difficulty.Unknown); break; - } + question.setDifficulty(DifficultyHelper.FromInt(level)); return question; } diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameContext.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameContext.java new file mode 100644 index 0000000..39009d3 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameContext.java @@ -0,0 +1,92 @@ +package my.neomer.sixtyseconds.gamemodes; + +import android.annotation.SuppressLint; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.ViewModel; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.app.FragmentActivity; + +import java.io.Serializable; + +import my.neomer.sixtyseconds.helpers.DifficultyHelper; +import my.neomer.sixtyseconds.model.Answer; +import my.neomer.sixtyseconds.model.Question; + +public class BaseGameContext extends ViewModel implements Parcelable { + + //region Parcelable + + protected BaseGameContext(Parcel in) { + difficulty = DifficultyHelper.FromInt(in.readInt()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public BaseGameContext createFromParcel(Parcel in) { + return new BaseGameContext(in); + } + + @Override + public BaseGameContext[] newArray(int size) { + return new BaseGameContext[size]; + } + }; + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(DifficultyHelper.ToInt(difficulty)); + } + + //endregion + + public BaseGameContext() { + + } + + private final MutableLiveData question = new MutableLiveData<>(); + private final MutableLiveData answer = new MutableLiveData<>(); + @SuppressLint("StaticFieldLeak") + private FragmentActivity activity; + private String guess; + private int adsSkipped; + private Question.Difficulty difficulty; + + public MutableLiveData getQuestion() { + return question; + } + public MutableLiveData getAnswer() { + return answer; + } + public FragmentActivity getActivity() { + return activity; + } + public void setActivity(FragmentActivity activity) { + this.activity = activity; + } + public String getGuess() { + return guess; + } + public void setGuess(String guess) { + this.guess = guess; + } + public int getAdsSkipped() { + return adsSkipped; + } + public void setAdsSkipped(int adsSkipped) { + this.adsSkipped = adsSkipped; + } + public void adsSkipped() { this.adsSkipped++; } + public Question.Difficulty getDifficulty() { + return difficulty; + } + public void setDifficulty(Question.Difficulty difficulty) { + this.difficulty = difficulty; + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameMode.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameMode.java new file mode 100644 index 0000000..70985e4 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/BaseGameMode.java @@ -0,0 +1,89 @@ +package my.neomer.sixtyseconds.gamemodes; + +import android.os.Parcel; +import android.support.annotation.NonNull; +import android.util.Log; + +import my.neomer.sixtyseconds.core.Pipeline; +import my.neomer.sixtyseconds.states.IState; +import my.neomer.sixtyseconds.states.IStateFinishListener; + +public abstract class BaseGameMode implements IGameMode, IStateFinishListener { + + private static final String LOG_TAG = "BaseGameMode"; + private BaseGameContext gameContext; + private Pipeline statePipeline; + + /** + * Новая игра. + */ + public BaseGameMode(@NonNull BaseGameContext gameContext, @NonNull Pipeline statePipeline) { + this.statePipeline = statePipeline; + this.gameContext = gameContext; + } + + @Override + public BaseGameContext getGameContext() { + return gameContext; + } + + @Override + public IState getCurrentState() { + return !statePipeline.isEnd() ? statePipeline.current() : null; + } + + @Override + public void onFinish(IState state) { + if (!statePipeline.isEnd()) { + IState nextState = statePipeline.next(); + if (nextState != null) { + runState(nextState); + } + } + } + + @Override + public void run() { + IState initialState = getCurrentState(); + if (initialState != null) { + runState(initialState); + } + } + + private void runState(@NonNull final IState state) { + final IStateFinishListener stateFinishListener = this; + state.prepareState(getGameContext(), stateFinishListener); + state.start(); + } + + //region Parcelable + + protected BaseGameMode(Parcel in) { + gameContext = (BaseGameContext) in.readParcelable(BaseGameContext.class.getClassLoader()); + Class pipelineClass = (Class) in.readSerializable(); + if (pipelineClass != null) { + statePipeline = in.readParcelable(pipelineClass.getClassLoader()); + } else { + Log.e(LOG_TAG, "Class de-serialization failed!"); + } + } + + protected BaseGameMode() { + + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(gameContext, 0); + dest.writeSerializable(statePipeline.getClass()); + dest.writeParcelable(statePipeline, 0); + } + + //endregion + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/IGameMode.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/IGameMode.java new file mode 100644 index 0000000..43af7b8 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/IGameMode.java @@ -0,0 +1,28 @@ +package my.neomer.sixtyseconds.gamemodes; + +import android.content.Context; +import android.os.Parcelable; + +import my.neomer.sixtyseconds.states.IState; + +/** + * Тип игры (одиночная, групповая итд). + * Реализация паттерна "Состояние" + */ +public interface IGameMode extends Parcelable { + + /** + * Контекст игры + */ + BaseGameContext getGameContext(); + + /** + * Текущее состояние игры + */ + IState getCurrentState(); + + /** + * Начать игру + */ + void run(); +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerContext.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerContext.java new file mode 100644 index 0000000..463da52 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerContext.java @@ -0,0 +1,5 @@ +package my.neomer.sixtyseconds.gamemodes; + +public class SinglePlayerContext extends BaseGameContext { + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerGameMode.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerGameMode.java new file mode 100644 index 0000000..01fdc8d --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerGameMode.java @@ -0,0 +1,54 @@ +package my.neomer.sixtyseconds.gamemodes; + +import android.os.Parcel; +import android.os.Parcelable; + +import my.neomer.sixtyseconds.core.InfinitePipeline; +import my.neomer.sixtyseconds.states.AdsDisplayingState; +import my.neomer.sixtyseconds.states.AnswerReceivingState; +import my.neomer.sixtyseconds.states.AnswerState; +import my.neomer.sixtyseconds.states.GuessInputState; +import my.neomer.sixtyseconds.states.IState; +import my.neomer.sixtyseconds.states.QuestionReceivingState; +import my.neomer.sixtyseconds.states.QuestionSettingsState; +import my.neomer.sixtyseconds.states.QuestionState; + +public class SinglePlayerGameMode extends BaseGameMode { + + //region Parcelable + + protected SinglePlayerGameMode(Parcel in) { + super(in); + + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public SinglePlayerGameMode createFromParcel(Parcel source) { + return new SinglePlayerGameMode(source); + } + + @Override + public SinglePlayerGameMode[] newArray(int size) { + return new SinglePlayerGameMode[size]; + } + }; + + //endregion + + public SinglePlayerGameMode() { + super(new SinglePlayerContext(), + new InfinitePipeline( + 1, + new QuestionSettingsState(), + new QuestionReceivingState(), + new QuestionState(), + new GuessInputState(), + new AnswerReceivingState(), + new AnswerState(), + new AdsDisplayingState() + )); + } + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesContext.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesContext.java new file mode 100644 index 0000000..a28ade7 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesContext.java @@ -0,0 +1,14 @@ +package my.neomer.sixtyseconds.gamemodes; + +import android.content.Context; +import android.support.v4.app.FragmentActivity; + +import my.neomer.sixtyseconds.model.Question; + +public class SinglePlayerWithRatesContext extends BaseGameContext { + + public SinglePlayerWithRatesContext() { + setDifficulty(Question.Difficulty.Hardest); + } + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesGameMode.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesGameMode.java new file mode 100644 index 0000000..42f9b11 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/SinglePlayerWithRatesGameMode.java @@ -0,0 +1,58 @@ +package my.neomer.sixtyseconds.gamemodes; + +import android.os.Parcel; +import android.os.Parcelable; + +import my.neomer.sixtyseconds.core.InfinitePipeline; +import my.neomer.sixtyseconds.states.AdsDisplayingState; +import my.neomer.sixtyseconds.states.AnswerReceivingState; +import my.neomer.sixtyseconds.states.AnswerState; +import my.neomer.sixtyseconds.states.GuessInputState; +import my.neomer.sixtyseconds.states.IState; +import my.neomer.sixtyseconds.states.QuestionReceivingState; +import my.neomer.sixtyseconds.states.QuestionState; + +public class SinglePlayerWithRatesGameMode extends BaseGameMode { + + //region Parcelable + + protected SinglePlayerWithRatesGameMode(Parcel in) { + super(in); + + } + + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public SinglePlayerWithRatesGameMode createFromParcel(Parcel source) { + return new SinglePlayerWithRatesGameMode(source); + } + + @Override + public SinglePlayerWithRatesGameMode[] newArray(int size) { + return new SinglePlayerWithRatesGameMode[size]; + } + }; + + //endregion + + /** + * Новая игра. + */ + public SinglePlayerWithRatesGameMode() { + super(new SinglePlayerWithRatesContext(), + new InfinitePipeline( + new QuestionReceivingState(), + new QuestionState(), + new GuessInputState(), + new AnswerReceivingState(), + new AnswerState(), + new AdsDisplayingState() + + )); + } + + + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/gamemodes/StatePipeline.java b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/StatePipeline.java new file mode 100644 index 0000000..4dc1a05 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/gamemodes/StatePipeline.java @@ -0,0 +1,2 @@ +package my.neomer.sixtyseconds.gamemodes; + diff --git a/app/src/main/java/my/neomer/sixtyseconds/ActivityHelper.java b/app/src/main/java/my/neomer/sixtyseconds/helpers/ActivityHelper.java similarity index 56% rename from app/src/main/java/my/neomer/sixtyseconds/ActivityHelper.java rename to app/src/main/java/my/neomer/sixtyseconds/helpers/ActivityHelper.java index 2ad2c79..29c36db 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/ActivityHelper.java +++ b/app/src/main/java/my/neomer/sixtyseconds/helpers/ActivityHelper.java @@ -1,4 +1,4 @@ -package my.neomer.sixtyseconds; +package my.neomer.sixtyseconds.helpers; import android.app.Activity; import android.view.View; @@ -12,13 +12,12 @@ public class ActivityHelper { */ public static void hideKeyboard(Activity activity) { InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); - //Find the currently focused view, so we can grab the correct window token from it. View view = activity.getCurrentFocus(); - //If no view currently has focus, create a new one, just so we can grab a window token from it - if (view == null) { - view = new View(activity); + if (view != null) { + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + } } - imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } } diff --git a/app/src/main/java/my/neomer/sixtyseconds/AppMetricaHelper.java b/app/src/main/java/my/neomer/sixtyseconds/helpers/AppMetricaHelper.java similarity index 75% rename from app/src/main/java/my/neomer/sixtyseconds/AppMetricaHelper.java rename to app/src/main/java/my/neomer/sixtyseconds/helpers/AppMetricaHelper.java index 330f73c..9498221 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/AppMetricaHelper.java +++ b/app/src/main/java/my/neomer/sixtyseconds/helpers/AppMetricaHelper.java @@ -1,4 +1,4 @@ -package my.neomer.sixtyseconds; +package my.neomer.sixtyseconds.helpers; public final class AppMetricaHelper { diff --git a/app/src/main/java/my/neomer/sixtyseconds/helpers/ApplicationResources.java b/app/src/main/java/my/neomer/sixtyseconds/helpers/ApplicationResources.java new file mode 100644 index 0000000..ae72bea --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/helpers/ApplicationResources.java @@ -0,0 +1,95 @@ +package my.neomer.sixtyseconds.helpers; + +import android.content.Context; +import android.media.AudioAttributes; +import android.media.SoundPool; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.InterstitialAd; +import com.google.android.gms.ads.MobileAds; + +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.ads.AdMobAdProvider; +import my.neomer.sixtyseconds.ads.IAdsProvider; +import my.neomer.sixtyseconds.transport.HttpQuestionProvider; +import my.neomer.sixtyseconds.transport.IQuestionProvider; + +public class ApplicationResources { + //region Singleton + + private static final ApplicationResources ourInstance = new ApplicationResources(); + private static final int MAX_STREAMS = 1; + + public static ApplicationResources getInstance() { + return ourInstance; + } + + private ApplicationResources() { + } + + //endregion + + private IQuestionProvider questionProvider = new HttpQuestionProvider(); + private IAdsProvider adsProvider = new AdMobAdProvider(); + + private SoundPool soundPool; + private int timeIsUpSoundId; + private int clickSoundId; + private int countDownSoundId; + private boolean isDebug; + + public void loadSounds(@NonNull Context context, @Nullable SoundPool.OnLoadCompleteListener completeListener) { + soundPool = new SoundPool.Builder() + .setMaxStreams(MAX_STREAMS) + .setAudioAttributes( + new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build()) + .build(); + + if (completeListener != null) { + soundPool.setOnLoadCompleteListener(completeListener); + } + + timeIsUpSoundId = soundPool.load(context, R.raw.time_is_up, 1); + countDownSoundId = soundPool.load(context, R.raw.countdown, 1); + clickSoundId = soundPool.load(context, R.raw.click, 1); + } + + public void loadAds(@NonNull Context context) { + adsProvider.initialize(context); + } + + public void playClickSound() { + soundPool.play(clickSoundId, 1, 1, 0, 0, 1); + } + + public void playCountDownSound() { + soundPool.play(countDownSoundId, 1, 1, 0,0,1); + } + + public void playTimeIsUpSound() { + soundPool.stop(countDownSoundId); + soundPool.play(timeIsUpSoundId, 1, 1, 0,0,1); + } + + public IQuestionProvider getQuestionProvider() { + return questionProvider; + } + + public IAdsProvider getAdsProvider() { + return adsProvider; + } + + public boolean isDebug() { + return isDebug; + } + + public void setDebug(boolean debug) { + isDebug = debug; + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/helpers/DifficultyHelper.java b/app/src/main/java/my/neomer/sixtyseconds/helpers/DifficultyHelper.java new file mode 100644 index 0000000..d8a623e --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/helpers/DifficultyHelper.java @@ -0,0 +1,32 @@ +package my.neomer.sixtyseconds.helpers; + +import my.neomer.sixtyseconds.model.Question; + +public final class DifficultyHelper { + + public static int ToInt(Question.Difficulty difficulty) { + if (difficulty == null) { + return -1; + } + switch (difficulty) { + case Easiest: return 0; + case Normal: return 1; + case Moderate: return 2; + case Professional: return 3; + case Hardest: return 4; + default: return -1; + } + } + + public static Question.Difficulty FromInt(int value) { + switch (value) { + case 0: return Question.Difficulty.Easiest; + case 1: return Question.Difficulty.Normal; + case 2: return Question.Difficulty.Moderate; + case 3: return Question.Difficulty.Professional; + case 4: return Question.Difficulty.Hardest; + default: return Question.Difficulty.Unknown; + } + } + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/AdsDisplayingState.java b/app/src/main/java/my/neomer/sixtyseconds/states/AdsDisplayingState.java new file mode 100644 index 0000000..e32a67a --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/AdsDisplayingState.java @@ -0,0 +1,78 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; +import android.os.Parcelable; + +import my.neomer.sixtyseconds.ads.IRewardedAdResultListener; +import my.neomer.sixtyseconds.helpers.ApplicationResources; + +public class AdsDisplayingState extends BaseState implements IRewardedAdResultListener { + + private static final String LOG_TAG = "AdsDisplayingState"; + private static final int ADS_SKIP_COUNT = 3; + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public AdsDisplayingState createFromParcel(Parcel source) { + return new AdsDisplayingState(source); + } + + @Override + public AdsDisplayingState[] newArray(int size) { + return new AdsDisplayingState[size]; + } + }; + + private AdsDisplayingState(Parcel in) { + super(in); + } + + //endregion + + public AdsDisplayingState() { + + } + + @Override + public void start() { + if (getGameContext().getAdsSkipped() < ADS_SKIP_COUNT || !ApplicationResources.getInstance().getAdsProvider().showInterstitialAd(this)) { + getGameContext().adsSkipped(); + finish(); + } + } + + @Override + public void pause() { + + } + + @Override + public void proceed() { + + } + + //region IRewardedAdResultListener implementation + + @Override + public void onAdClick() { + getGameContext().setAdsSkipped(0); + finish(); + } + + @Override + public void onAdComplete() { + getGameContext().setAdsSkipped(0); + finish(); + } + + @Override + public void onAdLoadFailed() { + getGameContext().adsSkipped(); + finish(); + } + + //endregion +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/AnswerReceivingState.java b/app/src/main/java/my/neomer/sixtyseconds/states/AnswerReceivingState.java new file mode 100644 index 0000000..10ea2e8 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/AnswerReceivingState.java @@ -0,0 +1,115 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.TypedValue; +import android.widget.Button; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import my.neomer.sixtyseconds.QuestionFragment; +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.helpers.ActivityHelper; +import my.neomer.sixtyseconds.helpers.ApplicationResources; +import my.neomer.sixtyseconds.model.Answer; +import my.neomer.sixtyseconds.transport.Callback; + +public class AnswerReceivingState extends BaseState + implements Callback { + + private static final String LOG_TAG = "AnswerReceivingState"; + + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public AnswerReceivingState createFromParcel(Parcel source) { + return new AnswerReceivingState(source); + } + + @Override + public AnswerReceivingState[] newArray(int size) { + return new AnswerReceivingState[size]; + } + }; + + private AnswerReceivingState(Parcel in) { + super(in); + } + + //endregion + + @BindView(R.id.txtTime) + TextView txtTimeCountdown; + + @BindView(R.id.btnStart) + Button btnStart; + + private QuestionFragment questionFragment; + + + public AnswerReceivingState() { + + } + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + super.prepareState(gameContext, stateFinishListener); + + ButterKnife.bind(this, getGameContext().getActivity()); + + questionFragment = (QuestionFragment)getGameContext().getActivity() + .getSupportFragmentManager() + .findFragmentById(R.id.fragment); + + btnStart.setOnClickListener(null); + ActivityHelper.hideKeyboard(getGameContext().getActivity()); + } + + @Override + public void start() { + btnStart.setText(R.string.wait_countdown_text); + txtTimeCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getGameContext().getActivity().getResources().getDimension(R.dimen.title_font_size)); + txtTimeCountdown.setText(R.string.updating_message); + questionFragment.clear(); + + ApplicationResources.getInstance() + .getQuestionProvider() + .getAnswer(getGameContext().getQuestion().getValue(), + getGameContext().getGuess(), + this); + } + + @Override + public void pause() { + + } + + @Override + public void proceed() { + + } + + @Override + public void finish() { + super.finish(); + } + + @Override + public void onReady(Answer data) { + getGameContext().getAnswer().setValue(data); + finish(); + } + + @Override + public void onFailure(Throwable t) { + Log.e(LOG_TAG, t.getMessage()); + start(); + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/AnswerState.java b/app/src/main/java/my/neomer/sixtyseconds/states/AnswerState.java new file mode 100644 index 0000000..6052579 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/AnswerState.java @@ -0,0 +1,95 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.TypedValue; +import android.widget.Button; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import my.neomer.sixtyseconds.QuestionFragment; +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.helpers.ActivityHelper; + +public class AnswerState extends BaseState { + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public AnswerState createFromParcel(Parcel source) { + return new AnswerState(source); + } + + @Override + public AnswerState[] newArray(int size) { + return new AnswerState[size]; + } + }; + + private AnswerState(Parcel in) { + super(in); + } + + //endregion + + @BindView(R.id.btnStart) + Button btnStart; + + @BindView(R.id.txtTime) + TextView txtTitle; + + private QuestionFragment questionFragment; + + public AnswerState() { + + } + + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + super.prepareState(gameContext, stateFinishListener); + + ButterKnife.bind(this, getGameContext().getActivity()); + + btnStart.setText(getGameContext().getActivity().getResources().getString(R.string.next_question_message)); + questionFragment = (QuestionFragment)getGameContext().getActivity() + .getSupportFragmentManager() + .findFragmentById(R.id.fragment); + + txtTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getGameContext().getActivity().getResources().getDimension(R.dimen.title_font_size)); + txtTitle.setText(R.string.answer_message); + + questionFragment.displayAnswer(getGameContext().getAnswer().getValue()); + } + + @Override + public void start() { + showVoting(); + } + + private void showVoting() { + } + + @Override + public void pause() { + + } + + @Override + public void proceed() { + + } + + @Override + @OnClick(R.id.btnStart) + public void finish() { + ActivityHelper.hideKeyboard(getGameContext().getActivity()); + super.finish(); + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/BaseEscalationState.java b/app/src/main/java/my/neomer/sixtyseconds/states/BaseEscalationState.java new file mode 100644 index 0000000..fd8f539 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/BaseEscalationState.java @@ -0,0 +1,92 @@ +package my.neomer.sixtyseconds.states; + +import android.os.AsyncTask; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.concurrent.TimeUnit; + +/** + * Базовый класс для состояний с эскалацией по времени. + * Указывается время отсчета. Для начала отсчета наследник должен вызвать метод StartTimer(), если эскалация не требуется, + * вызвать метод CancelTimer(). + * onTick(int) - вызыватся каждую секунду, передает оставшееся до эскалации время. + * onFinish() - вызывается при достижении эскалации. + * onCancel() - вызывается при отмене эскалации. + */ +public abstract class BaseEscalationState extends BaseState { + private static final String LOG_TAG = "BaseEscalationState"; + + //region Parcelable + + BaseEscalationState(Parcel in) { + super(in); + escalationTime = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(escalationTime); + } + + //endregion + + private EscalationTimer escalationTimer; + private int escalationTime; + + BaseEscalationState(int escalationTime) { + super(); + this.escalationTime = escalationTime; + } + + private void StartTimer() + { + Log.d(LOG_TAG, "BaseEscalationState.StartTimer()"); + escalationTimer = new EscalationTimer(this, escalationTime); + escalationTimer.enableEvents(); + escalationTimer.execute(); + } + + final void PauseTimer() { escalationTimer.pause(); } + + final void ProceedTimer() { escalationTimer.proceed(); } + + final void CancelTimer() + { + Log.d(LOG_TAG, "BaseEscalationState.CancelTimer()"); + if (!escalationTimer.isCancelled()) { + escalationTimer.cancel(true); + } + } + + @Override + public void pause() { + PauseTimer(); + } + + @Override + public void proceed() { + ProceedTimer(); + } + + @Override + public void finish() { + escalationTimer.disableEvents(); + CancelTimer(); + super.finish(); + } + + @Override + public void start() { + StartTimer(); + } + + protected abstract void onTick(int time); + protected abstract void onFinish(); + protected abstract void onCancel(); + + + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/BaseState.java b/app/src/main/java/my/neomer/sixtyseconds/states/BaseState.java new file mode 100644 index 0000000..4872043 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/BaseState.java @@ -0,0 +1,53 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; + +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; + +public abstract class BaseState implements IState { + + //region Parcelable + + BaseState(Parcel in) { + gameContext = (BaseGameContext) in.readSerializable(); + stateFinishListener = (IStateFinishListener) in.readSerializable(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(getGameContext(), 0); + dest.writeSerializable(stateFinishListener); + } + + //endregion + + private BaseGameContext gameContext; + private IStateFinishListener stateFinishListener; + + BaseState() { + + } + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + this.gameContext = gameContext; + this.stateFinishListener = stateFinishListener; + } + + BaseGameContext getGameContext() { + return gameContext; + } + + @Override + public void finish() { + if (stateFinishListener != null) { + stateFinishListener.onFinish(this); + } + } + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/EscalationTimer.java b/app/src/main/java/my/neomer/sixtyseconds/states/EscalationTimer.java new file mode 100644 index 0000000..3882e01 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/EscalationTimer.java @@ -0,0 +1,81 @@ +package my.neomer.sixtyseconds.states; + +import android.os.AsyncTask; +import android.util.Log; + +import java.util.concurrent.TimeUnit; + +class EscalationTimer extends AsyncTask +{ + private static final String LOG_TAG = "EscalationTimer"; + + private int escalationTime; + private BaseEscalationState state; + private volatile boolean pause = false; + private volatile boolean eventsDisabled = false; + + EscalationTimer(BaseEscalationState state, int escalationTime) { + this.escalationTime = escalationTime; + this.state = state; + } + + void setState(BaseEscalationState state) { + this.state = state; + } + + @Override + protected void onPostExecute(Void aVoid) { + if (!eventsDisabled) { + state.onFinish(); + } + super.onPostExecute(aVoid); + } + + @Override + protected void onProgressUpdate(Integer... values) { + if (!eventsDisabled) { + state.onTick(values[0]); + } + super.onProgressUpdate(values); + } + + @Override + protected void onCancelled() { + Log.d(LOG_TAG, "EscalationTimer.onCancelled()"); + if (!eventsDisabled) { + state.onCancel(); + } + super.onCancelled(); + } + + void disableEvents() { + eventsDisabled = true; + } + + void enableEvents() { + eventsDisabled = false; + } + + void proceed() { + pause = false; + } + + void pause() { + pause = true; + } + + @Override + protected Void doInBackground(Void... voids) { + for (int time = this.escalationTime; time > 0; --time) { + try { + publishProgress(time); + do { + TimeUnit.SECONDS.sleep(1); + } while (pause); + } catch (InterruptedException e) { + return null; + } + } + return null; + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/GuessInputState.java b/app/src/main/java/my/neomer/sixtyseconds/states/GuessInputState.java new file mode 100644 index 0000000..fa07841 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/GuessInputState.java @@ -0,0 +1,108 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v7.widget.CardView; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.helpers.ActivityHelper; +import my.neomer.sixtyseconds.helpers.ApplicationResources; +import my.neomer.sixtyseconds.model.Answer; +import my.neomer.sixtyseconds.transport.Callback; + +public class GuessInputState extends BaseEscalationState { + private static final String LOG_TAG = "GuessInputState"; + + //region Parcelable + + private GuessInputState(Parcel in) { + super(in); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public GuessInputState createFromParcel(Parcel source) { + return new GuessInputState(source); + } + + @Override + public GuessInputState[] newArray(int size) { + return new GuessInputState[size]; + } + }; + + //endregion + + @BindView(R.id.cardGuess) + CardView cardGuess; + + @BindView(R.id.progressBarGuess) + ProgressBar progressBarGuess; + + @BindView(R.id.btnSendGuess) + Button btnSendGuess; + + @BindView(R.id.txtGuess) + TextView txtGuess; + + public GuessInputState() { + super(20); + } + + @Override + protected void onTick(int time) { + progressBarGuess.setProgress(time); + } + + @Override + protected void onFinish() { + finish(); + } + + @Override + protected void onCancel() { + finish(); + } + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + super.prepareState(gameContext, stateFinishListener); + ButterKnife.bind(this, getGameContext().getActivity()); + + txtGuess.setText(""); + btnSendGuess.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ApplicationResources.getInstance().playClickSound(); + saveGuess(); + } + }); + } + + private void saveGuess() { + getGameContext().setGuess(txtGuess.getText().toString()); + finish(); + } + + @Override + public void start() { + super.start(); + cardGuess.setVisibility(View.VISIBLE); + } + + @Override + public void finish() { + ActivityHelper.hideKeyboard(getGameContext().getActivity()); + cardGuess.setVisibility(View.INVISIBLE); + super.finish(); + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/IState.java b/app/src/main/java/my/neomer/sixtyseconds/states/IState.java new file mode 100644 index 0000000..854458e --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/IState.java @@ -0,0 +1,40 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcelable; + +import java.io.Serializable; + +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.gamemodes.IGameMode; + +/** + * Состояние игры. + */ +public interface IState extends Parcelable { + + /** + * Подготовить контекст для перехода в данное состояние + */ + void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener); + + /** + * Начало + */ + void start(); + + /** + * Пауза + */ + void pause(); + + /** + * Продолжить выполнение + */ + void proceed(); + + /** + * Конец работы состояния, необходимо перейти в следующее + */ + void finish(); + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/IStateFinishListener.java b/app/src/main/java/my/neomer/sixtyseconds/states/IStateFinishListener.java new file mode 100644 index 0000000..2c40088 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/IStateFinishListener.java @@ -0,0 +1,9 @@ +package my.neomer.sixtyseconds.states; + +import java.io.Serializable; + +public interface IStateFinishListener extends Serializable { + + void onFinish(IState state); + +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/QuestionReceivingState.java b/app/src/main/java/my/neomer/sixtyseconds/states/QuestionReceivingState.java new file mode 100644 index 0000000..d0d21cc --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/QuestionReceivingState.java @@ -0,0 +1,116 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.constraint.ConstraintLayout; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import my.neomer.sixtyseconds.QuestionFragment; +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.helpers.ActivityHelper; +import my.neomer.sixtyseconds.helpers.ApplicationResources; +import my.neomer.sixtyseconds.model.Question; +import my.neomer.sixtyseconds.transport.Callback; + +/** + * Состояние: Получение вопроса. + */ +public class QuestionReceivingState extends BaseState + implements Callback { + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public QuestionReceivingState createFromParcel(Parcel source) { + return new QuestionReceivingState(source); + } + + @Override + public QuestionReceivingState[] newArray(int size) { + return new QuestionReceivingState[size]; + } + }; + + private QuestionReceivingState(Parcel in) { + super(in); + } + + //endregion + + public QuestionReceivingState() { + } + + private static final String TAG = "QuestionReceivingState"; + + @BindView(R.id.txtTime) + TextView txtTimeCountdown; + + @BindView(R.id.btnStart) + Button btnStart; + + @BindView(R.id.settingsLayout) + ConstraintLayout settingsLayout; + + private QuestionFragment questionFragment; + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + super.prepareState(gameContext, stateFinishListener); + questionFragment = (QuestionFragment)getGameContext().getActivity().getSupportFragmentManager().findFragmentById(R.id.fragment); + + ButterKnife.bind(this, getGameContext().getActivity()); + + settingsLayout.setVisibility(View.INVISIBLE); + btnStart.setOnClickListener(null); + ActivityHelper.hideKeyboard(getGameContext().getActivity()); + } + + @Override + public void start() { + btnStart.setText(R.string.wait_countdown_text); + txtTimeCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getGameContext().getActivity().getResources().getDimension(R.dimen.title_font_size)); + txtTimeCountdown.setText(R.string.updating_message); + questionFragment.clear(); + + ApplicationResources.getInstance() + .getQuestionProvider() + .getNextQuestion(getGameContext().getDifficulty(), this); + } + + @Override + public void pause() { + + } + + @Override + public void proceed() { + + } + + @Override + public void finish() { + super.finish(); + } + + @Override + public void onReady(Question data) { + getGameContext().getQuestion().setValue(data); + finish(); + } + + @Override + public void onFailure(Throwable t) { + Log.e(TAG, t.getMessage()); + start(); + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/QuestionSettingsState.java b/app/src/main/java/my/neomer/sixtyseconds/states/QuestionSettingsState.java new file mode 100644 index 0000000..2aaff51 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/QuestionSettingsState.java @@ -0,0 +1,83 @@ +package my.neomer.sixtyseconds.states; + +import android.app.MediaRouteButton; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.constraint.ConstraintLayout; +import android.view.View; +import android.widget.Button; +import android.widget.Spinner; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.model.Question; + +public class QuestionSettingsState extends BaseState { + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public QuestionSettingsState createFromParcel(Parcel source) { + return new QuestionSettingsState(source); + } + + @Override + public QuestionSettingsState[] newArray(int size) { + return new QuestionSettingsState[size]; + } + }; + + private QuestionSettingsState(Parcel in) { + super(in); + } + + //endregion + + @BindView(R.id.settingsLayout) + ConstraintLayout settingsLayout; + + @BindView(R.id.spinnerDifficulty) + Spinner spinnerDifficulty; + + @BindView(R.id.btnStart) + Button btnStart; + + public QuestionSettingsState() { + + } + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + super.prepareState(gameContext, stateFinishListener); + + ButterKnife.bind(this, getGameContext().getActivity()); + settingsLayout.setVisibility(View.VISIBLE); + } + + @OnClick(R.id.btnStart) + void saveGameSettings() { + getGameContext().setDifficulty( + Question.Difficulty.values()[Question.Difficulty.values().length - (int)spinnerDifficulty.getSelectedItemId()]); + finish(); + } + + @Override + public void start() { + + } + + @Override + public void pause() { + + } + + @Override + public void proceed() { + + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/states/QuestionState.java b/app/src/main/java/my/neomer/sixtyseconds/states/QuestionState.java new file mode 100644 index 0000000..e275e55 --- /dev/null +++ b/app/src/main/java/my/neomer/sixtyseconds/states/QuestionState.java @@ -0,0 +1,117 @@ +package my.neomer.sixtyseconds.states; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import my.neomer.sixtyseconds.QuestionFragment; +import my.neomer.sixtyseconds.R; +import my.neomer.sixtyseconds.gamemodes.BaseGameContext; +import my.neomer.sixtyseconds.helpers.ActivityHelper; +import my.neomer.sixtyseconds.helpers.ApplicationResources; + +public class QuestionState extends BaseEscalationState { + private static final String LOG_TAG = "QuestionState"; + + //region Parcelable + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public QuestionState createFromParcel(Parcel source) { + return new QuestionState(source); + } + + @Override + public QuestionState[] newArray(int size) { + return new QuestionState[size]; + } + }; + + private QuestionState(Parcel in) { + super(in); + } + + //endregion + + @BindView(R.id.txtTime) + TextView txtCountdown; + + @BindView(R.id.btnStart) + Button btnStart; + + private QuestionFragment questionFragment; + + public QuestionState() { + super(10); + } + + @Override + protected void onTick(int time) { + txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, getGameContext().getActivity().getResources().getDimension(R.dimen.clock_font_size)); + txtCountdown.setText(String.valueOf(time)); + + if (time == 5) { + ApplicationResources.getInstance().playCountDownSound(); + } + + } + + @Override + protected void onFinish() { + Log.d(LOG_TAG, "QuestionState.onFinish()"); + txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getGameContext().getActivity().getResources().getDimension(R.dimen.title_font_size)); + txtCountdown.setText(R.string.finish_message); + finish(); + } + + @Override + protected void onCancel() { + Log.d(LOG_TAG, "QuestionState.onCancel()"); + ApplicationResources.getInstance().playTimeIsUpSound(); + txtCountdown.setTextSize(TypedValue.COMPLEX_UNIT_PX, + getGameContext().getActivity().getResources().getDimension(R.dimen.title_font_size)); + txtCountdown.setText(R.string.cancel_message); + finish(); + } + + @Override + public void prepareState(BaseGameContext gameContext, IStateFinishListener stateFinishListener) { + Log.d(LOG_TAG, "QuestionState.prepareState()"); + super.prepareState(gameContext, stateFinishListener); + ButterKnife.bind(this, getGameContext().getActivity()); + + btnStart.setText(getGameContext().getActivity().getResources().getString(R.string.cancel_countdown_text)); + + questionFragment = (QuestionFragment)getGameContext().getActivity().getSupportFragmentManager().findFragmentById(R.id.fragment); + questionFragment.displayQuestion(getGameContext().getQuestion().getValue()); + ActivityHelper.hideKeyboard(getGameContext().getActivity()); + } + + @OnClick(R.id.btnStart) + void Escalation() + { + ApplicationResources.getInstance().playClickSound(); + CancelTimer(); + } + + @Override + public void start() { + super.start(); + } + + @Override + public void finish() { + Log.d(LOG_TAG, "QuestionState.finish()"); + btnStart.setOnClickListener(null); + super.finish(); + } +} diff --git a/app/src/main/java/my/neomer/sixtyseconds/transport/AskMeAPI.java b/app/src/main/java/my/neomer/sixtyseconds/transport/AskMeAPI.java index e7f0b68..3191a4e 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/transport/AskMeAPI.java +++ b/app/src/main/java/my/neomer/sixtyseconds/transport/AskMeAPI.java @@ -10,11 +10,14 @@ public interface AskMeAPI { @GET("question2.php") - Call getQuestion(@Query("user") String userId, @Query("version") String appVersion); + Call getQuestion(@Query("user") String userId, @Query("version") String appVersion, @Query("level") int difficulty); @GET("answer.php") Call getAnswer(@Query("user") String userId, @Query("question") long questionId, @Query("answer") String answer); @GET("vote.php") Call vote(@Query("user") String userId, @Query("question") long questionId, @Query("vote") int vote); + + @GET("adclick.php") + Call adclick(@Query("user") String userId); } diff --git a/app/src/main/java/my/neomer/sixtyseconds/transport/FakeQuestionProvider.java b/app/src/main/java/my/neomer/sixtyseconds/transport/FakeQuestionProvider.java index bd53d36..5711783 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/transport/FakeQuestionProvider.java +++ b/app/src/main/java/my/neomer/sixtyseconds/transport/FakeQuestionProvider.java @@ -14,13 +14,24 @@ public void setConfiguration(TransportConfiguration config) { } @Override - public void getNextQuestion(Callback callback) { - + public void getNextQuestion(Question.Difficulty difficulty, Callback callback) { + Question question = new Question(); + question.setId(0); + question.setVote(0); + question.setDifficulty(difficulty); + question.setText("Sample question text generated by FakeQuestionProvider"); + + callback.onReady(question); } @Override public void getAnswer(Question question, String guess, Callback callback) { + Answer answer = new Answer(); + answer.setAnswer("Sample answer on the question generated by FakeQuestionProvider"); + answer.setCorrect(true); + answer.setComment("Simple comment"); + callback.onReady(answer); } @Override diff --git a/app/src/main/java/my/neomer/sixtyseconds/transport/HttpQuestionProvider.java b/app/src/main/java/my/neomer/sixtyseconds/transport/HttpQuestionProvider.java index 7450d35..071d1cf 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/transport/HttpQuestionProvider.java +++ b/app/src/main/java/my/neomer/sixtyseconds/transport/HttpQuestionProvider.java @@ -6,6 +6,7 @@ import my.neomer.sixtyseconds.MyApp; import my.neomer.sixtyseconds.dao.AnswerDTO; import my.neomer.sixtyseconds.dao.QuestionDTO; +import my.neomer.sixtyseconds.helpers.DifficultyHelper; import my.neomer.sixtyseconds.model.Answer; import my.neomer.sixtyseconds.model.Question; import retrofit2.Call; @@ -29,11 +30,12 @@ public void setConfiguration(TransportConfiguration config) { } @Override - public void getNextQuestion(Callback callback) { + public void getNextQuestion(Question.Difficulty difficulty, Callback callback) { this.callbackQuestion = callback; + RetrofitService.getInstance() .getApi() - .getQuestion(configuration.getUser(), MyApp.Version) + .getQuestion(configuration.getUser(), MyApp.Version, DifficultyHelper.ToInt(difficulty)) .enqueue(this); } diff --git a/app/src/main/java/my/neomer/sixtyseconds/transport/IQuestionProvider.java b/app/src/main/java/my/neomer/sixtyseconds/transport/IQuestionProvider.java index 2b8eae3..e981a87 100644 --- a/app/src/main/java/my/neomer/sixtyseconds/transport/IQuestionProvider.java +++ b/app/src/main/java/my/neomer/sixtyseconds/transport/IQuestionProvider.java @@ -7,7 +7,7 @@ public interface IQuestionProvider { void setConfiguration(TransportConfiguration config); - void getNextQuestion(Callback callback); + void getNextQuestion(Question.Difficulty difficulty, Callback callback); void getAnswer(Question question, String guess, Callback callback); diff --git a/app/src/main/res/layout/activity_game_mode_selection.xml b/app/src/main/res/layout/activity_game_mode_selection.xml new file mode 100644 index 0000000..30e27ee --- /dev/null +++ b/app/src/main/res/layout/activity_game_mode_selection.xml @@ -0,0 +1,70 @@ + + + + + + + +