diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..948b738
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,56 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ buildToolsVersion '28.0.3'
+
+ defaultConfig {
+ applicationId "io.github.trytonvanmeer.libretrivia"
+ minSdkVersion 19
+ targetSdkVersion 28
+ versionCode 3
+ versionName "0.3"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ encoding = 'UTF-8'
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+ext {
+ butterknife = '9.0.0-rc2'
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'androidx.annotation:annotation:1.0.0'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.cardview:cardview:1.0.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
+ implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.recyclerview:recyclerview:1.0.0'
+
+ implementation "com.jakewharton:butterknife:${butterknife}"
+ annotationProcessor "com.jakewharton:butterknife-compiler:${butterknife}"
+
+ implementation 'com.google.code.gson:gson:2.8.5'
+ implementation 'com.mikepenz:aboutlibraries:6.2.0'
+
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
+ androidTestImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
+ androidTestImplementation 'androidx.test:core:1.0.0'
+ androidTestImplementation 'androidx.test:runner:1.1.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.0.0'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/io/github/trytonvanmeer/libretrivia/ApplicationTest.java b/app/src/androidTest/java/io/github/trytonvanmeer/libretrivia/ApplicationTest.java
new file mode 100644
index 0000000..fe1f95f
--- /dev/null
+++ b/app/src/androidTest/java/io/github/trytonvanmeer/libretrivia/ApplicationTest.java
@@ -0,0 +1,27 @@
+package io.github.trytonvanmeer.libretrivia;
+
+import android.content.Context;
+
+import org.junit.jupiter.api.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ApplicationTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = ApplicationProvider.getApplicationContext();
+
+ assertEquals("io.github.trytonvanmeer.libretrivia", appContext.getPackageName());
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6bfe14d
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png
new file mode 100644
index 0000000..c4487c4
Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/LibreTriviaApplication.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/LibreTriviaApplication.java
new file mode 100644
index 0000000..02f6085
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/LibreTriviaApplication.java
@@ -0,0 +1,21 @@
+package io.github.trytonvanmeer.libretrivia;
+
+import android.annotation.SuppressLint;
+import android.app.Application;
+import android.content.Context;
+
+
+public class LibreTriviaApplication extends Application {
+ @SuppressLint("StaticFieldLeak")
+ private static Context context;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ LibreTriviaApplication.context = getApplicationContext();
+ }
+
+ public static Context getAppContext() {
+ return LibreTriviaApplication.context;
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/BaseActivity.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/BaseActivity.java
new file mode 100644
index 0000000..23a88e3
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/BaseActivity.java
@@ -0,0 +1,59 @@
+package io.github.trytonvanmeer.libretrivia.activities;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.mikepenz.aboutlibraries.Libs;
+import com.mikepenz.aboutlibraries.LibsBuilder;
+
+import androidx.appcompat.app.AppCompatActivity;
+import io.github.trytonvanmeer.libretrivia.R;
+import io.github.trytonvanmeer.libretrivia.settings.SettingsActivity;
+
+@SuppressLint("Registered")
+public class BaseActivity extends AppCompatActivity {
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.app_menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.settings:
+ onSettings();
+ return true;
+ case R.id.about:
+ onAbout();
+ return true;
+ case android.R.id.home:
+ onBackPressed();
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void onSettings() {
+ Intent intent = new Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ }
+
+ private void onAbout() {
+ String appName = getResources().getString(R.string.app_name);
+ String appDescription = getResources().getString(R.string.app_description);
+
+ new LibsBuilder()
+ .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
+ .withAboutIconShown(true)
+ .withAboutAppName(appName)
+ .withAboutVersionShownName(true)
+ .withAboutDescription(appDescription)
+ .start(this);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/MainActivity.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/MainActivity.java
new file mode 100644
index 0000000..b93a71b
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/MainActivity.java
@@ -0,0 +1,67 @@
+package io.github.trytonvanmeer.libretrivia.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import io.github.trytonvanmeer.libretrivia.R;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaCategory;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaDifficulty;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuery;
+
+public class MainActivity extends BaseActivity {
+
+ @BindView(R.id.button_play)
+ Button buttonPlay;
+ @BindView(R.id.spinner_number)
+ Spinner spinnerNumber;
+ @BindView(R.id.spinner_category)
+ Spinner spinnerCategory;
+ @BindView(R.id.spinner_difficulty)
+ Spinner spinnerDifficulty;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ButterKnife.bind(this);
+
+ buttonPlay.setOnClickListener(v -> {
+ int amount = (int) spinnerNumber.getSelectedItem();
+ TriviaCategory category = (TriviaCategory) spinnerCategory.getSelectedItem();
+ TriviaDifficulty difficulty = (TriviaDifficulty) spinnerDifficulty.getSelectedItem();
+
+ TriviaQuery query = new TriviaQuery.Builder(amount)
+ .category(category)
+ .difficulty(difficulty)
+ .build();
+
+ Intent intent = new Intent(getApplicationContext(), TriviaGameActivity.class);
+ intent.putExtra(TriviaGameActivity.EXTRA_TRIVIA_QUERY, query);
+ startActivity(intent);
+ });
+
+
+ Integer[] numbers = new Integer[50];
+ for (int i = 0; i < 50; ) {
+ numbers[i] = ++i;
+ }
+ spinnerNumber.setAdapter(
+ new ArrayAdapter<>(
+ this, android.R.layout.simple_list_item_1, numbers)
+ );
+ spinnerNumber.setSelection(9);
+
+ spinnerCategory.setAdapter(
+ new ArrayAdapter<>(
+ this, android.R.layout.simple_list_item_1, TriviaCategory.values()));
+
+ spinnerDifficulty.setAdapter(
+ new ArrayAdapter<>(
+ this, android.R.layout.simple_list_item_1, TriviaDifficulty.values()));
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/TriviaGameActivity.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/TriviaGameActivity.java
new file mode 100644
index 0000000..779b792
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/TriviaGameActivity.java
@@ -0,0 +1,227 @@
+package io.github.trytonvanmeer.libretrivia.activities;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.io.IOException;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import io.github.trytonvanmeer.libretrivia.R;
+import io.github.trytonvanmeer.libretrivia.exceptions.NoTriviaResultsException;
+import io.github.trytonvanmeer.libretrivia.fragments.TriviaGameErrorFragment;
+import io.github.trytonvanmeer.libretrivia.fragments.TriviaQuestionFragment;
+import io.github.trytonvanmeer.libretrivia.interfaces.IDownloadTriviaQuestionReceiver;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaGame;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuery;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestion;
+import io.github.trytonvanmeer.libretrivia.util.ApiUtil;
+import io.github.trytonvanmeer.libretrivia.util.SoundUtil;
+
+public class TriviaGameActivity extends BaseActivity
+ implements IDownloadTriviaQuestionReceiver {
+ static final String EXTRA_TRIVIA_QUERY = "extra_trivia_query";
+ private final String STATE_TRIVIA_GAME = "state_trivia_game";
+
+ private TriviaGame game;
+
+ @BindView(R.id.progress_bar)
+ ProgressBar progressBar;
+ @BindView(R.id.trivia_status_bar)
+ LinearLayout triviaStatusBar;
+ @BindView(R.id.text_question_category)
+ TextView textViewQuestionCategory;
+ @BindView(R.id.text_question_difficulty)
+ TextView textViewQuestionDifficulty;
+ @BindView(R.id.text_question_progress)
+ TextView textViewQuestionProgress;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_trivia_game);
+ ButterKnife.bind(this);
+
+ if (savedInstanceState != null) {
+ this.game = (TriviaGame) savedInstanceState.getSerializable(STATE_TRIVIA_GAME);
+ } else {
+ Bundle bundle = getIntent().getExtras();
+ assert bundle != null;
+ TriviaQuery query = (TriviaQuery) bundle.get(EXTRA_TRIVIA_QUERY);
+
+ progressBar.setVisibility(View.VISIBLE);
+
+ DownloadTriviaQuestionsTask task = new DownloadTriviaQuestionsTask();
+ task.setReceiver(this);
+ task.execute(query);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putSerializable(STATE_TRIVIA_GAME, this.game);
+ }
+
+ @Override
+ public void onBackPressed() {
+ Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frame_trivia_game);
+
+ if (fragment instanceof TriviaGameErrorFragment) {
+ super.onBackPressed();
+ } else {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.ui_quit_game)
+ .setMessage(R.string.ui_quit_game_msg)
+ .setPositiveButton(android.R.string.yes, (dialog, which) ->
+ TriviaGameActivity.super.onBackPressed())
+ .setNegativeButton(android.R.string.no, (dialog, which) -> {
+ })
+ .show();
+ }
+ }
+
+ public void onTriviaQuestionsDownloaded(String json) {
+ if (json == null) {
+ onNetworkError();
+ return;
+ } else {
+ try {
+ this.game = new TriviaGame(ApiUtil.jsonToQuestionArray(json));
+ } catch (NoTriviaResultsException e) {
+ onNoTriviaResults();
+ return;
+ }
+ }
+
+ // Setup game layout
+ progressBar.setVisibility(View.GONE);
+ triviaStatusBar.setVisibility(View.VISIBLE);
+ updateStatusBar();
+ updateTriviaQuestion();
+ }
+
+ private void updateStatusBar() {
+ String progress = getResources().getString(R.string.ui_question_progress,
+ game.getQuestionProgress(), game.getQuestionsCount());
+
+ String category = (game.getCurrentQuestion().getCategory() != null)
+ ? game.getCurrentQuestion().getCategory().toString() : "";
+
+ String difficulty = game.getCurrentQuestion().getDifficulty().toString();
+
+ textViewQuestionProgress.setText(progress);
+ textViewQuestionCategory.setText(category);
+ textViewQuestionDifficulty.setText(difficulty);
+ }
+
+ private void updateTriviaQuestion() {
+ Fragment fragment = TriviaQuestionFragment.newInstance();
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.frame_trivia_game, fragment)
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .commit();
+ }
+
+ private void onNetworkError() {
+ String msg = getResources().getString(R.string.error_network);
+ Fragment errorFragment = TriviaGameErrorFragment.newInstance(msg);
+
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ ft.replace(R.id.frame_trivia_game, errorFragment);
+ ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+ ft.commit();
+ }
+
+ private void onNoTriviaResults() {
+ String msg = getResources().getString(R.string.error_no_trivia_results);
+ Fragment errorFragment = TriviaGameErrorFragment.newInstance(msg);
+
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ ft.replace(R.id.frame_trivia_game, errorFragment);
+ ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
+ ft.commit();
+ }
+
+ public TriviaQuestion getCurrentQuestion() {
+ return this.game.getCurrentQuestion();
+ }
+
+ public void onAnswerClick(Button answer, Button correctAnswer) {
+ boolean guess = game.nextQuestion(answer.getText().toString());
+
+ final int green = getResources().getColor(R.color.colorAccentGreen);
+ int color = guess ? green
+ : getResources().getColor(R.color.colorAccentRed);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ColorStateList stateList = ColorStateList.valueOf(color);
+ answer.setBackgroundTintList(stateList);
+
+ if (!guess) {
+ final ColorStateList greenStateList = ColorStateList.valueOf(green);
+ correctAnswer.setBackgroundTintList(greenStateList);
+ }
+ } else {
+ answer.getBackground().getCurrent().setColorFilter(
+ new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
+
+ if (!guess)
+ correctAnswer.getBackground().getCurrent().setColorFilter(
+ new PorterDuffColorFilter(green, PorterDuff.Mode.MULTIPLY));
+ }
+
+ SoundUtil.playSound(this, guess ?
+ SoundUtil.SOUND_ANSWER_CORRECT : SoundUtil.SOUND_ANSWER_WRONG);
+
+ new Handler().postDelayed(() -> {
+ if (game.isDone()) {
+ Intent intent = new Intent(getApplicationContext(), TriviaGameResultsActivity.class);
+ intent.putExtra(TriviaGameResultsActivity.EXTRA_TRIVIA_GAME, game);
+ startActivity(intent);
+ finish();
+ } else {
+ updateStatusBar();
+ updateTriviaQuestion();
+ }
+ }, 500);
+ }
+
+ private static class DownloadTriviaQuestionsTask extends AsyncTask {
+ private IDownloadTriviaQuestionReceiver receiver;
+
+ @Override
+ protected String doInBackground(TriviaQuery... query) {
+ String json;
+ try {
+ json = ApiUtil.GET(query[0]);
+ } catch (IOException e) {
+ return null;
+ }
+ return json;
+ }
+
+ @Override
+ protected void onPostExecute(String json) {
+ receiver.onTriviaQuestionsDownloaded(json);
+ }
+
+ private void setReceiver(IDownloadTriviaQuestionReceiver receiver) {
+ this.receiver = receiver;
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/TriviaGameResultsActivity.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/TriviaGameResultsActivity.java
new file mode 100644
index 0000000..6f33c18
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/activities/TriviaGameResultsActivity.java
@@ -0,0 +1,47 @@
+package io.github.trytonvanmeer.libretrivia.activities;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import io.github.trytonvanmeer.libretrivia.R;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaGame;
+
+public class TriviaGameResultsActivity extends BaseActivity {
+ static final String EXTRA_TRIVIA_GAME = "extra_trivia_game";
+
+ @BindView(R.id.text_results_correct)
+ TextView textResultsCorrect;
+ @BindView(R.id.text_results_wrong)
+ TextView textResultsWrong;
+ @BindView(R.id.text_results_total)
+ TextView textResultsTotal;
+ @BindView(R.id.button_return_to_menu)
+ Button buttonReturnToMenu;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_trivia_game_results);
+ ButterKnife.bind(this);
+
+ Bundle bundle = getIntent().getExtras();
+ TriviaGame game = (TriviaGame) bundle.get(EXTRA_TRIVIA_GAME);
+
+ int correctTotal = 0;
+
+ for (boolean result : game.getResults()) {
+ if (result) {
+ correctTotal++;
+ }
+ }
+
+ textResultsCorrect.setText(String.valueOf(correctTotal));
+ textResultsWrong.setText(String.valueOf(game.getQuestionsCount() - correctTotal));
+ textResultsTotal.setText(String.valueOf(game.getQuestionsCount()));
+
+ buttonReturnToMenu.setOnClickListener(v -> finish());
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/exceptions/NoTriviaResultsException.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/exceptions/NoTriviaResultsException.java
new file mode 100644
index 0000000..58dafff
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/exceptions/NoTriviaResultsException.java
@@ -0,0 +1,4 @@
+package io.github.trytonvanmeer.libretrivia.exceptions;
+
+public class NoTriviaResultsException extends Exception {
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/fragments/TriviaGameErrorFragment.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/fragments/TriviaGameErrorFragment.java
new file mode 100644
index 0000000..036c9ec
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/fragments/TriviaGameErrorFragment.java
@@ -0,0 +1,46 @@
+package io.github.trytonvanmeer.libretrivia.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import io.github.trytonvanmeer.libretrivia.R;
+
+public class TriviaGameErrorFragment extends Fragment {
+ private final static String ARG_ERROR_MSG = "arg_error_msg";
+
+ @BindView(R.id.text_error_msg)
+ TextView textView;
+
+ public TriviaGameErrorFragment() {
+ }
+
+ public static TriviaGameErrorFragment newInstance(String msg) {
+ Bundle args = new Bundle();
+ args.putString(ARG_ERROR_MSG, msg);
+
+ TriviaGameErrorFragment fragment = new TriviaGameErrorFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_trivia_game_error, container, false);
+ ButterKnife.bind(this, view);
+
+ Bundle args;
+ if ((args = getArguments()) != null) {
+ textView.setText(args.getString(ARG_ERROR_MSG));
+ }
+
+ return view;
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/fragments/TriviaQuestionFragment.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/fragments/TriviaQuestionFragment.java
new file mode 100644
index 0000000..470c364
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/fragments/TriviaQuestionFragment.java
@@ -0,0 +1,117 @@
+package io.github.trytonvanmeer.libretrivia.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import butterknife.BindViews;
+import butterknife.ButterKnife;
+import io.github.trytonvanmeer.libretrivia.R;
+import io.github.trytonvanmeer.libretrivia.activities.TriviaGameActivity;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestion;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestionMultiple;
+
+
+public class TriviaQuestionFragment extends Fragment {
+
+ private static final int buttonAnswerOneID = R.id.button_answer_one;
+ private static final int buttonAnswerTwoID = R.id.button_answer_two;
+ private static final int buttonAnswerThreeID = R.id.button_answer_three;
+ private static final int buttonAnswerFourID = R.id.button_answer_four;
+
+ @BindViews({
+ buttonAnswerOneID,
+ buttonAnswerTwoID,
+ buttonAnswerThreeID,
+ buttonAnswerFourID
+ })
+ Button[] buttonAnswers;
+
+ Button buttonAnswerCorrect;
+
+ Button buttonAnswerTrue;
+ Button buttonAnswerFalse;
+
+ public TriviaQuestionFragment() {
+ }
+
+ public static TriviaQuestionFragment newInstance() {
+ return new TriviaQuestionFragment();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ TriviaQuestion question = ((TriviaGameActivity) getActivity()).getCurrentQuestion();
+ View view;
+
+ if (question instanceof TriviaQuestionMultiple) {
+ view = inflater.inflate(R.layout.fragment_trivia_question_multiple, container, false);
+ ButterKnife.bind(this, view);
+ } else {
+ view = inflater.inflate(R.layout.fragment_trivia_question_boolean, container, false);
+ this.buttonAnswerTrue = view.findViewById(R.id.button_answer_true);
+ this.buttonAnswerFalse = view.findViewById(R.id.button_answer_false);
+ }
+
+ TextView textViewQuestion = view.findViewById(R.id.text_trivia_question);
+ textViewQuestion.setText(question.getQuestion());
+ setupButtons();
+
+ return view;
+ }
+
+ private void setupButtons() {
+ AnswerButtonListener listener = new AnswerButtonListener();
+ TriviaQuestion question = ((TriviaGameActivity) getActivity()).getCurrentQuestion();
+
+ if (question instanceof TriviaQuestionMultiple) {
+ List answers = Arrays.asList((
+ (TriviaQuestionMultiple) question).getAnswerList());
+ Collections.shuffle(answers);
+
+ for (int i = 0; i < buttonAnswers.length; i++) {
+ buttonAnswers[i].setText(answers.get(i));
+ buttonAnswers[i].setOnClickListener(listener);
+ if (question.checkAnswer(answers.get(i))) {
+ buttonAnswerCorrect = buttonAnswers[i];
+ }
+ }
+ } else {
+ buttonAnswerTrue.setOnClickListener(listener);
+ buttonAnswerFalse.setOnClickListener(listener);
+ }
+ }
+
+ private void disableButtons() {
+ TriviaQuestion question = ((TriviaGameActivity) getActivity()).getCurrentQuestion();
+ if (question instanceof TriviaQuestionMultiple) {
+ buttonAnswers[0].setEnabled(false);
+ buttonAnswers[1].setEnabled(false);
+ buttonAnswers[2].setEnabled(false);
+ buttonAnswers[3].setEnabled(false);
+ } else {
+ buttonAnswerTrue.setEnabled(false);
+ buttonAnswerFalse.setEnabled(false);
+ }
+ }
+
+ private class AnswerButtonListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ disableButtons();
+ ((TriviaGameActivity) getActivity()).onAnswerClick((Button) v, buttonAnswerCorrect);
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/interfaces/IDownloadTriviaQuestionReceiver.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/interfaces/IDownloadTriviaQuestionReceiver.java
new file mode 100644
index 0000000..4be0ee9
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/interfaces/IDownloadTriviaQuestionReceiver.java
@@ -0,0 +1,5 @@
+package io.github.trytonvanmeer.libretrivia.interfaces;
+
+public interface IDownloadTriviaQuestionReceiver {
+ void onTriviaQuestionsDownloaded(String json);
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/settings/SettingsActivity.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/settings/SettingsActivity.java
new file mode 100644
index 0000000..fff1083
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/settings/SettingsActivity.java
@@ -0,0 +1,37 @@
+package io.github.trytonvanmeer.libretrivia.settings;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class SettingsActivity extends AppCompatActivity {
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(null);
+ }
+
+ getFragmentManager()
+ .beginTransaction()
+ .replace(android.R.id.content, new SettingsFragment())
+ .commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/settings/SettingsFragment.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/settings/SettingsFragment.java
new file mode 100644
index 0000000..fa04ca5
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/settings/SettingsFragment.java
@@ -0,0 +1,15 @@
+package io.github.trytonvanmeer.libretrivia.settings;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+import androidx.annotation.Nullable;
+import io.github.trytonvanmeer.libretrivia.R;
+
+public class SettingsFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaCategory.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaCategory.java
new file mode 100644
index 0000000..65003de
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaCategory.java
@@ -0,0 +1,102 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import io.github.trytonvanmeer.libretrivia.LibreTriviaApplication;
+import io.github.trytonvanmeer.libretrivia.R;
+
+/*
+ Categories that a Trivia Question can fall into
+ */
+public enum TriviaCategory {
+ ANY(-1, "Any", R.string.ui_any),
+
+ GENERAL_KNOWLEDGE(9, "General Knowledge",
+ R.string.category_general_knowledge),
+ ENTERTAINMENT_BOOKS(10, "Entertainment: Books",
+ R.string.category_entertainment_books),
+ ENTERTAINMENT_FILM(11, "Entertainment: Film",
+ R.string.category_entertainment_film),
+ ENTERTAINMENT_MUSIC(12, "Entertainment: Music",
+ R.string.category_entertainment_music),
+ ENTERTAINMENT_MUSICALS_THEATRES(13, "Entertainment: Musicals & Theatres",
+ R.string.category_entertainment_musicals_theatres),
+ ENTERTAINMENT_TELEVISION(14, "Entertainment: Television",
+ R.string.category_entertainment_television),
+ ENTERTAINMENT_VIDEO_GAMES(15, "Entertainment: Video Games",
+ R.string.category_entertainment_video_games),
+ ENTERTAINMENT_BOARD_GAMES(16, "Entertainment: Board Games",
+ R.string.category_entertainment_board_games),
+ ENTERTAINMENT_JAPANESE_ANIME_MANGA(31, "Entertainment: Japanese Anime & Manga",
+ R.string.category_entertainment_japanese_anime_manga),
+ ENTERTAINMENT_CARTOON_ANIMATIONS(32, "Entertainment: Cartoons & Animation",
+ R.string.category_entertainment_cartoon_animations),
+ ENTERTAINMENT_COMICS(29, "Entertainment: Comics",
+ R.string.category_entertainment_comics),
+ SCIENCE_NATURE(17, "Science & Nature",
+ R.string.category_science_nature),
+ SCIENCE_COMPUTERS(18, "Science: Computers",
+ R.string.category_science_computers),
+ SCIENCE_MATHEMATICS(19, "Science: Mathematics",
+ R.string.category_science_mathematics),
+ SCIENCE_GADGETS(30, "Science: Gadgets",
+ R.string.category_science_gadgets),
+ MYTHOLOGY(20, "Mythology",
+ R.string.category_mythology),
+ SPORTS(21, "Sports",
+ R.string.category_sports),
+ GEOGRAPHY(22, "Geography",
+ R.string.category_geography),
+ HISTORY(23, "History",
+ R.string.category_history),
+ POLITICS(24, "Politics",
+ R.string.category_politics),
+ ART(25, "Art",
+ R.string.category_art),
+ CELEBRITIES(26, "Celebrities",
+ R.string.category_celebrities),
+ ANIMALS(27, "Animals",
+ R.string.category_animals),
+ VEHICLES(28, "Vehicles",
+ R.string.category_vehicles);
+
+ // The id of the category in the opentdb api
+ // see
+ private final int ID;
+ // The name of the category in the JSON response
+ private final String name;
+ // The display name of the category
+ private final int displayName;
+
+ private static final Map lookup = new HashMap<>();
+
+ static {
+ for (TriviaCategory category : TriviaCategory.values()) {
+ lookup.put(category.getName(), category);
+ }
+ }
+
+ TriviaCategory(int ID, String name, int displayName) {
+ this.ID = ID;
+ this.name = name;
+ this.displayName = displayName;
+ }
+
+ public int getID() {
+ return this.ID;
+ }
+
+ private String getName() {
+ return this.name;
+ }
+
+ public static TriviaCategory get(String name) {
+ return lookup.get(name);
+ }
+
+ @Override
+ public String toString() {
+ return LibreTriviaApplication.getAppContext().getResources().getString(this.displayName);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaDifficulty.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaDifficulty.java
new file mode 100644
index 0000000..b2c4084
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaDifficulty.java
@@ -0,0 +1,46 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import io.github.trytonvanmeer.libretrivia.LibreTriviaApplication;
+import io.github.trytonvanmeer.libretrivia.R;
+
+public enum TriviaDifficulty {
+ ANY("any", R.string.ui_any),
+
+ EASY("easy", R.string.difficulty_easy),
+ MEDIUM("medium", R.string.difficulty_medium),
+ HARD("hard", R.string.difficulty_hard);
+
+ // Name of difficulty used in queries
+ private final String name;
+ // Display name of the difficulty
+ private final int displayName;
+
+ private static final Map lookup = new HashMap<>();
+
+ static {
+ for (TriviaDifficulty difficulty : TriviaDifficulty.values()) {
+ lookup.put(difficulty.getName(), difficulty);
+ }
+ }
+
+ TriviaDifficulty(String name, int displayName) {
+ this.name = name;
+ this.displayName = displayName;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public static TriviaDifficulty get(String name) {
+ return lookup.get(name);
+ }
+
+ @Override
+ public String toString() {
+ return LibreTriviaApplication.getAppContext().getResources().getString(this.displayName);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaGame.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaGame.java
new file mode 100644
index 0000000..f69f78b
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaGame.java
@@ -0,0 +1,46 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class TriviaGame implements Serializable {
+ private int currentQuestion;
+ private final boolean[] results;
+ private final List questions;
+
+ public TriviaGame(List questions) {
+ this.currentQuestion = 0;
+ this.questions = questions;
+ this.results = new boolean[questions.size()];
+ }
+
+ public TriviaQuestion getCurrentQuestion() {
+ return this.questions.get(currentQuestion);
+ }
+
+ public int getQuestionProgress() {
+ return this.currentQuestion + 1;
+ }
+
+ public int getQuestionsCount() {
+ return this.questions.size();
+ }
+
+ public boolean[] getResults() {
+ return this.results;
+ }
+
+ public boolean nextQuestion(String guess) {
+ TriviaQuestion question = getCurrentQuestion();
+ boolean answer = question.checkAnswer(guess);
+
+ results[currentQuestion] = answer;
+ currentQuestion++;
+
+ return answer;
+ }
+
+ public boolean isDone() {
+ return (this.currentQuestion == questions.size());
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuery.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuery.java
new file mode 100644
index 0000000..fed6828
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuery.java
@@ -0,0 +1,78 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import java.io.Serializable;
+
+public class TriviaQuery implements Serializable {
+ private static final String BASE = "https://opentdb.com/api.php?";
+ private static final int DEFAULT_AMOUNT = 10;
+
+ private final int amount;
+ private final TriviaCategory category;
+ private final TriviaDifficulty difficulty;
+ private final TriviaType type;
+
+ private TriviaQuery(Builder builder) {
+ this.amount = builder.amount;
+ this.category = builder.category;
+ this.difficulty = builder.difficulty;
+ this.type = builder.type;
+ }
+
+ public static class Builder {
+ private final int amount;
+ private TriviaCategory category;
+ private TriviaDifficulty difficulty;
+ private TriviaType type;
+
+ public Builder() {
+ this.amount = DEFAULT_AMOUNT;
+ }
+
+ public Builder(int amount) {
+ if (amount > 50) {
+ this.amount = 50;
+ } else {
+ this.amount = amount;
+ }
+ }
+
+ public Builder category(TriviaCategory category) {
+ this.category = category;
+ return this;
+ }
+
+ public Builder difficulty(TriviaDifficulty difficulty) {
+ this.difficulty = difficulty;
+ return this;
+ }
+
+ public Builder type(TriviaType type) {
+ this.type = type;
+ return this;
+ }
+
+ public TriviaQuery build() {
+ return new TriviaQuery(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder url = new StringBuilder();
+
+ url.append(BASE);
+ url.append("amount=").append(this.amount);
+
+ if (this.category != null & this.category != TriviaCategory.ANY) {
+ url.append("&category=").append(this.category.getID());
+ }
+ if (this.difficulty != null & this.difficulty != TriviaDifficulty.ANY) {
+ url.append("&difficulty=").append(this.difficulty.getName());
+ }
+ if (this.type != null & this.type != TriviaType.ANY) {
+ url.append("&type=").append(this.type.getName());
+ }
+
+ return url.toString();
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestion.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestion.java
new file mode 100644
index 0000000..b708006
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestion.java
@@ -0,0 +1,31 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import java.io.Serializable;
+
+public abstract class TriviaQuestion implements Serializable {
+ private final TriviaCategory category;
+ private final TriviaDifficulty difficulty;
+
+ private final String question;
+
+ TriviaQuestion(TriviaCategory category, TriviaDifficulty difficulty, String question) {
+ this.category = category;
+ this.difficulty = difficulty;
+
+ this.question = question;
+ }
+
+ public TriviaCategory getCategory() {
+ return this.category;
+ }
+
+ public TriviaDifficulty getDifficulty() {
+ return this.difficulty;
+ }
+
+ public String getQuestion() {
+ return this.question;
+ }
+
+ public abstract boolean checkAnswer(String guess);
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestionBoolean.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestionBoolean.java
new file mode 100644
index 0000000..6a70516
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestionBoolean.java
@@ -0,0 +1,34 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import android.text.Html;
+
+import com.google.gson.JsonObject;
+
+public class TriviaQuestionBoolean extends TriviaQuestion {
+
+ private final Boolean correctAnswer;
+
+ public TriviaQuestionBoolean(TriviaCategory category, TriviaDifficulty difficulty,
+ String question, boolean correctAnswer) {
+ super(category, difficulty, question);
+ this.correctAnswer = correctAnswer;
+ }
+
+ @Override
+ public boolean checkAnswer(String guess) {
+ return this.correctAnswer.equals(Boolean.valueOf(guess));
+ }
+
+ public boolean checkAnswer(Boolean guess) {
+ return checkAnswer(guess.toString());
+ }
+
+ public static TriviaQuestionBoolean fromJson(JsonObject json) {
+ TriviaCategory category = TriviaCategory.get(json.get("category").getAsString());
+ TriviaDifficulty difficulty = TriviaDifficulty.get(json.get("difficulty").getAsString());
+ String question = Html.fromHtml(json.get("question").getAsString()).toString();
+ Boolean correctAnswer = json.get("correct_answer").getAsBoolean();
+
+ return new TriviaQuestionBoolean(category, difficulty, question, correctAnswer);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestionMultiple.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestionMultiple.java
new file mode 100644
index 0000000..7674790
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaQuestionMultiple.java
@@ -0,0 +1,48 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import android.text.Html;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+public class TriviaQuestionMultiple extends TriviaQuestion {
+ private final String correctAnswer;
+ private final String[] incorrectAnswers;
+
+ public TriviaQuestionMultiple(TriviaCategory category, TriviaDifficulty difficulty,
+ String question, String correctAnswer, String[] incorrectAnswers) {
+ super(category, difficulty, question);
+
+ this.correctAnswer = correctAnswer;
+ this.incorrectAnswers = incorrectAnswers;
+ }
+
+ public String[] getAnswerList() {
+ return new String[]{correctAnswer,
+ incorrectAnswers[0],
+ incorrectAnswers[1],
+ incorrectAnswers[2]};
+ }
+
+ @Override
+ public boolean checkAnswer(String guess) {
+ return this.correctAnswer.equals(guess);
+ }
+
+ public static TriviaQuestionMultiple fromJson(JsonObject json) {
+ TriviaCategory category = TriviaCategory.get(json.get("category").getAsString());
+ TriviaDifficulty difficulty = TriviaDifficulty.get(json.get("difficulty").getAsString());
+ String question = Html.fromHtml(json.get("question").getAsString()).toString();
+ String correctAnswer = Html.fromHtml(json.get("correct_answer").getAsString()).toString();
+
+ JsonArray incorrectAnswersJson = json.get("incorrect_answers").getAsJsonArray();
+ String[] incorrectAnswers = new String[]{
+ Html.fromHtml(incorrectAnswersJson.get(0).getAsString()).toString(),
+ Html.fromHtml(incorrectAnswersJson.get(1).getAsString()).toString(),
+ Html.fromHtml(incorrectAnswersJson.get(2).getAsString()).toString()
+ };
+
+ return new TriviaQuestionMultiple(
+ category, difficulty, question, correctAnswer, incorrectAnswers);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaType.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaType.java
new file mode 100644
index 0000000..8f8f76f
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/trivia/TriviaType.java
@@ -0,0 +1,45 @@
+package io.github.trytonvanmeer.libretrivia.trivia;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import io.github.trytonvanmeer.libretrivia.LibreTriviaApplication;
+import io.github.trytonvanmeer.libretrivia.R;
+
+public enum TriviaType {
+ ANY("any", R.string.ui_any),
+
+ MULTIPLE("multiple", R.string.question_type_multiple),
+ BOOLEAN("boolean", R.string.question_type_boolean);
+
+ // Name of type used in queries
+ private final String name;
+ // Display name of the type
+ private final int displayName;
+
+ private static final Map lookup = new HashMap<>();
+
+ static {
+ for (TriviaType type : TriviaType.values()) {
+ lookup.put(type.getName(), type);
+ }
+ }
+
+ TriviaType(String name, int displayName) {
+ this.name = name;
+ this.displayName = displayName;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public static TriviaType get(String name) {
+ return lookup.get(name);
+ }
+
+ @Override
+ public String toString() {
+ return LibreTriviaApplication.getAppContext().getResources().getString(this.displayName);
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/util/ApiUtil.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/util/ApiUtil.java
new file mode 100644
index 0000000..a6e4374
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/util/ApiUtil.java
@@ -0,0 +1,82 @@
+package io.github.trytonvanmeer.libretrivia.util;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+
+import io.github.trytonvanmeer.libretrivia.exceptions.NoTriviaResultsException;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuery;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestion;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestionBoolean;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestionMultiple;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaType;
+
+public class ApiUtil {
+
+ private static String readStream(InputStream in) throws IOException {
+ StringBuilder builder = new StringBuilder();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in), 1000);
+
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ builder.append(line);
+ }
+
+ in.close();
+ return builder.toString();
+ }
+
+ private static String GET(String query) throws IOException {
+ String response;
+
+ URL url = new URL(query);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ try {
+ InputStream in = new BufferedInputStream(connection.getInputStream());
+ response = readStream(in);
+ } finally {
+ connection.disconnect();
+ }
+
+ return response;
+ }
+
+ public static String GET(TriviaQuery query) throws IOException {
+ return GET(query.toString());
+ }
+
+ public static ArrayList jsonToQuestionArray(String json) throws NoTriviaResultsException {
+ JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
+
+ if (jsonObject.get("response_code").getAsInt() == 1) {
+ throw new NoTriviaResultsException();
+ }
+
+ JsonArray jsonArray = jsonObject.getAsJsonArray("results");
+
+ ArrayList questions = new ArrayList<>();
+
+ for (JsonElement element : jsonArray) {
+ JsonObject object = element.getAsJsonObject();
+ TriviaType type = TriviaType.get(object.get("type").getAsString());
+
+ if (type == TriviaType.MULTIPLE) {
+ questions.add(TriviaQuestionMultiple.fromJson(object));
+ } else {
+ questions.add(TriviaQuestionBoolean.fromJson(object));
+ }
+ }
+
+ return questions;
+ }
+}
diff --git a/app/src/main/java/io/github/trytonvanmeer/libretrivia/util/SoundUtil.java b/app/src/main/java/io/github/trytonvanmeer/libretrivia/util/SoundUtil.java
new file mode 100644
index 0000000..07e393f
--- /dev/null
+++ b/app/src/main/java/io/github/trytonvanmeer/libretrivia/util/SoundUtil.java
@@ -0,0 +1,25 @@
+package io.github.trytonvanmeer.libretrivia.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.media.MediaPlayer;
+import android.preference.PreferenceManager;
+
+import io.github.trytonvanmeer.libretrivia.R;
+
+public class SoundUtil {
+ public static final int SOUND_ANSWER_CORRECT = R.raw.sound_answer_correct;
+ public static final int SOUND_ANSWER_WRONG = R.raw.sound_answer_wrong;
+
+ public static void playSound(Context context, int sound) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ String key_sound = context.getResources().getString(R.string.pref_sound_answer);
+ boolean pref_sound = preferences.getBoolean(key_sound, true);
+
+ if (pref_sound) {
+ final MediaPlayer player = MediaPlayer.create(context, sound);
+ player.setVolume(0.25f, 0.25f);
+ player.start();
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..6b5d817
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_clipboard_text.xml b/app/src/main/res/drawable/ic_clipboard_text.xml
new file mode 100644
index 0000000..14e9928
--- /dev/null
+++ b/app/src/main/res/drawable/ic_clipboard_text.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_emoticon_sad.xml b/app/src/main/res/drawable/ic_emoticon_sad.xml
new file mode 100644
index 0000000..b269c0b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_emoticon_sad.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..20db57f
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_trivia_game.xml b/app/src/main/res/layout/activity_trivia_game.xml
new file mode 100644
index 0000000..e40bdc5
--- /dev/null
+++ b/app/src/main/res/layout/activity_trivia_game.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_trivia_game_results.xml b/app/src/main/res/layout/activity_trivia_game_results.xml
new file mode 100644
index 0000000..1806161
--- /dev/null
+++ b/app/src/main/res/layout/activity_trivia_game_results.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_trivia_game_error.xml b/app/src/main/res/layout/fragment_trivia_game_error.xml
new file mode 100644
index 0000000..e82fcdd
--- /dev/null
+++ b/app/src/main/res/layout/fragment_trivia_game_error.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_trivia_question_boolean.xml b/app/src/main/res/layout/fragment_trivia_question_boolean.xml
new file mode 100644
index 0000000..6c4b14f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_trivia_question_boolean.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_trivia_question_multiple.xml b/app/src/main/res/layout/fragment_trivia_question_multiple.xml
new file mode 100644
index 0000000..d9f9ecd
--- /dev/null
+++ b/app/src/main/res/layout/fragment_trivia_question_multiple.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/app_menu.xml b/app/src/main/res/menu/app_menu.xml
new file mode 100644
index 0000000..dac8bf0
--- /dev/null
+++ b/app/src/main/res/menu/app_menu.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..d59ec8e
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..d59ec8e
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..ba47b86
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e3cf1f9
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..e1fcc92
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..6d6c646
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..20c32ce
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..6254e94
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..bc9a985
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..ca98ecb
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..05ccdf7
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..599ddd6
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/raw/sound_answer_correct.wav b/app/src/main/res/raw/sound_answer_correct.wav
new file mode 100644
index 0000000..002edaa
Binary files /dev/null and b/app/src/main/res/raw/sound_answer_correct.wav differ
diff --git a/app/src/main/res/raw/sound_answer_wrong.wav b/app/src/main/res/raw/sound_answer_wrong.wav
new file mode 100644
index 0000000..c43ec66
Binary files /dev/null and b/app/src/main/res/raw/sound_answer_wrong.wav differ
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..1ec3d4c
--- /dev/null
+++ b/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,69 @@
+
+ Ein Open-Source-Trivia-Spiel
+
+
+ Allgemeinwissen
+ Unterhaltung: Bücher
+ Unterhaltung: Filme
+ Unterhaltung: Musik
+ Unterhaltung: Musicals und Theater
+ Unterhaltung: Fernsehen
+ Unterhaltung: Video Spiele
+ Unterhaltung: Brettspiele
+ Unterhaltung: Japanische Animes & Mangas
+ Unterhaltung: Cartoons & Animation
+ Unterhaltung: Comics
+ Wissenschaft und Natur
+ Wissenschaft: Computer
+ Wissenschaft: Mathematik
+ Wissenschaft: Gadgets
+ Mythologie
+ Sport
+ Geographie
+ Geschichte
+ Politik
+ Kunst
+ Prominente
+ Tiere
+ Fahrzeuge
+
+
+ Einfach
+ Mittel
+ Schwer
+
+
+ Multiple-Choice
+ Richtig / Falsch
+
+
+ Einstellungen
+ Über
+ Spielen
+ Spiel starten
+ Alle
+ Richtig
+ Falsch
+ Richtig
+ Falsch
+ Fragen
+ Kategorie
+ Schwierigkeit
+ %1$d⁄%2$d
+ Spiel verlassen
+ Bist du dir sicher, dass du dieses Spiel verlassen möchtest?
+ Richtige Antworten
+ Flasche Antworten
+ Insgesamte Fragen
+ Zurück ins Menü
+
+
+ Netzwerk Fehler!\n
+ Verbinden mit einem Netzwerk nicht möglich
+ Keine Trivia Ergebnisse!\n
+ Es wurden keine Trivia Fragen gefunden, die alle Bedingungen erfüllt haben.
+ Einstellungen
+
+
+ Ton
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..8f88c0e
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,12 @@
+
+
+ #F44336
+ #D32F2F
+ #536DFE
+ #212121
+ #757575
+ #FFFFFF
+
+ #D50000
+ #00C853
+
diff --git a/app/src/main/res/values/pref_keys.xml b/app/src/main/res/values/pref_keys.xml
new file mode 100644
index 0000000..776c47b
--- /dev/null
+++ b/app/src/main/res/values/pref_keys.xml
@@ -0,0 +1,4 @@
+
+ pref_key_category_sound
+ pref_sound_answer
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..59ccd25
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,69 @@
+
+ LibreTrivia
+ An open source trivia game.
+
+
+ General Knowledge
+ Entertainment: Books
+ Entertainment: Film
+ Entertainment: Music
+ Entertainment: Musicals & Theatres
+ Entertainment: Television
+ Entertainment: Video Games
+ Entertainment: Board Games
+ Entertainment: Japanese Anime & Manga
+ Entertainment: Cartoons & Animation
+ Entertainment: Comics
+ Science & Nature
+ Science: Computers
+ Science: Mathematics
+ Science: Gadgets
+ Mythology
+ Sports
+ Geography
+ History
+ Politics
+ Art
+ Celebrities
+ Animals
+ Vehicles
+
+
+ Easy
+ Medium
+ Hard
+
+
+ Multiple Choice
+ True / False
+
+
+ Settings
+ About
+ Play
+ Start Game
+ Any
+ True
+ False
+ Correct
+ Wrong
+ Questions
+ Category
+ Difficulty
+ %1$d⁄%2$d
+ Quit Game
+ Are you sure you want to quit this game?
+ Correct Answers
+ Wrong Answers
+ Total Questions
+ Return To Menu
+
+
+ Network error!\n Could not connect to a network.
+ No trivia results!\n Was not able to find trivia questions that satisfied all options.
+ Settings
+
+
+ Sound
+ Answer Sound Effect
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..247a02d
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
new file mode 100644
index 0000000..00975f9
--- /dev/null
+++ b/app/src/main/res/xml/preferences.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/io/github/trytonvanmeer/libretrivia/TriviaQueryTest.java b/app/src/test/java/io/github/trytonvanmeer/libretrivia/TriviaQueryTest.java
new file mode 100644
index 0000000..6f8c5a0
--- /dev/null
+++ b/app/src/test/java/io/github/trytonvanmeer/libretrivia/TriviaQueryTest.java
@@ -0,0 +1,40 @@
+package io.github.trytonvanmeer.libretrivia;
+
+import org.junit.jupiter.api.Test;
+
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaCategory;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaDifficulty;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuery;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaType;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TriviaQueryTest {
+
+ @Test
+ public void triviaQuery_MatchQuery() {
+ TriviaQuery query = new TriviaQuery.Builder().build();
+
+ assertEquals("https://opentdb.com/api.php?amount=10", query.toString());
+ }
+
+ @Test
+ public void triviaQuery_MatchQuery_WithParams() {
+ TriviaQuery query = new TriviaQuery.Builder(20)
+ .category(TriviaCategory.GENERAL_KNOWLEDGE)
+ .difficulty(TriviaDifficulty.EASY)
+ .type(TriviaType.MULTIPLE)
+ .build();
+
+ assertEquals(
+ "https://opentdb.com/api.php?amount=20&category=9&difficulty=easy&type=multiple",
+ query.toString());
+ }
+
+ @Test
+ public void triviaQuery_AmountExceedFifty() {
+ TriviaQuery query = new TriviaQuery.Builder(500).build();
+
+ assertEquals("https://opentdb.com/api.php?amount=50", query.toString());
+ }
+}
diff --git a/app/src/test/java/io/github/trytonvanmeer/libretrivia/TriviaQuestionTest.java b/app/src/test/java/io/github/trytonvanmeer/libretrivia/TriviaQuestionTest.java
new file mode 100644
index 0000000..0ddda97
--- /dev/null
+++ b/app/src/test/java/io/github/trytonvanmeer/libretrivia/TriviaQuestionTest.java
@@ -0,0 +1,107 @@
+package io.github.trytonvanmeer.libretrivia;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import org.junit.jupiter.api.Test;
+
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaCategory;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaDifficulty;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestionBoolean;
+import io.github.trytonvanmeer.libretrivia.trivia.TriviaQuestionMultiple;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TriviaQuestionTest {
+
+ @Test
+ public void triviaQuestionMultiple_CorrectAnswer() {
+ TriviaQuestionMultiple question = new TriviaQuestionMultiple(
+ TriviaCategory.GENERAL_KNOWLEDGE,
+ TriviaDifficulty.EASY,
+ "What is my name?",
+ "Bob",
+ new String[]{"Joe", "Tom", "James"}
+ );
+
+ assertTrue(question.checkAnswer("Bob"));
+ }
+
+ @Test
+ public void triviaQuestionMultiple_WrongAnswer() {
+ TriviaQuestionMultiple question = new TriviaQuestionMultiple(
+ TriviaCategory.GENERAL_KNOWLEDGE,
+ TriviaDifficulty.EASY,
+ "What is my name?",
+ "Bob",
+ new String[]{"Joe", "Tom", "James"}
+ );
+
+ assertFalse(question.checkAnswer("Tom"));
+ }
+
+ @Test
+ public void triviaQuestionMultiple_FromJson() {
+ JsonObject json = new JsonParser().parse(
+ "{\n" +
+ " \"category\": \"General Knowledge\",\n" +
+ " \"type\": \"multiple\",\n" +
+ " \"difficulty\": \"easy\",\n" +
+ " \"question\": \"Which company did Valve cooperate with in the creation of the Vive?\",\n" +
+ " \"correct_answer\": \"HTC\",\n" +
+ " \"incorrect_answers\": [\n" +
+ " \"Oculus\",\n" +
+ " \"Google\",\n" +
+ " \"Razer\"\n" +
+ " ]\n" +
+ " }"
+ ).getAsJsonObject();
+
+ TriviaQuestionMultiple question = TriviaQuestionMultiple.fromJson(json);
+ assertTrue(question.checkAnswer("HTC"));
+ }
+
+ @Test
+ public void triviaQuestionBoolean_CorrectAnswer() {
+ TriviaQuestionBoolean question = new TriviaQuestionBoolean(
+ TriviaCategory.ANIMALS,
+ TriviaDifficulty.EASY,
+ "Are cats animals?",
+ true
+ );
+
+ assertTrue(question.checkAnswer(true));
+ }
+
+ @Test
+ public void triviaQuestionBoolean_WrongAnswer() {
+ TriviaQuestionBoolean question = new TriviaQuestionBoolean(
+ TriviaCategory.ANIMALS,
+ TriviaDifficulty.EASY,
+ "Are cats animals?",
+ true
+ );
+
+ assertFalse(question.checkAnswer(false));
+ }
+
+ @Test
+ public void triviaQuestionBoolean_FromJson() {
+ JsonObject json = new JsonParser().parse(
+ "{\n" +
+ " \"category\": \"Entertainment: Video Games\",\n" +
+ " \"type\": \"boolean\",\n" +
+ " \"difficulty\": \"medium\",\n" +
+ " \"question\": \"In Portal, the Companion Cube's ARE sentient.\",\n" +
+ " \"correct_answer\": \"True\",\n" +
+ " \"incorrect_answers\": [\n" +
+ " \"False\"\n" +
+ " ]\n" +
+ " }"
+ ).getAsJsonObject();
+
+ TriviaQuestionBoolean question = TriviaQuestionBoolean.fromJson(json);
+ assertTrue(question.checkAnswer(true));
+ }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d6e9c47
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ google()
+ maven { url "https://jitpack.io" }
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.2.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ google()
+ maven { url "https://jitpack.io" }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..9e6fce1
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..29953ea
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d76b502
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'