70 |
71 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/PRIVACY:
--------------------------------------------------------------------------------
1 | Privacy Policy for Ikue - Japanese Dictionary
2 |
3 | No user information is collected or transmitted during usage of this application.
4 | The application does not have permissions to connect to the internet and therefore cannot transmit any data off the device.
5 | For any questions contact lc94dev@gmail.com
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ikue - Japanese Dictionary
2 |
3 | ## About
4 | Ikue is an open-source, offline, material design Japanese dictonary application that aims to be fast, intuitive, and lightweight.
5 | Features:
6 |
7 |
100% Offline
8 |
Material Design
9 |
All-In-One Search Bar
10 |
Word of the Day
11 |
Favourites
12 |
History
13 |
Detailed Entry Information
14 |
Wildcard Search Support
15 |
No ads
16 |
No permissions needed
17 |
18 |
19 | ## Download
20 |
21 |
22 |
23 | Latest APK
24 |
25 | ## Usage
26 | If you want to generate the APK yourself, make sure to use the Japanese Dictionary Parser to create your dictionary.db file, and place it in the assets/databases/ folder. Currently the app only uses the JMdict_e.xml information.
27 |
28 | ## License
29 | Copyright (c) 2024 Luke Casey
30 | See LICENSE for more information.
31 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | // Create a variable called keystorePropertiesFile, and initialize it to your
4 | // keystore.properties file, in the rootProject folder.
5 | def keystorePropertiesFile = rootProject.file("keystore.properties")
6 |
7 | // Initialize a new Properties() object called keystoreProperties.
8 | def keystoreProperties = new Properties()
9 |
10 | // Load your keystore.properties file into the keystoreProperties object.
11 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
12 |
13 | android {
14 | namespace 'com.ikue.japanesedictionary'
15 |
16 | signingConfigs {
17 | config {
18 | keyAlias keystoreProperties['keyAlias']
19 | keyPassword keystoreProperties['keyPassword']
20 | storeFile file(keystoreProperties['storeFile'])
21 | storePassword keystoreProperties['storePassword']
22 | }
23 | }
24 | defaultConfig {
25 | applicationId "com.ikue.japanesedictionary"
26 | minSdkVersion 21
27 | targetSdkVersion 34
28 | compileSdk 34
29 | versionCode 6
30 | versionName "1.0.5"
31 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
32 | }
33 | buildTypes {
34 | release {
35 | minifyEnabled true
36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
37 | signingConfig signingConfigs.config
38 | resValue "string", "app_name", "Ikue"
39 | }
40 | debug {
41 | applicationIdSuffix '.debug'
42 | versionNameSuffix '-debug'
43 | resValue "string", "app_name", "Ikue DEBUG"
44 | }
45 | }
46 | buildFeatures {
47 | buildConfig true
48 | }
49 |
50 | compileOptions {
51 | sourceCompatibility = JavaVersion.VERSION_17
52 | targetCompatibility = JavaVersion.VERSION_17
53 | }
54 | }
55 |
56 | repositories {
57 | google()
58 | mavenCentral()
59 | maven { url "https://jitpack.io" }
60 | }
61 |
62 | dependencies {
63 | implementation fileTree(dir: 'libs', include: ['*.jar'])
64 |
65 | implementation 'com.google.android.material:material:1.12.0'
66 | implementation 'androidx.appcompat:appcompat:1.7.0'
67 | implementation 'androidx.cardview:cardview:1.0.0'
68 | implementation 'androidx.recyclerview:recyclerview:1.3.2'
69 |
70 | implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
71 | implementation 'com.github.medyo:android-about-page:1.3'
72 | implementation 'com.github.colinrtwhite:licensesdialog:2.1.0'
73 |
74 | testImplementation 'junit:junit:4.13.2'
75 |
76 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
77 | androidTestImplementation 'androidx.test:runner:1.5.2'
78 | }
79 |
--------------------------------------------------------------------------------
/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 C:\Users\luke_c\AppData\Local\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 | -keep class android.support.v7.widget.SearchView { *; }
19 |
20 | -assumenosideeffects class android.util.Log {
21 | public static boolean isLoggable(java.lang.String, int);
22 | public static int v(...);
23 | public static int i(...);
24 | public static int w(...);
25 | public static int d(...);
26 | public static int e(...);
27 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ikue/japanesedictionary/DatabaseTest.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary;
2 |
3 | import android.content.Context;
4 |
5 | import com.ikue.japanesedictionary.database.DictionaryDbHelper;
6 | import com.ikue.japanesedictionary.models.DictionaryEntry;
7 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
8 | import com.ikue.japanesedictionary.utils.GlobalConstants.SearchTypes;
9 |
10 | import org.junit.After;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 |
15 | import java.util.List;
16 |
17 | import static org.junit.Assert.assertEquals;
18 | import static org.junit.Assert.assertFalse;
19 | import static org.junit.Assert.assertNotEquals;
20 | import static org.junit.Assert.assertNotNull;
21 | import static org.junit.Assert.assertTrue;
22 |
23 | import androidx.test.InstrumentationRegistry;
24 | import androidx.test.runner.AndroidJUnit4;
25 |
26 | /**
27 | * Instrumentation test, which will execute on an Android device.
28 | *
29 | * @see Testing documentation
30 | */
31 | @RunWith(AndroidJUnit4.class)
32 | public class DatabaseTest {
33 | private static DictionaryDbHelper helper;
34 |
35 | @Test
36 | public void useAppContext() {
37 | // Context of the app under test.
38 | Context appContext = InstrumentationRegistry.getTargetContext();
39 |
40 | assertTrue(appContext.getPackageName().equals("com.ikue.japanesedictionary")
41 | || appContext.getPackageName().equals("com.ikue.japanesedictionary.debug"));
42 | }
43 |
44 | @Before
45 | public void setup() {
46 | helper = DictionaryDbHelper.getInstance(InstrumentationRegistry.getTargetContext());
47 | }
48 |
49 | @Test
50 | public void testAddingToHistory() {
51 | int entryIdToAdd = 1000020;
52 | helper.addToHistory(entryIdToAdd);
53 |
54 | List results = helper.getHistory();
55 |
56 | if (results.get(0) != null) {
57 | assertEquals(results.get(0).getEntryId(), entryIdToAdd);
58 | }
59 |
60 | boolean containsEntryToAdd = false;
61 | for (DictionaryListEntry entry : results) {
62 | if (entry.getEntryId() == entryIdToAdd) {
63 | containsEntryToAdd = true;
64 | break;
65 | }
66 | }
67 | assertTrue(containsEntryToAdd);
68 |
69 | helper.removeFromHistory(entryIdToAdd);
70 | }
71 |
72 | @Test
73 | public void testRemovingFromHistory() {
74 | int entryIdToRemove = 1000010;
75 | helper.addToHistory(entryIdToRemove);
76 | helper.removeFromHistory(entryIdToRemove);
77 |
78 | List results = helper.getHistory();
79 |
80 | if (results.get(0) != null) {
81 | assertNotEquals(results.get(0).getEntryId(), entryIdToRemove);
82 | }
83 |
84 | for (DictionaryListEntry entry : results) {
85 | assertNotEquals(entry.getEntryId(), entryIdToRemove);
86 | }
87 | }
88 |
89 | @Test
90 | public void testGettingHistory_notEmpty() {
91 | helper.addToHistory(1000000);
92 |
93 | List history = helper.getHistory();
94 | assertFalse(history.isEmpty());
95 |
96 | helper.removeFromHistory(1000000);
97 | }
98 |
99 | @Test
100 | public void testAddingToFavourites() {
101 | int entryIdToAdd = 1000030;
102 | helper.addToFavourites(entryIdToAdd);
103 |
104 | List results = helper.getFavourites();
105 |
106 | if (results.get(0) != null) {
107 | assertEquals(results.get(0).getEntryId(), entryIdToAdd);
108 | }
109 |
110 | boolean containsAddedEntry = false;
111 | for (DictionaryListEntry entry : results) {
112 | if (entry.getEntryId() == entryIdToAdd) {
113 | containsAddedEntry = true;
114 | break;
115 | }
116 | }
117 | assertTrue(containsAddedEntry);
118 |
119 | helper.removeFromFavourites(entryIdToAdd);
120 | }
121 |
122 | @Test
123 | public void testRemovingFromFavourites() {
124 | int entryIdToRemove = 1000040;
125 | helper.addToFavourites(entryIdToRemove);
126 | helper.removeFromFavourites(entryIdToRemove);
127 |
128 | List results = helper.getFavourites();
129 |
130 | for (DictionaryListEntry entry : results) {
131 | assertNotEquals(entry.getEntryId(), entryIdToRemove);
132 | }
133 | }
134 |
135 | @Test
136 | public void testGettingFavourites_notEmpty() {
137 | helper.addToFavourites(1000050);
138 |
139 | List results = helper.getFavourites();
140 | assertFalse(results.isEmpty());
141 |
142 | helper.removeFromFavourites(1000050);
143 | }
144 |
145 | @Test
146 | public void testGettingEntryFromId() {
147 | int entryId = 1000060;
148 | DictionaryEntry entry = helper.getEntry(entryId);
149 |
150 | assertNotNull(entry);
151 | assertEquals(entry.getEntryId(), entryId);
152 | }
153 |
154 | @Test
155 | public void testGettingRandomEntry() {
156 | DictionaryEntry entry = helper.getRandomEntry();
157 |
158 | assertNotNull(entry);
159 |
160 | // 0 is returned when a random entry couldn't be retrieved
161 | // as well as the default int value
162 | assertNotEquals(0, entry.getEntryId());
163 |
164 | assertFalse(entry.getReadingElements().isEmpty());
165 | assertFalse(entry.getSenseElements().isEmpty());
166 | }
167 |
168 | @Test
169 | public void testSearchingDictionary_english() {
170 | List entries = helper.searchDictionary("house", SearchTypes.ENGLISH_TYPE);
171 |
172 | assertFalse(entries.isEmpty());
173 |
174 | // Every results should contain the string "pot"
175 | for (DictionaryListEntry entry : entries) {
176 | assertTrue(entry.getGlossValue().contains("house"));
177 | }
178 | }
179 |
180 | @Test
181 | public void testSearchingDictionary_romaji() {
182 | List entries = helper.searchDictionary("tabemono", SearchTypes.ROMAJI_TYPE);
183 |
184 | assertFalse(entries.isEmpty());
185 |
186 | // Every results should contain the string "たべもの". This is "tabemono" converted to kana
187 | for (DictionaryListEntry entry : entries) {
188 | assertTrue(entry.getReadingElementValue().contains("たべもの"));
189 | }
190 | }
191 |
192 | @Test
193 | public void testSearchingDictionary_kana() {
194 | List entries = helper.searchDictionary("としょかん", SearchTypes.KANA_TYPE);
195 |
196 | assertFalse(entries.isEmpty());
197 |
198 | // Every results should contain the string "としょかん".
199 | for (DictionaryListEntry entry : entries) {
200 | assertTrue(entry.getReadingElementValue().contains("としょかん"));
201 | }
202 | }
203 |
204 | @Test
205 | public void testSearchingDictionary_kanji() {
206 | List entries = helper.searchDictionary("学生", SearchTypes.KANJI_TYPE);
207 |
208 | assertFalse(entries.isEmpty());
209 |
210 | // Every results should contain the string "学生".
211 | for (DictionaryListEntry entry : entries) {
212 | assertTrue(entry.getKanjiElementValue().contains("学生"));
213 | }
214 | }
215 |
216 | @After
217 | public void cleanup() {
218 | helper.close();
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
48 |
49 |
50 |
54 |
57 |
58 |
59 |
65 |
68 |
69 |
70 |
75 |
78 |
79 |
80 |
86 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/activities/AboutActivity.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.activities;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 |
6 | import androidx.annotation.Nullable;
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | import com.colinrtwhite.licensesdialog.LicensesDialog;
10 | import com.colinrtwhite.licensesdialog.license.ApacheLicense20;
11 | import com.colinrtwhite.licensesdialog.license.GnuGeneralPublicLicense30;
12 | import com.colinrtwhite.licensesdialog.license.MitLicense;
13 | import com.colinrtwhite.licensesdialog.model.Copyright;
14 | import com.colinrtwhite.licensesdialog.model.Notice;
15 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
16 | import com.ikue.japanesedictionary.BuildConfig;
17 | import com.ikue.japanesedictionary.R;
18 | import com.ikue.japanesedictionary.utils.CreativeCommonsShareAlikeLicense;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | import mehdi.sakout.aboutpage.AboutPage;
24 | import mehdi.sakout.aboutpage.Element;
25 |
26 | public class AboutActivity extends AppCompatActivity {
27 |
28 | @Override
29 | protected void onCreate(@Nullable Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 |
32 | View aboutPage = new AboutPage(this)
33 | .isRTL(false)
34 | .setImage(R.mipmap.ic_launcher)
35 | .setDescription(getResources().getString(R.string.about_activity_description))
36 | .addItem(new Element().setTitle("Version: " + BuildConfig.VERSION_NAME))
37 | .addGroup(getResources().getString(R.string.about_activity_connect))
38 | .addEmail("lc94dev+ikue@gmail.com")
39 | .addGitHub("luke-c/Ikue")
40 | .addItem(getLicensesElement())
41 | .addItem(getTermsOfUseElement())
42 | .addItem(getThanksElement())
43 | .create();
44 |
45 | setContentView(aboutPage);
46 | }
47 |
48 | private Element getTermsOfUseElement() {
49 | Element element = new Element();
50 | element.setTitle(getResources().getString(R.string.about_activity_terms_of_use));
51 | element.setOnClickListener(view -> {
52 | Notice notice = new Notice("Ikue",
53 | GnuGeneralPublicLicense30.INSTANCE,
54 | "https://github.com/luke-c/Ikue",
55 | new Copyright("Luke Casey", 2024));
56 |
57 | List notices = List.of(notice);
58 |
59 |
60 | new LicensesDialog.Builder(AboutActivity.this)
61 | .setTitle(getResources().getString(R.string.about_activity_terms_of_use))
62 | .setNotices(notices)
63 | .setAlwaysExpandLicenses(true)
64 | .show();
65 | });
66 | return element;
67 | }
68 |
69 | private Element getLicensesElement() {
70 | Element element = new Element();
71 | element.setTitle(getResources().getString(R.string.about_activity_licenses));
72 | element.setOnClickListener(view -> {
73 | final List notices = new ArrayList<>();
74 | notices.add(new Notice("SQLiteAssetHelper",
75 | ApacheLicense20.INSTANCE,
76 | "https://github.com/jgilfelt/android-sqlite-asset-helper",
77 | new Copyright("readyState Software Ltd", 2011)
78 | ));
79 |
80 | notices.add(new Notice("Android About Page",
81 | MitLicense.INSTANCE,
82 | "https://github.com/medyo/android-about-page",
83 | new Copyright("Mehdi Sakout", 2016)
84 | ));
85 |
86 | notices.add(new Notice("WanaKanaJava",
87 | MitLicense.INSTANCE,
88 | "https://github.com/MasterKale/WanaKanaJava",
89 | new Copyright("Matthew Miller", 2013)
90 | ));
91 |
92 | notices.add(new Notice("ERDRG",
93 | new CreativeCommonsShareAlikeLicense(this),
94 | "http://www.edrdg.org/",
95 | null
96 | ));
97 |
98 | new LicensesDialog.Builder(AboutActivity.this)
99 | .setTitle(getResources().getString(R.string.about_activity_licenses))
100 | .setNotices(notices)
101 | .setIncludeOwnLicense(true)
102 | .show();
103 | });
104 | return element;
105 | }
106 |
107 | private Element getThanksElement() {
108 | Element element = new Element();
109 | element.setTitle(getResources().getString(R.string.about_activity_thanks));
110 | element.setOnClickListener(view -> new MaterialAlertDialogBuilder(this)
111 | .setTitle(getString(R.string.about_activity_thanks))
112 | .setMessage(getString(R.string.about_activity_erdrg_thanks))
113 | .setPositiveButton(getString(R.string.about_activity_close), (dialog, which) -> dialog.dismiss())
114 | .show());
115 | return element;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/activities/EntryDetailActivity.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.activities;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 | import androidx.fragment.app.Fragment;
9 | import androidx.fragment.app.FragmentManager;
10 |
11 | import com.ikue.japanesedictionary.fragments.EntryDetailFragment;
12 | import com.ikue.japanesedictionary.R;
13 |
14 | public class EntryDetailActivity extends AppCompatActivity {
15 |
16 | private static final String EXTRA_ENTRY_ID = "DETAIL_ACTIVITY_ENTRY_ID";
17 |
18 | @Override
19 | public void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_detail);
22 |
23 | FragmentManager fm = getSupportFragmentManager();
24 | Fragment fragment = fm.findFragmentById(R.id.fragment_container);
25 |
26 | if (fragment == null) {
27 | int entryId = getIntent().getIntExtra(EXTRA_ENTRY_ID, 0);
28 | fragment = EntryDetailFragment.newInstance(entryId);
29 | fm.beginTransaction()
30 | .add(R.id.fragment_container, fragment)
31 | .commit();
32 | }
33 | }
34 |
35 | public static Intent newIntent(Context packageContext, int entryId) {
36 | Intent i = new Intent(packageContext, EntryDetailActivity.class);
37 | i.putExtra(EXTRA_ENTRY_ID, entryId);
38 | return i;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/activities/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.activities;
2 |
3 | import android.app.SearchManager;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.os.Bundle;
9 | import android.preference.PreferenceManager;
10 | import android.view.Menu;
11 | import android.view.MenuItem;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.appcompat.app.ActionBar;
15 | import androidx.appcompat.app.AppCompatActivity;
16 | import androidx.appcompat.widget.SearchView;
17 | import androidx.appcompat.widget.Toolbar;
18 | import androidx.core.view.GravityCompat;
19 | import androidx.drawerlayout.widget.DrawerLayout;
20 | import androidx.fragment.app.Fragment;
21 | import androidx.fragment.app.FragmentManager;
22 | import androidx.fragment.app.FragmentPagerAdapter;
23 | import androidx.viewpager.widget.ViewPager;
24 |
25 | import com.google.android.material.appbar.AppBarLayout;
26 | import com.google.android.material.floatingactionbutton.FloatingActionButton;
27 | import com.google.android.material.navigation.NavigationView;
28 | import com.google.android.material.tabs.TabLayout;
29 | import com.ikue.japanesedictionary.R;
30 | import com.ikue.japanesedictionary.fragments.FavouritesFragment;
31 | import com.ikue.japanesedictionary.fragments.HistoryFragment;
32 | import com.ikue.japanesedictionary.fragments.HomeFragment;
33 |
34 | import java.util.ArrayList;
35 | import java.util.List;
36 |
37 | public class MainActivity extends AppCompatActivity {
38 |
39 | private DrawerLayout drawerLayout;
40 | private AppBarLayout appBarLayout;
41 | private NavigationView navigationView;
42 | private ViewPager viewPager;
43 | private MenuItem searchMenuItem;
44 | private FloatingActionButton fabButton;
45 |
46 | private SharedPreferences sharedPref;
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | setContentView(R.layout.activity_main);
52 |
53 | // When false, the system sets the default values only if this method has never been called in the past
54 | PreferenceManager.setDefaultValues(getApplicationContext(), R.xml.preferences, false);
55 |
56 | // Get the shared preferences
57 | sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
58 |
59 | appBarLayout = findViewById(R.id.appbar);
60 |
61 | // Add Toolbar to Main Screen
62 | Toolbar toolbar = findViewById(R.id.toolbar);
63 | setSupportActionBar(toolbar);
64 |
65 | // Set Viewpager for tabs
66 | viewPager = findViewById(R.id.viewpager);
67 | setupViewPager();
68 |
69 | // Set Tabs inside the Toolbar
70 | TabLayout tabs = findViewById(R.id.tabs);
71 | tabs.setupWithViewPager(viewPager);
72 |
73 | // Add icons to each tab
74 | tabs.getTabAt(0).setIcon(R.drawable.ic_history_white);
75 | tabs.getTabAt(1).setIcon(R.drawable.ic_home_white);
76 | tabs.getTabAt(2).setIcon(R.drawable.ic_star_white);
77 |
78 | // Create Navigation drawer and inflate
79 | navigationView = findViewById(R.id.nav_view);
80 | drawerLayout = findViewById(R.id.drawer);
81 |
82 | // Add menu icon to Toolbar
83 | ActionBar supportActionBar = getSupportActionBar();
84 | if(supportActionBar != null) {
85 | supportActionBar.setHomeAsUpIndicator(R.drawable.ic_menu_white);
86 | supportActionBar.setDisplayHomeAsUpEnabled(true);
87 | }
88 |
89 | // Set behaviour of Navigation drawer
90 | navigationView.setNavigationItemSelectedListener(item -> {
91 | // Set item as checked
92 | item.setChecked(true);
93 |
94 | int itemId = item.getItemId();
95 | if (itemId == R.id.nav_history_fragment) {
96 | viewPager.setCurrentItem(0, true);
97 | } else if (itemId == R.id.nav_home_fragment) {
98 | viewPager.setCurrentItem(1, true);
99 | } else if (itemId == R.id.nav_favourites_fragment) {
100 | viewPager.setCurrentItem(2, true);
101 | } else if (itemId == R.id.nav_settings_activity) {
102 | startActivity(new Intent(MainActivity.this, SettingsActivity.class));
103 | } else if (itemId == R.id.nav_about_activity) {
104 | startActivity(new Intent(MainActivity.this, AboutActivity.class));
105 | }
106 |
107 | // Closing drawer on item click
108 | drawerLayout.closeDrawers();
109 | return true;
110 | });
111 |
112 | fabButton = findViewById(R.id.fab);
113 | }
114 |
115 | @Override
116 | public boolean onCreateOptionsMenu(Menu menu) {
117 | // Inflates menu and adds to action if present
118 | getMenuInflater().inflate(R.menu.menu_main, menu);
119 |
120 | // Associate searchable configuration with the SearchView
121 | SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
122 | searchMenuItem = menu.findItem(R.id.action_search);
123 | SearchView searchView = (SearchView) searchMenuItem.getActionView();
124 |
125 | searchView.setOnFocusChangeListener((view, queryTextFocused) -> {
126 | if(!queryTextFocused) {
127 | // Close the SearchView when the user closes the keyboard
128 | searchMenuItem.collapseActionView();
129 | }
130 | });
131 |
132 | // Set the onClick here so we can guarantee we have a searchMenuItem
133 | fabButton.setOnClickListener(view -> {
134 | // Expand the AppBarLayout on click, so SearchView is visible
135 | appBarLayout.setExpanded(true);
136 |
137 | // Expand the SearchView
138 | searchMenuItem.expandActionView();
139 | });
140 |
141 | ComponentName cn = new ComponentName(this, SearchResultActivity.class);
142 | searchView.setSearchableInfo(searchManager.getSearchableInfo(cn));
143 |
144 | // Set a listener on the SearchView
145 | searchMenuItem.setOnActionExpandListener(
146 | new MenuItem.OnActionExpandListener() {
147 | @Override
148 | public boolean onMenuItemActionExpand(MenuItem item) {
149 | // When the SearchView is expanded, hide the FAB
150 | fabButton.hide();
151 | return true;
152 | }
153 |
154 | @Override
155 | public boolean onMenuItemActionCollapse(MenuItem item) {
156 | // Once the SearchView is collapsed, show the FAB again
157 | fabButton.show();
158 | return true;
159 | }
160 | });
161 | return true;
162 | }
163 |
164 | @Override
165 | public boolean onOptionsItemSelected(MenuItem item) {
166 | // Handle action bar item clicks
167 | int id = item.getItemId();
168 |
169 | if (id == R.id.action_settings) {
170 | startActivity(new Intent(MainActivity.this, SettingsActivity.class));
171 | } else if (id == android.R.id.home) {
172 | drawerLayout.openDrawer(GravityCompat.START);
173 | }
174 | return super.onOptionsItemSelected(item);
175 | }
176 |
177 | @Override
178 | protected void onDestroy() {
179 | super.onDestroy();
180 | }
181 |
182 | private void setupViewPager() {
183 | Adapter adapter = new Adapter(getSupportFragmentManager());
184 | adapter.addFragment(new HistoryFragment(), getString(R.string.history_fragment_title));
185 | adapter.addFragment(new HomeFragment(), getString(R.string.home_fragment_title));
186 | adapter.addFragment(new FavouritesFragment(), getString(R.string.favourites_fragment_title));
187 | viewPager.setAdapter(adapter);
188 |
189 | // Set default tab to the user's preference. Default is the 'Home' tab.
190 | viewPager.setCurrentItem(Integer.parseInt(sharedPref.getString("pref_startupPage", "1")));
191 |
192 | // Set the number of pages that should be retained to either side of the current page in
193 | // the view hierarchy in an idle state.
194 | viewPager.setOffscreenPageLimit(2);
195 |
196 | viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
197 | @Override
198 | public void onPageSelected(int position) {
199 | // When we change tab, set the current tab to be selected in the NavigationDrawer
200 | navigationView.getMenu().getItem(position).setChecked(true);
201 | }
202 | });
203 | }
204 |
205 | static class Adapter extends FragmentPagerAdapter {
206 | private final List fragmentList = new ArrayList<>();
207 | private final List fragmentTitleList = new ArrayList<>();
208 |
209 | public Adapter(FragmentManager manager) {
210 | super(manager);
211 | }
212 |
213 | @NonNull
214 | @Override
215 | public Fragment getItem(int position) {
216 | return fragmentList.get(position);
217 | }
218 |
219 | @Override
220 | public int getCount() {
221 | return fragmentList.size();
222 | }
223 |
224 | public void addFragment(Fragment fragment, String title) {
225 | fragmentList.add(fragment);
226 | fragmentTitleList.add(title);
227 | }
228 |
229 | @Override
230 | public CharSequence getPageTitle(int position) {
231 | return fragmentTitleList.get(position);
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/activities/SearchResultActivity.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.activities;
2 |
3 | import android.app.SearchManager;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 | import androidx.fragment.app.Fragment;
9 | import androidx.fragment.app.FragmentManager;
10 |
11 | import com.ikue.japanesedictionary.R;
12 | import com.ikue.japanesedictionary.fragments.SearchResultFragment;
13 |
14 | public class SearchResultActivity extends AppCompatActivity {
15 |
16 | @Override
17 | public void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_search);
20 |
21 | Intent intent = getIntent();
22 | String query = null;
23 |
24 | if(Intent.ACTION_SEARCH.equals(intent.getAction())) {
25 | query = intent.getStringExtra(SearchManager.QUERY);
26 | }
27 |
28 | FragmentManager fm = getSupportFragmentManager();
29 | Fragment fragment = fm.findFragmentById(R.id.fragment_container);
30 |
31 | if (fragment == null) {
32 | fragment = SearchResultFragment.newInstance(query);
33 | fm.beginTransaction()
34 | .add(R.id.fragment_container, fragment)
35 | .commit();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/activities/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.activities;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import androidx.appcompat.widget.Toolbar;
7 |
8 | import com.ikue.japanesedictionary.R;
9 | import com.ikue.japanesedictionary.fragments.SettingsFragment;
10 |
11 | public class SettingsActivity extends AppCompatActivity {
12 | @Override
13 | public void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 |
16 | setContentView(R.layout.activity_settings);
17 |
18 | // Setup the toolbar
19 | Toolbar toolbar = findViewById(R.id.toolbar);
20 | toolbar.setTitle(R.string.settings_activity_title);
21 | setSupportActionBar(toolbar);
22 | getSupportActionBar().setHomeButtonEnabled(true);
23 | getSupportActionBar().setDisplayHomeAsUpEnabled(true);
24 |
25 | if (getFragmentManager().findFragmentById(R.id.container)==null) {
26 | getFragmentManager().beginTransaction()
27 | .add(R.id.container,
28 | new SettingsFragment()).commit();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/activities/TipsActivity.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.activities;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import androidx.recyclerview.widget.LinearLayoutManager;
7 | import androidx.recyclerview.widget.RecyclerView;
8 |
9 | import com.ikue.japanesedictionary.R;
10 | import com.ikue.japanesedictionary.adapters.TipsAdapter;
11 | import com.ikue.japanesedictionary.utils.TipsUtils;
12 |
13 | public class TipsActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_tips);
19 |
20 | // Lookup the recyclerview in activity layout
21 | RecyclerView recyclerView = findViewById(R.id.tips_recyclerview);
22 |
23 | TipsAdapter adapter = new TipsAdapter(TipsUtils.getTips());
24 |
25 | // Attach the adapter to the recyclerview to populate items
26 | recyclerView.setAdapter(adapter);
27 |
28 | // Set layout manager to position the items
29 | recyclerView.setLayoutManager(new LinearLayoutManager(this));
30 | recyclerView.setHasFixedSize(true);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/adapters/DetailViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.adapters;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import com.ikue.japanesedictionary.R;
13 | import com.ikue.japanesedictionary.models.SenseElement;
14 |
15 | import java.util.List;
16 |
17 | public class DetailViewAdapter extends RecyclerView.Adapter {
18 | private static final int SENSE_ELEMENT_ITEM = 0;
19 |
20 | private final String LOG_TAG = this.getClass().toString();
21 |
22 | private final List items;
23 |
24 | public DetailViewAdapter(List items) {
25 | this.items = items;
26 | }
27 |
28 | @Override
29 | public int getItemCount() {
30 | return items.size();
31 | }
32 |
33 | @Override
34 | public int getItemViewType(int position) {
35 | return SENSE_ELEMENT_ITEM;
36 | }
37 |
38 | @NonNull
39 | @Override
40 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
41 |
42 | RecyclerView.ViewHolder viewHolder;
43 | LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
44 |
45 | if (viewType == SENSE_ELEMENT_ITEM) {
46 | View v1 = inflater.inflate(R.layout.sense_element_item, viewGroup, false);
47 | viewHolder = new SenseElementViewHolder(v1);
48 | } else {// TODO: Handle unknown view type error more gracefully
49 | Log.e(LOG_TAG, "Unknown view type");
50 | viewHolder = null;
51 | }
52 | assert viewHolder != null;
53 | return viewHolder;
54 | }
55 |
56 | @Override
57 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
58 | if (holder.getItemViewType() == SENSE_ELEMENT_ITEM) {
59 | SenseElementViewHolder vh1 = (SenseElementViewHolder) holder;
60 | configureMeaningWithHeaderViewHolder(vh1, position);
61 | } else {// TODO: Handle unknown view type error more gracefully
62 | Log.e(LOG_TAG, "Unknown view type");
63 | }
64 | }
65 |
66 | private void configureMeaningWithHeaderViewHolder(SenseElementViewHolder holder, int position) {
67 | SenseElement senseElement = items.get(position);
68 |
69 | // TODO: Simplify Part of Speech values, currently long and too detailed
70 | // Get all the Part of Speech elements, and join them into a single string.
71 | List partOfSpeech = senseElement.getPartOfSpeech();
72 | if(!partOfSpeech.isEmpty()) {
73 | holder.getPartOfSpeech().setText(TextUtils.join(", ", partOfSpeech));
74 | } else {
75 | holder.getPartOfSpeech().setVisibility(View.GONE);
76 | }
77 |
78 | String formattedPosition = holder.getMeaningNumber().getResources()
79 | .getString(R.string.detail_view_meaning_number, position + 1);
80 | holder.getMeaningNumber().setText(formattedPosition);
81 |
82 | // Get all the glosses for a Sense element, and join them into a single string
83 | List glosses = senseElement.getGlosses();
84 | if(!glosses.isEmpty()) {
85 | holder.getGlosses().setText(TextUtils.join("; ", glosses));
86 | } else {
87 | holder.getGlosses().setVisibility(View.GONE);
88 | }
89 |
90 | // Get all the Field of Application elements, and join them into a single string
91 | List fieldOfApplication = senseElement.getFieldOfApplication();
92 | if(!fieldOfApplication.isEmpty()) {
93 | holder.getFieldOfApplication().setText(TextUtils.join(", ", fieldOfApplication));
94 | } else {
95 | holder.getFieldOfApplication().setVisibility(View.GONE);
96 | }
97 |
98 | // Get all the Dialect elements, and join them into a single string
99 | List dialect = senseElement.getDialect();
100 | if(!dialect.isEmpty()) {
101 | holder.getDialect().setText(TextUtils.join(", ", dialect));
102 | } else {
103 | holder.getDialect().setVisibility(View.GONE);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/adapters/SearchResultAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.adapters;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.text.TextUtils;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.TextView;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.recyclerview.widget.RecyclerView;
13 |
14 | import com.ikue.japanesedictionary.R;
15 | import com.ikue.japanesedictionary.activities.EntryDetailActivity;
16 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
17 |
18 | import java.util.List;
19 |
20 | // Create the basic adapter extending from RecyclerView.Adapter
21 | // Note that we specify the custom ViewHolder which gives us access to our views
22 | public class SearchResultAdapter extends RecyclerView.Adapter {
23 |
24 | // Provide a direct reference to each of the views within a data item
25 | // Used to cache the views within the item layout for fast access
26 | public static class ViewHolder extends RecyclerView.ViewHolder {
27 | // Your holder should contain a member variable
28 | // for any view that will be set as you render a row
29 | public TextView primaryTextView;
30 | public TextView primaryTextView2;
31 | public TextView secondaryTextView;
32 |
33 | // We also create a constructor that accepts the entire item row
34 | // and does the view lookups to find each subview
35 | public ViewHolder(View itemView) {
36 | // Stores the itemView in a public final member variable that can be used
37 | // to access the context from any ViewHolder instance.
38 | super(itemView);
39 |
40 | primaryTextView = itemView.findViewById(R.id.list_primary);
41 | primaryTextView2 = itemView.findViewById(R.id.list_primary2);
42 | secondaryTextView = itemView.findViewById(R.id.list_secondary);
43 | }
44 | }
45 |
46 | // Store a member variable for the contacts
47 | private List searchResultItems;
48 | // Store the context for easy access
49 | private final Context context;
50 |
51 | // Pass in the contact array into the constructor
52 | public SearchResultAdapter(Context context, List searchResultItems) {
53 | this.searchResultItems = searchResultItems;
54 | this.context = context;
55 | }
56 |
57 | public void swapItems(List items) {
58 | this.searchResultItems = items;
59 | notifyDataSetChanged();
60 | }
61 |
62 | // Usually involves inflating a layout from XML and returning the holder
63 | @NonNull
64 | @Override
65 | public SearchResultAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
66 | Context context = parent.getContext();
67 | LayoutInflater inflater = LayoutInflater.from(context);
68 |
69 | // Inflate the custom layout
70 | View view = inflater.inflate(R.layout.search_list_item, parent, false);
71 |
72 | // Return a new holder instance
73 | return new ViewHolder(view);
74 | }
75 |
76 | // Involves populating data into the item through holder
77 | @Override
78 | public void onBindViewHolder(SearchResultAdapter.ViewHolder viewHolder, int position) {
79 | // Get the data model based on position
80 | DictionaryListEntry item = searchResultItems.get(position);
81 | final int entryId = item.getEntryId();
82 |
83 | // Set item views based on your views and data model
84 | TextView primaryText = viewHolder.primaryTextView;
85 | TextView primaryText2 = viewHolder.primaryTextView2;
86 | TextView secondaryText = viewHolder.secondaryTextView;
87 |
88 | viewHolder.itemView.setOnClickListener(view -> {
89 | Intent i = EntryDetailActivity.newIntent(view.getContext(), entryId);
90 | context.startActivity(i);
91 | });
92 |
93 | String kanjiValue = item.getKanjiElementValue();
94 | if(!kanjiValue.isEmpty()) {
95 | // If I don't reset the view to visible, when the view is recycled and you
96 | // scroll back to it the view is still set to gone from previous elements.
97 | primaryText.setVisibility(View.VISIBLE);
98 | secondaryText.setMaxLines(1);
99 | primaryText.setText(item.getKanjiElementValue());
100 | } else {
101 | primaryText.setVisibility(View.GONE);
102 | secondaryText.setMaxLines(2);
103 | }
104 |
105 | primaryText2.setText(item.getReadingElementValue());
106 |
107 | secondaryText.setText(TextUtils.join(", ", item.getGlossValue()));
108 | }
109 |
110 | // Returns the total count of items in the list
111 | @Override
112 | public int getItemCount() {
113 | return searchResultItems.size();
114 | }
115 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/adapters/SenseElementViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.adapters;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | import com.ikue.japanesedictionary.R;
9 |
10 | public class SenseElementViewHolder extends RecyclerView.ViewHolder {
11 |
12 | private final TextView partOfSpeech;
13 | private final TextView meaningNumber;
14 | private final TextView glosses;
15 | private final TextView fieldOfApplication;
16 | private final TextView dialect;
17 |
18 | public SenseElementViewHolder(View v) {
19 | super(v);
20 | partOfSpeech = v.findViewById(R.id.partOfSpeech);
21 | meaningNumber = v.findViewById(R.id.meaningNumber);
22 | glosses = v.findViewById(R.id.glosses);
23 | fieldOfApplication = v.findViewById(R.id.fieldOfApplication);
24 | dialect = v.findViewById(R.id.dialect);
25 | }
26 |
27 | public TextView getPartOfSpeech() {
28 | return partOfSpeech;
29 | }
30 |
31 |
32 | public TextView getDialect() {
33 | return dialect;
34 | }
35 |
36 |
37 | public TextView getFieldOfApplication() {
38 | return fieldOfApplication;
39 | }
40 |
41 |
42 | public TextView getGlosses() {
43 | return glosses;
44 | }
45 |
46 | public TextView getMeaningNumber() {
47 | return meaningNumber;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/adapters/TipsAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.adapters;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import com.ikue.japanesedictionary.R;
13 | import com.ikue.japanesedictionary.models.Tip;
14 |
15 | import java.util.List;
16 |
17 | public class TipsAdapter extends RecyclerView.Adapter {
18 | // Provide a direct reference to each of the views within a data item
19 | // Used to cache the views within the item layout for fast access
20 | public static class ViewHolder extends RecyclerView.ViewHolder {
21 | // Your holder should contain a member variable
22 | // for any view that will be set as you render a row
23 | public TextView tipTitle;
24 | public TextView tipContent;
25 |
26 | // We also create a constructor that accepts the entire item row
27 | // and does the view lookups to find each subview
28 | public ViewHolder(View itemView) {
29 | // Stores the itemView in a public final member variable that can be used
30 | // to access the context from any ViewHolder instance.
31 | super(itemView);
32 |
33 | tipTitle = itemView.findViewById(R.id.tips_card_title);
34 | tipContent = itemView.findViewById(R.id.tips_card_content);
35 | }
36 | }
37 |
38 | // Store a member variable for the contacts
39 | private final List tips;
40 | // Store the context for easy access
41 |
42 | // Pass in the contact array into the constructor
43 | public TipsAdapter(List tips) {
44 | this.tips = tips;
45 | }
46 |
47 | @NonNull
48 | @Override
49 | public TipsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
50 | Context context = parent.getContext();
51 | LayoutInflater inflater = LayoutInflater.from(context);
52 |
53 | // Inflate the custom layout
54 | View tipView = inflater.inflate(R.layout.item_tip, parent, false);
55 |
56 | // Return a new holder instance
57 | return new ViewHolder(tipView);
58 | }
59 |
60 | // Involves populating data into the item through holder
61 | @Override
62 | public void onBindViewHolder(TipsAdapter.ViewHolder viewHolder, int position) {
63 | // Get the data model based on position
64 | Tip tip = tips.get(position);
65 |
66 | // Set item views based on your views and data model
67 | TextView title = viewHolder.tipTitle;
68 | title.setText(tip.getTitle());
69 |
70 | TextView content = viewHolder.tipContent;
71 | content.setText(tip.getBody());
72 | }
73 |
74 | // Returns the total count of items in the list
75 | @Override
76 | public int getItemCount() {
77 | return tips.size();
78 | }
79 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/AddToHistoryTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.database.SQLException;
4 | import android.os.AsyncTask;
5 |
6 | import com.ikue.japanesedictionary.interfaces.AddToHistoryAsyncCallbacks;
7 |
8 | public class AddToHistoryTask extends AsyncTask {
9 | private final AddToHistoryAsyncCallbacks listener;
10 | private final DictionaryDbHelper helper;
11 | private final int entryId;
12 |
13 | public AddToHistoryTask(AddToHistoryAsyncCallbacks listener, DictionaryDbHelper helper,
14 | int entryId) {
15 | this.listener = listener;
16 | this.helper = helper;
17 | this.entryId = entryId;
18 | }
19 |
20 | @Override
21 | protected Boolean doInBackground(Void... params) {
22 | try {
23 | helper.addToHistory(entryId);
24 | } catch (SQLException e) {
25 | e.printStackTrace();
26 | return false;
27 | }
28 | return true;
29 | }
30 |
31 | @Override
32 | protected void onPostExecute(Boolean wasSuccessful) {
33 | // This method is executed in the UIThread
34 | // with access to the result of the long running task
35 | listener.onAddToHistoryResult(wasSuccessful);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/DictionaryDbSchema.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | // TODO: Add Jmnedict data
4 | public class DictionaryDbSchema {
5 |
6 | public static final class Jmdict {
7 |
8 | public static final class KanjiElementTable {
9 | public static final String NAME = "Jmdict_Kanji_Element";
10 |
11 | public static final class Cols {
12 | public static final String _ID = "_ID";
13 | public static final String ENTRY_ID = "ENTRY_ID";
14 | public static final String VALUE = "VALUE";
15 | }
16 | }
17 |
18 | public static final class ReadingElementTable {
19 | public static final String NAME = "Jmdict_Reading_Element";
20 |
21 | public static final class Cols {
22 | public static final String _ID = "_ID";
23 | public static final String ENTRY_ID = "ENTRY_ID";
24 | public static final String VALUE = "VALUE";
25 | public static final String IS_TRUE_READING = "NO_KANJI";
26 | }
27 | }
28 |
29 | public static final class ReadingRelationTable {
30 | public static final String NAME = "Jmdict_Reading_Relation";
31 |
32 | public static final class Cols {
33 | public static final String _ID = "_ID";
34 | public static final String ENTRY_ID = "ENTRY_ID";
35 | public static final String READING_ELEMENT_ID = "READING_ELEMENT_ID";
36 | public static final String VALUE = "VALUE";
37 | }
38 | }
39 |
40 | public static final class SenseElementTable {
41 | public static final String NAME = "Jmdict_Sense_Element";
42 |
43 | public static final class Cols {
44 | public static final String _ID = "_ID";
45 | public static final String ENTRY_ID = "ENTRY_ID";
46 | }
47 | }
48 |
49 | public static final class SensePosTable {
50 | public static final String NAME = "Jmdict_Sense_Pos";
51 |
52 | public static final class Cols {
53 | public static final String _ID = "_ID";
54 | public static final String SENSE_ID = "SENSE_ID";
55 | public static final String VALUE = "VALUE";
56 | }
57 | }
58 |
59 | public static final class SenseFieldTable {
60 | public static final String NAME = "Jmdict_Sense_Field";
61 |
62 | public static final class Cols {
63 | public static final String _ID = "_ID";
64 | public static final String SENSE_ID = "SENSE_ID";
65 | public static final String VALUE = "VALUE";
66 | }
67 | }
68 |
69 | public static final class SenseDialectTable {
70 | public static final String NAME = "Jmdict_Sense_Dialect";
71 |
72 | public static final class Cols {
73 | public static final String _ID = "_ID";
74 | public static final String SENSE_ID = "SENSE_ID";
75 | public static final String VALUE = "VALUE";
76 | }
77 | }
78 |
79 | public static final class GlossTable {
80 | public static final String NAME = "Jmdict_Gloss";
81 |
82 | public static final class Cols {
83 | public static final String _ID = "_ID";
84 | public static final String ENTRY_ID = "ENTRY_ID";
85 | public static final String SENSE_ID = "SENSE_ID";
86 | public static final String VALUE = "VALUE";
87 | }
88 | }
89 |
90 | public static final class PriorityTable {
91 | public static final String NAME = "Jmdict_Priority";
92 |
93 | public static final class Cols {
94 | public static final String _ID = "_ID";
95 | public static final String ENTRY_ID = "ENTRY_ID";
96 | public static final String VALUE = "VALUE";
97 | public static final String TYPE = "TYPE";
98 | }
99 | }
100 | }
101 |
102 | public static final class User {
103 |
104 | public static final class FavouritesTable {
105 | public static final String NAME = "User_Favourites";
106 |
107 | public static final class Cols {
108 | public static final String ENTRY_ID = "ENTRY_ID";
109 | public static final String SQLTIME = "SQLTIME";
110 | }
111 | }
112 |
113 | public static final class HistoryTable {
114 | public static final String NAME = "User_History";
115 |
116 | public static final class Cols {
117 | public static final String ENTRY_ID = "ENTRY_ID";
118 | public static final String SQLTIME = "SQLTIME";
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/GetEntryDetailTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.ikue.japanesedictionary.interfaces.DetailAsyncCallbacks;
6 | import com.ikue.japanesedictionary.models.DictionaryEntry;
7 |
8 | // The types specified here are the input data type, the progress type, and the result type
9 | public class GetEntryDetailTask extends AsyncTask {
10 | private final DetailAsyncCallbacks listener;
11 | private final DictionaryDbHelper helper;
12 | private final int entryId;
13 |
14 | public GetEntryDetailTask(DetailAsyncCallbacks listener, DictionaryDbHelper helper, int entryId) {
15 | this.listener = listener;
16 | this.helper = helper;
17 | this.entryId = entryId;
18 | }
19 |
20 | @Override
21 | protected DictionaryEntry doInBackground(Void... params) {
22 | return helper.getEntry(entryId);
23 | }
24 |
25 | @Override
26 | protected void onPostExecute(DictionaryEntry result) {
27 | // This method is executed in the UIThread
28 | // with access to the result of the long running task
29 | listener.onResult(result);
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/GetFavouritesTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.ikue.japanesedictionary.interfaces.GetFavouritesAsyncCallbacks;
6 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
7 |
8 | import java.util.List;
9 |
10 | // TODO: Switch to AsyncTaskLoader so the task can survive configuration changes
11 | // The types specified here are the input data type, the progress type, and the result type
12 | public class GetFavouritesTask extends AsyncTask> {
13 | private final GetFavouritesAsyncCallbacks listener;
14 | private final DictionaryDbHelper helper;
15 |
16 | public GetFavouritesTask(GetFavouritesAsyncCallbacks listener, DictionaryDbHelper helper) {
17 | this.listener = listener;
18 | this.helper = helper;
19 | }
20 |
21 | @Override
22 | protected List doInBackground(Void... params) {
23 | return helper.getFavourites();
24 | }
25 |
26 | @Override
27 | protected void onPostExecute(List result) {
28 | // This method is executed in the UIThread
29 | // with access to the result of the long running task
30 | listener.onResult(result);
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/GetHistoryTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.ikue.japanesedictionary.interfaces.GetHistoryAsyncCallbacks;
6 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
7 |
8 | import java.util.List;
9 |
10 | // TODO: Switch to AsyncTaskLoader so the task can survive configuration changes
11 | // The types specified here are the input data type, the progress type, and the result type
12 | public class GetHistoryTask extends AsyncTask> {
13 | private final GetHistoryAsyncCallbacks listener;
14 | private final DictionaryDbHelper helper;
15 |
16 | public GetHistoryTask(GetHistoryAsyncCallbacks listener, DictionaryDbHelper helper) {
17 | this.listener = listener;
18 | this.helper = helper;
19 | }
20 |
21 | @Override
22 | protected List doInBackground(Void... params) {
23 | return helper.getHistory();
24 | }
25 |
26 | @Override
27 | protected void onPostExecute(List result) {
28 | // This method is executed in the UIThread
29 | // with access to the result of the long running task
30 | listener.onResult(result);
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/GetRandomEntryTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.ikue.japanesedictionary.interfaces.DetailAsyncCallbacks;
6 | import com.ikue.japanesedictionary.models.DictionaryEntry;
7 |
8 | public class GetRandomEntryTask extends AsyncTask {
9 | private final DetailAsyncCallbacks listener;
10 | private final DictionaryDbHelper helper;
11 |
12 | public GetRandomEntryTask(DetailAsyncCallbacks listener, DictionaryDbHelper helper) {
13 | this.listener = listener;
14 | this.helper = helper;
15 | }
16 |
17 | @Override
18 | protected DictionaryEntry doInBackground(Void... params) {
19 | return helper.getRandomEntry();
20 | }
21 |
22 | @Override
23 | protected void onPostExecute(DictionaryEntry result) {
24 | // This method is executed in the UIThread
25 | // with access to the result of the long running task
26 | listener.onResult(result);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/SearchDatabaseTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import com.ikue.japanesedictionary.interfaces.SearchAsyncCallbacks;
6 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
7 |
8 | import java.util.List;
9 |
10 | // TODO: Switch to AsyncTaskLoader so the task can survive configuration changes
11 | // The types specified here are the input data type, the progress type, and the result type
12 | public class SearchDatabaseTask extends AsyncTask> {
13 | private final SearchAsyncCallbacks listener;
14 | private final DictionaryDbHelper helper;
15 | private final String searchQuery;
16 | private final int searchType;
17 |
18 | public SearchDatabaseTask(SearchAsyncCallbacks listener, DictionaryDbHelper helper,
19 | String searchQuery, int searchType) {
20 | this.listener = listener;
21 | this.helper = helper;
22 | this.searchQuery = searchQuery;
23 | this.searchType = searchType;
24 | }
25 |
26 | @Override
27 | protected void onPreExecute() {
28 | // Show the ProgressBar just before we search the database
29 | listener.toggleProgressBar(true);
30 | }
31 |
32 | @Override
33 | protected List doInBackground(Void... params) {
34 | // Make sure to trim any leading or trailing whitespace in the search query
35 | return helper.searchDictionary(searchQuery.trim(), searchType);
36 | }
37 |
38 | @Override
39 | protected void onPostExecute(List result) {
40 | // This method is executed in the UIThread
41 | // with access to the result of the long running task
42 | listener.onResult(result);
43 |
44 | // Update the view and hide the ProgressBar
45 | listener.toggleProgressBar(false);
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/database/ToggleFavouriteTask.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.database;
2 |
3 | import android.database.SQLException;
4 | import android.os.AsyncTask;
5 |
6 | import com.ikue.japanesedictionary.interfaces.ToggleFavouriteAsyncCallbacks;
7 |
8 | public class ToggleFavouriteTask extends AsyncTask {
9 | private final ToggleFavouriteAsyncCallbacks listener;
10 | private final DictionaryDbHelper helper;
11 | private final int entryId;
12 | private final boolean toBeAdded;
13 |
14 | public ToggleFavouriteTask(ToggleFavouriteAsyncCallbacks listener, DictionaryDbHelper helper,
15 | int entryId, boolean toBeAdded) {
16 | this.listener = listener;
17 | this.helper = helper;
18 | this.entryId = entryId;
19 | this.toBeAdded = toBeAdded;
20 | }
21 |
22 | @Override
23 | protected void onPreExecute() {
24 | }
25 |
26 | @Override
27 | protected Boolean doInBackground(Void... params) {
28 | try {
29 | if (toBeAdded) {
30 | helper.addToFavourites(entryId);
31 | } else {
32 | helper.removeFromFavourites(entryId);
33 | }
34 | } catch (SQLException e) {
35 | e.printStackTrace();
36 | return false;
37 | }
38 | return true;
39 | }
40 |
41 | @Override
42 | protected void onPostExecute(Boolean wasSuccessful) {
43 | // This method is executed in the UIThread
44 | // with access to the result of the long running task
45 | listener.onToggleFavouriteResult(toBeAdded, wasSuccessful);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/fragments/FavouritesFragment.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.fragments;
2 |
3 | import android.os.AsyncTask;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 | import androidx.recyclerview.widget.DividerItemDecoration;
12 | import androidx.recyclerview.widget.LinearLayoutManager;
13 | import androidx.recyclerview.widget.RecyclerView;
14 |
15 | import com.ikue.japanesedictionary.R;
16 | import com.ikue.japanesedictionary.adapters.SearchResultAdapter;
17 | import com.ikue.japanesedictionary.database.DictionaryDbHelper;
18 | import com.ikue.japanesedictionary.database.GetFavouritesTask;
19 | import com.ikue.japanesedictionary.interfaces.GetFavouritesAsyncCallbacks;
20 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | public class FavouritesFragment extends Fragment implements GetFavouritesAsyncCallbacks {
26 | // Singleton variable. DO NOT CHANGE
27 | private static DictionaryDbHelper helper;
28 |
29 | private static AsyncTask task;
30 | private GetFavouritesAsyncCallbacks listener;
31 |
32 | private SearchResultAdapter adapter;
33 |
34 | @Override
35 | public void onCreate(@Nullable Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 |
38 | // Retain the fragment so rotation does not repeatedly fire off new AsyncTasks
39 | setRetainInstance(true);
40 |
41 | // Set the OnTaskComplete listener
42 | listener = this;
43 |
44 | // Get a database on startup.
45 | helper = DictionaryDbHelper.getInstance(this.getActivity());
46 |
47 | adapter = new SearchResultAdapter(this.getContext(), new ArrayList<>());
48 | }
49 |
50 | @Nullable
51 | @Override
52 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
53 | return inflater.inflate(R.layout.recycler_view, container, false);
54 | }
55 |
56 | @Override
57 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
58 | // Setup RecyclerView
59 | RecyclerView recyclerView = view.findViewById(R.id.my_recycler_view);
60 | LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
61 | recyclerView.setLayoutManager(layoutManager);
62 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
63 | layoutManager.getOrientation());
64 | recyclerView.addItemDecoration(dividerItemDecoration);
65 | recyclerView.setAdapter(adapter);
66 | }
67 |
68 | @Override
69 | public void onResume() {
70 | super.onResume();
71 |
72 | // User may have added a favourite when they come back to the favourites list, so
73 | // re-query and get all favourites again
74 |
75 | // As onResume is always called we only need to do the query here. This will also be called
76 | // Whenever the user navigates back so we can ensure we always have the correct favourites
77 | task = new GetFavouritesTask(listener, helper).execute();
78 | }
79 |
80 | @Override
81 | public void onDestroy() {
82 | // Cancel the AsyncTask if it is running when Activity is about to close
83 | // cancel(false) is safer and doesn't force an instant cancellation
84 | if (task != null) {
85 | task.cancel(false);
86 | }
87 |
88 | // Close the SQLiteHelper instance
89 | helper.close();
90 | super.onDestroy();
91 | }
92 |
93 | @Override
94 | public void onResult(List results) {
95 | // TODO: Detect more granular changes instead of reloading the whole list
96 | adapter.swapItems(results);
97 | }
98 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/fragments/HistoryFragment.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.fragments;
2 |
3 | import android.os.AsyncTask;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 | import androidx.recyclerview.widget.DividerItemDecoration;
12 | import androidx.recyclerview.widget.LinearLayoutManager;
13 | import androidx.recyclerview.widget.RecyclerView;
14 |
15 | import com.ikue.japanesedictionary.R;
16 | import com.ikue.japanesedictionary.adapters.SearchResultAdapter;
17 | import com.ikue.japanesedictionary.database.DictionaryDbHelper;
18 | import com.ikue.japanesedictionary.database.GetHistoryTask;
19 | import com.ikue.japanesedictionary.interfaces.GetHistoryAsyncCallbacks;
20 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | public class HistoryFragment extends Fragment implements GetHistoryAsyncCallbacks {
26 | // Singleton variable. DO NOT CHANGE
27 | private static DictionaryDbHelper helper;
28 |
29 | private static AsyncTask task;
30 | private GetHistoryAsyncCallbacks listener;
31 |
32 | private SearchResultAdapter adapter;
33 |
34 | @Override
35 | public void onCreate(@Nullable Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 |
38 | // Retain the fragment so rotation does not repeatedly fire off new AsyncTasks
39 | setRetainInstance(true);
40 |
41 | // Set the OnTaskComplete listener
42 | listener = this;
43 |
44 | // Get a database on startup.
45 | helper = DictionaryDbHelper.getInstance(this.getActivity());
46 |
47 | adapter = new SearchResultAdapter(this.getContext(), new ArrayList<>());
48 | }
49 |
50 | @Nullable
51 | @Override
52 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
53 | return inflater.inflate(R.layout.recycler_view, container, false);
54 | }
55 |
56 | @Override
57 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
58 | // Setup RecyclerView
59 | RecyclerView recyclerView = view.findViewById(R.id.my_recycler_view);
60 | LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
61 | recyclerView.setLayoutManager(layoutManager);
62 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
63 | layoutManager.getOrientation());
64 | recyclerView.addItemDecoration(dividerItemDecoration);
65 | recyclerView.setAdapter(adapter);
66 | }
67 |
68 | @Override
69 | public void onResume() {
70 | super.onResume();
71 |
72 | // User may have viewed an entry when they come back to the history list, so
73 | // re-query and get the history again
74 |
75 | // As onResume is always called we only need to do the query here. This will also be called
76 | // Whenever the user navigates back so we can ensure we always have the correct history
77 | task = new GetHistoryTask(listener, helper).execute();
78 | }
79 |
80 | @Override
81 | public void onDestroy() {
82 | // Cancel the AsyncTask if it is running when Activity is about to close
83 | // cancel(false) is safer and doesn't force an instant cancellation
84 | if (task != null) {
85 | task.cancel(false);
86 | }
87 |
88 | // Close the SQLiteHelper instance
89 | helper.close();
90 | super.onDestroy();
91 | }
92 |
93 | @Override
94 | public void onResult(List results) {
95 | // TODO: Detect more granular changes instead of reloading the whole list
96 | adapter.swapItems(results);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/fragments/SearchResultFragment.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.fragments;
2 |
3 | import android.os.AsyncTask;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.Menu;
7 | import android.view.MenuInflater;
8 | import android.view.MenuItem;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 |
12 | import com.google.android.material.snackbar.Snackbar;
13 | import com.ikue.japanesedictionary.R;
14 | import com.ikue.japanesedictionary.adapters.SearchResultAdapter;
15 | import com.ikue.japanesedictionary.database.DictionaryDbHelper;
16 | import com.ikue.japanesedictionary.database.SearchDatabaseTask;
17 | import com.ikue.japanesedictionary.interfaces.SearchAsyncCallbacks;
18 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
19 | import com.ikue.japanesedictionary.utils.SearchUtils;
20 |
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | import static com.ikue.japanesedictionary.utils.GlobalConstants.SearchTypes.ENGLISH_TYPE;
25 | import static com.ikue.japanesedictionary.utils.GlobalConstants.SearchTypes.ROMAJI_TYPE;
26 |
27 | import androidx.annotation.NonNull;
28 | import androidx.annotation.Nullable;
29 | import androidx.appcompat.app.AppCompatActivity;
30 | import androidx.appcompat.widget.Toolbar;
31 | import androidx.core.widget.ContentLoadingProgressBar;
32 | import androidx.fragment.app.Fragment;
33 | import androidx.recyclerview.widget.DividerItemDecoration;
34 | import androidx.recyclerview.widget.LinearLayoutManager;
35 | import androidx.recyclerview.widget.RecyclerView;
36 |
37 | public class SearchResultFragment extends Fragment implements SearchAsyncCallbacks {
38 | private static final String ARG_SEARCH_TERM = "SEARCH_TERM";
39 |
40 | // Singleton variable. DO NOT CHANGE
41 | private static DictionaryDbHelper helper;
42 |
43 | private static AsyncTask task;
44 | private SearchResultAdapter adapter;
45 | private SearchAsyncCallbacks listener;
46 |
47 | private static int searchType;
48 | private static String searchQuery;
49 |
50 | private ContentLoadingProgressBar progressBar;
51 |
52 | public static SearchResultFragment newInstance(String query) {
53 | Bundle args = new Bundle();
54 | args.putString(ARG_SEARCH_TERM, query);
55 |
56 | SearchResultFragment fragment = new SearchResultFragment();
57 | fragment.setArguments(args);
58 |
59 | return fragment;
60 | }
61 |
62 | @Override
63 | public void onCreate(@Nullable Bundle savedInstanceState) {
64 | super.onCreate(savedInstanceState);
65 |
66 | // Retain the fragment so rotation does not repeatedly fire off new AsyncTasks
67 | setRetainInstance(true);
68 |
69 | // Set the OnTaskComplete listener
70 | listener = this;
71 |
72 | // Get a database on startup.
73 | helper = DictionaryDbHelper.getInstance(this.getActivity());
74 |
75 | adapter = new SearchResultAdapter(this.getContext(), new ArrayList<>());
76 |
77 | // Get the string the user searched for from the received Intent, and get the type
78 | searchQuery = requireArguments().getString(ARG_SEARCH_TERM, null);
79 | searchType = SearchUtils.getSearchType(SearchUtils.removeWildcards(searchQuery));
80 | }
81 |
82 | @Nullable
83 | @Override
84 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
85 | return inflater.inflate(R.layout.fragment_search, container, false); // Inflate the view
86 | }
87 |
88 | @Override
89 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
90 | // Toolbar
91 | Toolbar toolbar = view.findViewById(R.id.toolbar);
92 | AppCompatActivity activity = (AppCompatActivity) requireActivity();
93 | activity.setSupportActionBar(toolbar);
94 | if (activity.getSupportActionBar() != null) {
95 | activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
96 | activity.getSupportActionBar().setTitle(R.string.results_view_toolbar_title);
97 | activity.getSupportActionBar().setSubtitle(searchQuery);
98 | }
99 | setHasOptionsMenu(true);
100 |
101 | // Progressbar
102 | progressBar = view.findViewById(R.id.search_progress_bar);
103 |
104 | // Recycler view
105 | RecyclerView recyclerView = view.findViewById(R.id.search_recycler_view);
106 | LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
107 | recyclerView.setLayoutManager(layoutManager);
108 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
109 | layoutManager.getOrientation());
110 | recyclerView.addItemDecoration(dividerItemDecoration);
111 | recyclerView.setAdapter(adapter);
112 |
113 | // We need to run the AsyncTask here instead of onCreate so we know that ProgressBar has been
114 | // instantiated. If we run it on onCreate the AsyncTask will try to show a ProgressBar on a
115 | // possible non-existing ProgressBar and crash.
116 | task = new SearchDatabaseTask(listener, helper, searchQuery, searchType).execute();
117 | }
118 |
119 | @Override
120 | public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
121 | super.onCreateOptionsMenu(menu, inflater);
122 | inflater.inflate(R.menu.menu_search, menu);
123 | }
124 |
125 | @Override
126 | public boolean onOptionsItemSelected(MenuItem item) {
127 | // Handle action bar item clicks
128 | int id = item.getItemId();
129 |
130 | if (id == R.id.action_settings) {
131 | return true;
132 | } else if (id == android.R.id.home) {
133 | // We want to go back to the previous Activity
134 | // or the home screen
135 | requireActivity().getOnBackPressedDispatcher().onBackPressed();
136 | return true;
137 | }
138 | return super.onOptionsItemSelected(item);
139 | }
140 |
141 | @Override
142 | public void onDestroy() {
143 | // Cancel the AsyncTask if it is running when Activity is about to close
144 | // cancel(false) is safer and doesn't force an instant cancellation
145 | if (task != null) {
146 | task.cancel(false);
147 | }
148 |
149 | // Close the SQLiteHelper instance
150 | helper.close();
151 | super.onDestroy();
152 | }
153 |
154 | @Override
155 | public void onResult(List results) {
156 | adapter.swapItems(results);
157 | showSwitchSearchSnackbar();
158 | }
159 |
160 | @Override
161 | public void toggleProgressBar(boolean toShow) {
162 | // TODO: Fix ProgressBar not showing on executing new AsyncTask
163 | // This is a bug, see: https://code.google.com/p/android/issues/detail?id=233207
164 | if (toShow) {
165 | progressBar.show();
166 | } else {
167 | progressBar.hide();
168 | }
169 | }
170 |
171 | private void showSwitchSearchSnackbar() {
172 | // First make sure the view is not null
173 | if (getView() != null) {
174 | // Give the user the option to show Romaji results instead
175 | if (searchType == ENGLISH_TYPE) {
176 | Snackbar.make(getView(), R.string.search_view_snackbar_english, Snackbar.LENGTH_INDEFINITE)
177 | .setAction(R.string.search_view_snackbar_english_action, view -> {
178 | searchType = ROMAJI_TYPE;
179 | new SearchDatabaseTask(listener, helper, searchQuery, searchType).execute();
180 | }).show();
181 |
182 | // Give the user the option to show English results instead, due to our naive
183 | // classification of Romaji
184 | } else if (searchType == ROMAJI_TYPE) {
185 | // TODO: Show the actual term the user searched for (Hiragana or Katakana)
186 | Snackbar.make(getView(), R.string.search_view_snackbar_romaji, Snackbar.LENGTH_INDEFINITE)
187 | .setAction(R.string.search_view_snackbar_romaji_action, view -> {
188 | searchType = ENGLISH_TYPE;
189 | new SearchDatabaseTask(listener, helper, searchQuery, searchType).execute();
190 | }).show();
191 | }
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/fragments/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.preference.PreferenceFragment;
5 |
6 | import com.ikue.japanesedictionary.R;
7 |
8 | public class SettingsFragment extends PreferenceFragment {
9 | @Override
10 | public void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 |
13 | // Load the preferences from an XML resource
14 | addPreferencesFromResource(R.xml.preferences);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/interfaces/AddToHistoryAsyncCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.interfaces;
2 |
3 | public interface AddToHistoryAsyncCallbacks {
4 | void onAddToHistoryResult(boolean wasSuccessful);
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/interfaces/DetailAsyncCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.interfaces;
2 |
3 | import com.ikue.japanesedictionary.models.DictionaryEntry;
4 |
5 | public interface DetailAsyncCallbacks {
6 | void onResult(DictionaryEntry result);
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/interfaces/GetFavouritesAsyncCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.interfaces;
2 |
3 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
4 |
5 | import java.util.List;
6 |
7 | public interface GetFavouritesAsyncCallbacks {
8 | void onResult(List results);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/interfaces/GetHistoryAsyncCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.interfaces;
2 |
3 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
4 |
5 | import java.util.List;
6 |
7 | public interface GetHistoryAsyncCallbacks {
8 | void onResult(List results);
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/interfaces/SearchAsyncCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.interfaces;
2 |
3 | import com.ikue.japanesedictionary.models.DictionaryListEntry;
4 |
5 | import java.util.List;
6 |
7 | public interface SearchAsyncCallbacks {
8 | void toggleProgressBar(boolean toShow);
9 |
10 | void onResult(List results);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/interfaces/ToggleFavouriteAsyncCallbacks.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.interfaces;
2 |
3 | public interface ToggleFavouriteAsyncCallbacks {
4 | void onToggleFavouriteResult(boolean toBeAdded, boolean wasSuccessful);
5 | }
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/DictionaryEntry.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | import java.util.List;
4 |
5 | public class DictionaryEntry {
6 |
7 | // A unique number for each entry
8 | private int entryId;
9 |
10 | private boolean isFavourite;
11 |
12 | // Can be 0 or many
13 | private List kanjiElements;
14 |
15 | // Always 1 or many
16 | private List readingElements;
17 |
18 | // Always 1 or many
19 | private List senseElements;
20 |
21 | // Can be 0 or many
22 | private List priorities;
23 |
24 | public int getEntryId() {
25 | return entryId;
26 | }
27 |
28 | public void setEntryId(int entryId) {
29 | this.entryId = entryId;
30 | }
31 |
32 | public boolean getIsFavourite() { return isFavourite; }
33 |
34 | public void setIsFavourite(boolean isFavourite) { this.isFavourite = isFavourite; }
35 |
36 | public List getKanjiElements() {
37 | return kanjiElements;
38 | }
39 |
40 | public void setKanjiElements(List kanjiElements) {
41 | this.kanjiElements = kanjiElements;
42 | }
43 |
44 | public List getSenseElements() {
45 | return senseElements;
46 | }
47 |
48 | public void setSenseElements(List senseElements) {
49 | this.senseElements = senseElements;
50 | }
51 |
52 | public List getReadingElements() {
53 | return readingElements;
54 | }
55 |
56 | public void setReadingElements(List readingElements) {
57 | this.readingElements = readingElements;
58 | }
59 |
60 | public List getPriorities() {
61 | return priorities;
62 | }
63 |
64 | public void setPriorities(List priorities) {
65 | this.priorities = priorities;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/DictionaryListEntry.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | import java.util.List;
4 |
5 | public class DictionaryListEntry {
6 |
7 | // A unique ID for each entry in the dictionary
8 | private int entryId;
9 |
10 | // The first Kanji Element 'keb' value of an entry, null if there are no Kanji Elements.
11 | private String kanjiElementValue;
12 |
13 | // The first Reading Element 'reb' value of an entry, will never be null.
14 | private String readingElementValue;
15 |
16 | // All gloss values of an entry as a single string
17 | private List glossValue;
18 |
19 | public int getEntryId() {
20 | return entryId;
21 | }
22 |
23 | public void setEntryId(int entryId) {
24 | this.entryId = entryId;
25 | }
26 |
27 | public List getGlossValue() {
28 | return glossValue;
29 | }
30 |
31 | public void setGlossValue(List glossValue) {
32 | this.glossValue = glossValue;
33 | }
34 |
35 | public String getKanjiElementValue() {
36 | return kanjiElementValue;
37 | }
38 |
39 | public void setKanjiElementValue(String kanjiElementValue) {
40 | this.kanjiElementValue = kanjiElementValue;
41 | }
42 |
43 | public String getReadingElementValue() {
44 | return readingElementValue;
45 | }
46 |
47 | public void setReadingElementValue(String readingElementValue) {
48 | this.readingElementValue = readingElementValue;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/KanjiElement.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | public class KanjiElement {
4 |
5 | // Unique ID for the Kanji Element
6 | private int kanjiElementId;
7 |
8 | // Contains a word or short phrase in Japanese which is written using at least one
9 | // non-kana character (usually kanji, but can be other characters).
10 | private String value;
11 |
12 | public int getKanjiElementId() {
13 | return kanjiElementId;
14 | }
15 |
16 | public void setKanjiElementId(int kanjiElementId) {
17 | this.kanjiElementId = kanjiElementId;
18 | }
19 |
20 | public String getValue() {
21 | return value;
22 | }
23 |
24 | public void setValue(String value) {
25 | this.value = value;
26 | }
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/Priority.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | public class Priority {
4 |
5 | // Unique ID for the Priority
6 | private int priorityId;
7 |
8 | // Records information about the relative priority of the entry, e.g. news1/2, ichi1/2, spec1/2.
9 | private String value;
10 |
11 | // If false, then a priority associated with the Reading Element.
12 | private boolean isKanjiReadingPriority;
13 |
14 | public int getPriorityId() {
15 | return priorityId;
16 | }
17 |
18 | public void setPriorityId(int priorityId) {
19 | this.priorityId = priorityId;
20 | }
21 |
22 | public String getValue() {
23 | return value;
24 | }
25 |
26 | public void setValue(String value) {
27 | this.value = value;
28 | }
29 |
30 | public boolean isKanjiReadingPriority() {
31 | return isKanjiReadingPriority;
32 | }
33 |
34 | public void setKanjiReadingPriority(boolean kanjiReadingPriority) {
35 | isKanjiReadingPriority = kanjiReadingPriority;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/ReadingElement.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | import java.util.List;
4 |
5 | public class ReadingElement {
6 |
7 | // Unique ID for the Reading Element
8 | private int readingElementId;
9 |
10 | // Restricted to kana and related characters such as chouon and kurikaeshi.
11 | // Kana usage will be consistent between the keb and reb elements.
12 | private String value;
13 |
14 | // Indicates that the reb, while associated with the keb, cannot be regarded as a true
15 | // reading of the kanji. E.g. foreign place names.
16 | private boolean isTrueReading;
17 |
18 | // Used to indicate when the reading only applies to a subset of the keb elements in the entry.
19 | // In its absence, all readings apply to all kanji elements. The contents of this element
20 | // must exactly match those of one of the keb elements.
21 | private List readingRelation;
22 |
23 | public int getReadingElementId() {
24 | return readingElementId;
25 | }
26 |
27 | public void setReadingElementId(int readingElementId) {
28 | this.readingElementId = readingElementId;
29 | }
30 |
31 | public String getValue() {
32 | return value;
33 | }
34 |
35 | public void setValue(String value) {
36 | this.value = value;
37 | }
38 |
39 | public boolean isTrueReading() {
40 | return isTrueReading;
41 | }
42 |
43 | public void setTrueReading(boolean trueReading) {
44 | isTrueReading = trueReading;
45 | }
46 |
47 | public List getReadingRelation() {
48 | return readingRelation;
49 | }
50 |
51 | public void setReadingRelation(List readingRelation) {
52 | this.readingRelation = readingRelation;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/SenseElement.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | import java.util.List;
4 |
5 | public class SenseElement {
6 |
7 | // Unique ID for the Sense Element
8 | private int senseElementId;
9 |
10 | // Part of speech information about the Sense Element, e.g. Noun, Verb, Adverb.
11 | private List partOfSpeech;
12 |
13 | // Field of application information about the Sense Element, e.g. Medical, Computing.
14 | // Absence implies general application.
15 | private List fieldOfApplication;
16 |
17 | // Dialect information about the Sense Element, e.g. Kansai-ben, Nagoya-ben, Osaka-ben.
18 | private List dialect;
19 |
20 | // Target-language words or phrases which are equivalents to the Japanese word.
21 | // May be omitted in entries which are purely for cross-referencing.
22 | private List glosses;
23 |
24 | public List getDialect() {
25 | return dialect;
26 | }
27 |
28 | public void setDialect(List dialect) {
29 | this.dialect = dialect;
30 | }
31 |
32 | public List getFieldOfApplication() {
33 | return fieldOfApplication;
34 | }
35 |
36 | public void setFieldOfApplication(List fieldOfApplication) {
37 | this.fieldOfApplication = fieldOfApplication;
38 | }
39 |
40 | public List getGlosses() {
41 | return glosses;
42 | }
43 |
44 | public void setGlosses(List glosses) {
45 | this.glosses = glosses;
46 | }
47 |
48 | public List getPartOfSpeech() {
49 | return partOfSpeech;
50 | }
51 |
52 | public void setPartOfSpeech(List partOfSpeech) {
53 | this.partOfSpeech = partOfSpeech;
54 | }
55 |
56 | public int getSenseElementId() {
57 | return senseElementId;
58 | }
59 |
60 | public void setSenseElementId(int senseElementId) {
61 | this.senseElementId = senseElementId;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/models/Tip.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.models;
2 |
3 | public class Tip {
4 | private String title;
5 | private String body;
6 |
7 | public String getTitle() {
8 | return title;
9 | }
10 |
11 | public void setTitle(String title) {
12 | this.title = title;
13 | }
14 |
15 | public String getBody() {
16 | return body;
17 | }
18 |
19 | public void setBody(String body) {
20 | this.body = body;
21 | }
22 |
23 | public Tip(String title, String body) {
24 | this.title = title;
25 | this.body = body;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/CreativeCommonsShareAlikeLicense.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.colinrtwhite.licensesdialog.model.License;
8 | import com.ikue.japanesedictionary.R;
9 |
10 | import java.io.BufferedReader;
11 | import java.io.IOException;
12 | import java.io.InputStreamReader;
13 | import java.nio.charset.StandardCharsets;
14 |
15 | public final class CreativeCommonsShareAlikeLicense implements License {
16 |
17 | private final Context context;
18 |
19 | public CreativeCommonsShareAlikeLicense(Context context) {
20 | this.context = context;
21 | }
22 |
23 | @NonNull
24 | @Override
25 | public String getText() {
26 | return getContent(context, R.raw.cc_sa_40_full);
27 | }
28 |
29 | @NonNull
30 | @Override
31 | public String getTitle() {
32 | return "CC BY-SA 4.0 - ATTRIBUTION-SHAREALIKE 4.0 INTERNATIONAL";
33 | }
34 |
35 | @NonNull
36 | @Override
37 | public String getCopyrightPrefix() {
38 | return "Copyright";
39 | }
40 |
41 | private String getContent(final Context context, final int contentResourceId) {
42 | try (final BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(contentResourceId), StandardCharsets.UTF_8))) {
43 | return toString(reader);
44 | } catch (final IOException e) {
45 | throw new IllegalStateException(e);
46 | }
47 | }
48 |
49 | private String toString(final BufferedReader reader) throws IOException {
50 | final StringBuilder builder = new StringBuilder();
51 | String line;
52 | while ((line = reader.readLine()) != null) {
53 | builder.append(line).append(System.lineSeparator());
54 | }
55 | return builder.toString();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/DateTimeUtils.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 | import java.util.Date;
4 | import java.util.concurrent.TimeUnit;
5 |
6 | // Get the difference in hours between two dates.
7 | public class DateTimeUtils {
8 | public static long getDifferenceInDays(Date d1, Date d2) {
9 | long diff = d2.getTime() - d1.getTime();
10 | return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/DbUtils.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 |
4 | import androidx.annotation.NonNull;
5 |
6 | import com.ikue.japanesedictionary.database.DictionaryDbSchema;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Collections;
11 | import java.util.LinkedHashSet;
12 | import java.util.List;
13 |
14 | public class DbUtils {
15 | // Split the string on the separator character into a list, then
16 | // convert to a LinkedHashSet to remove duplicate values.
17 | @NonNull
18 | public static List formatString(String stringToFormat) {
19 | if (stringToFormat != null && !stringToFormat.isEmpty()) {
20 | return new ArrayList<>(new LinkedHashSet<>(Arrays.asList(stringToFormat.split("§"))));
21 | } else {
22 | // We never want a null value in our DictionaryEntry, so just return an empty list
23 | return Collections.emptyList();
24 | }
25 | }
26 |
27 | public static String getSearchByKanaQuery(boolean hasWildcard) {
28 | String searchType = hasWildcard ? "LIKE" : "=";
29 |
30 | String select = "SELECT re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + ", group_concat(ke."
31 | + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.VALUE + ", '§') AS kanji_value, group_concat(re."
32 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.VALUE + ", '§') AS read_value, group_concat(gloss."
33 | + DictionaryDbSchema.Jmdict.GlossTable.Cols.VALUE + ", '§') AS gloss_value ";
34 |
35 | String from = "FROM " + DictionaryDbSchema.Jmdict.ReadingElementTable.NAME + " AS re ";
36 |
37 | String join = "JOIN " + DictionaryDbSchema.Jmdict.GlossTable.NAME + " AS gloss ON re."
38 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " = gloss." + DictionaryDbSchema.Jmdict.GlossTable.Cols.ENTRY_ID
39 | + " LEFT JOIN " + DictionaryDbSchema.Jmdict.KanjiElementTable.NAME + " AS ke ON re."
40 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " = ke." + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.ENTRY_ID
41 | + " ";
42 |
43 | String where = "WHERE re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " IN ";
44 |
45 | String whereSubQuery = "(SELECT " + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " FROM "
46 | + DictionaryDbSchema.Jmdict.ReadingElementTable.NAME + " WHERE VALUE " + searchType + " ?) ";
47 |
48 | String groupBy = "GROUP BY re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID;
49 |
50 | return select + from + join + where + whereSubQuery + groupBy;
51 | }
52 |
53 | public static String getSearchByKanjiQuery(boolean hasWildcard) {
54 | String searchType = hasWildcard ? "LIKE" : "=";
55 |
56 | String select = "SELECT re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + ", group_concat(ke."
57 | + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.VALUE + ", '§') AS kanji_value, group_concat(re."
58 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.VALUE + ", '§') AS read_value, group_concat(gloss."
59 | + DictionaryDbSchema.Jmdict.GlossTable.Cols.VALUE + ", '§') AS gloss_value ";
60 |
61 | String from = "FROM " + DictionaryDbSchema.Jmdict.ReadingElementTable.NAME + " AS re ";
62 |
63 | String join = "JOIN " + DictionaryDbSchema.Jmdict.GlossTable.NAME + " AS gloss ON re."
64 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " = gloss." + DictionaryDbSchema.Jmdict.GlossTable.Cols.ENTRY_ID
65 | + " JOIN " + DictionaryDbSchema.Jmdict.KanjiElementTable.NAME + " AS ke ON re."
66 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " = ke." + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.ENTRY_ID
67 | + " ";
68 |
69 | String where = "WHERE ke." + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.ENTRY_ID + " IN ";
70 |
71 | String whereSubQuery = "(SELECT " + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.ENTRY_ID + " FROM "
72 | + DictionaryDbSchema.Jmdict.KanjiElementTable.NAME + " WHERE VALUE " + searchType + " ?) ";
73 |
74 | String groupBy = "GROUP BY re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID;
75 |
76 | return select + from + join + where + whereSubQuery + groupBy;
77 | }
78 |
79 | public static String getSearchByEnglishQuery(boolean hasWildcard) {
80 | String searchType = hasWildcard ? "LIKE" : "=";
81 |
82 | String select = "SELECT re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + ", group_concat(ke."
83 | + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.VALUE + ", '§') AS kanji_value, group_concat(re."
84 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.VALUE + ", '§') AS read_value, group_concat(gloss."
85 | + DictionaryDbSchema.Jmdict.GlossTable.Cols.VALUE + ", '§') AS gloss_value ";
86 |
87 | String from = "FROM " + DictionaryDbSchema.Jmdict.ReadingElementTable.NAME + " AS re ";
88 |
89 | String join = "LEFT JOIN " + DictionaryDbSchema.Jmdict.KanjiElementTable.NAME + " AS ke ON re."
90 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " = ke."
91 | + DictionaryDbSchema.Jmdict.KanjiElementTable.Cols.ENTRY_ID + " JOIN " + DictionaryDbSchema.Jmdict.GlossTable.NAME + " AS gloss ON re."
92 | + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID + " = gloss." + DictionaryDbSchema.Jmdict.GlossTable.Cols.ENTRY_ID + " ";
93 |
94 | String where = "WHERE gloss." + DictionaryDbSchema.Jmdict.GlossTable.Cols.ENTRY_ID + " IN ";
95 |
96 | String whereSubQuery = "(SELECT " + DictionaryDbSchema.Jmdict.GlossTable.Cols.ENTRY_ID + " FROM "
97 | + DictionaryDbSchema.Jmdict.GlossTable.NAME + " WHERE VALUE " + searchType + " ?) ";
98 |
99 | String groupBy = "GROUP BY re." + DictionaryDbSchema.Jmdict.ReadingElementTable.Cols.ENTRY_ID;
100 |
101 | return select + from + join + where + whereSubQuery + groupBy;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/EntryUtils.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 | import java.util.HashSet;
7 | import java.util.Map;
8 | import java.util.Set;
9 |
10 | public class EntryUtils {
11 | // These are priorities defined as "common" by JMdict
12 | // "Entries with news1, ichi1, spec1/2 and gai1 values are marked with a "(P)" in the EDICT and EDICT2 files."
13 | private static final String[] SET_VALUES = new String[] { "news1", "ichi1", "spec1", "spec2", "gai1" };
14 | private static final Set COMMON_PRIORITIES = new HashSet<>(Arrays.asList(SET_VALUES));
15 |
16 | // Return true if an entry is 'common'
17 | public static boolean isCommonEntry(Set priorities) {
18 | return !Collections.disjoint(priorities, COMMON_PRIORITIES);
19 | }
20 |
21 | // TODO: Extract to string resources
22 | public static Map getDetailedPriorityInformation() {
23 | Map detailedPriorities = new HashMap<>();
24 | detailedPriorities.put("news1", "Appears in the first 12,000 words of the \"wordfreq\" file compiled by Alexandre Girardi from the Mainichi Shimbun.");
25 | detailedPriorities.put("news2", "Appears in the second 12,000 words of the \"wordfreq\" file compiled by Alexandre Girardi from the Mainichi Shimbun.");
26 | detailedPriorities.put("ichi1", "Appears in \"Ichimango goi bunruishuu\", Senmon Kyouiku Publishing, Tokyo, 1998.");
27 | detailedPriorities.put("ichi2", "Appears in \"Ichimango goi bunruishuu\", Senmon Kyouiku Publishing, Tokyo, 1998. Demoted from ichi1 due to low frequency in newspapers and online.");
28 | detailedPriorities.put("spec1", "A small number of words use this marker when they are detected as being common, but are not included in other lists.");
29 | detailedPriorities.put("spec2", "A small number of words use this marker when they are detected as being common, but are not included in other lists.");
30 | detailedPriorities.put("gai1", "A common loanword. Based on the \"wordfreq\" file compiled by Alexandre Girardi from the Mainichi Shimbun.");
31 | detailedPriorities.put("gai2", "A common loanword. Based on the \"wordfreq\" file compiled by Alexandre Girardi from the Mainichi Shimbun.");
32 | detailedPriorities.put("nf", "An indicator of frequency-of-use ranking, with the number being the set of 500 words where the entry can be found.");
33 | detailedPriorities.put("common", "Entries with news1, ichi1, spec1/2, or gai1 values are marked as common.");
34 | return detailedPriorities;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/GlobalConstants.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 | public class GlobalConstants {
4 | public static class SearchTypes {
5 | public static final int KANA_TYPE = 0;
6 | public static final int ROMAJI_TYPE = 1;
7 | public static final int KANJI_TYPE = 2;
8 | public static final int ENGLISH_TYPE = 3;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/SearchUtils.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 | import java.util.regex.Matcher;
4 | import java.util.regex.Pattern;
5 |
6 | import static com.ikue.japanesedictionary.utils.GlobalConstants.SearchTypes.*;
7 |
8 | public class SearchUtils {
9 |
10 | // Get what type the search term is. Can either be Kanji, Kana, Romaji, or English.
11 | public static int getSearchType(String searchTerm) {
12 | boolean containsKana = false;
13 |
14 | // Check every character of the string
15 | for (char c : searchTerm.toCharArray()) {
16 | // If the current character is a Kanji (or Chinese/Korean character)
17 | if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
18 | // Once we find a single Kanji character, we know to search the Kanji Element
19 | return KANJI_TYPE;
20 | // If the current character is a Hiragana or Katakana character
21 | } else if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HIRAGANA
22 | || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.KATAKANA) {
23 | // We can't immediately return a KANA_TYPE yet because there could be Kanji
24 | // characters further in the string
25 | containsKana = true;
26 | }
27 | }
28 | // If we have parsed the whole string, have not encountered a Kanji character, and there
29 | // is at least one Kana character in the string then we know to search the Reading Element
30 | if (containsKana) {
31 | return KANA_TYPE;
32 | } else {
33 | // False because we don't care about obsolete Kana
34 | WanaKanaJava wk = new WanaKanaJava(false);
35 | String kanaForm = wk.toKana(searchTerm);
36 |
37 | for (char c : kanaForm.toCharArray()) {
38 | // If a character couldn't be converted to Hiragana or Katakana, then we can assume
39 | // the user meant to search in English (or mistyped when using Romaji)
40 | if (Character.UnicodeBlock.of(c) != Character.UnicodeBlock.HIRAGANA
41 | && Character.UnicodeBlock.of(c) != Character.UnicodeBlock.KATAKANA) {
42 | return ENGLISH_TYPE;
43 | }
44 | }
45 | // If every character successfully converted to Romaji, then we assume the user
46 | // meant to search in Romaji. (Naive!)
47 | // TODO: Additional checks before assuming Romaji
48 | return ROMAJI_TYPE;
49 | }
50 | }
51 |
52 | // Check if a given string is all uppercase
53 | public static boolean isStringAllUppercase(String string) {
54 | for (char c : string.toCharArray()) {
55 | if(!Character.isUpperCase(c) && !Character.isWhitespace(c)) {
56 | return false;
57 | }
58 | }
59 | return true;
60 | }
61 |
62 | // Check a given string for any pseudo wildcard characters
63 | public static boolean containsWildcards(String string) {
64 | Pattern pattern = Pattern.compile("\\*+|\\?+");
65 | Matcher matcher = pattern.matcher(string);
66 | // Check if any wildcards are being used
67 | return matcher.find();
68 | }
69 |
70 | // Remove any pseudo wildcard characters from a given string
71 | public static String removeWildcards(String string) {
72 | return string.replaceAll("\\*+|\\?+", "");
73 | }
74 |
75 | // Replace the pseudo wildcard characters we use with the real ones to query
76 | // with SQLite
77 | public static String getTrueWildcardString(String string) {
78 | String firstRound = string.replaceAll("\\*", "%");
79 | return firstRound.replaceAll("\\?", "_");
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ikue/japanesedictionary/utils/TipsUtils.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary.utils;
2 |
3 | import com.ikue.japanesedictionary.models.Tip;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.Random;
8 |
9 | // TODO: Extract to string resources
10 | public class TipsUtils {
11 | private static final List TIPS = initTips();
12 |
13 | private static List initTips() {
14 | List tips = new ArrayList<>();
15 | tips.add(new Tip("Wildcards #1", "You can use the two wildcards * and ? to search for unknown characters. * searches for zero or more characters, and ? searches for exactly one character."));
16 | tips.add(new Tip("Wildcards #2", "If you want to search for a word containing \"物\", you can by searching \"*物*\"."));
17 | tips.add(new Tip("Wildcards #3", "If you want to search for a word that starts with \"日\" you can by searching \"日*\"."));
18 | tips.add(new Tip("Wildcards #4", "If you want to search for a word ending with \"本\" you can by searching \"*本\"."));
19 | tips.add(new Tip("Wildcards #5", "If you want to search for a two character word that starts with \"食\", you can by searching \"食?\"."));
20 | tips.add(new Tip("Wildcards #6", "If you want to search for a three character word that contains \"大\" in the middle, you can by searching \"?大?\"."));
21 | tips.add(new Tip("Search #1", "You can search in Japanese, English, or even Romaji."));
22 | tips.add(new Tip("Search #2", "If there is some ambiguity as to whether you intended to search in English or Romaji, a popup will appear at the bottom of the results screen letting you switch results."));
23 | tips.add(new Tip("Search #3", "If you type in upper-case with a Romaji search, the upper-case characters will be converted to Katakana. For example, \"MOTEru\" will be converted to \"モテる\"."));
24 | tips.add(new Tip("Search #4", "Searches in English are case insensitive."));
25 | tips.add(new Tip("Search #5", "Ikue automatically converts latin characters into Kana when possible. For example, \"tabemono\" will be converted to \"たべもの\"."));
26 | tips.add(new Tip("Settings #1", "You can change the default search in the settings. The default search is for an exact match."));
27 | tips.add(new Tip("Settings #2", "You can limit the number of search results in the settings. Doing so will increase the speed of wildcard searches."));
28 | tips.add(new Tip("Settings #3", "You can limit the number of entries to show in your history page in the settings. This won't have any impact on performance."));
29 | tips.add(new Tip("Settings #4", "You can limit the number of entries to show in your favourites page in the settings. This won't have any impact on performance."));
30 | tips.add(new Tip("Settings #5", "You can change the default page you see when you launch the application in the settings."));
31 | tips.add(new Tip("History #1", "When you view an entry's detail page, that entry is automatically added to your history page."));
32 | tips.add(new Tip("Favourites #1", "You can favourite/un-favourite an entry by clicking on the star icon in an entry's detail page."));
33 | return tips;
34 | }
35 |
36 | // Get a random tip
37 | public static Tip getRandomTip() {
38 | return TIPS.get(new Random().nextInt(TIPS.size()));
39 | }
40 |
41 | public static List getTips() {
42 | return TIPS;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_history_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info_outline_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_black.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_border_black.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_border_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_detail.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
23 |
24 |
27 |
28 |
33 |
34 |
41 |
42 |
50 |
51 |
52 |
53 |
54 |
59 |
60 |
69 |
70 |
71 |
72 |
80 |
81 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_search.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_tips.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_detail.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
46 |
47 |
55 |
56 |
62 |
63 |
66 |
67 |
76 |
77 |
83 |
84 |
87 |
88 |
97 |
98 |
103 |
104 |
105 |
106 |
117 |
118 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
24 |
25 |
28 |
29 |
34 |
35 |
45 |
46 |
55 |
56 |
66 |
67 |
80 |
81 |
82 |
83 |
91 |
92 |
103 |
104 |
105 |
106 |
107 |
115 |
116 |
120 |
121 |
131 |
132 |
143 |
144 |
154 |
155 |
162 |
163 |
164 |
165 |
166 |
174 |
175 |
179 |
180 |
191 |
192 |
202 |
203 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
28 |
29 |
30 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tip.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
28 |
29 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/navheader.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/recycler_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
31 |
32 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/sense_element_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
24 |
25 |
35 |
36 |
43 |
44 |
45 |
53 |
54 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_search.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | History
5 | Home
6 | Favourites
7 |
8 |
9 | 0
10 | 1
11 | 2
12 |
13 |
14 |
15 | 10
16 | 25
17 | 50
18 | 100
19 | 200
20 | No limit
21 |
22 |
23 | 10
24 | 25
25 | 50
26 | 100
27 | 200
28 | 0
29 |
30 |
31 |
32 | Exact match
33 | All results ending with search term
34 | All results starting with search term
35 | All results containing search term
36 |
37 |
38 | 0
39 | 1
40 | 2
41 | 3
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 | #3F51B5
5 | #3949AB
6 | #00B0FF
7 | @color/white
8 | #FFFFFF
9 | #424242
10 | #9E9E9E
11 | #EFEFEF
12 | #757575
13 | #FAFAFA
14 | #F5F5F5
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 192dp
5 | 20sp
6 | 48dp
7 | 48dp
8 | 16sp
9 | 17sp
10 | 14sp
11 | 15sp
12 | 16dp
13 | 222dp
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Settings
3 | Search
4 | Results
5 |
6 | Meanings
7 | Other forms
8 | Frequency information
9 | %1$d.
10 | English, Japanese or Romaji
11 | Searching in English
12 | Show Romaji
13 | Searching in Romaji
14 | Show English
15 | Add to Favourites
16 | Error adding entry to Favourites
17 | Added entry to Favourites
18 | Error removing entry from Favourites
19 | Removed entry from Favourites
20 | Error adding entry to History
21 | History
22 | Home
23 | Favourites
24 | Settings
25 | About
26 | Ikue is an open source Japanese dictionary application.
27 | Connect with us
28 | Open source licenses
29 | Terms of use
30 | Special thanks
31 | A huge thank you to the EDRDG group, and especially Jim Breen. Without the data provided by them this application would not be possible.
32 | Close
33 |
34 |
35 | Startup page
36 | 1
37 | Case sensitive Romaji search
38 | Convert upper-case characters to Katakana
39 | Search
40 | Limit number of results
41 | 0
42 | General
43 | History
44 | Favourites
45 | Limit number of results
46 | Limit number of results
47 | Default search type
48 | 0
49 |
50 | Share with:
51 | Word of the Day
52 | Learn more
53 | Contact us
54 | Do you have a suggestion for a feature? Found a bug? Feel like something could be done better or improved? Let us know!
55 | Send Feedback
56 | Tips
57 | See all
58 | Share button
59 |
60 | common
61 | Send email
62 |
63 | Tips
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
15 |
20 |
27 |
28 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
11 |
19 |
27 |
28 |
29 |
32 |
40 |
41 |
42 |
45 |
53 |
54 |
55 |
58 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ikue/japanesedictionary/DateTimeUtilsUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary;
2 |
3 | import com.ikue.japanesedictionary.utils.DateTimeUtils;
4 |
5 | import org.junit.Test;
6 |
7 | import java.text.SimpleDateFormat;
8 | import java.util.Locale;
9 |
10 | import static org.junit.Assert.assertTrue;
11 |
12 | /**
13 | * Example local unit test, which will execute on the development machine (host).
14 | *
15 | * @see Testing documentation
16 | */
17 | public class DateTimeUtilsUnitTest {
18 | @Test
19 | public void testGetDifferenceInDays() throws Exception {
20 | SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.UK);
21 |
22 | assertTrue((DateTimeUtils.getDifferenceInDays(sdf.parse("21/12/2012"), sdf.parse("21/12/2012")) == 0));
23 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("21/12/2012"), sdf.parse("21/12/2013")) > 1);
24 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("21/12/2012"), sdf.parse("21/12/2011")) < 0);
25 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("21/12/2015"), sdf.parse("21/06/2015")) < 0);
26 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("21/01/2015"), sdf.parse("21/08/2015")) > 1);
27 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("21/01/2015"), sdf.parse("31/01/2015")) > 1);
28 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("31/01/2015"), sdf.parse("30/01/2015")) < 0);
29 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("01/01/2017"), sdf.parse("31/02/2016")) < 0);
30 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("01/01/2017"), sdf.parse("01/02/2017")) > 1);
31 | assertTrue(DateTimeUtils.getDifferenceInDays(sdf.parse("01/01/2017"), sdf.parse("01/01/2018")) > 1);
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/ikue/japanesedictionary/DbUtilsUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary;
2 |
3 | import com.ikue.japanesedictionary.utils.DbUtils;
4 |
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | import java.util.Arrays;
9 | import java.util.List;
10 |
11 | import static junit.framework.Assert.assertEquals;
12 | import static junit.framework.Assert.assertFalse;
13 | import static junit.framework.Assert.assertTrue;
14 |
15 |
16 | public class DbUtilsUnitTest {
17 | @Test
18 | public void testFormatString_nullParameter() {
19 | assertTrue(DbUtils.formatString(null).isEmpty());
20 | assertTrue(DbUtils.formatString("").isEmpty());
21 | }
22 |
23 | @Test
24 | public void testFormatString_withSeparator() {
25 | List strings = DbUtils.formatString("this§is§a§test");
26 | assertEquals(4, strings.size());
27 |
28 | assertTrue(strings.contains("this"));
29 | assertTrue(strings.contains("is"));
30 | assertTrue(strings.contains("a"));
31 | assertTrue(strings.contains("test"));
32 |
33 | String[] expected = new String[] {"this", "is", "a", "test"};
34 | Assert.assertTrue(strings.containsAll(Arrays.asList(expected)));
35 | }
36 |
37 | @Test
38 | public void testGetSearchByKanaQuery_withWildcard() {
39 | String query = DbUtils.getSearchByEnglishQuery(true);
40 | assertTrue(query.contains("LIKE"));
41 | }
42 |
43 | @Test
44 | public void testGetSearchByKanaQuery_withoutWildcard() {
45 | String query = DbUtils.getSearchByEnglishQuery(false);
46 | assertFalse(query.contains("LIKE"));
47 | }
48 |
49 | @Test
50 | public void testGetSearchByKanjiQuery_withWildcard() {
51 | String query = DbUtils.getSearchByKanjiQuery(true);
52 | assertTrue(query.contains("LIKE"));
53 | }
54 |
55 | @Test
56 | public void testGetSearchByKanjiQuery_withoutWildcard() {
57 | String query = DbUtils.getSearchByKanjiQuery(false);
58 | assertFalse(query.contains("LIKE"));
59 | }
60 |
61 | @Test
62 | public void testGetSearchByEnglishQuery_withWildcard() {
63 | String query = DbUtils.getSearchByEnglishQuery(true);
64 | assertTrue(query.contains("LIKE"));
65 | }
66 |
67 | @Test
68 | public void testGetSearchByEnglishQuery_withoutWildcard() {
69 | String query = DbUtils.getSearchByEnglishQuery(false);
70 | assertFalse(query.contains("LIKE"));
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ikue/japanesedictionary/EntryUtilsUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary;
2 |
3 | import com.ikue.japanesedictionary.utils.EntryUtils;
4 |
5 | import org.junit.Test;
6 |
7 | import java.util.Arrays;
8 | import java.util.HashSet;
9 | import java.util.Map;
10 |
11 | import static junit.framework.Assert.assertEquals;
12 | import static junit.framework.Assert.assertFalse;
13 | import static junit.framework.Assert.assertTrue;
14 |
15 | public class EntryUtilsUnitTest {
16 | @Test
17 | public void testIsCommonEntry() {
18 | assertTrue(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("news1", "news2", "ichi1"))));
19 | assertTrue(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("spec1", "news2", "nf05"))));
20 | assertTrue(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("random", "ichi1", "nf99"))));
21 | assertTrue(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("random", "news2", "spec2"))));
22 | assertTrue(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("news2", "gai1", "spec2"))));
23 |
24 | assertFalse(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("news2", "gai2", "nf01"))));
25 | assertFalse(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("ichi2", "news2", "gai2"))));
26 | assertFalse(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("ichi2", "news2"))));
27 | assertFalse(EntryUtils.isCommonEntry(new HashSet<>(Arrays.asList("nf123", "news2", "random"))));
28 | }
29 |
30 | @Test
31 | public void testGetDetailedPriorityInformation() {
32 | Map information = EntryUtils.getDetailedPriorityInformation();
33 |
34 | assertEquals(10, information.size());
35 |
36 | assertTrue(information.containsKey("news1"));
37 | assertTrue(information.containsKey("news2"));
38 | assertTrue(information.containsKey("ichi1"));
39 | assertTrue(information.containsKey("ichi2"));
40 | assertTrue(information.containsKey("spec1"));
41 | assertTrue(information.containsKey("spec2"));
42 | assertTrue(information.containsKey("gai1"));
43 | assertTrue(information.containsKey("gai2"));
44 | assertTrue(information.containsKey("nf"));
45 | assertTrue(information.containsKey("common"));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ikue/japanesedictionary/TipsUtilsUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ikue.japanesedictionary;
2 |
3 | import com.ikue.japanesedictionary.models.Tip;
4 | import com.ikue.japanesedictionary.utils.TipsUtils;
5 |
6 | import org.junit.Test;
7 |
8 | import static junit.framework.Assert.assertFalse;
9 | import static junit.framework.Assert.assertNotNull;
10 |
11 | public class TipsUtilsUnitTest {
12 | @Test
13 | public void testGetRandomTip() {
14 | Tip tip = TipsUtils.getRandomTip();
15 |
16 | assertNotNull(tip.getTitle());
17 | assertNotNull(tip.getBody());
18 |
19 | assertFalse(tip.getTitle().isEmpty());
20 | assertFalse(tip.getBody().isEmpty());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | maven { url "https://jitpack.io" }
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:8.4.1'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | mavenCentral()
16 | maven { url "https://jitpack.io" }
17 | }
18 | }
19 |
20 | tasks.register('clean', Delete) {
21 | delete rootProject.layout.buildDirectory
22 | }
23 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | android.useAndroidX=true
20 | org.gradle.configuration-cache=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luke-c/Ikue/74e6534f77eb8256d57f32d6e25746634e18c836/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 10 16:39:37 BST 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------