├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── quaap │ │ └── primary │ │ ├── AboutActivity.java │ │ ├── Levels.java │ │ ├── MainActivity.java │ │ ├── NounCleanActivity.java │ │ ├── Primary.java │ │ ├── ScoresActivity.java │ │ ├── SettingsActivity.java │ │ ├── base │ │ ├── CommonBaseActivity.java │ │ ├── Level.java │ │ ├── StdGameActivity.java │ │ ├── StdLevel.java │ │ ├── SubjectBaseActivity.java │ │ ├── SubjectMenuActivity.java │ │ ├── component │ │ │ ├── ActivityWriter.java │ │ │ ├── HorzItemList.java │ │ │ ├── InputMode.java │ │ │ ├── Keyboard.java │ │ │ ├── SoundEffects.java │ │ │ └── TextToVoice.java │ │ └── data │ │ │ ├── AppData.java │ │ │ ├── SubjectGroup.java │ │ │ ├── Subjects.java │ │ │ └── UserData.java │ │ ├── math │ │ ├── BasicMathActivity.java │ │ ├── BasicMathLevel.java │ │ ├── MathOp.java │ │ ├── Negatives.java │ │ ├── SortingActivity.java │ │ └── SortingLevel.java │ │ ├── partsofspeech │ │ └── plurals │ │ │ ├── PluralActivity.java │ │ │ └── PluralLevel.java │ │ ├── spelling │ │ ├── SpellingActivity.java │ │ └── SpellingLevel.java │ │ └── timemoney │ │ ├── TimeActivity.java │ │ └── TimeLevel.java │ ├── primary_launcher-web.png │ └── res │ ├── layout │ ├── activity_about.xml │ ├── activity_login.xml │ ├── activity_noun_clean.xml │ ├── activity_scores.xml │ ├── activity_subject_menu.xml │ ├── horz_list_view.xml │ ├── level_complete.xml │ ├── scores1.xml │ ├── scores2.xml │ ├── spinner_item.xml │ ├── std_game_layout.xml │ ├── std_math_prob.xml │ ├── std_plural_prob.xml │ ├── std_sorting_prob.xml │ ├── std_spelling_prob.xml │ ├── std_time_prob.xml │ ├── subject_view.xml │ ├── typed_input.xml │ └── user_avatar.xml │ ├── menu │ └── main_menu.xml │ ├── mipmap-hdpi │ └── primary_launcher.png │ ├── mipmap-mdpi │ └── primary_launcher.png │ ├── mipmap-xhdpi │ └── primary_launcher.png │ ├── mipmap-xxhdpi │ └── primary_launcher.png │ ├── mipmap-xxxhdpi │ └── primary_launcher.png │ ├── raw │ ├── baba.ogg │ ├── badbing.ogg │ ├── badbong.ogg │ ├── drumrollhit.ogg │ ├── goodbing.ogg │ ├── highclick.ogg │ ├── hit.ogg │ ├── lowclick.ogg │ ├── next_level.png │ └── repeat_level.png │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ ├── keyboards.xml │ ├── nouns.xml │ ├── plurals.xml │ ├── preferences.xml │ ├── spelling.xml │ ├── strings.xml │ ├── styles.xml │ └── subjects.xml │ └── xml │ ├── preference_headers.xml │ └── preferences.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Primary 2 | 3 | A simple educational practice game for kids and adults. 4 | 5 | In version 0.2 there are levels dealing with Math and Language Arts. 6 | 7 | Version 0.3 will add Time and Clocks. 8 | 9 | You can find the latest installable version on [FDroid](https://f-droid.org/repository/browse/?fdid=com.quaap.primary). 10 | 11 | The app is designed to allow other level sets, and in the future I'd like to add 12 | other math levels (ideas: word problems, easier and harder math, fractions, decimals, time, etc) and other subjects 13 | as well (ideas: reading, maps, geometry, science, etc?) 14 | 15 | (Note: This app is still in the beta stage and things are likely to change.) 16 | 17 | Features: 18 | * Progressive levels. 19 | * Slightly gamified. 20 | * Hints given after a bit of time to keep younger players from getting stuck. 21 | * Supports multiple users. 22 | * Big page of all scores. 23 | * Can keep a log of answers in external csv files for record-keeping purposes (eg homeschooling). 24 | 25 | Math Features: 26 | * Basic addition, subtration, multiplication, and division levelsets. 27 | * Some levels multiple choice, some keyboard-entry. 28 | * Sorting of 1, 2, and 3 digit numbers. 29 | 30 | Language Arts Features: 31 | * Spelling common words (uses Text-To-Speech) 32 | * Pluralization of common nouns 33 | * Some levels multiple choice, some keyboard-entry. 34 | 35 | ![Primary main menu](http://quaap.com/D/media/pa.png) 36 | ![Math 1](http://quaap.com/D/media/pe.png) 37 | ![Math 2](http://quaap.com/D/media/pi.png) 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion '27.0.3' 6 | defaultConfig { 7 | applicationId "com.quaap.primary" 8 | minSdkVersion 19 9 | targetSdkVersion 28 10 | 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | 22 | implementation 'com.android.support:appcompat-v7:28.0.0-rc01' 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/tom/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 31 | 35 | 39 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary; 2 | 3 | /** 4 | * Created by tom on 12/15/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | import android.content.pm.PackageInfo; 19 | import android.content.pm.PackageManager; 20 | import android.os.Build; 21 | import android.os.Bundle; 22 | import android.text.Html; 23 | import android.text.method.LinkMovementMethod; 24 | import android.widget.TextView; 25 | 26 | import com.quaap.primary.base.CommonBaseActivity; 27 | 28 | public class AboutActivity extends CommonBaseActivity { 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_about); 34 | 35 | 36 | try { 37 | PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); 38 | String version = pInfo.versionName; 39 | 40 | TextView txtappname = findViewById(R.id.txtappname); 41 | txtappname.setText(getString(R.string.app_name) + " " + version); 42 | 43 | } catch (PackageManager.NameNotFoundException e) { 44 | e.printStackTrace(); 45 | } 46 | 47 | TextView txtAbout = findViewById(R.id.txtAbout); 48 | txtAbout.setMovementMethod(LinkMovementMethod.getInstance()); 49 | if (Build.VERSION.SDK_INT >= 24) { 50 | txtAbout.setText(Html.fromHtml(getString(R.string.about_primary), 0)); 51 | } else { 52 | txtAbout.setText(Html.fromHtml(getString(R.string.about_primary))); 53 | 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/NounCleanActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.widget.Button; 7 | import android.widget.TextView; 8 | 9 | import com.quaap.primary.base.component.ActivityWriter; 10 | 11 | import java.io.File; 12 | import java.io.FileWriter; 13 | import java.io.IOException; 14 | 15 | public class NounCleanActivity extends AppCompatActivity { 16 | 17 | private String [] plurals; 18 | private File outdir; 19 | private FileWriter badpluralsfile; 20 | private FileWriter goodpluralsfile; 21 | 22 | private int onval = 0; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_noun_clean); 28 | 29 | Button good = findViewById(R.id.noun_clean_good); 30 | Button bad = findViewById(R.id.noun_clean_bad); 31 | 32 | 33 | 34 | 35 | 36 | 37 | good.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View view) { 40 | 41 | try { 42 | goodpluralsfile.write("" + plurals[onval] + "" + plurals[onval+1] + ""); 43 | goodpluralsfile.write("\n"); 44 | goodpluralsfile.flush(); 45 | onval += 2; 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | 50 | loadNextPlural(); 51 | } 52 | }); 53 | 54 | bad.setOnClickListener(new View.OnClickListener() { 55 | @Override 56 | public void onClick(View view) { 57 | try { 58 | badpluralsfile.write(plurals[onval]); 59 | badpluralsfile.write("\n"); 60 | badpluralsfile.flush(); 61 | onval += 2; 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | loadNextPlural(); 66 | } 67 | }); 68 | 69 | 70 | plurals = getResources().getStringArray(R.array.plurals); 71 | 72 | } 73 | 74 | 75 | 76 | private void loadNextPlural() { 77 | TextView noun = findViewById(R.id.noun_clean_noun); 78 | TextView plural = findViewById(R.id.noun_clean_plural); 79 | 80 | 81 | noun.setText(plurals[onval]); 82 | plural.setText(plurals[onval+1]); 83 | } 84 | 85 | /** 86 | * Dispatch onPause() to fragments. 87 | */ 88 | @Override 89 | protected void onPause() { 90 | 91 | getSharedPreferences(this.getClass().getName(), MODE_PRIVATE).edit().putInt("onval",onval).apply(); 92 | super.onPause(); 93 | try { 94 | badpluralsfile.close(); 95 | goodpluralsfile.close(); 96 | } catch (IOException e) { 97 | e.printStackTrace(); 98 | } 99 | } 100 | 101 | 102 | @Override 103 | protected void onResume() { 104 | super.onResume(); 105 | onval = getSharedPreferences(this.getClass().getName(), MODE_PRIVATE).getInt("onval",onval); 106 | 107 | outdir = ActivityWriter.getAppDocumentsDir(NounCleanActivity.this); 108 | 109 | try { 110 | badpluralsfile = new FileWriter(new File(outdir,"badplurals.txt"), true); 111 | goodpluralsfile = new FileWriter(new File(outdir,"goodplurals.txt"), true); 112 | } catch (IOException e) { 113 | throw new RuntimeException(e); 114 | } 115 | 116 | loadNextPlural(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/Primary.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary; 2 | 3 | 4 | import android.app.Application; 5 | 6 | import com.quaap.primary.base.component.SoundEffects; 7 | import com.quaap.primary.base.component.TextToVoice; 8 | 9 | /** 10 | * Created by tom on 12/29/16. 11 | *

12 | * Copyright (C) 2016 tom 13 | *

14 | * This program is free software; you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation; either version 3 of the License, or 17 | * (at your option) any later version. 18 | *

19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | */ 24 | public class Primary extends Application { 25 | 26 | private TextToVoice mTtv; 27 | 28 | 29 | private SoundEffects mSoundEffects; 30 | 31 | @Override 32 | public void onCreate() { 33 | super.onCreate(); 34 | mSoundEffects = new SoundEffects(this); 35 | } 36 | 37 | 38 | public SoundEffects getSoundEffects() { 39 | return mSoundEffects; 40 | } 41 | 42 | public TextToVoice getTextToVoice() { 43 | if (mTtv == null) { 44 | mTtv = new TextToVoice(this); 45 | } 46 | return mTtv; 47 | } 48 | 49 | @Override 50 | public void onTerminate() { 51 | if (mTtv != null) { 52 | mTtv.shutDown(); 53 | mTtv = null; 54 | } 55 | mSoundEffects.release(); 56 | super.onTerminate(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/ScoresActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary; 2 | 3 | /** 4 | * Created by tom on 12/15/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | import android.os.Bundle; 19 | import android.view.ViewGroup; 20 | import android.widget.GridLayout; 21 | import android.widget.LinearLayout; 22 | import android.widget.TextView; 23 | 24 | import com.quaap.primary.base.CommonBaseActivity; 25 | import com.quaap.primary.base.data.AppData; 26 | import com.quaap.primary.base.data.Subjects; 27 | import com.quaap.primary.base.data.UserData; 28 | 29 | import java.util.Map; 30 | 31 | public class ScoresActivity extends CommonBaseActivity { 32 | 33 | private AppData mAppdata; 34 | 35 | private Subjects subjects; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_scores); 41 | 42 | //ViewGroup scroll = (ViewGroup) findViewById(R.id.scores_scroll); 43 | 44 | LinearLayout list = findViewById(R.id.scores_list); 45 | 46 | subjects = Subjects.getInstance(this); 47 | 48 | mAppdata = AppData.getAppData(this); 49 | 50 | for (String username : mAppdata.listUsers()) { 51 | showUserData(list, username); 52 | } 53 | 54 | } 55 | 56 | private void showUserData(ViewGroup list, String username) { 57 | UserData user = mAppdata.getUser(username); 58 | 59 | TextView uname = new TextView(this); 60 | String avname = user.getAvatar() + " " + user.getUsername() + ": " + user.getTotalPoints(); 61 | uname.setPadding(0, 23, 0, 2); 62 | uname.setText(avname); 63 | uname.setTextSize(24); 64 | list.addView(uname); 65 | 66 | GridLayout userlayout = new GridLayout(this); 67 | //userlayout.setOrientation(GridLayout.VERTICAL); 68 | userlayout.setColumnCount(2); 69 | userlayout.setPadding(24, 8, 4, 16); 70 | list.addView(userlayout); 71 | 72 | for (String sub : user.getSubjectsStarted()) { 73 | 74 | UserData.Subject subject = user.getSubjectForUser(sub); 75 | 76 | addTextView(userlayout, sub + " (" + subjects.get(sub).getName() + "): ", 20, 0); 77 | addTextView(userlayout, subject.getTotalPoints() + ""); 78 | 79 | Map thist = subject.getTodayPointHistory(); 80 | 81 | for (String day : AppData.sort(thist.keySet())) { 82 | addTextView(userlayout, day, 18, 32); 83 | addTextView(userlayout, thist.get(day) + ""); 84 | 85 | } 86 | 87 | } 88 | 89 | } 90 | 91 | private void addTextView(GridLayout viewg, String text) { 92 | addTextView(viewg, text, 14, 0); 93 | } 94 | 95 | private void addTextView(GridLayout viewg, String text, float fsize, int lpadding) { 96 | TextView tview = new TextView(this); 97 | tview.setTextSize(fsize); 98 | tview.setPadding(lpadding + 16, 6, 6, 6); 99 | tview.setText(text); 100 | viewg.addView(tview); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceActivity; 6 | import android.preference.PreferenceFragment; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by tom on 12/29/16. 12 | *

13 | * Copyright (C) 2016 tom 14 | *

15 | * This program is free software; you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation; either version 3 of the License, or 18 | * (at your option) any later version. 19 | *

20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | * GNU General Public License for more details. 24 | */ 25 | public class SettingsActivity extends PreferenceActivity { 26 | 27 | 28 | @Override 29 | protected boolean isValidFragment(String fragmentName) { 30 | if (fragmentName.equals("com.quaap.primary.SettingsActivity$SettingsFragment")) { 31 | return true; 32 | } 33 | return super.isValidFragment(fragmentName); 34 | } 35 | 36 | @Override 37 | public Intent getIntent() { 38 | 39 | final Intent modIntent = new Intent(super.getIntent()); 40 | modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SettingsFragment.class.getName()); 41 | modIntent.putExtra(EXTRA_NO_HEADERS, true); 42 | return modIntent; 43 | } 44 | 45 | @Override 46 | public void onBuildHeaders(List

target) { 47 | loadHeadersFromResource(R.xml.preference_headers, target); 48 | } 49 | 50 | public static class SettingsFragment extends PreferenceFragment { 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | addPreferencesFromResource(R.xml.preferences); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/CommonBaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.support.annotation.NonNull; 8 | import android.support.v4.app.ActivityCompat; 9 | import android.support.v4.content.ContextCompat; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.widget.Toast; 14 | 15 | import com.quaap.primary.AboutActivity; 16 | import com.quaap.primary.R; 17 | import com.quaap.primary.ScoresActivity; 18 | import com.quaap.primary.SettingsActivity; 19 | 20 | /** 21 | * Created by tom on 12/29/16. 22 | *

23 | * Copyright (C) 2016 tom 24 | *

25 | * This program is free software; you can redistribute it and/or modify 26 | * it under the terms of the GNU General Public License as published by 27 | * the Free Software Foundation; either version 3 of the License, or 28 | * (at your option) any later version. 29 | *

30 | * This program is distributed in the hope that it will be useful, 31 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | * GNU General Public License for more details. 34 | */ 35 | @SuppressLint("Registered") 36 | public class CommonBaseActivity extends AppCompatActivity { 37 | 38 | // Things here are common to ALL activities. 39 | 40 | private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 121; 41 | 42 | @Override 43 | public boolean onCreateOptionsMenu(Menu menu) { 44 | super.onCreateOptionsMenu(menu); 45 | // Inflate the menu; this adds items to the action bar if it is present. 46 | getMenuInflater().inflate(R.menu.main_menu, menu); 47 | 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean onOptionsItemSelected(MenuItem item) { 53 | int id = item.getItemId(); 54 | 55 | Intent intent = null; 56 | switch (id) { 57 | case R.id.menu_about: 58 | intent = new Intent(this, AboutActivity.class); 59 | break; 60 | case R.id.menu_scores: 61 | intent = new Intent(this, ScoresActivity.class); 62 | break; 63 | case R.id.menu_settings: 64 | intent = new Intent(this, SettingsActivity.class); 65 | break; 66 | } 67 | if (intent != null) { 68 | 69 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 70 | intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 71 | intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 72 | startActivity(intent); 73 | } 74 | return super.onOptionsItemSelected(item); 75 | } 76 | 77 | 78 | boolean hasStorageAccess() { 79 | return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; 80 | } 81 | 82 | 83 | protected void checkStorageAccess() { 84 | boolean beendenied = getSharedPreferences(this.getClass().getName(), MODE_PRIVATE).getBoolean("denied", false); 85 | if (!beendenied && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 86 | ActivityCompat.requestPermissions(this, 87 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 88 | REQUEST_WRITE_EXTERNAL_STORAGE); 89 | } 90 | } 91 | 92 | @Override 93 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 94 | switch (requestCode) { 95 | case REQUEST_WRITE_EXTERNAL_STORAGE: { 96 | // If request is cancelled, the result arrays are empty. 97 | if (grantResults.length > 0 98 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 99 | 100 | Toast.makeText(this, R.string.write_perms_granted, Toast.LENGTH_SHORT).show(); 101 | } else { 102 | Toast.makeText(this, R.string.write_perms_denied, Toast.LENGTH_LONG).show(); 103 | getSharedPreferences(this.getClass().getName(), MODE_PRIVATE).edit().putBoolean("denied", true).apply(); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/Level.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by tom on 12/15/16. 10 | *

11 | * Copyright (C) 2016 Tom Kliethermes 12 | *

13 | * This program is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3 of the License, or 16 | * (at your option) any later version. 17 | *

18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | */ 23 | public abstract class Level { 24 | 25 | private final static Map nextlevelnum = new HashMap<>(); 26 | private final int mRounds; 27 | private final int mLevel; 28 | private final int mSubjectkey; 29 | 30 | Level(int subjectkey, int rounds) { 31 | mSubjectkey = subjectkey; 32 | mRounds = rounds; 33 | synchronized (nextlevelnum) { 34 | Integer lnum = nextlevelnum.get(mSubjectkey); 35 | if (lnum == null) lnum = 0; 36 | lnum++; 37 | nextlevelnum.put(mSubjectkey, lnum); 38 | mLevel = lnum; 39 | } 40 | } 41 | 42 | public abstract String getDescription(Context context); 43 | 44 | public abstract String getShortDescription(Context context); 45 | 46 | public int getLevelNum() { 47 | return mLevel; 48 | } 49 | 50 | public int getRounds() { 51 | return mRounds; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/StdGameActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base; 2 | 3 | 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.support.v7.app.ActionBar; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.GridLayout; 11 | import android.widget.LinearLayout; 12 | import android.widget.TextView; 13 | 14 | import com.quaap.primary.R; 15 | import com.quaap.primary.base.component.InputMode; 16 | import com.quaap.primary.base.component.Keyboard; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Timer; 21 | import java.util.TimerTask; 22 | 23 | /** 24 | * Created by tom on 12/28/16. 25 | *

26 | * Copyright (C) 2016 tom 27 | *

28 | * This program is free software; you can redistribute it and/or modify 29 | * it under the terms of the GNU General Public License as published by 30 | * the Free Software Foundation; either version 3 of the License, or 31 | * (at your option) any later version. 32 | *

33 | * This program is distributed in the hope that it will be useful, 34 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 35 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 36 | * GNU General Public License for more details. 37 | */ 38 | public abstract class StdGameActivity extends SubjectBaseActivity { 39 | 40 | // Things here are common to game activities which want ot use the standard layout. 41 | 42 | private final int mProblemView; 43 | 44 | private volatile Timer timer; 45 | private volatile TimerTask hinttask; 46 | 47 | private volatile boolean showHint = false; 48 | private volatile int hintTick; 49 | 50 | private static final int BASE_HINT_TIME = 20000; 51 | private static final int BASE_HINT_REPEAT_TIME = 3000; 52 | 53 | public StdGameActivity(int problemView) { 54 | super(R.layout.std_game_layout); 55 | mProblemView = problemView; 56 | } 57 | 58 | @Override 59 | protected void onCreate(Bundle savedInstanceState) { 60 | super.onCreate(savedInstanceState); 61 | ViewGroup probarea = findViewById(R.id.problem_area); 62 | 63 | LayoutInflater.from(this).inflate(mProblemView, probarea); 64 | 65 | } 66 | 67 | @Override 68 | protected void onPause() { 69 | 70 | 71 | cancelHint(); 72 | 73 | if (timer != null) { 74 | timer.cancel(); 75 | timer.purge(); 76 | timer = null; 77 | } 78 | 79 | super.onPause(); 80 | } 81 | 82 | @Override 83 | protected void onResume() { 84 | super.onResume(); 85 | timer = new Timer(); 86 | 87 | 88 | 89 | } 90 | 91 | @Override 92 | public void onConfigurationChanged(Configuration newConfig) { 93 | super.onConfigurationChanged(newConfig); 94 | 95 | GridLayout answerarea = findViewById(R.id.answer_area); 96 | LinearLayout centercol = findViewById(R.id.centercol); 97 | ActionBar actionBar = getSupportActionBar(); 98 | if (isLandscape()) { 99 | 100 | //answerarea.setOrientation(GridLayout.VERTICAL); 101 | setColumnCount(answerarea,2); 102 | 103 | centercol.setOrientation(LinearLayout.HORIZONTAL); 104 | 105 | if (actionBar != null) { 106 | actionBar.hide(); 107 | } 108 | } else { 109 | //answerarea.setOrientation(GridLayout.VERTICAL); 110 | 111 | 112 | setColumnCount(answerarea,1); 113 | 114 | centercol.setOrientation(LinearLayout.VERTICAL); 115 | 116 | if (actionBar != null) { 117 | actionBar.show(); 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | private void setColumnCount(GridLayout answerarea, int count) { 125 | List kids = new ArrayList<>(); 126 | for(int i = 0; i< answerarea.getChildCount(); i++) { 127 | kids.add(answerarea.getChildAt(i)); 128 | } 129 | answerarea.removeAllViews(); 130 | 131 | answerarea.setColumnCount(count); 132 | 133 | for (View k: kids) { 134 | k.setLayoutParams(new GridLayout.LayoutParams()); 135 | answerarea.addView(k); 136 | } 137 | } 138 | 139 | @Override 140 | protected void onSetStatus(String text) { 141 | final TextView status = findViewById(R.id.txtstatus); 142 | status.setText(text); 143 | 144 | } 145 | 146 | 147 | @Override 148 | protected void onShowLevel() { 149 | 150 | 151 | if (((StdLevel) getLevel()).getInputMode() == InputMode.Buttons) { 152 | showHint = false; 153 | } else { 154 | showHint = true; 155 | } 156 | Keyboard.hideKeys(getKeysArea()); 157 | } 158 | 159 | @Override 160 | protected void onBeforeShowProb() { 161 | View problem_area = findViewById(R.id.problem_area); 162 | problem_area.setVisibility(View.INVISIBLE); 163 | View answer_area = findViewById(R.id.answer_area); 164 | answer_area.setVisibility(View.INVISIBLE); 165 | } 166 | 167 | 168 | @Override 169 | protected void onAfterShowProb() { 170 | View problem_area = findViewById(R.id.problem_area); 171 | problem_area.setVisibility(View.VISIBLE); 172 | View answer_area = findViewById(R.id.answer_area); 173 | answer_area.setVisibility(View.VISIBLE); 174 | } 175 | 176 | @Override 177 | protected void answerDone(boolean isright, String problem, String answer, String useranswer) { 178 | if (isright) cancelHint(); 179 | super.answerDone(isright, problem, answer, useranswer); 180 | } 181 | 182 | protected ViewGroup getKeysArea() { 183 | return (ViewGroup) findViewById(R.id.keypad_area); 184 | } 185 | 186 | protected GridLayout getAnswerArea() { 187 | return (GridLayout) findViewById(R.id.answer_area); 188 | } 189 | 190 | 191 | protected void startHint(int difficultyFactor) { 192 | startHint(BASE_HINT_TIME + 1000*difficultyFactor, BASE_HINT_REPEAT_TIME + 250*difficultyFactor); 193 | 194 | } 195 | 196 | private void startHint(int firstHintDelayMillis, int repeatHintDelaysMillis) { 197 | 198 | cancelHint(); 199 | 200 | if (showHint) { 201 | hintTick = 0; 202 | hinttask = new TimerTask() { 203 | @Override 204 | public void run() { 205 | handler.post(new Runnable() { 206 | @Override 207 | public void run() { 208 | onPerformHint(hintTick++); 209 | } 210 | }); 211 | } 212 | }; 213 | 214 | if (timer!=null) { 215 | timer.scheduleAtFixedRate(hinttask, firstHintDelayMillis, repeatHintDelaysMillis); 216 | } 217 | } 218 | } 219 | 220 | protected int getHintTicks() { 221 | return hintTick; 222 | } 223 | 224 | protected void onPerformHint(int hintTick) { 225 | //override to do something. 226 | if (hintTick==0) { 227 | getSoundEffects().playHighClick(); 228 | } 229 | } 230 | 231 | protected void cancelHint() { 232 | if (hinttask != null) { 233 | hinttask.cancel(); 234 | hinttask = null; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/StdLevel.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base; 2 | 3 | import android.content.Context; 4 | 5 | import com.quaap.primary.R; 6 | import com.quaap.primary.base.component.InputMode; 7 | 8 | /** 9 | * Created by tom on 12/31/16. 10 | *

11 | * Copyright (C) 2016 tom 12 | *

13 | * This program is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3 of the License, or 16 | * (at your option) any later version. 17 | *

18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | */ 23 | public abstract class StdLevel extends Level { 24 | private final InputMode mInputMode; 25 | 26 | protected StdLevel(int subjectkey, int rounds, InputMode mInputMode) { 27 | super(subjectkey, rounds); 28 | this.mInputMode = mInputMode; 29 | } 30 | 31 | 32 | public InputMode getInputMode() { 33 | return mInputMode; 34 | } 35 | 36 | protected String getInputModeString(Context context) { 37 | return getInputMode() == InputMode.Buttons ? context.getString(R.string.disp_buttons) : context.getString(R.string.disp_input); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/SubjectMenuActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.os.Bundle; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AlertDialog; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.Button; 13 | import android.widget.LinearLayout; 14 | import android.widget.TextView; 15 | 16 | import com.quaap.primary.MainActivity; 17 | import com.quaap.primary.R; 18 | import com.quaap.primary.base.data.AppData; 19 | import com.quaap.primary.base.data.Subjects; 20 | import com.quaap.primary.base.data.UserData; 21 | 22 | /** 23 | * Created by tom on 12/15/16. 24 | *

25 | * Copyright (C) 2016 Tom Kliethermes 26 | *

27 | * This program is free software; you can redistribute it and/or modify 28 | * it under the terms of the GNU General Public License as published by 29 | * the Free Software Foundation; either version 3 of the License, or 30 | * (at your option) any later version. 31 | *

32 | * This program is distributed in the hope that it will be useful, 33 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 34 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 35 | * GNU General Public License for more details. 36 | */ 37 | 38 | /* 39 | Future Subjects: 40 | Math 1, but typed answers. 41 | Spelling. 42 | Reading/vocabulary. 43 | New big words. 44 | Goofy math word problems. 45 | 46 | 47 | */ 48 | 49 | public class SubjectMenuActivity extends CommonBaseActivity implements Button.OnClickListener { 50 | 51 | 52 | private UserData mUserData; 53 | private UserData.Subject mSubjectData; 54 | 55 | private Subjects.Desc mSubject; 56 | private String mSubjectCode; 57 | 58 | private String username; 59 | 60 | 61 | @Override 62 | protected void onCreate(Bundle savedInstanceState) { 63 | super.onCreate(savedInstanceState); 64 | 65 | //Log.d("onCreate", "onCreate savedInstanceState=" + (savedInstanceState==null?"null":"notnull")); 66 | if (savedInstanceState == null) { 67 | 68 | Intent intent = getIntent(); 69 | mSubjectCode = intent.getStringExtra(MainActivity.SUBJECTCODE); 70 | username = intent.getStringExtra(MainActivity.USERNAME); 71 | 72 | } else { 73 | mSubjectCode = savedInstanceState.getString("mSubjectCode", mSubjectCode); 74 | username = savedInstanceState.getString("username", username); 75 | } 76 | //Log.d("onCreate", "onCreate username=" + username); 77 | 78 | if (mSubjectCode == null || username == null) { 79 | SharedPreferences state = getSharedPreferences(this.getClass().getName(), MODE_PRIVATE); 80 | mSubjectCode = state.getString("mSubjectCode", mSubjectCode); 81 | username = state.getString("username", username); 82 | } 83 | //Log.d("onCreate", "onCreate username=" + username); 84 | Subjects subjects = Subjects.getInstance(this); 85 | mSubject = subjects.get(mSubjectCode); 86 | 87 | ActionBar actionBar = getSupportActionBar(); 88 | if (actionBar != null) { 89 | actionBar.setTitle(getString(R.string.app_name) + ": " + mSubject.getName() + " (" + username + ")"); 90 | } 91 | 92 | mUserData = AppData.getAppData(this).getUser(username); 93 | mSubjectData = mUserData.getSubjectForUser(mSubjectCode); 94 | setContentView(R.layout.activity_subject_menu); 95 | 96 | 97 | Button resume_button = findViewById(R.id.resume_button); 98 | resume_button.setTag(-1); 99 | resume_button.setOnClickListener(this); 100 | 101 | Button clear_button = findViewById(R.id.clear_button); 102 | clear_button.setOnClickListener(new View.OnClickListener() { 103 | @Override 104 | public void onClick(View view) { 105 | new AlertDialog.Builder(SubjectMenuActivity.this) 106 | .setIcon(android.R.drawable.ic_dialog_alert) 107 | .setTitle(getString(R.string.clear_progress)) 108 | .setMessage(R.string.sure_clear_progress) 109 | .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() { 110 | @Override 111 | public void onClick(DialogInterface dialog, int which) { 112 | clearProgress(); 113 | } 114 | 115 | }) 116 | .setNegativeButton(getString(R.string.cancel), null) 117 | .show(); 118 | 119 | 120 | } 121 | }); 122 | } 123 | 124 | @Override 125 | protected void onSaveInstanceState(Bundle outState) { 126 | //Log.d("subjectmenu", "onSaveInstanceState called! username=" + username); 127 | outState.putString("mSubjectCode", mSubjectCode); 128 | outState.putString("username", username); 129 | super.onSaveInstanceState(outState); 130 | 131 | } 132 | 133 | @Override 134 | protected void onResume() { 135 | super.onResume(); 136 | //Log.d("subjectmenu", "onResume called! username=" + username); 137 | 138 | show_hide_gip(); 139 | showLevelButtons(); 140 | 141 | } 142 | 143 | 144 | @Override 145 | protected void onPause() { 146 | SharedPreferences state = getSharedPreferences(this.getClass().getName(), MODE_PRIVATE); 147 | 148 | state.edit() 149 | .putString("mSubjectCode", mSubjectCode) 150 | .putString("username", username) 151 | .apply(); 152 | 153 | super.onPause(); 154 | //Log.d("subjectmenu", "onPause called! username=" + username); 155 | 156 | } 157 | 158 | 159 | @Override 160 | public void onBackPressed() { 161 | 162 | super.onBackPressed(); 163 | finish(); 164 | } 165 | 166 | 167 | private void showLevelButtons() { 168 | int highest = mUserData.getSubjectForUser(mSubjectCode).getHighestLevelNum(); 169 | 170 | LinearLayout button_layout = findViewById(R.id.button_layout); 171 | 172 | button_layout.removeAllViews(); 173 | 174 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 175 | lp.gravity = Gravity.CENTER_VERTICAL; 176 | 177 | for (Level level : mSubject.getLevels()) { 178 | 179 | LinearLayout levelrow = new LinearLayout(this); 180 | levelrow.setOrientation(LinearLayout.HORIZONTAL); 181 | button_layout.addView(levelrow); 182 | 183 | Button levelbutt = new Button(this); 184 | levelbutt.setLayoutParams(lp); 185 | levelbutt.setText(getString(R.string.level, level.getLevelNum())); 186 | levelbutt.setTag(level.getLevelNum() - 1); 187 | levelbutt.setOnClickListener(this); 188 | levelrow.addView(levelbutt); 189 | 190 | TextView desc = new TextView(this); 191 | desc.setText(level.getDescription(this)); 192 | desc.setLayoutParams(lp); 193 | desc.setTextSize(16); 194 | levelrow.addView(desc); 195 | 196 | boolean beenthere = level.getLevelNum() - 1 <= highest; 197 | levelbutt.setEnabled(beenthere); 198 | desc.setEnabled(beenthere); 199 | } 200 | } 201 | 202 | private void clearProgress() { 203 | mSubjectData.clearProgress(); 204 | 205 | show_hide_gip(); 206 | showLevelButtons(); 207 | } 208 | 209 | private void show_hide_gip() { 210 | LinearLayout gip_layout = findViewById(R.id.gip_layout); 211 | TextView score_overview = findViewById(R.id.score_overview); 212 | if (mSubjectData.getLevelNum() == -1) { 213 | gip_layout.setVisibility(View.GONE); 214 | score_overview.setText(" "); 215 | } else { 216 | gip_layout.setVisibility(View.VISIBLE); 217 | int correct = mSubjectData.getTotalCorrect(); 218 | int incorrect = mSubjectData.getTotalIncorrect(); 219 | 220 | int highest = mSubjectData.getHighestLevelNum() + 1; 221 | int levs = mSubject.getLevels().length; 222 | if (highest>levs) { 223 | highest = levs; // in case an upgrade reduces the number of levels in a subject 224 | } 225 | 226 | int tscore = mSubjectData.getTotalPoints(); 227 | if (correct + incorrect > 0) { 228 | String score = getString(R.string.score_overview, highest, correct, (correct + incorrect), tscore); 229 | score_overview.setText(score); 230 | } 231 | } 232 | } 233 | 234 | 235 | @Override 236 | public void onClick(View view) { 237 | Intent intent = new Intent(this, mSubject.getActivityclass()); 238 | if ((int) view.getTag() != -1) { 239 | intent.putExtra(SubjectBaseActivity.START_AT_ZERO, true); 240 | intent.putExtra(SubjectBaseActivity.LEVELNUM, (int) view.getTag()); 241 | } 242 | intent.putExtra(MainActivity.USERNAME, username); 243 | intent.putExtra(MainActivity.SUBJECTCODE, mSubjectCode); 244 | startActivity(intent); 245 | } 246 | 247 | 248 | 249 | } 250 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/component/ActivityWriter.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.component; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | import android.util.Log; 6 | 7 | import com.quaap.primary.R; 8 | 9 | import java.io.File; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | import java.util.Locale; 15 | 16 | /** 17 | * Created by tom on 12/15/16. 18 | *

19 | * Copyright (C) 2016 Tom Kliethermes 20 | *

21 | * This program is free software; you can redistribute it and/or modify 22 | * it under the terms of the GNU General Public License as published by 23 | * the Free Software Foundation; either version 3 of the License, or 24 | * (at your option) any later version. 25 | *

26 | * This program is distributed in the hope that it will be useful, 27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 29 | * GNU General Public License for more details. 30 | */ 31 | public class ActivityWriter { 32 | 33 | private final SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); 34 | 35 | private final FileWriter mFw; 36 | 37 | 38 | public ActivityWriter(Context context, String username, String subject) throws IOException { 39 | 40 | username = username.replaceAll("\\W", "_"); 41 | 42 | 43 | SimpleDateFormat fileFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); 44 | String fname = username + " " + subject + " " + fileFormat.format(new Date()); 45 | fname = fname.replaceAll("[/\\\\.(){}$*|?<>\\[\\]]", " "); 46 | File f = new File(getAppDocumentsDir(context), fname + ".csv"); 47 | boolean newfile = !f.exists(); 48 | mFw = new FileWriter(f, true); 49 | if (newfile) { 50 | writeRow(context.getString(R.string.csv_time), context.getString(R.string.csv_level), 51 | context.getString(R.string.csv_problem), context.getString(R.string.csv_answer), 52 | context.getString(R.string.csv_useranswer), context.getString(R.string.csv_correct), 53 | context.getString(R.string.csv_tspent), context.getString(R.string.csv_run_percent), 54 | context.getString(R.string.csv_points)); 55 | } 56 | } 57 | 58 | private static String csvEscape(String value) { 59 | 60 | if (value == null) { 61 | return ""; 62 | } 63 | if (value.length() == 0) { 64 | return "\"\""; 65 | } 66 | if (value.matches("^-?(\\d+(\\.\\d+)?|\\.\\d+)$")) { 67 | return value; 68 | } 69 | return "\"" + value.replaceAll("\"", "\"\"") + "\""; 70 | 71 | } 72 | 73 | public synchronized void close() throws IOException { 74 | mFw.close(); 75 | } 76 | 77 | public boolean isExternalStorageWritable() { 78 | String state = Environment.getExternalStorageState(); 79 | return Environment.MEDIA_MOUNTED.equals(state); 80 | } 81 | 82 | public static File getAppDocumentsDir(Context context) { 83 | File file = new File(Environment.getExternalStoragePublicDirectory( 84 | Environment.DIRECTORY_DOCUMENTS), context.getString(R.string.app_name)); 85 | if (!file.exists() && !file.mkdirs()) { 86 | Log.e("Primary", "Directory could not be created"); 87 | } 88 | return file; 89 | } 90 | 91 | public synchronized void log(int level, String problem, String answer, String useranswer, boolean correct, long millis, float runningpercent, int points) { 92 | writeRow(mDateFormat.format(new Date()), level, problem, answer, useranswer, correct, 93 | millis, String.format(Locale.getDefault(), "%4.1f", runningpercent), points); 94 | } 95 | 96 | 97 | private synchronized void writeRow(Object... items) { 98 | try { 99 | for (int i = 0; i < items.length; i++) { 100 | mFw.write(csvEscape(items[i].toString())); 101 | if (i < items.length - 1) { 102 | mFw.write(","); 103 | } 104 | } 105 | mFw.write("\n"); 106 | mFw.flush(); 107 | } catch (IOException e) { 108 | Log.e("Primary", "Could not write row.", e); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/component/HorzItemList.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.component; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Build; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.HorizontalScrollView; 10 | import android.widget.ImageView; 11 | import android.widget.LinearLayout; 12 | import android.widget.TextView; 13 | 14 | import com.quaap.primary.R; 15 | 16 | import java.util.Collection; 17 | import java.util.Collections; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * Created by tom on 12/20/16. 23 | *

24 | * Copyright (C) 2016 tom 25 | *

26 | * This program is free software; you can redistribute it and/or modify 27 | * it under the terms of the GNU General Public License as published by 28 | * the Free Software Foundation; either version 3 of the License, or 29 | * (at your option) any later version. 30 | *

31 | * This program is distributed in the hope that it will be useful, 32 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 34 | * GNU General Public License for more details. 35 | */ 36 | public abstract class HorzItemList { 37 | //This could be a custom view 38 | 39 | 40 | private final int normalColor = Color.argb(64, 200, 200, 200); 41 | private final Activity mParent; 42 | private final int mItemLayoutId; 43 | private final View mHorzList; 44 | private final LinearLayout mItemsListView; 45 | private final Map mListItems = new HashMap<>(); 46 | private final Map mListItemsRev = new HashMap<>(); 47 | private String selected; 48 | 49 | public HorzItemList(Activity parent, int includeID, int itemLayoutId) { 50 | this(parent, includeID, itemLayoutId, null); 51 | } 52 | 53 | public HorzItemList(Activity parent, int includeID, int itemLayoutId, String[] itemkeys) { 54 | mParent = parent; 55 | 56 | mItemLayoutId = itemLayoutId; 57 | 58 | mHorzList = parent.findViewById(includeID); 59 | mItemsListView = mHorzList.findViewById(R.id.items_list_area); 60 | ImageView newbutton = mHorzList.findViewById(R.id.add_list_item_button); 61 | newbutton.setOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View view) { 64 | onNewItemClicked(); 65 | } 66 | }); 67 | populate(itemkeys); 68 | } 69 | 70 | public void populate(String[] itemkeys) { 71 | if (itemkeys != null) { 72 | for (int i = 0; i < itemkeys.length; i++) { 73 | addItem(i, itemkeys[i]); 74 | } 75 | } 76 | } 77 | 78 | public void showAddButton(boolean show) { 79 | mHorzList.findViewById(R.id.add_list_item_button).setVisibility(show ? View.VISIBLE : View.GONE); 80 | } 81 | 82 | public ViewGroup addItem(String key) { 83 | return addItem(-1, key); 84 | } 85 | 86 | private ViewGroup addItem(int pos, String key) { 87 | if (mListItems.containsKey(key)) { 88 | return mListItems.get(key); 89 | } 90 | ViewGroup item = (ViewGroup) LayoutInflater.from(mParent).inflate(mItemLayoutId, null); 91 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 92 | lp.setMargins(10, 10, 10, 10); 93 | item.setLayoutParams(lp); 94 | 95 | item.setBackgroundColor(normalColor); 96 | 97 | if (pos == -1) pos = mListItems.size(); 98 | populateItem(key, item, pos); 99 | 100 | 101 | item.setOnClickListener(new View.OnClickListener() { 102 | @Override 103 | public void onClick(View view) { 104 | setSelected(view); 105 | onItemClicked(mListItemsRev.get(view), (LinearLayout) view); 106 | } 107 | }); 108 | item.setTag(key); 109 | mItemsListView.addView(item); 110 | mListItems.put(key, item); 111 | mListItemsRev.put(item, key); 112 | 113 | return item; 114 | } 115 | 116 | private void setBackground(View item, int drawableId) { 117 | if (Build.VERSION.SDK_INT >= 21) { 118 | item.setBackground(mParent.getResources().getDrawable(android.R.drawable.btn_default, mParent.getTheme())); 119 | } else { 120 | item.setBackground(mParent.getResources().getDrawable(android.R.drawable.btn_default)); 121 | } 122 | 123 | } 124 | 125 | public void removeItem(String key) { 126 | if (!mListItems.containsKey(key)) { 127 | return; 128 | } 129 | ViewGroup item = mListItems.get(key); 130 | mItemsListView.removeView(item); 131 | mListItemsRev.remove(item); 132 | mListItems.remove(key); 133 | if (key.equals(selected)) { 134 | setSelected((String) null); 135 | } 136 | 137 | } 138 | 139 | public void clear() { 140 | mItemsListView.removeAllViews(); 141 | mListItems.clear(); 142 | mListItemsRev.clear(); 143 | selected = null; 144 | } 145 | 146 | public void setItemTextField(View item, int itemFieldId, String value) { 147 | TextView itemfield = item.findViewById(itemFieldId); 148 | itemfield.setText(value); 149 | } 150 | 151 | public void setItemBackground(View item, int itemFieldId, int color) { 152 | View itemfield = item.findViewById(itemFieldId); 153 | itemfield.setBackgroundColor(color); 154 | } 155 | 156 | public Collection getKeys() { 157 | return Collections.unmodifiableSet(mListItems.keySet()); 158 | } 159 | 160 | public ViewGroup getItem(String key) { 161 | return mListItems.get(key); 162 | } 163 | 164 | protected void onNewItemClicked() { 165 | 166 | } 167 | 168 | protected void onItemClicked(String key, ViewGroup item) { 169 | 170 | } 171 | // protected abstract void onNewItemClicked(); 172 | // 173 | // protected abstract void onItemClicked(String key, ViewGroup item); 174 | // 175 | // protected abstract void populateItem(String key, ViewGroup item, int i); 176 | 177 | protected void populateItem(String key, ViewGroup item, int i) { 178 | 179 | } 180 | 181 | public boolean hasSelected() { 182 | return selected != null; 183 | } 184 | 185 | public String getSelected() { 186 | return selected; 187 | } 188 | 189 | public void setSelected(String key) { 190 | 191 | final int selectedColor = Color.CYAN; 192 | 193 | if (selected != null) { 194 | View old_selected = mListItems.get(selected); 195 | if (old_selected != null) { 196 | old_selected.setBackgroundColor(normalColor); 197 | } 198 | } 199 | selected = key; 200 | if (selected != null) { 201 | HorizontalScrollView hsv = mHorzList.findViewById(R.id.horz_list_scroller); 202 | View new_selected = mListItems.get(selected); 203 | // boolean nextone = false; 204 | // for (String sk: mListItems.keySet()) { 205 | // if (nextone) { 206 | // hsv.requestChildFocus(mListItems.get(sk), mListItems.get(sk)); 207 | // break; 208 | // } 209 | // if (sk.equals(selected)) { 210 | // nextone = true; 211 | // } 212 | // } 213 | if (new_selected != null) { 214 | new_selected.setBackgroundColor(selectedColor); 215 | hsv.requestChildFocus(new_selected, new_selected); 216 | // focusChild(hsv, new_selected); 217 | } 218 | } 219 | } 220 | 221 | private void setSelected(View item) { 222 | setSelected(mListItemsRev.get(item)); 223 | } 224 | 225 | // private void focusChild(final HorizontalScrollView scroll, final View view) { 226 | // new Handler().post(new Runnable() { 227 | // @Override 228 | // public void run() { 229 | // int left = view.getLeft(); 230 | // int right = view.getRight(); 231 | // int width = scroll.getWidth(); 232 | // scroll.smoothScrollTo(((left + right - width) / 2), 0); 233 | // } 234 | // }); 235 | // } 236 | } 237 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/component/InputMode.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.component; 2 | 3 | /** 4 | * Created by tom on 12/15/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | 19 | public enum InputMode { 20 | None, Buttons, Input 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/component/Keyboard.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.component; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.Point; 6 | import android.graphics.Typeface; 7 | import android.os.Build; 8 | import android.util.TypedValue; 9 | import android.view.Display; 10 | import android.view.Gravity; 11 | import android.view.KeyEvent; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.WindowManager; 15 | import android.widget.EditText; 16 | import android.widget.LinearLayout; 17 | import android.widget.TextView; 18 | 19 | import com.quaap.primary.R; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | 25 | /** 26 | * Created by tom on 12/29/16. 27 | *

28 | * Copyright (C) 2016 tom 29 | *

30 | * This program is free software; you can redistribute it and/or modify 31 | * it under the terms of the GNU General Public License as published by 32 | * the Free Software Foundation; either version 3 of the License, or 33 | * (at your option) any later version. 34 | *

35 | * This program is distributed in the hope that it will be useful, 36 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 | * GNU General Public License for more details. 39 | */ 40 | public class Keyboard { 41 | 42 | 43 | private static final String KEY_BACKSP = "\u0008"; 44 | private static final String KEY_DONE = "\n"; 45 | private final Context mContext; 46 | 47 | private final Map mKeyMap = new HashMap<>(); 48 | 49 | private Keyboard(Context context) { 50 | mContext = context; 51 | } 52 | 53 | private Keyboard(Context context, Map keyMap) { 54 | mContext = context; 55 | mKeyMap.putAll(keyMap); 56 | } 57 | 58 | 59 | public synchronized static void showKeyboard(Context context, final EditText editText, ViewGroup parentlayout) { 60 | new Keyboard(context).showKeyboard(editText, parentlayout); 61 | } 62 | 63 | public synchronized static void showKeyboard(Context context, final EditText editText, ViewGroup parentlayout, Map keyMap) { 64 | new Keyboard(context, keyMap).showKeyboard(editText, parentlayout); 65 | } 66 | 67 | public synchronized static void showNumberpad(Context context, final EditText editText, ViewGroup parentlayout) { 68 | new Keyboard(context).showNumberpad(editText, parentlayout); 69 | } 70 | 71 | public synchronized static void showNumberpad(Context context, final EditText editText, ViewGroup parentlayout, Map keyMap) { 72 | new Keyboard(context, keyMap).showNumberpad(editText, parentlayout); 73 | } 74 | 75 | public synchronized static void hideKeys(ViewGroup parentlayout) { 76 | parentlayout.removeAllViews(); 77 | 78 | } 79 | 80 | private void showKeyboard(final EditText editText, ViewGroup parentlayout) { 81 | String[] keys = mContext.getResources().getStringArray(R.array.keyboard_keys); 82 | int rows = mContext.getResources().getInteger(R.integer.keyboard_rows); 83 | 84 | showKeys(editText, parentlayout, keys, rows); 85 | } 86 | 87 | 88 | private void showNumberpad(final EditText editText, ViewGroup parentlayout) { 89 | String[] keys = mContext.getResources().getStringArray(R.array.keypad_keys); 90 | int rows = mContext.getResources().getInteger(R.integer.keypad_rows); 91 | 92 | showKeys(editText, parentlayout, keys, rows); 93 | } 94 | 95 | private void showKeys(final EditText editText, ViewGroup parentlayout, String[] keys, int rows) { 96 | 97 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 98 | editText.setShowSoftInputOnFocus(false); 99 | } else { 100 | try { 101 | final Method method = EditText.class.getMethod( 102 | "setShowSoftInputOnFocus" 103 | , boolean.class); 104 | method.setAccessible(true); 105 | method.invoke(editText, false); 106 | } catch (Exception e) { 107 | // ignore 108 | } 109 | } 110 | 111 | parentlayout.removeAllViews(); 112 | LinearLayout glayout = new LinearLayout(mContext); 113 | glayout.setOrientation(LinearLayout.VERTICAL); 114 | glayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 115 | glayout.setBackgroundColor(Color.WHITE); 116 | glayout.setPadding(2, 2, 2, 2); 117 | 118 | WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 119 | Display display = wm.getDefaultDisplay(); 120 | Point size = new Point(); 121 | display.getSize(size); 122 | 123 | int cols = keys.length / rows; 124 | if (keys.length % 2 != 0) cols += 1; 125 | 126 | // float xfac = .95f; 127 | // int orientation = mContext.getResources().getConfiguration().orientation; 128 | // if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 129 | // xfac = .83f; 130 | // } 131 | 132 | // int keywidth = (int) (size.x / cols * xfac); 133 | // int keyheight = (int) (keywidth * 1.4); 134 | // 135 | // if (keyheight > 100) keyheight = 100; 136 | 137 | System.out.println("size: " + size.x + ", " + size.y); 138 | 139 | LinearLayout rowlayout = new LinearLayout(mContext); 140 | rowlayout.setOrientation(LinearLayout.HORIZONTAL); 141 | glayout.addView(rowlayout); 142 | int num=0; 143 | for (String k : keys) { 144 | 145 | if (num++ % cols == 0) { 146 | rowlayout = new LinearLayout(mContext); 147 | rowlayout.setOrientation(LinearLayout.HORIZONTAL); 148 | glayout.addView(rowlayout); 149 | } 150 | 151 | if (mKeyMap.containsKey(k)) { 152 | k = mKeyMap.get(k); 153 | } 154 | 155 | TextView key = new TextView(mContext); 156 | 157 | key.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1)); 158 | 159 | key.setClickable(true); 160 | key.setPadding(4, 4, 4, 4); 161 | key.setGravity(Gravity.CENTER); 162 | key.setBackgroundResource(android.R.drawable.btn_default_small); 163 | //key.setTextSize((int) (keyheight / 3.5)); 164 | key.setTextSize(TypedValue.COMPLEX_UNIT_DIP,18); 165 | key.setTypeface(null, Typeface.BOLD); 166 | //key.setMinimumWidth(0); 167 | //key.setMinimumHeight(0); 168 | //key.setHeight(keyheight); 169 | //key.setWidth(keywidth); 170 | 171 | if (k.equals(KEY_BACKSP)) { 172 | key.setText("\u2190"); 173 | //key.setWidth(keywidth+5); 174 | } else if (k.equals(KEY_DONE)) { 175 | key.setText("\u2713"); 176 | key.setTextColor(Color.rgb(0, 160, 0)); 177 | //key.setWidth(keywidth+5); 178 | } else if (k.equals(" ")) { 179 | key.setText("\u2423"); 180 | } else { 181 | key.setText(k); 182 | } 183 | key.setTag(k); 184 | key.setOnClickListener(new View.OnClickListener() { 185 | @Override 186 | public void onClick(View view) { 187 | String k = (String) view.getTag(); 188 | if (k.equals(KEY_BACKSP)) { 189 | editText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); 190 | } else if (k.equals(KEY_DONE)) { 191 | editText.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); 192 | } else { 193 | editText.getText().insert(editText.getSelectionStart(), k); 194 | 195 | 196 | } 197 | 198 | } 199 | }); 200 | rowlayout.addView(key); 201 | } 202 | 203 | parentlayout.addView(glayout); 204 | 205 | editText.requestFocus(); 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/component/SoundEffects.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.component; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.media.AudioAttributes; 6 | import android.media.AudioManager; 7 | import android.media.SoundPool; 8 | import android.os.Build; 9 | import android.os.Handler; 10 | import android.preference.PreferenceManager; 11 | import android.util.Log; 12 | 13 | import com.quaap.primary.R; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | /** 19 | * Created by tom on 1/5/17. 20 | *

21 | * Copyright (C) 2017 tom 22 | *

23 | * This program is free software; you can redistribute it and/or modify 24 | * it under the terms of the GNU General Public License as published by 25 | * the Free Software Foundation; either version 3 of the License, or 26 | * (at your option) any later version. 27 | *

28 | * This program is distributed in the hope that it will be useful, 29 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | * GNU General Public License for more details. 32 | */ 33 | public class SoundEffects { 34 | 35 | private final SoundPool mSounds; 36 | 37 | private final Map mSoundIds = new HashMap<>(); 38 | 39 | private static final int GOODBING = 0; 40 | private static final int BADBING = 1; 41 | private static final int HIGHCLICK = 2; 42 | private static final int LOWCLICK = 3; 43 | private static final int BABA = 4; 44 | private static final int DRUMROLLHIT = 5; 45 | private static final int HIT = 6; 46 | 47 | private final int [] soundFiles = { 48 | R.raw.goodbing, 49 | R.raw.badbing, 50 | R.raw.highclick, 51 | R.raw.lowclick, 52 | R.raw.baba, 53 | R.raw.drumrollhit, 54 | R.raw.hit 55 | }; 56 | private final float [] soundVolumes = { 57 | .6f, 58 | .4f, 59 | .5f, 60 | .5f, 61 | .7f, 62 | .7f, 63 | .3f, 64 | }; 65 | 66 | private final SharedPreferences appPreferences; 67 | 68 | private volatile boolean mReady = false; 69 | 70 | private volatile boolean mMute = false; 71 | 72 | public SoundEffects(final Context context) { 73 | if (Build.VERSION.SDK_INT>=21) { 74 | AudioAttributes attributes = new AudioAttributes.Builder() 75 | .setUsage(AudioAttributes.USAGE_GAME) 76 | .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 77 | .build(); 78 | mSounds = new SoundPool.Builder() 79 | .setAudioAttributes(attributes) 80 | .setMaxStreams(4) 81 | .build(); 82 | } else { 83 | mSounds = new SoundPool(4, AudioManager.STREAM_MUSIC, 0); 84 | } 85 | appPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); 86 | 87 | 88 | new Handler().postDelayed(new Runnable() { 89 | @Override 90 | public void run() { 91 | for (int i=0; i 16 | * Copyright (C) 2016 tom 17 | *

18 | * This program is free software; you can redistribute it and/or modify 19 | * it under the terms of the GNU General Public License as published by 20 | * the Free Software Foundation; either version 3 of the License, or 21 | * (at your option) any later version. 22 | *

23 | * This program is distributed in the hope that it will be useful, 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | * GNU General Public License for more details. 27 | */ 28 | public class TextToVoice implements TextToSpeech.OnInitListener { 29 | private TextToSpeech mTts = null; 30 | private final Context mContext; 31 | private boolean isInit = false; 32 | 33 | private float mPitch = .8f; 34 | private float mSpeed = .6f; 35 | private int utterid = 0; 36 | private VoiceReadyListener mFil; 37 | private boolean fullyInited; 38 | 39 | public TextToVoice(Context context) { 40 | mContext = context; 41 | try { 42 | Log.d("TextToVoice", "started " + System.currentTimeMillis()); 43 | mTts = new TextToSpeech(mContext, this); 44 | 45 | setPitch(mPitch); 46 | setSpeed(mSpeed); 47 | mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() { 48 | @Override 49 | public void onStart(String s) { 50 | 51 | } 52 | 53 | @Override 54 | public void onDone(String s) { 55 | //Log.d("TextToSpeech", "Done!" + System.currentTimeMillis()); 56 | if (!fullyInited) { 57 | fullyInited = true; 58 | if (mFil != null) mFil.onVoiceReady(TextToVoice.this); 59 | } else { 60 | if (mFil != null) mFil.onSpeakComplete(TextToVoice.this); 61 | } 62 | } 63 | 64 | @Override 65 | public void onError(String s) { 66 | Log.e("TextToSpeech", "Error with " + s); 67 | if (mFil != null) mFil.onError(TextToVoice.this); 68 | } 69 | }); 70 | 71 | 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } 75 | } 76 | 77 | @Override 78 | public void onInit(int status) { 79 | if (status == TextToSpeech.SUCCESS) { 80 | int result = mTts.setLanguage(Locale.getDefault()); 81 | isInit = true; 82 | 83 | if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { 84 | 85 | Log.e("error", "This Language is not supported"); 86 | } 87 | Log.d("TextToSpeech", "Initialization Suceeded! " + System.currentTimeMillis()); 88 | 89 | speak(mContext.getString(R.string.voice_ready) + ","); 90 | } else { 91 | Log.e("error", "Initialization Failed! " + status); 92 | } 93 | } 94 | 95 | public void shutDown() { 96 | isInit = false; 97 | fullyInited = false; 98 | if (mTts != null) { 99 | mTts.shutdown(); 100 | mTts = null; 101 | } 102 | } 103 | 104 | public void speak(String text) { 105 | // Log.d("TextToSpeech", text + " " + System.currentTimeMillis()); 106 | if (isInit) { 107 | if (Build.VERSION.SDK_INT >= 21) { 108 | mTts.speak(text, TextToSpeech.QUEUE_ADD, null, "utt" + utterid); 109 | } else { 110 | mTts.speak(text, TextToSpeech.QUEUE_ADD, null); 111 | } 112 | utterid++; 113 | } else { 114 | Log.e("error", "TTS Not Initialized"); 115 | } 116 | } 117 | 118 | public void stop() { 119 | if (fullyInited) { 120 | mTts.stop(); 121 | } 122 | } 123 | 124 | public float getPitch() { 125 | return mPitch; 126 | } 127 | 128 | private void setPitch(float pitch) { 129 | mPitch = pitch; 130 | mTts.setPitch(pitch); 131 | } 132 | 133 | public float getSpeed() { 134 | return mSpeed; 135 | } 136 | 137 | private void setSpeed(float speed) { 138 | mSpeed = speed; 139 | mTts.setSpeechRate(speed); 140 | } 141 | 142 | public boolean isReady() { 143 | return fullyInited; 144 | } 145 | 146 | public void setVoiceReadyListener(VoiceReadyListener fil) { 147 | mFil = fil; 148 | } 149 | 150 | 151 | public interface VoiceReadyListener { 152 | void onVoiceReady(TextToVoice ttv); 153 | 154 | void onSpeakComplete(TextToVoice ttv); 155 | 156 | void onError(TextToVoice ttv); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/data/AppData.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.data; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.TreeSet; 14 | 15 | import static android.content.Context.MODE_PRIVATE; 16 | 17 | /** 18 | * Created by tom on 12/20/16. 19 | *

20 | * Copyright (C) 2016 tom 21 | *

22 | * This program is free software; you can redistribute it and/or modify 23 | * it under the terms of the GNU General Public License as published by 24 | * the Free Software Foundation; either version 3 of the License, or 25 | * (at your option) any later version. 26 | *

27 | * This program is distributed in the hope that it will be useful, 28 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | * GNU General Public License for more details. 31 | */ 32 | public class AppData { 33 | 34 | private static final String USERS_KEY = "users"; 35 | private final SharedPreferences mPrefs; 36 | 37 | private final Context mContext; 38 | private final Map users = new HashMap<>(); 39 | 40 | private AppData(Context context) { 41 | mContext = context; 42 | mPrefs = mContext.getSharedPreferences("app", MODE_PRIVATE); 43 | } 44 | 45 | public static AppData getAppData(Context context) { 46 | return new AppData(context); 47 | } 48 | 49 | public static UserData.Subject getSubjectForUser(Context context, String username, String subject) { 50 | return AppData.getAppData(context).getUser(username).getSubjectForUser(subject); 51 | } 52 | 53 | public static List sort(Collection collection) { 54 | List list = new ArrayList<>(collection); 55 | 56 | Collections.sort(list); 57 | return list; 58 | } 59 | 60 | Context getContext() { 61 | return mContext; 62 | } 63 | 64 | public void setLastSelectedUser(String username) { 65 | mPrefs.edit().putString("lastselecteduser", username).apply(); 66 | } 67 | 68 | public String getLastSelectedUser(String defaultname) { 69 | return mPrefs.getString("lastselecteduser", defaultname); 70 | } 71 | 72 | public List listUsers() { 73 | return sort(getUsersSet()); 74 | } 75 | 76 | private Set getUsersSet() { 77 | 78 | Set usernames = new TreeSet<>(); 79 | usernames = mPrefs.getStringSet(USERS_KEY, usernames); 80 | return usernames; 81 | } 82 | 83 | public UserData addUser(String username, String avatar) { 84 | 85 | Set usernames = getUsersSet(); 86 | if (usernames.contains(username)) { 87 | return null; 88 | } 89 | usernames.add(username); 90 | mPrefs.edit().putStringSet(USERS_KEY, usernames).apply(); 91 | 92 | UserData user = getUser(username); 93 | user.setAvatar(avatar); 94 | 95 | return user; 96 | } 97 | 98 | private boolean avatarInUse(String avatar) { 99 | return mPrefs.getBoolean("avatar:" + avatar, false); 100 | } 101 | 102 | public void setAvatarInUse(String avatar, boolean inuse) { 103 | if (avatar != null) { 104 | mPrefs.edit().putBoolean("avatar:" + avatar, inuse).apply(); 105 | } 106 | } 107 | 108 | public UserData getUser(String username) { 109 | if (username == null) { 110 | return null; 111 | } 112 | UserData data = users.get(username); 113 | if (data == null) { 114 | data = new UserData(this, username); 115 | users.put(username, data); 116 | } 117 | return data; 118 | } 119 | 120 | public boolean deleteUser(String username) { 121 | if (username == null) { 122 | return false; 123 | } 124 | users.remove(username); 125 | 126 | Set usernames = getUsersSet(); 127 | if (usernames.contains(username)) { 128 | usernames.remove(username); 129 | mPrefs.edit().putStringSet(USERS_KEY, usernames).apply(); 130 | return true; 131 | } 132 | return false; 133 | } 134 | 135 | public List getUnusedAvatars() { 136 | return getUnusedAvatars(null); 137 | } 138 | 139 | public List getUnusedAvatars(String additional) { 140 | List avatarlist = new ArrayList<>(); 141 | for (String avatar : UserData.avatars) { 142 | if (!avatarInUse(avatar)) { 143 | avatarlist.add(avatar); 144 | } 145 | } 146 | if (additional != null) avatarlist.add(additional); 147 | Collections.sort(avatarlist); 148 | return avatarlist; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/data/SubjectGroup.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.data; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | 6 | import com.quaap.primary.R; 7 | 8 | /** 9 | * Created by tom on 1/2/17. 10 | *

11 | * Copyright (C) 2017 tom 12 | *

13 | * This program is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3 of the License, or 16 | * (at your option) any later version. 17 | *

18 | * This program is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | * GNU General Public License for more details. 22 | */ 23 | public enum SubjectGroup { 24 | Math("M", R.string.group_m, R.color.group_color_m), 25 | LanguageArts("LA", R.string.group_la, R.color.group_color_la), 26 | TimeMoney("TM", R.string.group_tm, R.color.group_color_tm), 27 | Science("Sc", R.string.group_sc, R.color.group_color_sc), 28 | Music("Mu", R.string.group_mu, R.color.group_color_mu), 29 | Art("Ar", R.string.group_ar, R.color.group_color_ar); 30 | 31 | 32 | private final String mCode; 33 | private final int mResId; 34 | private final int mColorResId; 35 | 36 | SubjectGroup(String code, int resId, int colorResId) { 37 | mCode = code; 38 | mResId = resId; 39 | mColorResId = colorResId; 40 | } 41 | 42 | public String getCode() { 43 | return mCode; 44 | } 45 | 46 | public int getColor(Context context) { 47 | if (Build.VERSION.SDK_INT >= 23) { 48 | return context.getColor(mColorResId); 49 | } else { 50 | return context.getResources().getColor(mColorResId); 51 | } 52 | } 53 | 54 | public String getText(Context context) { 55 | return context.getString(mResId); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/base/data/Subjects.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.base.data; 2 | 3 | import android.content.Context; 4 | 5 | import com.quaap.primary.Levels; 6 | import com.quaap.primary.base.Level; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by tom on 12/25/16. 15 | *

16 | * Copyright (C) 2016 tom 17 | *

18 | * This program is free software; you can redistribute it and/or modify 19 | * it under the terms of the GNU General Public License as published by 20 | * the Free Software Foundation; either version 3 of the License, or 21 | * (at your option) any later version. 22 | *

23 | * This program is distributed in the hope that it will be useful, 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | * GNU General Public License for more details. 27 | */ 28 | 29 | public class Subjects { 30 | 31 | private static Subjects inst; 32 | private final Desc[] subjects; 33 | private final Map subjectMap = new HashMap<>(); 34 | private final List codes = new ArrayList<>(); 35 | private Subjects(Context context) { 36 | subjects = loadSubjects(context); 37 | for (Desc subject : subjects) { 38 | subjectMap.put(subject.getCode(), subject); 39 | codes.add(subject.getCode()); 40 | } 41 | } 42 | 43 | public synchronized static Subjects getInstance(Context context) { 44 | if (inst == null) { 45 | inst = new Subjects(context); 46 | } 47 | return inst; 48 | } 49 | 50 | private static Desc[] loadSubjects(Context context) { 51 | 52 | return Levels.getSubjectInstances(context); 53 | } 54 | 55 | public List getCodes() { 56 | 57 | return codes; 58 | } 59 | 60 | public String getNextCode(String code) { 61 | int pos = codes.indexOf(code); 62 | if (pos + 1 < codes.size()) { 63 | return codes.get(pos + 1); 64 | } 65 | return null; 66 | } 67 | 68 | 69 | public int getCount() { 70 | return subjects.length; 71 | } 72 | 73 | public Desc get(String code) { 74 | return subjectMap.get(code); 75 | } 76 | 77 | public Desc get(int num) { 78 | return subjects[num]; 79 | } 80 | 81 | public static class Desc { 82 | 83 | private SubjectGroup group; 84 | private String code; 85 | private String name; 86 | private String desc; 87 | private Class activityclass; 88 | 89 | 90 | private final Level[] levels; 91 | 92 | public Desc(Context context, SubjectGroup group, int code, int name, int desc, Class activityClass, Level[] levels) { 93 | this.setGroup(group); 94 | this.setCode(context.getString(code)); 95 | this.setName(context.getString(name)); 96 | this.setDesc(context.getString(desc)); 97 | this.setActivityclass(activityClass); 98 | this.levels = levels; 99 | 100 | } 101 | 102 | public String getCode() { 103 | return code; 104 | } 105 | 106 | void setCode(String code) { 107 | this.code = code; 108 | } 109 | 110 | public String getName() { 111 | return name; 112 | } 113 | 114 | void setName(String name) { 115 | this.name = name; 116 | } 117 | 118 | public String getDesc() { 119 | return desc; 120 | } 121 | 122 | void setDesc(String desc) { 123 | this.desc = desc; 124 | } 125 | 126 | public Class getActivityclass() { 127 | return activityclass; 128 | } 129 | 130 | void setActivityclass(Class activityclass) { 131 | this.activityclass = activityclass; 132 | } 133 | 134 | public Level[] getLevels() { 135 | return levels; 136 | } 137 | 138 | public SubjectGroup getGroup() { 139 | return group; 140 | } 141 | 142 | void setGroup(SubjectGroup group) { 143 | this.group = group; 144 | } 145 | 146 | 147 | } 148 | } -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/math/BasicMathActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.math; 2 | 3 | 4 | /** 5 | * Created by tom on 12/15/16. 6 | *

7 | * Copyright (C) 2016 Tom Kliethermes 8 | *

9 | * This program is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 3 of the License, or 12 | * (at your option) any later version. 13 | *

14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | */ 19 | 20 | import android.annotation.SuppressLint; 21 | import android.os.Bundle; 22 | import android.view.Gravity; 23 | import android.widget.GridLayout; 24 | import android.widget.TextView; 25 | 26 | import com.quaap.primary.R; 27 | import com.quaap.primary.base.StdGameActivity; 28 | import com.quaap.primary.base.SubjectBaseActivity; 29 | import com.quaap.primary.base.component.InputMode; 30 | 31 | import java.util.ArrayList; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Locale; 35 | 36 | public class BasicMathActivity extends StdGameActivity implements SubjectBaseActivity.AnswerGivenListener, SubjectBaseActivity.AnswerTypedListener { 37 | 38 | private int num1; 39 | private int num2; 40 | private MathOp op; 41 | private int answer; 42 | 43 | public BasicMathActivity() { 44 | super(R.layout.std_math_prob); 45 | 46 | } 47 | 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | } 54 | 55 | @Override 56 | protected void onPause() { 57 | if (op!=null) { 58 | saveLevelValue("num1", num1); 59 | saveLevelValue("num2", num2); 60 | saveLevelValue("op", op.name()); 61 | } 62 | super.onPause(); 63 | } 64 | 65 | 66 | 67 | @Override 68 | protected void onShowProbImpl() { 69 | 70 | TextView txtMathHint = findViewById(R.id.txtMathHint); 71 | txtMathHint.setText(""); 72 | 73 | TextView num1txt = findViewById(R.id.num1); 74 | TextView num2txt = findViewById(R.id.num2); 75 | TextView optxt = findViewById(R.id.op); 76 | 77 | num1 = getSavedLevelValue("num1", Integer.MIN_VALUE); 78 | num2 = getSavedLevelValue("num2", Integer.MIN_VALUE); 79 | op = MathOp.valueOf(getSavedLevelValue("op", "Plus")); 80 | if (num1 == Integer.MIN_VALUE || num2 == Integer.MIN_VALUE) { 81 | makeRandomProblem(); 82 | } else { 83 | deleteSavedLevelValue("num1"); 84 | deleteSavedLevelValue("num2"); 85 | deleteSavedLevelValue("op"); 86 | } 87 | 88 | num1txt.setText(String.format(Locale.getDefault(), "%d", num1)); 89 | num2txt.setText(String.format(Locale.getDefault(), "%d", num2)); 90 | optxt.setText(op.toString()); 91 | answer = getAnswer(num1, num2, op); 92 | 93 | // LinearLayout answerarea = (LinearLayout)findViewById(R.id.answer_area); 94 | float fontsize = num1txt.getTextSize(); 95 | BasicMathLevel level = (BasicMathLevel) getLevel(); 96 | 97 | int fac = Math.max(3, (int)Math.sqrt(level.getMaxNum() + (op.ordinal()*2))); 98 | 99 | if (level.getInputMode() == InputMode.Buttons) { 100 | setFasttimes(fac * 300, fac * 400, fac * 600); 101 | makeAnswerButtons(getAnswerArea(), fontsize); 102 | } else if (level.getInputMode() == InputMode.Input) { 103 | setFasttimes(fac * 400, fac * 600, fac * 800); 104 | makeInputBox(getAnswerArea(), getKeysArea(), this, INPUTTYPE_NUMBER, 3, fontsize / 2); 105 | startHint(op.ordinal() + 1); 106 | 107 | } else { 108 | throw new IllegalArgumentException("Unknown inputMode! " + level.getInputMode()); 109 | } 110 | 111 | 112 | } 113 | 114 | 115 | @Override 116 | protected void onPerformHint(int hintTick) { 117 | String a = answer+""; 118 | if (hintTick level.getRounds() / 2) { //increase difficulty in second half of level 205 | if (negsallowed) { 206 | num1 = Math.random() > 5 ? getRand(max / 2 - 1, max) : getRand(min, min / 2 + 1); 207 | } else { 208 | num1 = getRand(max / 2 - 1, max); 209 | } 210 | } else { 211 | num1 = getRand(min / 2, max / 2 + 1); 212 | } 213 | if (level.getDoubles()) { 214 | if (num1==0) { 215 | num1 = getRand(1, max); 216 | } 217 | num2 = num1; 218 | } else { 219 | num2 = getRand(min, max); 220 | if ((num2 == 0 || num2 == 1) && Math.random() > .3) 221 | num2 = getRand(2, max); //reduce number of x+0 and x+1 222 | 223 | if (level.getNegatives() == Negatives.Required && num1 >= 0 && num2 >= 0) { //force a negative value 224 | num1 = -getRand(1, max); 225 | } 226 | 227 | } 228 | if ((op == MathOp.Minus && level.getNegatives() == Negatives.None) || op == MathOp.Divide) { 229 | if (num1 < num2) { 230 | int tmp = num1; 231 | num1 = num2; 232 | num2 = tmp; 233 | } 234 | if (op == MathOp.Divide) { 235 | if (num2 == 0) { 236 | num2 = getRand(1, max); 237 | if (num1 < num2) num1 = getRand(num2, max); 238 | } 239 | // if (num1 % num2 != 0) { 240 | num1 = num1 * num2; 241 | // } 242 | } 243 | } 244 | if (op == MathOp.Minus) { 245 | num1 = num1 + num2; 246 | } 247 | //prevent 2 identical problems in a row 248 | } while (tries++ < 50 && seenProblem(num1, num2, op)); 249 | 250 | 251 | } 252 | 253 | private List getAnswerChoices(int numans) { 254 | List answers = new ArrayList<>(); 255 | answers.add(answer); 256 | 257 | boolean allownegs = ((BasicMathLevel) getLevel()).getNegatives() != Negatives.None; 258 | int range = Math.abs(answer / 10); 259 | for (int i = 1; i < numans; i++) { 260 | int tmpans; 261 | do { 262 | 263 | if (allownegs) { 264 | 265 | if (getRand(10) > 8) { 266 | tmpans = Math.abs(num1) + Math.abs(num2); 267 | } else if (getRand(10) > 5) { 268 | tmpans = -answer; 269 | } else { 270 | tmpans = answer + getRand(-3 - range, 3 + range); 271 | } 272 | 273 | } else if (getRand(10) > 3) { 274 | tmpans = answer + getRand(-3 - range, 3 + range); //normal random 275 | } else { 276 | //tricky answers 277 | switch (op) { 278 | case Plus: 279 | tmpans = Math.max(num1, num2) - Math.min(num1, num2); 280 | break; 281 | case Times: 282 | tmpans = num1 * (num2 + getRand(1, 2)); 283 | break; 284 | case Divide: 285 | tmpans = num1 - num2; 286 | break; 287 | case Minus: 288 | default: 289 | tmpans = num1 + num2; 290 | break; 291 | } 292 | } 293 | 294 | } while (answers.contains(tmpans) || (!allownegs && tmpans < 0)); 295 | answers.add(tmpans); 296 | } 297 | Collections.shuffle(answers); 298 | return answers; 299 | } 300 | 301 | 302 | @SuppressLint("RtlHardcoded") 303 | private void makeAnswerButtons(GridLayout answerarea, float fontsize) { 304 | 305 | List answers = getAnswerChoices(4); 306 | 307 | 308 | 309 | makeChoiceButtons(answerarea, answers, this, fontsize, null, Gravity.RIGHT); 310 | 311 | } 312 | 313 | 314 | } 315 | 316 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/math/BasicMathLevel.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.math; 2 | 3 | /** 4 | * Created by tom on 12/15/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | import android.content.Context; 19 | 20 | import com.quaap.primary.R; 21 | import com.quaap.primary.base.StdLevel; 22 | import com.quaap.primary.base.component.InputMode; 23 | 24 | public class BasicMathLevel extends StdLevel { 25 | 26 | private final MathOp mMaxMathOp; 27 | private final MathOp mMinMathOp; 28 | private final int mMaxNum; 29 | private final Negatives mNegatives; 30 | private final boolean mDoubles; 31 | 32 | public BasicMathLevel(int subjectkey, MathOp maxMathOp, int maxNum, int rounds, InputMode inputMode) { 33 | this(subjectkey, maxMathOp, maxMathOp, maxNum, rounds, inputMode); 34 | } 35 | 36 | public BasicMathLevel(int subjectkey, MathOp maxMathOp, MathOp minMathOp, int maxNum, int rounds, InputMode inputMode) { 37 | this(subjectkey, maxMathOp, minMathOp, maxNum, Negatives.None, rounds, inputMode); 38 | } 39 | 40 | public BasicMathLevel(int subjectkey, MathOp maxMathOp, int maxNum, Negatives negatives, int rounds, InputMode inputMode) { 41 | this(subjectkey, maxMathOp, maxMathOp, maxNum, negatives, rounds, inputMode); 42 | } 43 | public BasicMathLevel(int subjectkey, MathOp maxMathOp, MathOp minMathOp, int maxNum, Negatives negatives, int rounds, InputMode inputMode) { 44 | this(subjectkey, maxMathOp, minMathOp, maxNum, negatives, rounds, false, inputMode); 45 | } 46 | public BasicMathLevel(int subjectkey, MathOp maxMathOp, MathOp minMathOp, int maxNum, Negatives negatives, int rounds, boolean doubles, InputMode inputMode) { 47 | super(subjectkey, rounds, inputMode); 48 | 49 | mMaxMathOp = maxMathOp; 50 | mMinMathOp = minMathOp; 51 | mMaxNum = maxNum; 52 | mNegatives = negatives; 53 | mDoubles = doubles; 54 | 55 | } 56 | 57 | @Override 58 | public String getDescription(Context context) { 59 | String ops = getOpsStr(context); 60 | String max = (mNegatives != Negatives.None ? "\u00B1" : "") + mMaxNum; 61 | return ops + " / " +(getDoubles()?context.getString(R.string.doubles):"")+ context.getString(R.string.max, max) + ". " + getInputModeString(context); 62 | 63 | } 64 | 65 | @Override 66 | public String getShortDescription(Context context) { 67 | String ops = getOpsSymStr(); 68 | if (mMaxMathOp == mMinMathOp) { 69 | ops = mMaxMathOp.name(); 70 | } 71 | String max = (mNegatives != Negatives.None ? "\u00B1" : "") + mMaxNum; 72 | return context.getString(R.string.max, max) + ". " + ops; 73 | } 74 | 75 | private String getOpsStr(Context context) { 76 | String ops = ""; 77 | for (MathOp m : MathOp.values()) { 78 | if (m.ordinal() >= mMinMathOp.ordinal() && m.ordinal() <= mMaxMathOp.ordinal()) { 79 | ops += context.getString(m.getResId()); 80 | if (m.ordinal() < mMaxMathOp.ordinal()) { 81 | ops += ", "; 82 | } 83 | } 84 | } 85 | return ops; 86 | } 87 | 88 | private String getOpsSymStr() { 89 | String ops = ""; 90 | for (MathOp m : MathOp.values()) { 91 | if (m.ordinal() >= mMinMathOp.ordinal() && m.ordinal() <= mMaxMathOp.ordinal()) { 92 | ops += m.toString(); 93 | if (m.ordinal() < mMaxMathOp.ordinal()) { 94 | ops += ", "; 95 | } 96 | } 97 | } 98 | return ops; 99 | } 100 | 101 | public MathOp getMaxMathOp() { 102 | return mMaxMathOp; 103 | } 104 | 105 | public MathOp getMinMathOp() { 106 | return mMinMathOp; 107 | } 108 | 109 | public int getMaxNum() { 110 | return mMaxNum; 111 | } 112 | 113 | public Negatives getNegatives() { 114 | return mNegatives; 115 | } 116 | 117 | public boolean getDoubles() { 118 | return mDoubles; 119 | } 120 | 121 | 122 | // public int getMaxNum(int prevCorrect) { 123 | // return (int)(mMaxNum * ((double)Math.max(prevCorrect, mRounds/5.0)/mRounds)); 124 | // } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/math/MathOp.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.math; 2 | 3 | import com.quaap.primary.R; 4 | 5 | import java.security.SecureRandom; 6 | 7 | /** 8 | * Created by tom on 12/14/16. 9 | *

10 | * Copyright (C) 2016 Tom Kliethermes 11 | *

12 | * This program is free software; you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation; either version 3 of the License, or 15 | * (at your option) any later version. 16 | *

17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | */ 22 | public enum MathOp { 23 | Plus("+", R.string.plus), 24 | Minus("-", R.string.minus), 25 | Times("\u00D7", R.string.times), 26 | Divide("\u00F7", R.string.divide); 27 | 28 | private static final SecureRandom random = new SecureRandom(); 29 | private final String mDisplay; 30 | private final int mResid; 31 | 32 | MathOp(String display, int resid) { 33 | mDisplay = display; 34 | mResid = resid; 35 | } 36 | 37 | public static MathOp random(MathOp upto) { 38 | return randomEnum(MathOp.class, MathOp.Plus, upto); 39 | } 40 | 41 | public static MathOp random(MathOp start, MathOp upto) { 42 | return randomEnum(MathOp.class, start, upto); 43 | } 44 | 45 | private static > T randomEnum(Class clazz, T start, T upto) { 46 | 47 | int max = upto.ordinal(); 48 | int min = start.ordinal(); 49 | 50 | int x = random.nextInt(max - min + 1) + min; 51 | return clazz.getEnumConstants()[x]; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return mDisplay; 57 | } 58 | 59 | public int getResId() { 60 | return mResid; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/math/Negatives.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.math; 2 | 3 | /** 4 | * Created by tom on 12/14/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | public enum Negatives { 19 | None, Allowed, Required 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/math/SortingLevel.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.math; 2 | 3 | import android.content.Context; 4 | 5 | import com.quaap.primary.R; 6 | import com.quaap.primary.base.StdLevel; 7 | import com.quaap.primary.base.component.InputMode; 8 | 9 | /** 10 | * Created by tom on 1/1/17. 11 | *

12 | * Copyright (C) 2017 tom 13 | *

14 | * This program is free software; you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation; either version 3 of the License, or 17 | * (at your option) any later version. 18 | *

19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | */ 24 | public class SortingLevel extends StdLevel { 25 | private final int mNumItems; 26 | private final int mMaxNum; 27 | 28 | public SortingLevel(int subjectkey, int numItems, int maxNum, int rounds) { 29 | super(subjectkey, rounds, InputMode.None); 30 | mNumItems = numItems; 31 | mMaxNum = maxNum; 32 | 33 | } 34 | 35 | public int getNumItems() { 36 | return mNumItems; 37 | } 38 | 39 | public int getMaxNum() { 40 | return mMaxNum; 41 | } 42 | 43 | @Override 44 | public String getDescription(Context context) { 45 | return context.getString(R.string.sort_desc, mNumItems, mMaxNum); 46 | } 47 | 48 | @Override 49 | public String getShortDescription(Context context) { 50 | return context.getString(R.string.sort_sdesc, mNumItems, mMaxNum); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/partsofspeech/plurals/PluralActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.partsofspeech.plurals; 2 | 3 | /** 4 | * Created by tom on 12/15/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | import android.os.Bundle; 19 | import android.util.Log; 20 | import android.widget.LinearLayout; 21 | import android.widget.TextView; 22 | 23 | import com.quaap.primary.R; 24 | import com.quaap.primary.base.StdGameActivity; 25 | import com.quaap.primary.base.StdLevel; 26 | import com.quaap.primary.base.SubjectBaseActivity; 27 | import com.quaap.primary.base.component.InputMode; 28 | 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | import java.util.Collections; 32 | import java.util.List; 33 | import java.util.Map; 34 | import java.util.TreeMap; 35 | 36 | public class PluralActivity extends StdGameActivity 37 | implements SubjectBaseActivity.AnswerGivenListener, 38 | SubjectBaseActivity.AnswerTypedListener { 39 | 40 | 41 | private final int numanswers = 4; 42 | 43 | private List words; 44 | private String word; 45 | private String answer; 46 | private String[] unpluralMap; 47 | private Map pluralsMap; 48 | private String[] wordScores; 49 | 50 | public PluralActivity() { 51 | super(R.layout.std_plural_prob); 52 | } 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | 58 | words = Arrays.asList(getResources().getStringArray(R.array.common_nouns)); 59 | 60 | unpluralMap = getResources().getStringArray(R.array.unplural); 61 | 62 | wordScores = getResources().getStringArray(R.array.plural_word_scores); 63 | 64 | pluralsMap = arrayPairsToMap(getResources().getStringArray(R.array.plurals)); 65 | 66 | 67 | } 68 | 69 | private Map arrayPairsToMap(String[] array) { 70 | 71 | if (array.length % 2 != 0) { 72 | throw new IllegalArgumentException("array to map must have even number of elements"); 73 | } 74 | Map map = new TreeMap<>(); 75 | for (int j = 0; j < array.length; j += 2) { 76 | map.put(array[j], array[j + 1]); 77 | } 78 | return map; 79 | } 80 | 81 | @Override 82 | protected void onPause() { 83 | 84 | if (word!=null) { 85 | saveLevelValue("word", word); 86 | } 87 | super.onPause(); 88 | } 89 | 90 | @Override 91 | protected void onResume() { 92 | 93 | super.onResume(); 94 | if (isLandscape() && ((StdLevel) getLevel()).getInputMode() == InputMode.Input) { 95 | LinearLayout problem_area = findViewById(R.id.problem_area); 96 | problem_area.setOrientation(LinearLayout.HORIZONTAL); 97 | } 98 | 99 | } 100 | 101 | @Override 102 | protected void onShowLevel() { 103 | super.onShowLevel(); 104 | 105 | 106 | } 107 | 108 | @Override 109 | protected void onShowProbImpl() { 110 | 111 | PluralLevel level = (PluralLevel) getLevel(); 112 | word = getSavedLevelValue("word", (String) null); 113 | if (word == null) { 114 | int tries = 0; 115 | do { 116 | int score; 117 | do { 118 | word = words.get(getRand(words.size() - 1)); 119 | score = scoreWord(word); 120 | //System.out.println(word + " " + score + " " + pluralsMap.containsKey(word)); 121 | } 122 | while (!pluralsMap.containsKey(word) || (getRand(10) > 2 && score < 2) || (getRand(10) > 3 && score < 3)); //try to get tricky words 123 | 124 | } 125 | while (tries++ < 200 && (word.length() < level.getMinWordLength() || word.length() > level.getMaxWordLength() || seenProblem(word))); 126 | } else { 127 | deleteSavedLevelValue("word"); 128 | } 129 | 130 | int score = scoreWord(word); 131 | if (((PluralLevel) getLevel()).getInputMode() == InputMode.Buttons) { 132 | setFasttimes(800 + score*100, 900 + score*250, 1000 + score*320); 133 | } else { 134 | setFasttimes(900 + score*100, 1100 + score*300, 1400 + score*500); 135 | } 136 | 137 | 138 | answer = pluralsMap.get(word); 139 | Log.d("plural", word + " -> " + answer); 140 | 141 | TextView plural = findViewById(R.id.txtplural); 142 | plural.setText(capitalize(word)); 143 | 144 | 145 | final TextView hint = findViewById(R.id.plurHint); 146 | hint.setText(""); 147 | 148 | if (level.getInputMode() == InputMode.Buttons) { 149 | List answers = getAnswerChoices(answer); 150 | 151 | makeChoiceButtons(getAnswerArea(), answers, this); 152 | 153 | } else if (level.getInputMode() == InputMode.Input) { 154 | 155 | makeInputBox(getAnswerArea(), getKeysArea(), this, INPUTTYPE_TEXT, 6, 0, word); 156 | 157 | startHint(word.length()); 158 | 159 | } else { 160 | throw new IllegalArgumentException("Unknown inputMode! " + level.getInputMode()); 161 | } 162 | 163 | } 164 | 165 | @Override 166 | public boolean onAnswerTyped(String answer) { 167 | return onAnswerGiven(answer); 168 | } 169 | 170 | @Override 171 | public boolean onAnswerGiven(String answer) { 172 | 173 | boolean isright = answer.toLowerCase().trim().equals(this.answer.toLowerCase()); 174 | 175 | answerDone(isright, word, this.answer, answer.trim()); 176 | 177 | return isright; 178 | } 179 | 180 | /** 181 | * Calculate the points from the current problem 182 | * By default this is based on simply the level number. 183 | * 184 | * Override this in each activity. 185 | * 186 | * Range, in general: 187 | * difficulty 1: 1 - 50 188 | * difficulty 2: up to 100 189 | * difficulty 3: up to 200 190 | * difficulty 4: up to 500 191 | * difficulty 5: up to 1000 192 | * 193 | * @return the points for the current problem 194 | */ 195 | @Override 196 | protected int onCalculatePoints() { 197 | float scoremult = answer.length() - getHintTicks(); 198 | 199 | if (scoremult<=0) { //if the hint is fully shown, give partial credit. 200 | scoremult=.3f; 201 | } 202 | return super.onCalculatePoints() + (int) (1 + word.length() * scoreWord(word) * scoremult); 203 | } 204 | 205 | 206 | private List getAnswerChoices(String realanswer) { 207 | List answers = new ArrayList<>(); 208 | answers.add(realanswer); 209 | int maxtries = unpluralMap.length; 210 | int tries; 211 | do { 212 | String badspell; 213 | tries = 0; 214 | do { 215 | badspell = unplural(word); 216 | } while (tries++ < maxtries && answers.contains(badspell)); 217 | if (tries < maxtries) { 218 | answers.add(badspell); 219 | } 220 | 221 | } while (answers.size() < numanswers && tries < maxtries); 222 | 223 | Collections.shuffle(answers); 224 | return answers; 225 | } 226 | 227 | @Override 228 | protected void onPerformHint(int hintTick) { 229 | final TextView hint = findViewById(R.id.plurHint); 230 | if (hintTick < answer.length()) { 231 | 232 | hint.setText(answer.substring(0, hintTick+1)); 233 | 234 | } else { 235 | cancelHint(); 236 | } 237 | super.onPerformHint(hintTick); 238 | 239 | } 240 | 241 | private String unplural(String word) { 242 | 243 | for (int j = 0; j < 1; j++) { 244 | int i = ((int) (Math.random() * ((unpluralMap.length - 1) / 2)) * 2); 245 | word = word.replaceFirst(unpluralMap[i], unpluralMap[i + 1]); 246 | } 247 | return word; 248 | } 249 | 250 | private int scoreWord(String candidate) { 251 | 252 | for (int difflevel = wordScores.length - 1; difflevel >= 0; difflevel--) { 253 | if (candidate.matches(wordScores[difflevel])) { 254 | return difflevel + 1; 255 | } 256 | 257 | } 258 | 259 | return 1; 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/partsofspeech/plurals/PluralLevel.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.partsofspeech.plurals; 2 | 3 | import android.content.Context; 4 | 5 | import com.quaap.primary.R; 6 | import com.quaap.primary.base.StdLevel; 7 | import com.quaap.primary.base.component.InputMode; 8 | 9 | /** 10 | * Created by tom on 12/18/16. 11 | *

12 | * Copyright (C) 2016 tom 13 | *

14 | * This program is free software; you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation; either version 3 of the License, or 17 | * (at your option) any later version. 18 | *

19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | */ 24 | public class PluralLevel extends StdLevel { 25 | 26 | private static final int wDiff = 3; 27 | private final int mMaxwordlength; 28 | private final int mMinwordlength; 29 | 30 | public PluralLevel(int subjectkey, int maxwordlength, int rounds, InputMode inputMode) { 31 | this(subjectkey, maxwordlength > wDiff ? maxwordlength - wDiff : 1, maxwordlength, rounds, inputMode); 32 | } 33 | 34 | private PluralLevel(int subjectkey, int minwordlength, int maxwordlength, int rounds, InputMode inputMode) { 35 | super(subjectkey, rounds, inputMode); 36 | 37 | mMinwordlength = minwordlength; 38 | mMaxwordlength = maxwordlength; 39 | 40 | } 41 | 42 | @Override 43 | public String getDescription(Context context) { 44 | return context.getString(R.string.length, mMaxwordlength) + " " + getInputModeString(context); 45 | } 46 | 47 | @Override 48 | public String getShortDescription(Context context) { 49 | return "Wl: " + mMaxwordlength; 50 | } 51 | 52 | public int getMaxWordLength() { 53 | return mMaxwordlength; 54 | } 55 | 56 | public int getMinWordLength() { 57 | return mMinwordlength; 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/spelling/SpellingActivity.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.spelling; 2 | 3 | /** 4 | * Created by tom on 12/15/16. 5 | *

6 | * Copyright (C) 2016 Tom Kliethermes 7 | *

8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 3 of the License, or 11 | * (at your option) any later version. 12 | *

13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | */ 18 | import android.os.Bundle; 19 | import android.os.Handler; 20 | import android.util.Log; 21 | import android.view.View; 22 | import android.widget.Button; 23 | import android.widget.LinearLayout; 24 | import android.widget.TextView; 25 | 26 | import com.quaap.primary.Primary; 27 | import com.quaap.primary.R; 28 | import com.quaap.primary.base.StdGameActivity; 29 | import com.quaap.primary.base.SubjectBaseActivity; 30 | import com.quaap.primary.base.component.InputMode; 31 | import com.quaap.primary.base.component.TextToVoice; 32 | 33 | import java.util.ArrayList; 34 | import java.util.Arrays; 35 | import java.util.Collections; 36 | import java.util.List; 37 | 38 | public class SpellingActivity extends StdGameActivity 39 | implements TextToVoice.VoiceReadyListener, 40 | SubjectBaseActivity.AnswerGivenListener, 41 | SubjectBaseActivity.AnswerTypedListener { 42 | 43 | 44 | private final Handler handler = new Handler(); 45 | private final int numanswers = 4; 46 | private TextToVoice v; 47 | private List words; 48 | 49 | private String word; 50 | private String[] unspellMap; 51 | 52 | 53 | private String wordStart; 54 | 55 | 56 | public SpellingActivity() { 57 | super(R.layout.std_spelling_prob); 58 | } 59 | 60 | @Override 61 | protected void onCreate(Bundle savedInstanceState) { 62 | 63 | unspellMap = getResources().getStringArray(R.array.unspell); 64 | 65 | if (unspellMap.length % 2 != 0) { 66 | throw new IllegalArgumentException("unspell array must have even number of arguments"); 67 | } 68 | 69 | super.onCreate(savedInstanceState); 70 | 71 | 72 | Button b = findViewById(R.id.btn_repeat); 73 | b.setOnClickListener(new View.OnClickListener() { 74 | @Override 75 | public void onClick(View view) { 76 | v.speak(word); 77 | } 78 | }); 79 | } 80 | 81 | @Override 82 | protected void onPause() { 83 | if (v!=null) { 84 | v.stop(); 85 | 86 | saveLevelValue("word", word); 87 | } 88 | super.onPause(); 89 | } 90 | 91 | @Override 92 | protected void onResume() { 93 | //timer = new Timer(); 94 | 95 | setReadyForProblem(false); 96 | View problem_area = findViewById(R.id.problem_area); 97 | problem_area.setVisibility(View.VISIBLE); 98 | findViewById(R.id.spelling_problem_area).setVisibility(View.INVISIBLE); 99 | findViewById(R.id.spell_loading).setVisibility(View.VISIBLE); 100 | 101 | super.onResume(); 102 | 103 | 104 | if (isLandscape()) { 105 | LinearLayout spelling_problem_area = findViewById(R.id.spelling_problem_area); 106 | spelling_problem_area.setOrientation(LinearLayout.HORIZONTAL); 107 | } 108 | 109 | v = ((Primary) getApplicationContext()).getTextToVoice(); 110 | v.setVoiceReadyListener(this); 111 | if (v.isReady()) { 112 | setWeReady(); 113 | } 114 | 115 | 116 | } 117 | 118 | @Override 119 | protected void onShowLevel() { 120 | super.onShowLevel(); 121 | words = Arrays.asList(getResources().getStringArray(((SpellingLevel) getLevel()).getmWordlistId())); 122 | 123 | SpellingLevel level = (SpellingLevel) getLevel(); 124 | int fac = level.getMaxwordlength(); 125 | if (level.getInputMode() == InputMode.Buttons) { 126 | setFasttimes(600 + 50*fac, 700 + 75*fac, 800 + 125*fac); 127 | 128 | } else { 129 | setFasttimes(800 + 200*fac, 900 + 400*fac, 1000 + 600*fac); 130 | } 131 | 132 | } 133 | 134 | @Override 135 | protected void onBeforeShowProb() { 136 | 137 | } 138 | @Override 139 | protected void onShowProbImpl() { 140 | 141 | word = getSavedLevelValue("word", (String) null); 142 | if (word == null) { 143 | int tries = 0; 144 | do { 145 | word = words.get(getRand(words.size() - 1)); 146 | } while (tries++ < 50 && seenProblem(word)); 147 | } else { 148 | deleteSavedLevelValue("word"); 149 | } 150 | 151 | Log.d("spell", word); 152 | 153 | SpellingLevel level = (SpellingLevel) getLevel(); 154 | if (level.getInputMode() == InputMode.Input) { 155 | 156 | makeInputBox(getAnswerArea(), getKeysArea(), SpellingActivity.this, INPUTTYPE_TEXT, 6, 0); 157 | } 158 | 159 | v.speak(word); 160 | ((TextView) findViewById(R.id.spell_hint)).setText(""); 161 | 162 | } 163 | 164 | @Override 165 | public boolean onAnswerTyped(String answer) { 166 | return onAnswerGiven(answer); 167 | } 168 | 169 | @Override 170 | public boolean onAnswerGiven(String answer) { 171 | 172 | boolean isright = answer.toLowerCase().trim().equals(word.toLowerCase()); 173 | 174 | answerDone(isright, word, word, answer.trim()); 175 | 176 | return isright; 177 | } 178 | 179 | /** 180 | * Calculate the points from the current problem 181 | * By default this is based on simply the level number. 182 | * 183 | * Override this in each activity. 184 | * 185 | * Range, in general: 186 | * difficulty 1: 1 - 50 187 | * difficulty 2: up to 100 188 | * difficulty 3: up to 200 189 | * difficulty 4: up to 500 190 | * difficulty 5: up to 1000 191 | * 192 | * @return the points for the current problem 193 | */ 194 | @Override 195 | protected int onCalculatePoints() { 196 | float scoremult = word.length() - getHintTicks(); 197 | 198 | if (scoremult<=0) { //if the hint is fully shown, give partial credit. 199 | scoremult=.3f; 200 | } 201 | return super.onCalculatePoints() + (int) (1 + word.length() * scoremult); 202 | 203 | } 204 | 205 | @Override 206 | public void onVoiceReady(TextToVoice ttv) { 207 | handler.post(new Runnable() { 208 | @Override 209 | public void run() { 210 | setWeReady(); 211 | } 212 | }); 213 | Log.d("sp1", "onVoiceReady called"); 214 | } 215 | 216 | private void setWeReady() { 217 | findViewById(R.id.spelling_problem_area).setVisibility(View.VISIBLE); 218 | findViewById(R.id.spell_loading).setVisibility(View.GONE); 219 | setReadyForProblem(true); 220 | } 221 | 222 | @Override 223 | public void onSpeakComplete(TextToVoice ttv) { 224 | if (wordStart == null || !wordStart.equals(word)) { 225 | startTimer(); 226 | startHint(word.length()); 227 | wordStart = word; 228 | handler.post(new Runnable() { 229 | @Override 230 | public void run() { 231 | SpellingLevel level = (SpellingLevel) getLevel(); 232 | if (level.getInputMode() == InputMode.Buttons) { 233 | List answers = getAnswerChoices(word); 234 | 235 | makeChoiceButtons(getAnswerArea(), answers, SpellingActivity.this); 236 | 237 | } 238 | } 239 | }); 240 | } 241 | } 242 | 243 | @Override 244 | protected void onPerformHint(int hintTick) { 245 | final TextView hint = findViewById(R.id.spell_hint); 246 | if (hintTick < word.length()) { 247 | 248 | 249 | hint.setText(word.substring(0, hintTick+1)); 250 | 251 | if (hintTick == 1 || (word.length() > 3 && hintTick == word.length())) { 252 | v.speak(word); 253 | } 254 | 255 | } else { 256 | cancelHint(); 257 | } 258 | super.onPerformHint(hintTick); 259 | 260 | } 261 | 262 | @Override 263 | public void onError(TextToVoice ttv) { 264 | 265 | } 266 | 267 | private List getAnswerChoices(String realanswer) { 268 | List answers = new ArrayList<>(); 269 | int maxtries = unspellMap.length; 270 | int tries; 271 | do { 272 | String badspell; 273 | tries = 0; 274 | do { 275 | badspell = unspell(word); 276 | } while (tries++ < maxtries && answers.contains(badspell)); 277 | if (tries < maxtries) { 278 | answers.add(badspell); 279 | } 280 | 281 | } while (answers.size() < numanswers && tries < maxtries); 282 | 283 | Collections.shuffle(answers); 284 | return answers; 285 | } 286 | 287 | 288 | private String unspell(String word) { 289 | 290 | for (int j = 0; j < 1; j++) { 291 | int i = ((int) (Math.random() * ((unspellMap.length - 1) / 2)) * 2); 292 | 293 | String find = unspellMap[i]; 294 | String replacement = unspellMap[i + 1]; 295 | 296 | word = word.replaceFirst(find, replacement); 297 | } 298 | return word; 299 | } 300 | 301 | 302 | } 303 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/spelling/SpellingLevel.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.spelling; 2 | 3 | import android.content.Context; 4 | 5 | import com.quaap.primary.R; 6 | import com.quaap.primary.base.StdLevel; 7 | import com.quaap.primary.base.component.InputMode; 8 | 9 | /** 10 | * Created by tom on 12/18/16. 11 | *

12 | * Copyright (C) 2016 tom 13 | *

14 | * This program is free software; you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation; either version 3 of the License, or 17 | * (at your option) any later version. 18 | *

19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | */ 24 | public class SpellingLevel extends StdLevel { 25 | 26 | private final int mWordlistId; 27 | private final int mMaxwordlength; 28 | 29 | 30 | public SpellingLevel(int subjectkey, int wordlistid, int maxwordlength, int rounds, InputMode inputMode) { 31 | super(subjectkey, rounds, inputMode); 32 | mWordlistId = wordlistid; 33 | mMaxwordlength = maxwordlength; 34 | 35 | } 36 | 37 | 38 | @Override 39 | public String getDescription(Context context) { 40 | return context.getString(R.string.length, mMaxwordlength) + " " + getInputModeString(context); 41 | } 42 | 43 | @Override 44 | public String getShortDescription(Context context) { 45 | return "Wl: " + mMaxwordlength; 46 | } 47 | 48 | public int getmWordlistId() { 49 | return mWordlistId; 50 | } 51 | 52 | public int getMaxwordlength() { 53 | return mMaxwordlength; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/quaap/primary/timemoney/TimeLevel.java: -------------------------------------------------------------------------------- 1 | package com.quaap.primary.timemoney; 2 | 3 | import android.content.Context; 4 | 5 | import com.quaap.primary.R; 6 | import com.quaap.primary.base.StdLevel; 7 | import com.quaap.primary.base.component.InputMode; 8 | 9 | /** 10 | * Created by tom on 1/4/17. 11 | *

12 | * Copyright (C) 2017 tom 13 | *

14 | * This program is free software; you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation; either version 3 of the License, or 17 | * (at your option) any later version. 18 | *

19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | */ 24 | public class TimeLevel extends StdLevel { 25 | 26 | public enum MinuteGranularity { 27 | Hour(R.string.min_gran_hour), 28 | Half(R.string.min_gran_half), 29 | Quarter(R.string.min_gran_quarter), 30 | Five(R.string.min_gran_five), 31 | One(R.string.min_gran_one); 32 | 33 | private final int mResId; 34 | MinuteGranularity(int resId) { 35 | mResId = resId; 36 | } 37 | 38 | String getString(Context context) { 39 | return context.getString(mResId); 40 | } 41 | } 42 | //public enum SecondGranularity {Minute, Half, Quarter, Five, One} 43 | 44 | private final MinuteGranularity mMinuteGranularity; 45 | 46 | private final boolean mFuzzy; 47 | public TimeLevel(int subjectkey, MinuteGranularity minGran, int rounds, InputMode inputMode) { 48 | this(subjectkey, minGran, rounds, inputMode, false); 49 | } 50 | public TimeLevel(int subjectkey, MinuteGranularity minGran, int rounds, InputMode inputMode, boolean useFuzzy) { 51 | super(subjectkey, rounds, inputMode); 52 | mMinuteGranularity = minGran; 53 | mFuzzy = useFuzzy; 54 | } 55 | 56 | public boolean useFuzzy() { 57 | return mFuzzy; 58 | } 59 | 60 | public MinuteGranularity getMinuteGranularity() { 61 | return mMinuteGranularity; 62 | } 63 | 64 | @Override 65 | public String getDescription(Context context) { 66 | return mMinuteGranularity.getString(context) + ". " + getInputModeString(context); 67 | } 68 | 69 | @Override 70 | public String getShortDescription(Context context) { 71 | return mMinuteGranularity.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/primary_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quaap/Primary/95bdb1d12df5599d7b833db36365e5f21be24336/app/src/main/primary_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 22 | 23 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 |