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> |