Add kanji result support
app/build.gradle
| 20 | 20 | ||
| 21 | 21 | dependencies { | |
| 22 | 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) | |
| 23 | - | implementation 'androidx.appcompat:appcompat:1.3.0' | |
| 23 | + | implementation 'androidx.appcompat:appcompat:1.3.1' | |
| 24 | 24 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' | |
| 25 | 25 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' | |
| 26 | 26 | implementation 'androidx.preference:preference:1.1.1' | |
| 27 | 27 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" | |
| 28 | + | implementation "androidx.viewpager2:viewpager2:1.0.0" | |
| 28 | 29 | implementation 'com.andree-surya:moji4j:1.0.0' | |
| 29 | 30 | implementation 'com.google.android:flexbox:2.0.1' | |
| 30 | 31 | implementation 'com.google.android.material:material:1.4.0' |
app/src/main/java/eu/lepiller/nani/DictionaryActivity.java
| 14 | 14 | import android.widget.AdapterView; | |
| 15 | 15 | import android.widget.ArrayAdapter; | |
| 16 | 16 | import android.widget.ListView; | |
| 17 | - | import android.widget.ScrollView; | |
| 18 | 17 | import android.widget.Spinner; | |
| 19 | 18 | ||
| 20 | 19 | import com.google.android.material.snackbar.Snackbar; |
app/src/main/java/eu/lepiller/nani/MainActivity.java
| 5 | 5 | import android.os.AsyncTask; | |
| 6 | 6 | import android.os.Bundle; | |
| 7 | 7 | import com.google.android.material.snackbar.Snackbar; | |
| 8 | + | ||
| 9 | + | import androidx.annotation.NonNull; | |
| 8 | 10 | import androidx.appcompat.app.AppCompatActivity; | |
| 9 | 11 | import androidx.appcompat.widget.Toolbar; | |
| 12 | + | import androidx.fragment.app.FragmentManager; | |
| 10 | 13 | import androidx.preference.PreferenceManager; | |
| 14 | + | import androidx.viewpager2.widget.ViewPager2; | |
| 11 | 15 | ||
| 12 | - | import android.text.Html; | |
| 13 | 16 | import android.util.Log; | |
| 14 | 17 | import android.view.View; | |
| 15 | 18 | import android.view.Menu; | |
… | |||
| 19 | 22 | import android.widget.SearchView; | |
| 20 | 23 | import android.widget.TextView; | |
| 21 | 24 | ||
| 25 | + | import com.google.android.material.tabs.TabLayout; | |
| 26 | + | import com.google.android.material.tabs.TabLayoutMediator; | |
| 22 | 27 | import com.moji4j.MojiConverter; | |
| 23 | 28 | import com.moji4j.MojiDetector; | |
| 24 | 29 | ||
| 25 | 30 | import java.util.ArrayList; | |
| 31 | + | import java.util.List; | |
| 26 | 32 | ||
| 27 | 33 | import eu.lepiller.nani.dictionary.DictionaryException; | |
| 28 | 34 | import eu.lepiller.nani.dictionary.DictionaryFactory; | |
| 29 | 35 | import eu.lepiller.nani.dictionary.IncompatibleFormatException; | |
| 30 | 36 | import eu.lepiller.nani.dictionary.NoDictionaryException; | |
| 31 | 37 | import eu.lepiller.nani.dictionary.NoResultDictionaryException; | |
| 38 | + | import eu.lepiller.nani.result.KanjiResult; | |
| 32 | 39 | import eu.lepiller.nani.result.Result; | |
| 33 | - | import se.fekete.furiganatextview.furiganaview.FuriganaTextView; | |
| 40 | + | ||
| 41 | + | import static java.lang.Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS; | |
| 34 | 42 | ||
| 35 | 43 | public class MainActivity extends AppCompatActivity implements OnTaskCompleted<SearchResult>, SharedPreferences.OnSharedPreferenceChangeListener { | |
| 36 | - | private LinearLayout result_view, result_layout; | |
| 44 | + | private LinearLayout result_layout; | |
| 37 | 45 | private TextView feedback_text; | |
| 38 | 46 | private SearchView search_form; | |
| 39 | - | private static ArrayList<Result> savedResults; | |
| 40 | 47 | private RadicalSelectorView radical_selector; | |
| 41 | 48 | private String readingStyle = "furigana"; | |
| 49 | + | ViewPager2 viewPager2; | |
| 50 | + | ResultPagerAdapter pagerAdapter; | |
| 42 | 51 | ||
| 43 | - | private static final String TAG = "MAIN"; | |
| 52 | + | static final String TAG = "MAIN"; | |
| 44 | 53 | ||
| 45 | 54 | @Override | |
| 46 | 55 | protected void onCreate(Bundle savedInstanceState) { | |
… | |||
| 49 | 58 | Toolbar toolbar = findViewById(R.id.toolbar); | |
| 50 | 59 | setSupportActionBar(toolbar); | |
| 51 | 60 | ||
| 52 | - | result_view = findViewById(R.id.results_view); | |
| 53 | 61 | result_layout = findViewById(R.id.result_layout); | |
| 54 | 62 | feedback_text = findViewById(R.id.feedback); | |
| 55 | 63 | search_form = findViewById(R.id.search_form); | |
| 56 | 64 | radical_selector = findViewById(R.id.radical_selector); | |
| 57 | - | ||
| 58 | - | Button radical_button = findViewById(R.id.radical_button); | |
| 65 | + | TabLayout tabLayout = findViewById(R.id.tab_layout); | |
| 66 | + | viewPager2 = findViewById(R.id.pager); | |
| 59 | 67 | ||
| 60 | 68 | PreferenceManager.setDefaultValues(this, R.xml.preferences, false); | |
| 61 | 69 | ||
… | |||
| 65 | 73 | int radSizePref = getRadSizePref(sharedPref); | |
| 66 | 74 | readingStyle = getReadingSizePref(sharedPref); | |
| 67 | 75 | ||
| 76 | + | Button radical_button = findViewById(R.id.radical_button); | |
| 77 | + | ||
| 78 | + | pagerAdapter = new ResultPagerAdapter(this.getApplicationContext()); | |
| 79 | + | pagerAdapter.setReadingStyle(readingStyle); | |
| 80 | + | pagerAdapter.notifyDataSetChanged(); | |
| 81 | + | viewPager2.setAdapter(pagerAdapter); | |
| 82 | + | ||
| 83 | + | tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_results)); | |
| 84 | + | tabLayout.addTab(tabLayout.newTab().setText(R.string.tab_kanji)); | |
| 85 | + | ||
| 86 | + | new TabLayoutMediator(tabLayout, viewPager2, | |
| 87 | + | new TabLayoutMediator.TabConfigurationStrategy() { | |
| 88 | + | @Override | |
| 89 | + | public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) { | |
| 90 | + | tab.setText(position == 0? R.string.tab_results: R.string.tab_kanji); | |
| 91 | + | } | |
| 92 | + | } | |
| 93 | + | ).attach(); | |
| 94 | + | ||
| 68 | 95 | try { | |
| 69 | 96 | radical_selector.setRadSize(radSizePref); | |
| 70 | 97 | radical_selector.setDictionary(DictionaryFactory.getRadicalDictionary(getApplicationContext())); | |
… | |||
| 84 | 111 | return false; | |
| 85 | 112 | } | |
| 86 | 113 | ||
| 87 | - | result_view.removeAllViews(); | |
| 114 | + | pagerAdapter.setKanjiResults(new ArrayList<KanjiResult>()); | |
| 115 | + | pagerAdapter.setResults(new ArrayList<Result>()); | |
| 116 | + | pagerAdapter.notifyDataSetChanged(); | |
| 117 | + | ||
| 88 | 118 | search_form.setEnabled(false); | |
| 89 | 119 | feedback_text.setText(R.string.feedback_progress); | |
| 90 | 120 | ||
… | |||
| 135 | 165 | startActivity(intent); | |
| 136 | 166 | } | |
| 137 | 167 | }); | |
| 138 | - | ||
| 139 | - | if(savedResults != null) { | |
| 140 | - | showResults(savedResults); | |
| 141 | - | } | |
| 142 | 168 | } | |
| 143 | 169 | ||
| 144 | 170 | @Override | |
… | |||
| 148 | 174 | radical_selector.setRadSize(getRadSizePref(sharedPreferences)); | |
| 149 | 175 | } else if(key.compareTo(SettingsActivity.KEY_PREF_READING_STYLE) == 0) { | |
| 150 | 176 | readingStyle = getReadingSizePref(sharedPreferences); | |
| 177 | + | pagerAdapter.setReadingStyle(readingStyle); | |
| 178 | + | pagerAdapter.notifyDataSetChanged(); | |
| 151 | 179 | } | |
| 152 | 180 | } | |
| 153 | 181 | ||
… | |||
| 177 | 205 | ArrayList<String> tried = new ArrayList<>(); | |
| 178 | 206 | tried.add(text); | |
| 179 | 207 | ||
| 208 | + | List<KanjiResult> kanjiResults = new ArrayList<>(); | |
| 209 | + | for(String c: text.split("")) { | |
| 210 | + | if(Character.UnicodeBlock.of(c.codePointAt(0)) == CJK_UNIFIED_IDEOGRAPHS) { | |
| 211 | + | KanjiResult r; | |
| 212 | + | try { | |
| 213 | + | r = DictionaryFactory.searchKanji(c); | |
| 214 | + | } catch(DictionaryException e) { | |
| 215 | + | return new SearchResult(e); | |
| 216 | + | } | |
| 217 | + | Log.d(TAG, "kanji " + c + ", result: " + r); | |
| 218 | + | if(r != null) | |
| 219 | + | kanjiResults.add(r); | |
| 220 | + | } | |
| 221 | + | } | |
| 222 | + | ||
| 180 | 223 | ArrayList<Result> searchResult; | |
| 181 | 224 | try { | |
| 182 | 225 | searchResult = DictionaryFactory.search(text); | |
… | |||
| 184 | 227 | return new SearchResult(e); | |
| 185 | 228 | } | |
| 186 | 229 | ||
| 187 | - | ||
| 188 | 230 | if(searchResult.size() == 0) { | |
| 189 | 231 | MojiConverter converter = new MojiConverter(); | |
| 190 | 232 | try { | |
… | |||
| 199 | 241 | return new SearchResult(new NoResultDictionaryException(tried)); | |
| 200 | 242 | } | |
| 201 | 243 | ||
| 202 | - | return new SearchResult(searchResult, text, true); | |
| 244 | + | return new SearchResult(searchResult, kanjiResults, text, true); | |
| 203 | 245 | } else { | |
| 204 | - | return new SearchResult(searchResult, text, false); | |
| 246 | + | return new SearchResult(searchResult, kanjiResults, text, false); | |
| 205 | 247 | } | |
| 206 | 248 | } | |
| 207 | 249 | ||
… | |||
| 243 | 285 | ||
| 244 | 286 | feedback_text.setText(""); | |
| 245 | 287 | ||
| 246 | - | ArrayList<Result> searchResult = r.getResults(); | |
| 288 | + | List<Result> searchResult = r.getResults(); | |
| 289 | + | List<KanjiResult> kanjiResults = r.getKanjiResults(); | |
| 290 | + | Log.d(TAG, "results. Kanjis: " + r.getKanjiResults().size()); | |
| 247 | 291 | ||
| 248 | 292 | MojiDetector detector = new MojiDetector(); | |
| 249 | 293 | String text = r.getText(); | |
… | |||
| 260 | 304 | } | |
| 261 | 305 | }); | |
| 262 | 306 | } | |
| 263 | - | savedResults = searchResult; | |
| 264 | - | if(searchResult != null) | |
| 265 | - | showResults(searchResult); | |
| 266 | - | } | |
| 267 | - | ||
| 268 | - | void showResults(ArrayList<Result> searchResult) { | |
| 269 | - | if(searchResult.size() == 0) { | |
| 270 | - | feedback_text.setText(R.string.feedback_no_result); | |
| 271 | - | return; | |
| 272 | - | } | |
| 273 | - | ||
| 274 | - | int num = 0; | |
| 275 | - | for(Result result: searchResult) { | |
| 276 | - | num++; | |
| 277 | - | if (num > 10) | |
| 278 | - | break; | |
| 279 | - | View child_result = getLayoutInflater().inflate(R.layout.layout_result, result_view, false); | |
| 280 | - | ||
| 281 | - | FuriganaTextView kanji_view = child_result.findViewById(R.id.kanji_view); | |
| 282 | - | TextView reading_view = child_result.findViewById(R.id.reading_view); | |
| 283 | - | LinearLayout senses_view = child_result.findViewById(R.id.sense_view); | |
| 284 | - | TextView additional_info = child_result.findViewById(R.id.additional_info_view); | |
| 285 | - | TextView pitch_view = child_result.findViewById(R.id.pitch_view); | |
| 286 | - | ||
| 287 | - | // Populate the data into the template view using the data object | |
| 288 | - | if(readingStyle.compareTo("furigana") == 0) { | |
| 289 | - | kanji_view.setFuriganaText(result.getKanjiFurigana()); | |
| 290 | - | } else { | |
| 291 | - | kanji_view.setFuriganaText(result.getKanji()); | |
| 292 | - | reading_view.setVisibility(View.VISIBLE); | |
| 293 | - | reading_view.setText(readingStyle.compareTo("kana") == 0? result.getReading(): result.getRomajiReading()); | |
| 294 | - | } | |
| 295 | - | ||
| 296 | - | // If pitch information is available, make it visible | |
| 297 | - | String pitch = result.getPitch(); | |
| 298 | - | if(pitch != null) { | |
| 299 | - | Log.d(TAG, "pitch: "+pitch); | |
| 300 | - | pitch_view.setVisibility(View.VISIBLE); | |
| 301 | - | pitch_view.setText(pitch); | |
| 302 | - | pitch_view.setOnClickListener(new View.OnClickListener() { | |
| 303 | - | @Override | |
| 304 | - | public void onClick(View v) { | |
| 305 | - | Intent intent = new Intent(MainActivity.this, HelpPitchActivity.class); | |
| 306 | - | startActivity(intent); | |
| 307 | - | } | |
| 308 | - | }); | |
| 309 | - | } | |
| 310 | - | ||
| 311 | - | StringBuilder additional = new StringBuilder(); | |
| 312 | - | boolean separator = false; | |
| 313 | - | for (String s : result.getAlternatives()) { | |
| 314 | - | if (separator) | |
| 315 | - | additional.append(getResources().getString(R.string.sense_separator)); | |
| 316 | - | else | |
| 317 | - | separator = true; | |
| 318 | - | additional.append(s); | |
| 319 | - | } | |
| 320 | - | if(result.getAlternatives().size() > 1) | |
| 321 | - | additional_info.setText(String.format(getResources().getString(R.string.sense_alternatives), additional.toString())); | |
| 322 | - | else | |
| 323 | - | additional_info.setVisibility(View.GONE); | |
| 324 | 307 | ||
| 325 | - | senses_view.removeAllViews(); | |
| 326 | - | ||
| 327 | - | int sense_pos = 1; | |
| 328 | - | for (Result.Sense sense : result.getSenses()) { | |
| 329 | - | View child = getLayoutInflater().inflate(R.layout.layout_sense, senses_view, false); | |
| 330 | - | TextView id_view = child.findViewById(R.id.id_view); | |
| 331 | - | TextView lang_view = child.findViewById(R.id.lang_view); | |
| 332 | - | TextView sense_view = child.findViewById(R.id.definition_view); | |
| 333 | - | ||
| 334 | - | id_view.setText(String.format(getResources().getString(R.string.sense_number), sense_pos)); | |
| 335 | - | lang_view.setText(sense.getLanguage()); | |
| 336 | - | ||
| 337 | - | StringBuilder sb = new StringBuilder(); | |
| 338 | - | sb.append("<font color=\"#909090\"><i>"); | |
| 339 | - | boolean separator1 = false; | |
| 340 | - | for(String s : sense.getInfos()) { | |
| 341 | - | if (separator1) | |
| 342 | - | sb.append(getResources().getString(R.string.sense_separator)); | |
| 343 | - | else | |
| 344 | - | separator1 = true; | |
| 345 | - | sb.append(s); | |
| 346 | - | } | |
| 347 | - | sb.append("</i></font>"); | |
| 348 | - | if(separator1) | |
| 349 | - | sb.append(" "); | |
| 350 | - | ||
| 351 | - | separator1 = false; | |
| 352 | - | for (String s : sense.getGlosses()) { | |
| 353 | - | if (separator1) | |
| 354 | - | sb.append(getResources().getString(R.string.sense_separator)); | |
| 355 | - | else | |
| 356 | - | separator1 = true; | |
| 357 | - | sb.append(s); | |
| 358 | - | } | |
| 359 | - | sense_view.setText(Html.fromHtml(sb.toString())); | |
| 360 | - | ||
| 361 | - | senses_view.addView(child); | |
| 362 | - | sense_pos++; | |
| 308 | + | if(searchResult != null || kanjiResults != null) { | |
| 309 | + | if((searchResult == null || searchResult.size() == 0) && kanjiResults.size() == 0) { | |
| 310 | + | feedback_text.setText(R.string.feedback_no_result); | |
| 363 | 311 | } | |
| 364 | - | ||
| 365 | - | result_view.addView(child_result); | |
| 366 | 312 | } | |
| 313 | + | ||
| 314 | + | pagerAdapter.setKanjiResults(kanjiResults); | |
| 315 | + | pagerAdapter.setResults(searchResult); | |
| 316 | + | pagerAdapter.notifyDataSetChanged(); | |
| 367 | 317 | } | |
| 368 | 318 | ||
| 369 | 319 | @Override | |
app/src/main/java/eu/lepiller/nani/ResultPagerAdapter.java unknown status 1
| 1 | + | package eu.lepiller.nani; | |
| 2 | + | ||
| 3 | + | import android.content.Context; | |
| 4 | + | import android.content.Intent; | |
| 5 | + | import android.os.Build; | |
| 6 | + | import android.text.Html; | |
| 7 | + | import android.util.Log; | |
| 8 | + | import android.view.Gravity; | |
| 9 | + | import android.view.LayoutInflater; | |
| 10 | + | import android.view.View; | |
| 11 | + | import android.view.ViewGroup; | |
| 12 | + | import android.widget.LinearLayout; | |
| 13 | + | import android.widget.TextView; | |
| 14 | + | ||
| 15 | + | import androidx.annotation.NonNull; | |
| 16 | + | import androidx.recyclerview.widget.RecyclerView; | |
| 17 | + | ||
| 18 | + | import org.w3c.dom.Text; | |
| 19 | + | ||
| 20 | + | import java.util.ArrayList; | |
| 21 | + | import java.util.HashMap; | |
| 22 | + | import java.util.List; | |
| 23 | + | import java.util.Map; | |
| 24 | + | ||
| 25 | + | import eu.lepiller.nani.result.KanjiResult; | |
| 26 | + | import eu.lepiller.nani.result.Result; | |
| 27 | + | import se.fekete.furiganatextview.furiganaview.FuriganaTextView; | |
| 28 | + | ||
| 29 | + | public class ResultPagerAdapter extends RecyclerView.Adapter<ResultPagerAdapter.ViewHolder> { | |
| 30 | + | static List<Result> results = new ArrayList<>(); | |
| 31 | + | static List<KanjiResult> kanjiResults = new ArrayList<>(); | |
| 32 | + | private static String readingStyle = "furigana"; | |
| 33 | + | private Context context; | |
| 34 | + | ||
| 35 | + | static final String TAG = "RESULTS_PAGER"; | |
| 36 | + | ||
| 37 | + | public static class ViewHolder extends RecyclerView.ViewHolder { | |
| 38 | + | private LinearLayout result_view; | |
| 39 | + | private Context context; | |
| 40 | + | ||
| 41 | + | ViewHolder(View view, Context context) { | |
| 42 | + | super(view); | |
| 43 | + | this.context = context; | |
| 44 | + | result_view = view.findViewById(R.id.results_view); | |
| 45 | + | ||
| 46 | + | Log.d(TAG, "createView"); | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | final String getContent(List<String> content) { | |
| 50 | + | StringBuilder sb = new StringBuilder(); | |
| 51 | + | boolean separator1 = false; | |
| 52 | + | for (String s : content) { | |
| 53 | + | if (separator1) | |
| 54 | + | sb.append(context.getResources().getString(R.string.sense_separator)); | |
| 55 | + | else | |
| 56 | + | separator1 = true; | |
| 57 | + | sb.append(s); | |
| 58 | + | } | |
| 59 | + | return sb.toString(); | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | void showKanjiResults() { | |
| 63 | + | result_view.removeAllViews(); | |
| 64 | + | if(kanjiResults == null) | |
| 65 | + | return; | |
| 66 | + | ||
| 67 | + | Log.d(TAG, "(show) kanjis: " + kanjiResults.size()); | |
| 68 | + | ||
| 69 | + | for(KanjiResult result: kanjiResults) { | |
| 70 | + | View child_result = LayoutInflater.from(context).inflate(R.layout.layout_kanji, result_view, false); | |
| 71 | + | ||
| 72 | + | TextView kanjiView = child_result.findViewById(R.id.kanji_view); | |
| 73 | + | TextView strokesView = child_result.findViewById(R.id.kanji_strokes); | |
| 74 | + | LinearLayout senses_view = child_result.findViewById(R.id.sense_view); | |
| 75 | + | TextView onView = child_result.findViewById(R.id.on_reading); | |
| 76 | + | TextView kunView = child_result.findViewById(R.id.kun_reading); | |
| 77 | + | TextView nanoriView = child_result.findViewById(R.id.nanori_reading); | |
| 78 | + | ||
| 79 | + | kanjiView.setText(result.getKanji()); | |
| 80 | + | strokesView.setText(String.format(context.getResources().getQuantityString(R.plurals.kanji_stroke, result.getStroke()), | |
| 81 | + | result.getStroke())); | |
| 82 | + | ||
| 83 | + | senses_view.removeAllViews(); | |
| 84 | + | Map<String, List<String>> meanings = new HashMap<>(); | |
| 85 | + | for(KanjiResult.Sense sense: result.getSenses()) { | |
| 86 | + | List<String> content = meanings.get(sense.getLang()); | |
| 87 | + | if(content == null) | |
| 88 | + | content = new ArrayList<>(); | |
| 89 | + | content.add(sense.getContent()); | |
| 90 | + | meanings.put(sense.getLang(), content); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | int sense_pos = 1; | |
| 94 | + | for(String lang: meanings.keySet()) { | |
| 95 | + | View child = LayoutInflater.from(context).inflate(R.layout.layout_sense, senses_view, false); | |
| 96 | + | TextView id_view = child.findViewById(R.id.id_view); | |
| 97 | + | TextView lang_view = child.findViewById(R.id.lang_view); | |
| 98 | + | TextView sense_view = child.findViewById(R.id.definition_view); | |
| 99 | + | ||
| 100 | + | id_view.setText(String.format(context.getResources().getString(R.string.sense_number), sense_pos)); | |
| 101 | + | lang_view.setText(lang); | |
| 102 | + | ||
| 103 | + | StringBuilder sb = new StringBuilder(); | |
| 104 | + | boolean separator1 = false; | |
| 105 | + | for (String s : meanings.get(lang)) { | |
| 106 | + | if (separator1) | |
| 107 | + | sb.append(context.getResources().getString(R.string.sense_separator)); | |
| 108 | + | else | |
| 109 | + | separator1 = true; | |
| 110 | + | sb.append(s); | |
| 111 | + | } | |
| 112 | + | sense_view.setText(Html.fromHtml(sb.toString())); | |
| 113 | + | ||
| 114 | + | senses_view.addView(child); | |
| 115 | + | sense_pos++; | |
| 116 | + | } | |
| 117 | + | kunView.setText(String.format(context.getResources().getString(R.string.kun_reading), | |
| 118 | + | getContent(result.getKun()))); | |
| 119 | + | onView.setText(String.format(context.getResources().getString(R.string.on_reading), | |
| 120 | + | getContent(result.getOn()))); | |
| 121 | + | nanoriView.setText(String.format(context.getResources().getString(R.string.nanori_reading), | |
| 122 | + | getContent(result.getNanori()))); | |
| 123 | + | ||
| 124 | + | result_view.addView(child_result); | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | void showResults() { | |
| 129 | + | result_view.removeAllViews(); | |
| 130 | + | if(results == null) | |
| 131 | + | return; | |
| 132 | + | ||
| 133 | + | int num = 0; | |
| 134 | + | for(Result result: results) { | |
| 135 | + | num++; | |
| 136 | + | if (num > 10) | |
| 137 | + | break; | |
| 138 | + | View child_result = LayoutInflater.from(context).inflate(R.layout.layout_result, result_view, false); | |
| 139 | + | ||
| 140 | + | FuriganaTextView kanji_view = child_result.findViewById(R.id.kanji_view); | |
| 141 | + | TextView reading_view = child_result.findViewById(R.id.reading_view); | |
| 142 | + | LinearLayout senses_view = child_result.findViewById(R.id.sense_view); | |
| 143 | + | TextView additional_info = child_result.findViewById(R.id.additional_info_view); | |
| 144 | + | TextView pitch_view = child_result.findViewById(R.id.pitch_view); | |
| 145 | + | ||
| 146 | + | // Populate the data into the template view using the data object | |
| 147 | + | if(readingStyle.compareTo("furigana") == 0) { | |
| 148 | + | kanji_view.setFuriganaText(result.getKanjiFurigana()); | |
| 149 | + | } else { | |
| 150 | + | kanji_view.setFuriganaText(result.getKanji()); | |
| 151 | + | reading_view.setVisibility(View.VISIBLE); | |
| 152 | + | reading_view.setText(readingStyle.compareTo("kana") == 0? result.getReading(): result.getRomajiReading()); | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | // If pitch information is available, make it visible | |
| 156 | + | String pitch = result.getPitch(); | |
| 157 | + | if(pitch != null) { | |
| 158 | + | pitch_view.setVisibility(View.VISIBLE); | |
| 159 | + | pitch_view.setText(pitch); | |
| 160 | + | pitch_view.setOnClickListener(new View.OnClickListener() { | |
| 161 | + | @Override | |
| 162 | + | public void onClick(View v) { | |
| 163 | + | Intent intent = new Intent(context, HelpPitchActivity.class); | |
| 164 | + | context.startActivity(intent); | |
| 165 | + | } | |
| 166 | + | }); | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | if(result.getAlternatives().size() > 1) | |
| 170 | + | additional_info.setText(String.format(context.getResources().getString(R.string.sense_alternatives), getContent(result.getAlternatives()))); | |
| 171 | + | else | |
| 172 | + | additional_info.setVisibility(View.GONE); | |
| 173 | + | ||
| 174 | + | senses_view.removeAllViews(); | |
| 175 | + | ||
| 176 | + | int sense_pos = 1; | |
| 177 | + | for (Result.Sense sense : result.getSenses()) { | |
| 178 | + | View child = LayoutInflater.from(context).inflate(R.layout.layout_sense, senses_view, false); | |
| 179 | + | TextView id_view = child.findViewById(R.id.id_view); | |
| 180 | + | TextView lang_view = child.findViewById(R.id.lang_view); | |
| 181 | + | TextView sense_view = child.findViewById(R.id.definition_view); | |
| 182 | + | ||
| 183 | + | id_view.setText(String.format(context.getResources().getString(R.string.sense_number), sense_pos)); | |
| 184 | + | lang_view.setText(sense.getLanguage()); | |
| 185 | + | ||
| 186 | + | StringBuilder sb = new StringBuilder(); | |
| 187 | + | sb.append("<font color=\"#909090\"><i>"); | |
| 188 | + | sb.append(getContent(sense.getInfos())); | |
| 189 | + | sb.append("</i></font>"); | |
| 190 | + | if(sense.getInfos().size() > 0) | |
| 191 | + | sb.append(" "); | |
| 192 | + | sb.append(getContent(sense.getGlosses())); | |
| 193 | + | sense_view.setText(Html.fromHtml(sb.toString())); | |
| 194 | + | ||
| 195 | + | senses_view.addView(child); | |
| 196 | + | sense_pos++; | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | result_view.addView(child_result); | |
| 200 | + | } | |
| 201 | + | } | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | private LayoutInflater mInflater; | |
| 205 | + | ResultPagerAdapter(Context context) { | |
| 206 | + | this.mInflater = LayoutInflater.from(context); | |
| 207 | + | this.context = context; | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | @NonNull | |
| 211 | + | @Override | |
| 212 | + | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { | |
| 213 | + | View view = mInflater.inflate(R.layout.fragment_results, parent, false); | |
| 214 | + | return new ViewHolder(view, context); | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | @Override | |
| 218 | + | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { | |
| 219 | + | if(position == 0) | |
| 220 | + | holder.showResults(); | |
| 221 | + | else | |
| 222 | + | holder.showKanjiResults(); | |
| 223 | + | } | |
| 224 | + | ||
| 225 | + | @Override | |
| 226 | + | public int getItemCount() { | |
| 227 | + | return 2; | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | public void setReadingStyle(String readingStyle) { | |
| 231 | + | ResultPagerAdapter.readingStyle = readingStyle; | |
| 232 | + | Log.d(TAG, "reading style updated to " + readingStyle); | |
| 233 | + | notifyDataSetChanged(); | |
| 234 | + | } | |
| 235 | + | ||
| 236 | + | public void setKanjiResults(List<KanjiResult> kanjiResults) { | |
| 237 | + | if(kanjiResults == null) | |
| 238 | + | return; | |
| 239 | + | ||
| 240 | + | ResultPagerAdapter.kanjiResults.clear(); | |
| 241 | + | ResultPagerAdapter.kanjiResults.addAll(kanjiResults); | |
| 242 | + | Log.d(TAG, "kanjis: " + ResultPagerAdapter.kanjiResults.size()); | |
| 243 | + | notifyDataSetChanged(); | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | public void setResults(List<Result> results) { | |
| 247 | + | if(results == null) | |
| 248 | + | return; | |
| 249 | + | ||
| 250 | + | ResultPagerAdapter.results.clear(); | |
| 251 | + | ResultPagerAdapter.results.addAll(results); | |
| 252 | + | Log.d(TAG, "results: " + ResultPagerAdapter.results.size()); | |
| 253 | + | notifyDataSetChanged(); | |
| 254 | + | } | |
| 255 | + | } |
app/src/main/java/eu/lepiller/nani/SearchResult.java
| 1 | 1 | package eu.lepiller.nani; | |
| 2 | 2 | ||
| 3 | - | import java.util.ArrayList; | |
| 3 | + | import java.util.List; | |
| 4 | 4 | ||
| 5 | 5 | import eu.lepiller.nani.dictionary.DictionaryException; | |
| 6 | + | import eu.lepiller.nani.result.KanjiResult; | |
| 6 | 7 | import eu.lepiller.nani.result.Result; | |
| 7 | 8 | ||
| 8 | 9 | class SearchResult { | |
| 9 | - | private ArrayList<Result> results; | |
| 10 | + | private List<Result> results; | |
| 11 | + | private List<KanjiResult> kanjiResults; | |
| 10 | 12 | private DictionaryException exception; | |
| 11 | 13 | private boolean converted; | |
| 12 | 14 | private String text; | |
| 13 | 15 | ||
| 14 | - | SearchResult(ArrayList<Result> results, String text, boolean converted) { | |
| 16 | + | SearchResult(List<Result> results, List<KanjiResult> kanjiResults, String text, boolean converted) { | |
| 15 | 17 | this.results = results; | |
| 18 | + | this.kanjiResults = kanjiResults; | |
| 16 | 19 | this.converted = converted; | |
| 17 | 20 | this.text = text; | |
| 18 | 21 | } | |
… | |||
| 29 | 32 | return exception; | |
| 30 | 33 | } | |
| 31 | 34 | ||
| 32 | - | ArrayList<Result> getResults() { | |
| 35 | + | List<Result> getResults() { | |
| 33 | 36 | return results; | |
| 34 | 37 | } | |
| 35 | 38 | ||
| 39 | + | List<KanjiResult> getKanjiResults() { | |
| 40 | + | return kanjiResults; | |
| 41 | + | } | |
| 42 | + | ||
| 36 | 43 | boolean isConverted() { | |
| 37 | 44 | return converted; | |
| 38 | 45 | } | |
app/src/main/java/eu/lepiller/nani/dictionary/DictionaryFactory.java
| 18 | 18 | import java.util.List; | |
| 19 | 19 | import java.util.Locale; | |
| 20 | 20 | import java.util.Map; | |
| 21 | + | import java.util.Stack; | |
| 21 | 22 | ||
| 23 | + | import eu.lepiller.nani.result.KanjiResult; | |
| 22 | 24 | import eu.lepiller.nani.result.Result; | |
| 23 | 25 | ||
| 24 | 26 | public class DictionaryFactory { | |
… | |||
| 129 | 131 | chooseLanguage(synopsis), | |
| 130 | 132 | chooseLanguage(description), | |
| 131 | 133 | cacheDir, url, size, entries, sha256, lang); | |
| 134 | + | } else if (type.compareTo("kanjidic") == 0) { | |
| 135 | + | d = new KanjiDict(name, | |
| 136 | + | chooseLanguage(synopsis), | |
| 137 | + | chooseLanguage(description), | |
| 138 | + | cacheDir, url, size, entries, sha256, lang); | |
| 132 | 139 | } | |
| 133 | 140 | ||
| 134 | 141 | if(d != null) { | |
… | |||
| 264 | 271 | return new ArrayList<>(results.values()); | |
| 265 | 272 | } | |
| 266 | 273 | ||
| 274 | + | public static KanjiResult searchKanji(String kanji) throws DictionaryException { | |
| 275 | + | if(instance == null) | |
| 276 | + | throw new NoDictionaryException(); | |
| 277 | + | ||
| 278 | + | int available = 0; | |
| 279 | + | Stack<KanjiResult> results = new Stack<>(); | |
| 280 | + | ||
| 281 | + | for(Dictionary d: dictionaries) { | |
| 282 | + | if (d instanceof KanjiDict && d.isDownloaded()) { | |
| 283 | + | available++; | |
| 284 | + | KanjiResult kanjiResult = ((KanjiDict) d).search(kanji); | |
| 285 | + | if(kanjiResult != null) { | |
| 286 | + | results.add(kanjiResult); | |
| 287 | + | } | |
| 288 | + | } | |
| 289 | + | } | |
| 290 | + | ||
| 291 | + | Log.d(TAG, "search kanji, " + results.size() + " results"); | |
| 292 | + | ||
| 293 | + | if(results.size() == 0) | |
| 294 | + | return null; | |
| 295 | + | ||
| 296 | + | KanjiResult res = results.pop(); | |
| 297 | + | for(KanjiResult r: results) { | |
| 298 | + | res.merge(r); | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | return res; | |
| 302 | + | } | |
| 303 | + | ||
| 267 | 304 | private static void augment(Result r) throws IncompatibleFormatException { | |
| 268 | 305 | for(Dictionary d: dictionaries) { | |
| 269 | 306 | if(d instanceof ResultAugmenterDictionary && d.isDownloaded()) { | |
app/src/main/java/eu/lepiller/nani/dictionary/FileDictionary.java
| 254 | 254 | static<T> T searchTrie(RandomAccessFile file, long triePos, byte[] txt, TrieValsDecoder<T> decoder) throws IOException { | |
| 255 | 255 | file.seek(triePos); | |
| 256 | 256 | if(txt.length == 0) { | |
| 257 | + | Log.v(TAG, "found trie value, reading values"); | |
| 257 | 258 | return decoder.decodeVals(file, triePos); | |
| 258 | 259 | } | |
| 259 | 260 | ||
… | |||
| 266 | 267 | byte letter = file.readByte(); | |
| 267 | 268 | Log.v(TAG, "Possible transition " + letter + "; Expected transition: " + txt[0]); | |
| 268 | 269 | if(letter == txt[0]) { | |
| 269 | - | Log.v(TAG, "Taking transition "+letter); | |
| 270 | + | long nextPos = file.readInt(); | |
| 271 | + | Log.v(TAG, "Taking transition "+letter+" to " + nextPos); | |
| 270 | 272 | byte[] ntxt = new byte[txt.length-1]; | |
| 271 | 273 | System.arraycopy(txt, 1, ntxt, 0, txt.length-1); | |
| 272 | - | return searchTrie(file, file.readInt(), ntxt, decoder); | |
| 274 | + | return searchTrie(file, nextPos, ntxt, decoder); | |
| 273 | 275 | } else { | |
| 274 | 276 | file.skipBytes(4); | |
| 275 | 277 | } | |
app/src/main/java/eu/lepiller/nani/dictionary/KanjiDict.java unknown status 1
| 1 | + | package eu.lepiller.nani.dictionary; | |
| 2 | + | ||
| 3 | + | import android.util.Log; | |
| 4 | + | ||
| 5 | + | import java.io.File; | |
| 6 | + | import java.io.FileNotFoundException; | |
| 7 | + | import java.io.IOException; | |
| 8 | + | import java.io.RandomAccessFile; | |
| 9 | + | import java.util.ArrayList; | |
| 10 | + | import java.util.Arrays; | |
| 11 | + | import java.util.List; | |
| 12 | + | ||
| 13 | + | import eu.lepiller.nani.R; | |
| 14 | + | import eu.lepiller.nani.result.KanjiResult; | |
| 15 | + | ||
| 16 | + | public class KanjiDict extends FileDictionary { | |
| 17 | + | final private static String TAG = "KANJIDIC"; | |
| 18 | + | private Huffman readingHuffman, meaningHuffman; | |
| 19 | + | ||
| 20 | + | KanjiDict(String name, String description, String fullDescription, File cacheDir, String url, int fileSize, int entries, String hash, String lang) { | |
| 21 | + | super(name, description, fullDescription, cacheDir, url, fileSize, entries, hash, lang); | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | @Override | |
| 25 | + | public void remove() { | |
| 26 | + | super.remove(); | |
| 27 | + | readingHuffman = null; | |
| 28 | + | meaningHuffman = null; | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | @Override | |
| 32 | + | int getDrawableId() { | |
| 33 | + | return R.drawable.ic_nani_edrdg; | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | KanjiResult getValue(RandomAccessFile file, long pos, String kanji) throws IOException { | |
| 37 | + | Log.d(TAG, "getValue at " + pos); | |
| 38 | + | file.seek(pos); | |
| 39 | + | int stroke = file.readByte(); | |
| 40 | + | Log.d(TAG, "strokes: " + stroke); | |
| 41 | + | ||
| 42 | + | List<String> senses = getHuffmanStringList(file, meaningHuffman); | |
| 43 | + | List<KanjiResult.Sense> meanings = new ArrayList<>(); | |
| 44 | + | for(String s: senses) { | |
| 45 | + | meanings.add(new KanjiResult.Sense(this.getLang(), s)); | |
| 46 | + | } | |
| 47 | + | List<String> kun = getHuffmanStringList(file, readingHuffman); | |
| 48 | + | List<String> on = getHuffmanStringList(file, readingHuffman); | |
| 49 | + | List<String> nanori = getHuffmanStringList(file, readingHuffman); | |
| 50 | + | ||
| 51 | + | return new KanjiResult(kanji, stroke, meanings, kun, on, nanori); | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | KanjiResult search(final String kanji) throws IncompatibleFormatException { | |
| 55 | + | if (isDownloaded()) { | |
| 56 | + | try { | |
| 57 | + | Log.d(TAG, "search for kanji " + kanji); | |
| 58 | + | RandomAccessFile file = new RandomAccessFile(getFile(), "r"); | |
| 59 | + | byte[] header = new byte[16]; | |
| 60 | + | int l = file.read(header); | |
| 61 | + | if (l != header.length) | |
| 62 | + | return null; | |
| 63 | + | ||
| 64 | + | // Check file format version | |
| 65 | + | if(!Arrays.equals(header, "NANI_KANJIDIC001".getBytes())) { | |
| 66 | + | StringBuilder error = new StringBuilder("search: incompatible header: ["); | |
| 67 | + | boolean first = true; | |
| 68 | + | for(byte b: header) { | |
| 69 | + | if(first) | |
| 70 | + | first = false; | |
| 71 | + | else | |
| 72 | + | error.append(", "); | |
| 73 | + | error.append(b); | |
| 74 | + | } | |
| 75 | + | error.append("]."); | |
| 76 | + | Log.d(TAG, error.toString()); | |
| 77 | + | throw new IncompatibleFormatException(getName()); | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | Log.d(TAG, "header OK"); | |
| 81 | + | ||
| 82 | + | byte[] search = kanji.toLowerCase().getBytes(); | |
| 83 | + | file.skipBytes(4); // size | |
| 84 | + | meaningHuffman = loadHuffman(file); | |
| 85 | + | readingHuffman = loadHuffman(file); | |
| 86 | + | long kanjiTriePos = file.getFilePointer(); | |
| 87 | + | ||
| 88 | + | Log.d(TAG, "trie pos: " + kanjiTriePos); | |
| 89 | + | ||
| 90 | + | return searchTrie(file, kanjiTriePos, search, new TrieValsDecoder<KanjiResult>() { | |
| 91 | + | @Override | |
| 92 | + | public KanjiResult decodeVals(RandomAccessFile file1, long pos) throws IOException { | |
| 93 | + | Log.d(TAG, "decoding val"); | |
| 94 | + | file1.seek(pos); | |
| 95 | + | return getValue(file1, file1.readInt(), kanji); | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | @Override | |
| 99 | + | public void skipVals(RandomAccessFile file1, long pos) throws IOException { | |
| 100 | + | file1.seek(pos); | |
| 101 | + | file1.skipBytes(4); | |
| 102 | + | } | |
| 103 | + | }); | |
| 104 | + | } catch (FileNotFoundException e) { | |
| 105 | + | e.printStackTrace(); | |
| 106 | + | } catch (IOException e) { | |
| 107 | + | e.printStackTrace(); | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | return null; | |
| 111 | + | } | |
| 112 | + | } |
app/src/main/java/eu/lepiller/nani/result/KanjiResult.java unknown status 1
| 1 | + | package eu.lepiller.nani.result; | |
| 2 | + | ||
| 3 | + | import java.util.ArrayList; | |
| 4 | + | import java.util.List; | |
| 5 | + | ||
| 6 | + | public class KanjiResult { | |
| 7 | + | public static class Sense { | |
| 8 | + | private String lang, content; | |
| 9 | + | public Sense(String lang, String content) { | |
| 10 | + | this.lang = lang; | |
| 11 | + | this.content = content; | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | public String getContent() { | |
| 15 | + | return content; | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | public String getLang() { | |
| 19 | + | return lang; | |
| 20 | + | } | |
| 21 | + | } | |
| 22 | + | private String kanji; | |
| 23 | + | private int stroke; | |
| 24 | + | private List<Sense> senses; | |
| 25 | + | private List<String> on; | |
| 26 | + | private List<String> kun; | |
| 27 | + | private List<String> nanori; | |
| 28 | + | ||
| 29 | + | public KanjiResult(String kanji, int stroke, List<Sense> senses, List<String> kun, List<String> on, List<String> nanori) { | |
| 30 | + | this.kanji = kanji; | |
| 31 | + | this.stroke = stroke; | |
| 32 | + | this.senses = senses; | |
| 33 | + | this.kun = kun; | |
| 34 | + | this.on = on; | |
| 35 | + | this.nanori = nanori; | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | public List<String> addUniq(List<String> a, List<String> b) { | |
| 39 | + | List<String> res = new ArrayList<>(); | |
| 40 | + | for(String s: a) { | |
| 41 | + | if(res.contains(s)) | |
| 42 | + | continue; | |
| 43 | + | res.add(s); | |
| 44 | + | } | |
| 45 | + | for(String s: b) { | |
| 46 | + | if(res.contains(s)) | |
| 47 | + | continue; | |
| 48 | + | res.add(s); | |
| 49 | + | } | |
| 50 | + | return res; | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | public void merge(KanjiResult other) { | |
| 54 | + | this.senses.addAll(other.senses); | |
| 55 | + | this.on = addUniq(this.on, other.on); | |
| 56 | + | this.on = addUniq(this.kun, other.kun); | |
| 57 | + | this.on = addUniq(this.nanori, other.nanori); | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | public List<Sense> getSenses() { | |
| 61 | + | return senses; | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | public int getStroke() { | |
| 65 | + | return stroke; | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | public List<String> getKun() { | |
| 69 | + | return kun; | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | public List<String> getNanori() { | |
| 73 | + | return nanori; | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | public List<String> getOn() { | |
| 77 | + | return on; | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | public String getKanji() { | |
| 81 | + | return kanji; | |
| 82 | + | } | |
| 83 | + | } |
app/src/main/res/layout/content_main.xml
| 40 | 40 | android:layout_width="match_parent" | |
| 41 | 41 | android:layout_height="wrap_content" /> | |
| 42 | 42 | ||
| 43 | - | <ScrollView | |
| 43 | + | <LinearLayout | |
| 44 | 44 | android:layout_width="match_parent" | |
| 45 | 45 | android:layout_height="0dp" | |
| 46 | 46 | android:layout_weight="1"> | |
| 47 | 47 | ||
| 48 | - | <LinearLayout | |
| 49 | - | android:id="@+id/results_view" | |
| 48 | + | <androidx.viewpager2.widget.ViewPager2 | |
| 50 | 49 | android:layout_width="match_parent" | |
| 51 | - | android:layout_height="wrap_content" | |
| 52 | - | android:layout_marginTop="16dp" | |
| 53 | - | android:layout_marginBottom="8dp" | |
| 54 | - | android:orientation="vertical" /> | |
| 55 | - | </ScrollView> | |
| 50 | + | android:layout_height="match_parent" | |
| 51 | + | android:id="@+id/pager" /> | |
| 52 | + | </LinearLayout> | |
| 56 | 53 | ||
| 57 | 54 | <LinearLayout | |
| 58 | 55 | android:id="@+id/bottom_layout" | |
… | |||
| 60 | 57 | android:layout_height="wrap_content" | |
| 61 | 58 | android:orientation="horizontal"> | |
| 62 | 59 | ||
| 63 | - | <Space | |
| 60 | + | <com.google.android.material.tabs.TabLayout | |
| 61 | + | android:id="@+id/tab_layout" | |
| 64 | 62 | android:layout_width="0dp" | |
| 65 | 63 | android:layout_height="wrap_content" | |
| 66 | 64 | android:layout_weight="1" /> | |
… | |||
| 70 | 68 | android:layout_width="wrap_content" | |
| 71 | 69 | android:layout_height="wrap_content" | |
| 72 | 70 | android:minWidth="0dp" | |
| 73 | - | android:text="???" /> | |
| 71 | + | android:text="???" | |
| 72 | + | android:layout_gravity="center_vertical"/> | |
| 74 | 73 | </LinearLayout> | |
| 75 | 74 | ||
| 76 | 75 | </LinearLayout> | |
app/src/main/res/layout/fragment_results.xml unknown status 1
| 1 | + | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | + | <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + | xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + | android:layout_width="match_parent" | |
| 5 | + | android:layout_height="match_parent" | |
| 6 | + | android:orientation="vertical"> | |
| 7 | + | ||
| 8 | + | <LinearLayout | |
| 9 | + | android:id="@+id/results_view" | |
| 10 | + | android:layout_width="match_parent" | |
| 11 | + | android:layout_height="wrap_content" | |
| 12 | + | android:orientation="vertical" /> | |
| 13 | + | </ScrollView> | |
| 13 | < | ||
| 0 | 14 | < | \ No newline at end of file |
app/src/main/res/layout/layout_kanji.xml unknown status 1
| 1 | + | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | + | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + | android:layout_width="match_parent" | |
| 4 | + | android:layout_height="wrap_content" | |
| 5 | + | android:orientation="horizontal" | |
| 6 | + | android:layout_marginBottom="16dp"> | |
| 7 | + | ||
| 8 | + | <LinearLayout | |
| 9 | + | android:layout_width="wrap_content" | |
| 10 | + | android:layout_height="wrap_content" | |
| 11 | + | android:orientation="vertical" | |
| 12 | + | android:layout_gravity="center_vertical"> | |
| 13 | + | <TextView | |
| 14 | + | android:layout_width="wrap_content" | |
| 15 | + | android:layout_height="wrap_content" | |
| 16 | + | android:id="@+id/kanji_view" | |
| 17 | + | android:textSize="@dimen/title_size" | |
| 18 | + | android:layout_gravity="center" | |
| 19 | + | android:layout_marginLeft="8dp" | |
| 20 | + | android:layout_marginStart="8dp" | |
| 21 | + | android:layout_marginRight="8dp" | |
| 22 | + | android:layout_marginEnd="8dp" /> | |
| 23 | + | ||
| 24 | + | <TextView | |
| 25 | + | android:layout_width="wrap_content" | |
| 26 | + | android:layout_height="wrap_content" | |
| 27 | + | android:layout_gravity="center_horizontal" | |
| 28 | + | android:id="@+id/kanji_strokes" /> | |
| 29 | + | </LinearLayout> | |
| 30 | + | ||
| 31 | + | <LinearLayout | |
| 32 | + | android:layout_width="0dp" | |
| 33 | + | android:layout_height="wrap_content" | |
| 34 | + | android:layout_weight="1" | |
| 35 | + | android:orientation="vertical"> | |
| 36 | + | <LinearLayout | |
| 37 | + | android:orientation="vertical" | |
| 38 | + | android:id="@+id/sense_view" | |
| 39 | + | android:layout_width="wrap_content" | |
| 40 | + | android:layout_height="wrap_content"> | |
| 41 | + | ||
| 42 | + | </LinearLayout> | |
| 43 | + | ||
| 44 | + | <TextView | |
| 45 | + | android:layout_width="match_parent" | |
| 46 | + | android:layout_height="wrap_content" | |
| 47 | + | android:layout_marginLeft="8dp" | |
| 48 | + | android:layout_marginStart="8dp" | |
| 49 | + | android:id="@+id/kun_reading" /> | |
| 50 | + | ||
| 51 | + | <TextView | |
| 52 | + | android:layout_width="match_parent" | |
| 53 | + | android:layout_height="wrap_content" | |
| 54 | + | android:layout_marginLeft="8dp" | |
| 55 | + | android:layout_marginStart="8dp" | |
| 56 | + | android:id="@+id/on_reading" /> | |
| 57 | + | ||
| 58 | + | <TextView | |
| 59 | + | android:layout_width="match_parent" | |
| 60 | + | android:layout_height="wrap_content" | |
| 61 | + | android:layout_marginLeft="8dp" | |
| 62 | + | android:layout_marginStart="8dp" | |
| 63 | + | android:id="@+id/nanori_reading" /> | |
| 64 | + | </LinearLayout> | |
| 65 | + | ||
| 66 | + | ||
| 67 | + | </LinearLayout> | |
| 67 | < | ||
| 0 | 68 | < | \ No newline at end of file |
app/src/main/res/layout/layout_result.xml
| 1 | 1 | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | - | xmlns:tools="http://schemas.android.com/tools" | |
| 4 | 3 | android:layout_width="match_parent" | |
| 5 | 4 | android:layout_height="wrap_content" | |
| 6 | 5 | xmlns:app="http://schemas.android.com/apk/res-auto" | |
| 7 | - | android:layout_marginBottom="32dp" | |
| 8 | - | android:orientation="vertical"> | |
| 6 | + | android:orientation="vertical" | |
| 7 | + | android:layout_marginBottom="8dp"> | |
| 9 | 8 | ||
| 10 | 9 | <LinearLayout | |
| 11 | 10 | android:layout_width="wrap_content" |
app/src/main/res/layout/layout_result_list.xml unknown status 1
| 1 | + | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | + | <ScrollView | |
| 3 | + | xmlns:android="http://schemas.android.com/apk/res/android" | |
| 4 | + | android:layout_width="match_parent" | |
| 5 | + | android:layout_height="match_parent"> | |
| 6 | + | ||
| 7 | + | <LinearLayout | |
| 8 | + | android:id="@+id/results_view" | |
| 9 | + | android:layout_width="match_parent" | |
| 10 | + | android:layout_height="wrap_content" | |
| 11 | + | android:layout_marginTop="16dp" | |
| 12 | + | android:layout_marginBottom="8dp" | |
| 13 | + | android:orientation="vertical" /> | |
| 14 | + | ||
| 15 | + | </ScrollView> | |
| 15 | < | ||
| 0 | 16 | < | \ No newline at end of file |
app/src/main/res/values-fr/strings.xml
| 59 | 59 | <string name="dictionary_expected_size_kb">Taille : %s Ko</string> | |
| 60 | 60 | <!-- Result view --> | |
| 61 | 61 | <string name="sense_number">%d.</string> | |
| 62 | - | <string name="sense_separator">" ; "</string> | |
| 63 | 62 | <string name="sense_alternatives">Formes alternatives : %s</string> | |
| 64 | 63 | <!-- About activity --> | |
| 65 | 64 | <string name="nani_about">????Nani\????? est un dictionnaire japonais hors-ligne pour Android. Il vous aide ?? trouver et comprendre des mots sans acc??s internet, en t??l??chargeant des sources de dictionnaires. |
app/src/main/res/values-uk/strings.xml
| 64 | 64 | <string name="view_source">?????????????????????? ???????????????????? ??????</string> | |
| 65 | 65 | <string name="report">???????????????????? ?????? ??????????????</string> | |
| 66 | 66 | <string name="sense_alternatives">?????????????????????????? ??????????: %s</string> | |
| 67 | - | <string name="sense_separator">"; "</string> | |
| 68 | 67 | <string name="sense_number">%d.</string> | |
| 69 | 68 | <string name="dictionary_expected_size_mb">???????????? ??????????: %s????</string> | |
| 70 | 69 | <string name="dictionary_expected_size_kb">???????????? ??????????: %s????</string> |
app/src/main/res/values/dimens.xml
| 2 | 2 | <dimen name="fab_margin">16dp</dimen> | |
| 3 | 3 | <dimen name="title_size">32sp</dimen> | |
| 4 | 4 | <dimen name="subtitle_size">24sp</dimen> | |
| 5 | + | <dimen name="text_margin">16dp</dimen> | |
| 5 | 6 | </resources> |
app/src/main/res/values/strings.xml
| 80 | 80 | ||
| 81 | 81 | <!-- Result view --> | |
| 82 | 82 | <string name="sense_number">%d.</string> | |
| 83 | - | <string name="sense_separator">"; "</string> | |
| 83 | + | <string name="sense_separator" translatable="false">"???"</string> | |
| 84 | 84 | <string name="sense_alternatives">Alternative forms: %s</string> | |
| 85 | + | <string name="tab_kanji">Kanji</string> | |
| 86 | + | <string name="tab_results">Word</string> | |
| 87 | + | <plurals name="kanji_stroke"> | |
| 88 | + | <item quantity="one">%d stroke</item> | |
| 89 | + | <item quantity="other">%d strokes</item> | |
| 90 | + | </plurals> | |
| 91 | + | <string name="kun_reading">kunyomi: %s</string> | |
| 92 | + | <string name="on_reading">onyomi: %s</string> | |
| 93 | + | <string name="nanori_reading">nanori: %s</string> | |
| 85 | 94 | ||
| 86 | 95 | <!-- About activity --> | |
| 87 | 96 | <string name="nani_about">???Nani???? is an offline Japanese dictionary for Android. It helps | |
… | |||
| 239 | 248 | records the position of the downstep, as there can only be one downstep in a word. </string> | |
| 240 | 249 | <string name="help_pitch_learn_more">Learn more on Wikipedia</string> | |
| 241 | 250 | <string name="help_pitch_wiki_link">https://en.wikipedia.org/wiki/Japanese_pitch_accent</string> | |
| 251 | + | <string name="large_text"> | |
| 252 | + | "Material is the metaphor.\n\n" | |
| 253 | + | ||
| 254 | + | "A material metaphor is the unifying theory of a rationalized space and a system of motion." | |
| 255 | + | "The material is grounded in tactile reality, inspired by the study of paper and ink, yet " | |
| 256 | + | "technologically advanced and open to imagination and magic.\n" | |
| 257 | + | "Surfaces and edges of the material provide visual cues that are grounded in reality. The " | |
| 258 | + | "use of familiar tactile attributes helps users quickly understand affordances. Yet the " | |
| 259 | + | "flexibility of the material creates new affordances that supercede those in the physical " | |
| 260 | + | "world, without breaking the rules of physics.\n" | |
| 261 | + | "The fundamentals of light, surface, and movement are key to conveying how objects move, " | |
| 262 | + | "interact, and exist in space and in relation to each other. Realistic lighting shows " | |
| 263 | + | "seams, divides space, and indicates moving parts.\n\n" | |
| 264 | + | ||
| 265 | + | "Bold, graphic, intentional.\n\n" | |
| 266 | + | ||
| 267 | + | "The foundational elements of print based design typography, grids, space, scale, color, " | |
| 268 | + | "and use of imagery guide visual treatments. These elements do far more than please the " | |
| 269 | + | "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge " | |
| 270 | + | "imagery, large scale typography, and intentional white space create a bold and graphic " | |
| 271 | + | "interface that immerse the user in the experience.\n" | |
| 272 | + | "An emphasis on user actions makes core functionality immediately apparent and provides " | |
| 273 | + | "waypoints for the user.\n\n" | |
| 274 | + | ||
| 275 | + | "Motion provides meaning.\n\n" | |
| 276 | + | ||
| 277 | + | "Motion respects and reinforces the user as the prime mover. Primary user actions are " | |
| 278 | + | "inflection points that initiate motion, transforming the whole design.\n" | |
| 279 | + | "All action takes place in a single environment. Objects are presented to the user without " | |
| 280 | + | "breaking the continuity of experience even as they transform and reorganize.\n" | |
| 281 | + | "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. " | |
| 282 | + | "Feedback is subtle yet clear. Transitions are ef???cient yet coherent.\n\n" | |
| 283 | + | ||
| 284 | + | "3D world.\n\n" | |
| 285 | + | ||
| 286 | + | "The material environment is a 3D space, which means all objects have x, y, and z " | |
| 287 | + | "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the " | |
| 288 | + | "positive z-axis extending towards the viewer. Every sheet of material occupies a single " | |
| 289 | + | "position along the z-axis and has a standard 1dp thickness.\n" | |
| 290 | + | "On the web, the z-axis is used for layering and not for perspective. The 3D world is " | |
| 291 | + | "emulated by manipulating the y-axis.\n\n" | |
| 292 | + | ||
| 293 | + | "Light and shadow.\n\n" | |
| 294 | + | ||
| 295 | + | "Within the material environment, virtual lights illuminate the scene. Key lights create " | |
| 296 | + | "directional shadows, while ambient light creates soft shadows from all angles.\n" | |
| 297 | + | "Shadows in the material environment are cast by these two light sources. In Android " | |
| 298 | + | "development, shadows occur when light sources are blocked by sheets of material at " | |
| 299 | + | "various positions along the z-axis. On the web, shadows are depicted by manipulating the " | |
| 300 | + | "y-axis only. The following example shows the card with a height of 6dp.\n\n" | |
| 301 | + | ||
| 302 | + | "Resting elevation.\n\n" | |
| 303 | + | ||
| 304 | + | "All material objects, regardless of size, have a resting elevation, or default elevation " | |
| 305 | + | "that does not change. If an object changes elevation, it should return to its resting " | |
| 306 | + | "elevation as soon as possible.\n\n" | |
| 307 | + | ||
| 308 | + | "Component elevations.\n\n" | |
| 309 | + | ||
| 310 | + | "The resting elevation for a component type is consistent across apps (e.g., FAB elevation " | |
| 311 | + | "does not vary from 6dp in one app to 16dp in another app).\n" | |
| 312 | + | "Components may have different resting elevations across platforms, depending on the depth " | |
| 313 | + | "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n" | |
| 314 | + | ||
| 315 | + | "Responsive elevation and dynamic elevation offsets.\n\n" | |
| 316 | + | ||
| 317 | + | "Some component types have responsive elevation, meaning they change elevation in response " | |
| 318 | + | "to user input (e.g., normal, focused, and pressed) or system events. These elevation " | |
| 319 | + | "changes are consistently implemented using dynamic elevation offsets.\n" | |
| 320 | + | "Dynamic elevation offsets are the goal elevation that a component moves towards, relative " | |
| 321 | + | "to the component???s resting state. They ensure that elevation changes are consistent " | |
| 322 | + | "across actions and component types. For example, all components that lift on press have " | |
| 323 | + | "the same elevation change relative to their resting elevation.\n" | |
| 324 | + | "Once the input event is completed or cancelled, the component will return to its resting " | |
| 325 | + | "elevation.\n\n" | |
| 326 | + | ||
| 327 | + | "Avoiding elevation interference.\n\n" | |
| 328 | + | ||
| 329 | + | "Components with responsive elevations may encounter other components as they move between " | |
| 330 | + | "their resting elevations and dynamic elevation offsets. Because material cannot pass " | |
| 331 | + | "through other material, components avoid interfering with one another any number of ways, " | |
| 332 | + | "whether on a per component basis or using the entire app layout.\n" | |
| 333 | + | "On a component level, components can move or be removed before they cause interference. " | |
| 334 | + | "For example, a floating action button (FAB) can disappear or move off screen before a " | |
| 335 | + | "user picks up a card, or it can move if a snackbar appears.\n" | |
| 336 | + | "On the layout level, design your app layout to minimize opportunities for interference. " | |
| 337 | + | "For example, position the FAB to one side of stream of a cards so the FAB won???t interfere " | |
| 338 | + | "when a user tries to pick up one of cards.\n\n" | |
| 339 | + | </string> | |
| 242 | 340 | </resources> | |