├── .gitignore ├── .idea ├── appInsightsSettings.xml ├── codeStyleSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── PRIVACY ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ikue │ │ └── japanesedictionary │ │ └── DatabaseTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ikue │ │ │ └── japanesedictionary │ │ │ ├── activities │ │ │ ├── AboutActivity.java │ │ │ ├── EntryDetailActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── SearchResultActivity.java │ │ │ ├── SettingsActivity.java │ │ │ └── TipsActivity.java │ │ │ ├── adapters │ │ │ ├── DetailViewAdapter.java │ │ │ ├── SearchResultAdapter.java │ │ │ ├── SenseElementViewHolder.java │ │ │ └── TipsAdapter.java │ │ │ ├── database │ │ │ ├── AddToHistoryTask.java │ │ │ ├── DictionaryDbHelper.java │ │ │ ├── DictionaryDbSchema.java │ │ │ ├── GetEntryDetailTask.java │ │ │ ├── GetFavouritesTask.java │ │ │ ├── GetHistoryTask.java │ │ │ ├── GetRandomEntryTask.java │ │ │ ├── SearchDatabaseTask.java │ │ │ └── ToggleFavouriteTask.java │ │ │ ├── fragments │ │ │ ├── EntryDetailFragment.java │ │ │ ├── FavouritesFragment.java │ │ │ ├── HistoryFragment.java │ │ │ ├── HomeFragment.java │ │ │ ├── SearchResultFragment.java │ │ │ └── SettingsFragment.java │ │ │ ├── interfaces │ │ │ ├── AddToHistoryAsyncCallbacks.java │ │ │ ├── DetailAsyncCallbacks.java │ │ │ ├── GetFavouritesAsyncCallbacks.java │ │ │ ├── GetHistoryAsyncCallbacks.java │ │ │ ├── SearchAsyncCallbacks.java │ │ │ └── ToggleFavouriteAsyncCallbacks.java │ │ │ ├── models │ │ │ ├── DictionaryEntry.java │ │ │ ├── DictionaryListEntry.java │ │ │ ├── KanjiElement.java │ │ │ ├── Priority.java │ │ │ ├── ReadingElement.java │ │ │ ├── SenseElement.java │ │ │ └── Tip.java │ │ │ └── utils │ │ │ ├── CreativeCommonsShareAlikeLicense.java │ │ │ ├── DateTimeUtils.java │ │ │ ├── DbUtils.java │ │ │ ├── EntryUtils.java │ │ │ ├── GlobalConstants.java │ │ │ ├── SearchUtils.java │ │ │ ├── TipsUtils.java │ │ │ └── WanaKanaJava.java │ └── res │ │ ├── drawable │ │ ├── ic_history_white.xml │ │ ├── ic_home_white.xml │ │ ├── ic_info_outline_white.xml │ │ ├── ic_menu_white.xml │ │ ├── ic_search_white.xml │ │ ├── ic_settings_white.xml │ │ ├── ic_share_black_24dp.xml │ │ ├── ic_star_black.xml │ │ ├── ic_star_border_black.xml │ │ ├── ic_star_border_white.xml │ │ └── ic_star_white.xml │ │ ├── layout │ │ ├── activity_detail.xml │ │ ├── activity_main.xml │ │ ├── activity_search.xml │ │ ├── activity_settings.xml │ │ ├── activity_tips.xml │ │ ├── fragment_detail.xml │ │ ├── fragment_home.xml │ │ ├── fragment_search.xml │ │ ├── item_tip.xml │ │ ├── navheader.xml │ │ ├── recycler_view.xml │ │ ├── search_list_item.xml │ │ └── sense_element_item.xml │ │ ├── menu │ │ ├── menu_main.xml │ │ ├── menu_navigation.xml │ │ └── menu_search.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── raw │ │ └── cc_sa_40_full.txt │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── preferences.xml │ │ └── searchable.xml │ └── test │ └── java │ └── com │ └── ikue │ └── japanesedictionary │ ├── DateTimeUtilsUnitTest.java │ ├── DbUtilsUnitTest.java │ ├── EntryUtilsUnitTest.java │ ├── SearchUtilsUnitTest.java │ └── TipsUtilsUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/an,android 3 | 4 | #!! ERROR: an is undefined. Use list command to see defined gitignore types !!# 5 | 6 | ### Android ### 7 | # Built application files 8 | *.apk 9 | *.ap_ 10 | 11 | # Files for the ART/Dalvik VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # Generated files 18 | bin/ 19 | gen/ 20 | out/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Android Studio Navigation editor temp files 36 | .navigation/ 37 | 38 | # Android Studio captures folder 39 | captures/ 40 | 41 | # Intellij 42 | *.iml 43 | .idea/ 44 | 45 | # Keystore files 46 | *.jks 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | ### Android Patch ### 55 | gen-external-apklibs 56 | 57 | # End of https://www.gitignore.io/api/an,android 58 | .idea/dictionaries/ 59 | app/libs/ 60 | app/src/main/assets/ 61 | keystore.properties 62 | -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 227 | 229 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 21 | 204 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 22 | 23 | 45 | 64 | 65 | 66 | 67 | 68 | 69 | 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 | 18 | 19 | ## Download 20 | 21 | Get it on Google Play 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 |