├── settings.gradle
├── fastlane
└── metadata
│ └── android
│ ├── eo
│ ├── short_description.txt
│ ├── changelogs
│ │ ├── 25.txt
│ │ ├── 26.txt
│ │ └── 27.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── screenshot-0.png
│ │ │ ├── screenshot-1.png
│ │ │ └── screenshot-2.png
│ └── full_description.txt
│ ├── en-US
│ ├── short_description.txt
│ ├── changelogs
│ │ ├── 25.txt
│ │ ├── 26.txt
│ │ └── 27.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── screenshot-0.png
│ │ │ ├── screenshot-1.png
│ │ │ └── screenshot-2.png
│ └── full_description.txt
│ └── pl
│ ├── short_description.txt
│ └── full_description.txt
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── app
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-ldpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable
│ │ │ └── clear_button.xml
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ ├── layout
│ │ │ ├── definition.xml
│ │ │ ├── search_note.xml
│ │ │ ├── section_header.xml
│ │ │ ├── about_view.xml
│ │ │ ├── no_flashcard_view.xml
│ │ │ ├── languages.xml
│ │ │ ├── preferences.xml
│ │ │ ├── article.xml
│ │ │ └── search.xml
│ │ └── menu
│ │ │ ├── main_menu.xml
│ │ │ ├── definition_menu.xml
│ │ │ ├── search_menu.xml
│ │ │ └── article_menu.xml
│ │ ├── java
│ │ └── uk
│ │ │ └── co
│ │ │ └── busydoingnothing
│ │ │ └── prevo
│ │ │ ├── LanguagesActivity.java
│ │ │ ├── Language.java
│ │ │ ├── SearchResult.java
│ │ │ ├── SpannedCopy.java
│ │ │ ├── ReferenceSpan.java
│ │ │ ├── StartActivity.java
│ │ │ ├── DelayedScrollView.java
│ │ │ ├── DefinitionView.java
│ │ │ ├── Hats.java
│ │ │ ├── TrieCache.java
│ │ │ ├── SelectedLanguages.java
│ │ │ ├── BinaryReader.java
│ │ │ ├── LanguageDatabaseHelper.java
│ │ │ ├── PreferenceAdapter.java
│ │ │ ├── LanguageList.java
│ │ │ ├── SelectLanguageActivity.java
│ │ │ ├── PreferenceActivity.java
│ │ │ ├── MenuHelper.java
│ │ │ ├── SearchAdapter.java
│ │ │ ├── LanguagesAdapter.java
│ │ │ ├── SearchActivity.java
│ │ │ ├── Trie.java
│ │ │ └── ArticleActivity.java
│ │ └── AndroidManifest.xml
└── build.gradle
├── .gitignore
├── scripts
└── generate-dictionary.sh
├── .gitmodules
├── TODO
├── README.md
├── gradlew.bat
├── gradlew
└── COPYING
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/short_description.txt:
--------------------------------------------------------------------------------
1 | Esperanta vortaro
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Esperanto dictionary
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/25.txt:
--------------------------------------------------------------------------------
1 | - Update the ReVo data to 2024-04-08
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/26.txt:
--------------------------------------------------------------------------------
1 | - Update the ReVo data to 2024-04-08
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/pl/short_description.txt:
--------------------------------------------------------------------------------
1 | Słownik Esperanto i esperancko-polski
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/changelogs/25.txt:
--------------------------------------------------------------------------------
1 | - Ĝisdatigi la datumojn de ReVo al 2024-04-08
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/changelogs/26.txt:
--------------------------------------------------------------------------------
1 | - Ĝisdatigi la datumojn de ReVo al 2024-04-08
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/27.txt:
--------------------------------------------------------------------------------
1 | - Update the ReVo data to 2025-01-09
2 | - Add a button to clear the search text
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/eo/images/icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/app/src/main/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/changelogs/27.txt:
--------------------------------------------------------------------------------
1 | - Ĝisdatigi la datumojn de ReVo al 2025-01-09
2 | - Aldoni butonon por forigi la serĉtekston
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/images/phoneScreenshots/screenshot-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/eo/images/phoneScreenshots/screenshot-0.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/images/phoneScreenshots/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/eo/images/phoneScreenshots/screenshot-1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/images/phoneScreenshots/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/eo/images/phoneScreenshots/screenshot-2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-0.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bpeel/prevo/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/screenshot-2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | PReVo is a portable version of Reta Vortaro (the free and libre Esperanto dictionary) for Android. It’s usable without Internet access and is quickly searched with the keyboard.
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle/
3 | build/
4 | local.properties
5 | .idea
6 | .DS_Store
7 | .externalNativeBuild
8 | /prevodb-build
9 |
10 | /app/src/main/assets/articles/article-*.bin
11 | /app/src/main/assets/indices/index-*.bin
12 | /app/src/main/res/xml/languages.xml
13 |
--------------------------------------------------------------------------------
/scripts/generate-dictionary.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -eu
4 |
5 | cd "$(dirname "$0")"/..
6 |
7 | mkdir -p prevodb-build
8 | cd prevodb-build
9 |
10 | ../extern/prevodb/autogen.sh
11 | make -j$(nproc)
12 |
13 | ./src/prevodb \
14 | -i ../extern/revo-fonto \
15 | -i ../extern/voko-grundo \
16 | -o ../app/src/main
17 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "extern/voko-grundo"]
2 | path = extern/voko-grundo
3 | url = https://github.com/revuloj/voko-grundo.git
4 | [submodule "extern/revo-fonto"]
5 | path = extern/revo-fonto
6 | url = https://github.com/revuloj/revo-fonto.git
7 | [submodule "extern/prevodb"]
8 | path = extern/prevodb
9 | url = https://github.com/bpeel/prevodb
10 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/eo/full_description.txt:
--------------------------------------------------------------------------------
1 | PReVo estas portebla versio de Reta Vortaro (la libera interreta esperanta vortaro) por Android. Ĝi estas uzebla sen retkonekto kaj ĝi estas rapide serĉebla per la klavaro. Aldone al difinoj de vortoj en Esperanto, la vortaro ankaŭ liveras tradukojn al diversaj aliaj lingvoj.
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/pl/full_description.txt:
--------------------------------------------------------------------------------
1 | PreVo jest przenośną wersją Reta Vortaro (bezpłatnego i wolnego słownika międzynarodowego języka Esperanto) na system Android. Działa bez połączenia z siecią, pozwala na szybkie wyszukiwanie za pomocą klawiatury. Oprócz definicji w języku Esperanto, baza zawiera tłumaczenia na inne języki (w tym polski).
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/clear_button.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | • Ebligi ligilojn al specifaj lingvoj.
2 |
3 | • Pli bone traktu la etikedon 'uzo'
4 |
5 | • Ebligi ĝisdatigi la datumon pli ofte ol ĝisdatigi la pakaĵon. Eble
6 | oni povus instali la datumon aparte ĉe SD-karto kaj havi funkcion ĉe
7 | la menuo por ĝisdatigi ĝin.
8 |
9 | • Ebligi agordi la kolorojn
10 |
11 | • Rapide forviŝu la serĉtekston. Eble kiam la serĉilo unue montriĝas
12 | ĝi elektu la tutan tekston por rapide forigi ĝin.
13 |
14 | • Akceptu la h-sistemon
15 |
16 | - Eble tio nur bonus se ĝi traktus la literoj h kaj kiel ĉapelo kaj
17 | kiel normala litero. Ekzemple, 'flughaveno' trovus kaj na
18 | flughaveno kaj na fluĝaveno.
19 |
20 | - Tio povus uziĝi en ĝenerala sistemo por pli fleksa serĉo.
21 | Ekzemple, tajpi na “cafe” trovu ankaŭ na “café”.
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/definition.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_note.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/section_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/about_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/LanguagesActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2013 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | /* This is a dummy subclass so that we don't have to change the name
21 | * of the original initial activity that is launched at startup.
22 | * Apparently changing the name of that activity can break user's
23 | * shortcuts */
24 |
25 | public class LanguagesActivity extends StartActivity
26 | {
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/no_flashcard_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/languages.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
24 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/article.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
24 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/Language.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | public class Language
21 | {
22 | private String name;
23 | private String code;
24 |
25 | public Language (String name,
26 | String code)
27 | {
28 | this.name = name;
29 | this.code = code;
30 | }
31 |
32 | public String getName ()
33 | {
34 | return name;
35 | }
36 |
37 | public String getCode ()
38 | {
39 | return code;
40 | }
41 |
42 | @Override
43 | public String toString ()
44 | {
45 | return name;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/definition_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/search_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/SearchResult.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | public class SearchResult
21 | {
22 | String word;
23 | int article;
24 | int mark;
25 |
26 | public SearchResult (String word,
27 | int article,
28 | int mark)
29 | {
30 | this.word = word;
31 | this.article = article;
32 | this.mark = mark;
33 | }
34 |
35 | public String getWord ()
36 | {
37 | return word;
38 | }
39 |
40 | public int getArticle ()
41 | {
42 | return article;
43 | }
44 |
45 | public int getMark ()
46 | {
47 | return mark;
48 | }
49 |
50 | @Override
51 | public String toString ()
52 | {
53 | return word;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | namespace "uk.co.busydoingnothing.prevo"
5 |
6 | compileSdkVersion 28
7 |
8 | defaultConfig {
9 | applicationId "uk.co.busydoingnothing.prevo"
10 | minSdkVersion 16
11 | targetSdkVersion 34
12 | versionCode 27
13 | versionName "0.27"
14 | }
15 |
16 | signingConfigs {
17 | release {
18 | storeFile file(RELEASE_STORE_FILE)
19 | storePassword RELEASE_STORE_PASSWORD
20 | keyAlias RELEASE_KEY_ALIAS
21 | keyPassword RELEASE_KEY_PASSWORD
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | signingConfig signingConfigs.release
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 |
33 | applicationVariants.configureEach {
34 | tasks["merge${name.capitalize()}Resources"].dependsOn(buildDictionary)
35 | tasks["merge${name.capitalize()}Assets"].dependsOn(buildDictionary)
36 | }
37 | }
38 |
39 | dependencies {
40 | implementation 'com.android.support:appcompat-v7:28.0.0'
41 | }
42 |
43 | task buildDictionary {
44 | outputs.dir 'src/main/assets/articles'
45 | outputs.dir 'src/main/assets/indices'
46 | outputs.file 'src/main/res/xml/languages.xml'
47 |
48 | doLast {
49 | exec {
50 | executable '../scripts/generate-dictionary.sh'
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/article_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
39 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/SpannedCopy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.util.Log;
21 | import android.content.ClipData;
22 | import android.content.Context;
23 | import android.content.ClipboardManager;
24 | import android.text.Html;
25 | import android.text.Spanned;
26 |
27 | public abstract class SpannedCopy
28 | {
29 | public static final String TAG = "prevoclip";
30 |
31 | public static void copyText (Context context,
32 | CharSequence label,
33 | CharSequence text)
34 | {
35 | ClipboardManager clipboard =
36 | (ClipboardManager) context.getSystemService (Context.CLIPBOARD_SERVICE);
37 |
38 | ClipData clipData;
39 |
40 | if (text instanceof Spanned)
41 | {
42 | String htmlText = Html.toHtml ((Spanned) text);
43 |
44 | clipData = ClipData.newHtmlText (label, text, htmlText);
45 | }
46 | else
47 | clipData = ClipData.newPlainText (label, text);
48 |
49 | clipboard.setPrimaryClip (clipData);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/ReferenceSpan.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Intent;
21 | import android.text.style.ClickableSpan;
22 | import android.util.Log;
23 | import android.view.View;
24 |
25 | public class ReferenceSpan extends ClickableSpan
26 | {
27 | private int articleNumber;
28 | private int sectionNumber;
29 |
30 | private static String TAG = "PrevoReference";
31 |
32 | public ReferenceSpan (int articleNumber,
33 | int sectionNumber)
34 | {
35 | this.articleNumber = articleNumber;
36 | this.sectionNumber = sectionNumber;
37 | }
38 |
39 | @Override
40 | public void onClick (View view)
41 | {
42 | Log.i (TAG,
43 | "Jumping to article (" + articleNumber + "," + sectionNumber + ")");
44 |
45 | Intent intent = new Intent (view.getContext (),
46 | ArticleActivity.class);
47 | intent.putExtra (ArticleActivity.EXTRA_ARTICLE_NUMBER,
48 | articleNumber);
49 | intent.putExtra (ArticleActivity.EXTRA_MARK_NUMBER,
50 | sectionNumber);
51 | view.getContext ().startActivity (intent);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/StartActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2013 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Activity;
21 | import android.content.SharedPreferences;
22 | import android.os.Bundle;
23 | import android.support.v7.app.AppCompatActivity;
24 |
25 | /* This activity is just like a landing page to select the right
26 | * initial activity. If the user has already previously selected a
27 | * language then we'll default to searching in that language again,
28 | * otherwise we show the language select page */
29 |
30 | public class StartActivity extends AppCompatActivity
31 | {
32 | @Override
33 | public void onCreate (Bundle savedInstanceState)
34 | {
35 | String lastLanguage;
36 |
37 | super.onCreate (savedInstanceState);
38 |
39 | SharedPreferences prefs =
40 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
41 | Activity.MODE_PRIVATE);
42 |
43 | lastLanguage = prefs.getString (MenuHelper.PREF_LAST_LANGUAGE, null);
44 |
45 | if (lastLanguage == null)
46 | MenuHelper.goChooseLanguage (this);
47 | else
48 | MenuHelper.goSearch (this);
49 |
50 | /* Finish this activity to get it out of the call stack */
51 | finish ();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
22 |
23 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
41 |
42 |
45 |
46 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PReVo
2 | =====
3 |
4 | PReVo is an android application containing a portable version of the
5 | Reta Vortaro which is an open source dictionary for Esperanto. It
6 | contains all of the dictionary data in the package so the application
7 | does not need to access the internet.
8 |
9 | The official website for the program is here:
10 |
11 | http://www.busydoingnothing.co.uk/prevo/
12 |
13 | It is also available in the Google play store here:
14 |
15 | https://play.google.com/store/apps/details?id=uk.co.busydoingnothing.prevo
16 |
17 | or with F-Droid here:
18 |
19 | https://f-droid.org/en/packages/uk.co.busydoingnothing.prevo/
20 |
21 | Building
22 | --------
23 |
24 | This git repo does not include the assets containing the dictionary
25 | data. Instead they are built from the ReVo data and the prevodb
26 | program which are in git submodules. In order to get these modules, be
27 | sure to run the following git command:
28 |
29 | ```bash
30 | git submodule update --init
31 | ```
32 |
33 | The prevodb program will be built as part of the app build in order to
34 | generate the dictionary data, so you need to make sure you have a
35 | compiler for the host machine installed. It will also need the
36 | developer packages for expat and glib.
37 |
38 | Assuming you have the Android SDK installed correctly, you can build
39 | the app either with Android Studio or the command line as follows.
40 |
41 | Debug mode:
42 |
43 | cd $HOME/prevo
44 | ./gradlew assembleDebug
45 |
46 | Release mode:
47 |
48 | cd $HOME/prevo
49 | ./gradlew assembleRelease
50 |
51 | You should then have the final package in either
52 | `app/build/outputs/apk/debug/` or `app/build/outputs/apk/release/`
53 | depending on the build type.
54 |
55 | Building a specific release
56 | ---------------------------
57 |
58 | The releases are all tagged and signed in the git repo using the
59 | following public key:
60 |
61 | http://www.busydoingnothing.co.uk/neilroberts.asc
62 |
63 | The git submodules were added in version 0.25 so the signed tag
64 | contains the commit hash of the dependencies used as well. For older
65 | versions, the message for each tag contains the git hashes used for
66 | the ReVo sources and the prevodb program. This information can be used
67 | to build a copy of a release using exactly the same data. You can see
68 | this information for example with:
69 |
70 | git show 0.12
71 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/DelayedScrollView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import android.util.AttributeSet;
22 | import android.view.View;
23 | import android.widget.ScrollView;
24 |
25 | /* This is the same as a ScrollView except that it can set a scrollTo
26 | * to occur after a layout has occured. If you don't wait until after
27 | * the layout has occured then the views won't have the right
28 | * position */
29 |
30 | public class DelayedScrollView extends ScrollView
31 | {
32 | private boolean hadLayout = false;
33 | private View delayedView = null;
34 |
35 | public DelayedScrollView (Context context)
36 | {
37 | super (context);
38 | }
39 |
40 | public DelayedScrollView (Context context, AttributeSet attrs)
41 | {
42 | super (context, attrs);
43 | }
44 |
45 | public DelayedScrollView (Context context, AttributeSet attrs, int defStyle)
46 | {
47 | super (context, attrs, defStyle);
48 | }
49 |
50 | public void delayedScrollTo (View view)
51 | {
52 | if (hadLayout)
53 | {
54 | scrollTo (0, view.getTop ());
55 | delayedView = null;
56 | }
57 | else
58 | delayedView = view;
59 | }
60 |
61 | @Override
62 | protected void onLayout (boolean changed,
63 | int left,
64 | int top,
65 | int right,
66 | int bottom)
67 | {
68 | super.onLayout (changed, left, top, right, bottom);
69 |
70 | hadLayout = true;
71 |
72 | if (delayedView != null)
73 | delayedScrollTo (delayedView);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/DefinitionView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.util.AttributeSet;
21 | import android.widget.TextView;
22 | import android.content.Context;
23 | import android.view.ContextMenu.ContextMenuInfo;
24 | import android.support.v7.widget.AppCompatTextView;
25 |
26 | public class DefinitionView extends AppCompatTextView
27 | {
28 | private CharSequence word;
29 | private CharSequence definition;
30 |
31 | public class DefinitionContextMenuInfo implements ContextMenuInfo
32 | {
33 | public CharSequence word;
34 | public CharSequence definition;
35 | }
36 |
37 | private DefinitionContextMenuInfo contextMenuInfo;
38 |
39 | public DefinitionView (Context context)
40 | {
41 | super (context);
42 | }
43 |
44 | public DefinitionView (Context context, AttributeSet attrs)
45 | {
46 | super (context, attrs);
47 | }
48 |
49 | public DefinitionView (Context context, AttributeSet attrs, int defStyle)
50 | {
51 | super (context, attrs, defStyle);
52 | }
53 |
54 | public DefinitionView (Context context,
55 | CharSequence word,
56 | CharSequence definition)
57 | {
58 | super (context);
59 | setWord (word, definition);
60 | }
61 |
62 | public void setWord (CharSequence word, CharSequence definition)
63 | {
64 | this.word = word;
65 | this.definition = definition;
66 | contextMenuInfo = null;
67 | }
68 |
69 | public ContextMenuInfo getContextMenuInfo ()
70 | {
71 | if (contextMenuInfo == null)
72 | {
73 | contextMenuInfo = new DefinitionContextMenuInfo ();
74 | contextMenuInfo.word = this.word;
75 | contextMenuInfo.definition = this.definition;
76 | }
77 |
78 | return contextMenuInfo;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
23 |
32 |
42 |
49 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/Hats.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | public class Hats
21 | {
22 | public static String removeHats (CharSequence string)
23 | {
24 | StringBuilder result = new StringBuilder ();
25 |
26 | for (int i = 0; i < string.length (); i++)
27 | {
28 | if (i + 1 < string.length () &&
29 | (string.charAt (i + 1) == 'x' ||
30 | string.charAt (i + 1) == 'X'))
31 | {
32 | switch (string.charAt (i))
33 | {
34 | case 'c':
35 | result.append ('ĉ');
36 | break;
37 | case 'C':
38 | result.append ('Ĉ');
39 | break;
40 | case 'g':
41 | result.append ('ĝ');
42 | break;
43 | case 'G':
44 | result.append ('Ĝ');
45 | break;
46 | case 'h':
47 | result.append ('ĥ');
48 | break;
49 | case 'H':
50 | result.append ('Ĥ');
51 | break;
52 | case 'j':
53 | result.append ('ĵ');
54 | break;
55 | case 'J':
56 | result.append ('Ĵ');
57 | break;
58 | case 's':
59 | result.append ('ŝ');
60 | break;
61 | case 'S':
62 | result.append ('Ŝ');
63 | break;
64 | case 'u':
65 | result.append ('ŭ');
66 | break;
67 | case 'U':
68 | result.append ('Ŭ');
69 | break;
70 | default:
71 | result.append (string.subSequence (i, i + 2));
72 | break;
73 | }
74 | i++;
75 | }
76 | else
77 | result.append (string.charAt (i));
78 | }
79 |
80 | return result.toString ();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/TrieCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import java.io.InputStream;
22 | import java.io.IOException;
23 | import java.util.Iterator;
24 | import java.util.LinkedList;
25 |
26 | class TrieCacheEntry
27 | {
28 | public String language;
29 | public Trie trie;
30 | }
31 |
32 | public class TrieCache
33 | {
34 | private static final int CACHE_LENGTH = 5;
35 | private static LinkedList entries =
36 | new LinkedList ();
37 |
38 | private static Trie loadTrie (Context context,
39 | String language)
40 | throws IOException
41 | {
42 | InputStream indexIn =
43 | context.getAssets ().open ("indices/index-" + language + ".bin");
44 | return new Trie (indexIn);
45 | }
46 |
47 | public static Trie getTrie (Context context,
48 | String language)
49 | throws IOException
50 | {
51 | TrieCacheEntry foundEntry;
52 |
53 | synchronized (entries)
54 | {
55 | getEntry:
56 | {
57 | for (Iterator it = entries.iterator ();
58 | it.hasNext ();)
59 | {
60 | TrieCacheEntry entry = it.next ();
61 |
62 | if (entry.language.equals (language))
63 | {
64 | foundEntry = entry;
65 | /* Remove the entry so we can add it back to the
66 | * beginning */
67 | it.remove ();
68 | break getEntry;
69 | }
70 | }
71 |
72 | Trie trie = loadTrie (context, language);
73 | foundEntry = new TrieCacheEntry ();
74 | foundEntry.language = language;
75 | foundEntry.trie = trie;
76 |
77 | if (entries.size () >= CACHE_LENGTH)
78 | entries.removeLast ();
79 | }
80 |
81 | entries.addFirst (foundEntry);
82 | }
83 |
84 | return foundEntry.trie;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/SelectedLanguages.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2013 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Activity;
21 | import android.content.Context;
22 | import android.content.SharedPreferences;
23 | import java.util.Arrays;
24 |
25 | public class SelectedLanguages
26 | {
27 | public static final String PREF = "selectedLanguages";
28 |
29 | private String[] languages;
30 |
31 | public SelectedLanguages (Context context)
32 | {
33 | SharedPreferences prefs =
34 | context.getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
35 | Activity.MODE_PRIVATE);
36 | String languagesString = prefs.getString (PREF, null);
37 |
38 | if (languagesString != null)
39 | {
40 | int length = languagesString.length ();
41 |
42 | if (length == 0)
43 | languages = new String[0];
44 | else
45 | {
46 | int commas = 0;
47 | int i;
48 |
49 | for (i = 0; i < length; i++)
50 | if (languagesString.charAt (i) == ',')
51 | commas++;
52 |
53 | languages = new String[commas + 1];
54 | int lastPos = 0;
55 |
56 | for (i = 0; i < commas; i++)
57 | {
58 | int commaPos = languagesString.indexOf (',', lastPos);
59 | languages[i] = languagesString.substring (lastPos, commaPos);
60 | lastPos = commaPos + 1;
61 | }
62 |
63 | languages[commas] = languagesString.substring (lastPos);
64 |
65 | Arrays.sort (languages);
66 | }
67 | }
68 | }
69 |
70 | public boolean contains (String language)
71 | {
72 | if (containsAll ())
73 | return true;
74 |
75 | /* Esperanto is always included */
76 | if (language.equals ("eo"))
77 | return true;
78 |
79 | return Arrays.binarySearch (languages, language) >= 0;
80 | }
81 |
82 | public boolean containsAll ()
83 | {
84 | /* null is used to indicate that we want all languages */
85 | return languages == null;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 | PReVo
22 | Elekti lingvon
23 | Serĉi
24 | Agordoj
25 | Neniu vorto troviĝis
26 | Neniu vorto troviĝis en %1$s, jen vortoj en %2$s
27 | Tajpi ion por serĉi
28 | Ĉefaj lingvoj
29 | Ĉiuj lingvoj
30 | Elekti lingvon
31 | Serĉi
32 | Pri
33 | Kopii difinon
34 | Krei fulmkarton, difino→vorto
35 | Krei fulmkarton, vorto→difino
36 | Serĉi vorton ĉe Vortaro.net
37 | Zomi la tekston
38 | Agordoj
39 | Serĉi en %s
40 | Mankas aplikaĵo kiu povas krei fulmkarton. Eble vi volos instali “Anki”; almenaŭ version 2.0.
41 | Difino
42 | Fermi
43 | Montrataj tradukoj
44 | Montri ĉiujn tradukojn
45 | Montri neniun tradukon
46 |
47 | PReVo @VERSION@\n
48 | \nCopyright © 2012, 2013, 2016, 2017, 2018, 2019, 2020 Neil Roberts\n
49 | \nLa datumoj por la vortaro venas de Reta Vortaro.\n
50 | \nThis program comes with ABSOLUTELY NO WARRANTY. This is free
51 | software and you are welcome to redistribute it under certain
52 | conditions. Click here for details.\n
53 | \nPolitiko de privateco
54 |
55 |
56 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/BinaryReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import java.io.InputStream;
21 | import java.io.IOException;
22 |
23 | public class BinaryReader
24 | {
25 | private long position = 0;
26 | private InputStream in;
27 | private byte numberBuffer[] = new byte[4];
28 |
29 | public BinaryReader (InputStream in)
30 | {
31 | this.in = in;
32 | }
33 |
34 | private static void throwEOF ()
35 | throws IOException
36 | {
37 | throw new IOException ("Unexpected EOF");
38 | }
39 |
40 | public void readAll (byte[] array,
41 | int offset,
42 | int length)
43 | throws IOException
44 | {
45 | while (length > 0)
46 | {
47 | int got = in.read (array, offset, length);
48 |
49 | if (got == -1)
50 | throwEOF ();
51 |
52 | position += got;
53 |
54 | offset += got;
55 | length -= got;
56 | }
57 | }
58 |
59 | public int readByte ()
60 | throws IOException
61 | {
62 | int ret = in.read ();
63 |
64 | if (ret == -1)
65 | throwEOF ();
66 |
67 | position += 1;
68 |
69 | return ret;
70 | }
71 |
72 | public void readAll (byte[] array)
73 | throws IOException
74 | {
75 | readAll (array, 0, array.length);
76 | }
77 |
78 | public int readShort ()
79 | throws IOException
80 | {
81 | readAll (numberBuffer, 0, 2);
82 |
83 | return (numberBuffer[0] & 0xff) | ((numberBuffer[1] & 0xff) << 8);
84 | }
85 |
86 | public int readInt ()
87 | throws IOException
88 | {
89 | readAll (numberBuffer, 0, 4);
90 |
91 | return ((numberBuffer[0] & 0xff) |
92 | ((numberBuffer[1] & 0xff) << 8) |
93 | ((numberBuffer[2] & 0xff) << 16) |
94 | ((numberBuffer[3] & 0xff) << 24));
95 | }
96 |
97 | public long getPosition ()
98 | {
99 | return position;
100 | }
101 |
102 | public void skip (long byteCount)
103 | throws IOException
104 | {
105 | long skipped;
106 |
107 | /* First try skipping with the input stream's method */
108 | skipped = in.skip (byteCount);
109 | position += skipped;
110 | byteCount -= skipped;
111 |
112 | /* If that doesn't work we'll try manually reading into a buffer */
113 | if (byteCount > 0)
114 | {
115 | byte buf[] = new byte[1024];
116 |
117 | do
118 | {
119 | int toRead;
120 |
121 | if (buf.length < byteCount)
122 | toRead = buf.length;
123 | else
124 | toRead = (int) byteCount;
125 |
126 | readAll (buf, 0, toRead);
127 |
128 | byteCount -= toRead;
129 | position += toRead;
130 | }
131 | while (byteCount > 0);
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/LanguageDatabaseHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import android.database.Cursor;
22 | import android.database.sqlite.SQLiteDatabase;
23 | import android.database.sqlite.SQLiteOpenHelper;
24 | import android.util.Log;
25 |
26 | public class LanguageDatabaseHelper extends SQLiteOpenHelper
27 | {
28 | static private final String DATABASE_NAME = "language";
29 | static private final int DATABASE_VERSION = 1;
30 | static private final String TABLE_CREATE =
31 | "create table `language` " +
32 | "(`code` char(3) primary key not null, " +
33 | "`usage_count` integer not null default 0)";
34 | static private final int MAX_RESULTS = 2;
35 |
36 | static private final String TAG = "prevo";
37 |
38 | private Context context;
39 |
40 | public LanguageDatabaseHelper (Context context)
41 | {
42 | super (context, DATABASE_NAME, null, DATABASE_VERSION);
43 |
44 | this.context = context;
45 | }
46 |
47 | @Override
48 | public void onCreate (SQLiteDatabase db)
49 | {
50 | db.execSQL (TABLE_CREATE);
51 | }
52 |
53 | @Override
54 | public void onUpgrade (SQLiteDatabase db,
55 | int oldVersion,
56 | int newVersion)
57 | {
58 | Log.wtf (TAG, "Unexpected database upgrade requested from " + oldVersion +
59 | "to " + newVersion);
60 | }
61 |
62 | public String[] getLanguages ()
63 | {
64 | SQLiteDatabase db = getReadableDatabase ();
65 |
66 | Cursor cursor = db.query ("language",
67 | new String[] { "code" },
68 | "`code` != 'eo'", /* where clause */
69 | null, /* selection args */
70 | null, /* group by */
71 | null, /* having */
72 | "`usage_count` desc", /* order by */
73 | Integer.toString (MAX_RESULTS));
74 |
75 | String[] results = new String[cursor.getCount ()];
76 | int i = 0;
77 |
78 | for (cursor.moveToFirst (); !cursor.isAfterLast (); cursor.moveToNext ())
79 | results[i++] = cursor.getString (0);
80 |
81 | cursor.close ();
82 |
83 | db.close ();
84 |
85 | return results;
86 | }
87 |
88 | public void useLanguage (String code)
89 | {
90 | SQLiteDatabase db = getWritableDatabase ();
91 |
92 | db.execSQL ("insert or ignore into `language` " +
93 | "(`code`, `usage_count`) values " +
94 | "(?, ?)",
95 | new Object[] { code, 0 });
96 | db.execSQL ("update `language` set `usage_count` = `usage_count` + 1 " +
97 | "where `code` = ?",
98 | new Object[] { code });
99 |
100 | db.close ();
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/PreferenceAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import android.content.SharedPreferences;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.BaseAdapter;
26 | import android.widget.TextView;
27 |
28 | public class PreferenceAdapter extends BaseAdapter
29 | {
30 | private Language[] allLanguages;
31 |
32 | private Context context;
33 |
34 | private static final int TYPE_HEADER = 0;
35 | private static final int TYPE_LANGUAGE = 1;
36 | private static final int TYPE_ACTION = 2;
37 | private static final int TYPE_COUNT = 3;
38 |
39 | public static final int FIRST_LANGUAGE_POSITION = 3;
40 |
41 | public PreferenceAdapter (Context context)
42 | {
43 | this.context = context;
44 |
45 | LanguageList languageList = LanguageList.getDefault (context);
46 |
47 | /* Grab a list of all languages except Esperanto because that
48 | * isn't a translation */
49 | Language[] allLanguages = languageList.getAllLanguages ();
50 | this.allLanguages = new Language[allLanguages.length - 1];
51 |
52 | for (int src = 0, dst = 0; src < allLanguages.length; src++)
53 | if (!allLanguages[src].getCode ().equals ("eo"))
54 | this.allLanguages[dst++] = allLanguages[src];
55 | }
56 |
57 | @Override
58 | public int getCount ()
59 | {
60 | return allLanguages.length + FIRST_LANGUAGE_POSITION;
61 | }
62 |
63 | @Override
64 | public Object getItem (int position)
65 | {
66 | if (position == 0)
67 | return context.getString (R.string.show_translations);
68 | else if (position == 1)
69 | return context.getString (R.string.show_all_translations);
70 | else if (position == 2)
71 | return context.getString (R.string.show_no_translations);
72 | else
73 | return allLanguages[position - FIRST_LANGUAGE_POSITION];
74 | }
75 |
76 | @Override
77 | public long getItemId (int position)
78 | {
79 | return position;
80 | }
81 |
82 | @Override
83 | public int getItemViewType (int position)
84 | {
85 | if (position == 0)
86 | return TYPE_HEADER;
87 | else if (position == 1 || position == 2)
88 | return TYPE_ACTION;
89 | else
90 | return TYPE_LANGUAGE;
91 | }
92 |
93 | @Override
94 | public int getViewTypeCount ()
95 | {
96 | return TYPE_COUNT;
97 | }
98 |
99 | @Override
100 | public View getView (int position, View convertView, ViewGroup parent)
101 | {
102 | TextView tv;
103 |
104 | if (convertView == null)
105 | {
106 | LayoutInflater layoutInflater = LayoutInflater.from (context);
107 | int id;
108 |
109 | switch (getItemViewType (position))
110 | {
111 | case TYPE_HEADER:
112 | id = android.R.layout.preference_category;
113 | break;
114 |
115 | case TYPE_ACTION:
116 | id = android.R.layout.simple_list_item_1;
117 | break;
118 |
119 | case TYPE_LANGUAGE:
120 | id = android.R.layout.simple_list_item_multiple_choice;
121 | break;
122 |
123 | default:
124 | throw new IllegalStateException ();
125 | }
126 |
127 | tv = (TextView) layoutInflater.inflate (id, parent, false);
128 | }
129 | else
130 | {
131 | tv = (TextView) convertView;
132 | }
133 |
134 | tv.setText (getItem (position).toString ());
135 |
136 | return tv;
137 | }
138 |
139 | @Override
140 | public boolean hasStableIds ()
141 | {
142 | return true;
143 | }
144 |
145 | @Override
146 | public boolean isEmpty ()
147 | {
148 | return false;
149 | }
150 |
151 | @Override
152 | public boolean isEnabled (int position)
153 | {
154 | return getItemViewType (position) != TYPE_HEADER;
155 | }
156 |
157 | @Override
158 | public boolean areAllItemsEnabled ()
159 | {
160 | return false;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/LanguageList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013, 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import java.util.Arrays;
22 | import java.util.Comparator;
23 | import java.util.Vector;
24 | import org.xmlpull.v1.XmlPullParser;
25 | import org.xmlpull.v1.XmlPullParserException;
26 |
27 | public class LanguageList
28 | {
29 | private Language[] languagesByName;
30 | private Language[] languagesByCode;
31 |
32 | private static LanguageList defaultLanguageList;
33 | private LanguageCodeComparator codeComparator;
34 |
35 | public LanguageList (Context context)
36 | {
37 | try
38 | {
39 | XmlPullParser parser;
40 | StringBuilder language = new StringBuilder ();
41 |
42 | Vector languages = new Vector ();
43 |
44 | parser = context.getResources ().getXml (R.xml.languages);
45 |
46 | while (true)
47 | {
48 | int eventType = parser.getEventType ();
49 |
50 | if (eventType == XmlPullParser.START_TAG)
51 | {
52 | if (parser.getName ().equals ("lang"))
53 | {
54 | String code = parser.getAttributeValue (null, "code");
55 |
56 | language.setLength (0);
57 |
58 | while (true)
59 | {
60 | parser.next ();
61 | eventType = parser.getEventType ();
62 | if (eventType == XmlPullParser.END_TAG)
63 | break;
64 | else if (eventType == XmlPullParser.TEXT)
65 | language.append (parser.getText ());
66 | }
67 |
68 | languages.add (new Language (language.toString (),
69 | code));
70 | }
71 | }
72 | else if (eventType == XmlPullParser.END_DOCUMENT)
73 | break;
74 |
75 | parser.next ();
76 | }
77 |
78 | languagesByName = languages.toArray (new Language[languages.size ()]);
79 | }
80 | catch (XmlPullParserException e)
81 | {
82 | throw new IllegalStateException (e);
83 | }
84 | catch (java.io.IOException e)
85 | {
86 | throw new IllegalStateException (e);
87 | }
88 |
89 | codeComparator = new LanguageCodeComparator ();
90 |
91 | languagesByCode = new Language[languagesByName.length];
92 | for (int i = 0; i < languagesByName.length; i++)
93 | languagesByCode[i] = languagesByName[i];
94 |
95 | Arrays.sort (languagesByCode, codeComparator);
96 | }
97 |
98 | public static LanguageList getDefault (Context context)
99 | {
100 | if (defaultLanguageList == null)
101 | defaultLanguageList = new LanguageList (context);
102 |
103 | return defaultLanguageList;
104 | }
105 |
106 | public Language[] getAllLanguages ()
107 | {
108 | return languagesByName;
109 | }
110 |
111 | public String getLanguageName (String languageCode,
112 | boolean withArticle)
113 | {
114 | if (languageCode.equals ("eo"))
115 | return "esperanto";
116 |
117 | int lang = Arrays.binarySearch (languagesByCode,
118 | new Language ("", languageCode),
119 | codeComparator);
120 |
121 | if (lang < 0)
122 | return languageCode;
123 |
124 | String name = languagesByCode[lang].getName ();
125 |
126 | if (withArticle)
127 | return "la " + name;
128 | else
129 | return name;
130 | }
131 |
132 | public String getLanguageName (String languageCode)
133 | {
134 | return getLanguageName (languageCode, false);
135 | }
136 |
137 | private static class LanguageCodeComparator implements Comparator
138 | {
139 | public int compare (Language a,
140 | Language b)
141 | {
142 | return a.getCode ().compareTo (b.getCode ());
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/SelectLanguageActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013, 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Activity;
21 | import android.app.Dialog;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.content.res.Resources;
25 | import android.os.Bundle;
26 | import android.support.v7.app.AppCompatActivity;
27 | import android.text.method.LinkMovementMethod;
28 | import android.view.LayoutInflater;
29 | import android.view.Menu;
30 | import android.view.MenuInflater;
31 | import android.view.MenuItem;
32 | import android.view.View;
33 | import android.widget.AdapterView;
34 | import android.widget.ListView;
35 | import android.widget.TextView;
36 |
37 | public class SelectLanguageActivity extends AppCompatActivity
38 | implements SharedPreferences.OnSharedPreferenceChangeListener
39 | {
40 | private LanguageDatabaseHelper dbHelper;
41 | private LanguagesAdapter adapter;
42 |
43 | private boolean stopped;
44 | private boolean reloadQueued;
45 |
46 | @Override
47 | public void onCreate (Bundle savedInstanceState)
48 | {
49 | super.onCreate (savedInstanceState);
50 | setTitle (R.string.select_language);
51 | setContentView (R.layout.languages);
52 |
53 | ListView lv = (ListView) findViewById(R.id.list);
54 |
55 | adapter = new LanguagesAdapter (this);
56 | lv.setAdapter (adapter);
57 |
58 | dbHelper = new LanguageDatabaseHelper (this);
59 |
60 | stopped = true;
61 | reloadQueued = true;
62 |
63 | lv.setTextFilterEnabled (true);
64 |
65 | lv.setOnItemClickListener (new AdapterView.OnItemClickListener ()
66 | {
67 | public void onItemClick (AdapterView> parent,
68 | View view,
69 | int position,
70 | long id)
71 | {
72 | LanguagesAdapter adapter =
73 | (LanguagesAdapter) parent.getAdapter ();
74 | Object item = adapter.getItem (position);
75 |
76 | if (item instanceof Language)
77 | {
78 | Language lang = (Language) item;
79 | Intent intent =
80 | MenuHelper.createSearchIntent (parent.getContext (),
81 | lang.getCode ());
82 |
83 | startActivity (intent);
84 | }
85 | }
86 | });
87 |
88 | SharedPreferences prefs =
89 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
90 | Activity.MODE_PRIVATE);
91 | prefs.registerOnSharedPreferenceChangeListener (this);
92 | }
93 |
94 | @Override
95 | public void onStart ()
96 | {
97 | super.onStart ();
98 |
99 | stopped = false;
100 |
101 | adapter.setMainLanguages (dbHelper.getLanguages ());
102 |
103 | if (reloadQueued)
104 | {
105 | adapter.reload ();
106 | reloadQueued = false;
107 | }
108 | }
109 |
110 | @Override
111 | public void onStop ()
112 | {
113 | stopped = true;
114 |
115 | super.onStop ();
116 | }
117 |
118 | @Override
119 | public void onDestroy ()
120 | {
121 | SharedPreferences prefs =
122 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
123 | Activity.MODE_PRIVATE);
124 |
125 | prefs.unregisterOnSharedPreferenceChangeListener (this);
126 |
127 | super.onDestroy ();
128 | }
129 |
130 | @Override
131 | public boolean onCreateOptionsMenu (Menu menu)
132 | {
133 | MenuInflater inflater = getMenuInflater ();
134 |
135 | inflater.inflate (R.menu.main_menu, menu);
136 |
137 | return true;
138 | }
139 |
140 | @Override
141 | public boolean onOptionsItemSelected (MenuItem item)
142 | {
143 | if (MenuHelper.onOptionsItemSelected (this, item))
144 | return true;
145 |
146 | return super.onOptionsItemSelected (item);
147 | }
148 |
149 | @Override
150 | protected Dialog onCreateDialog (int id)
151 | {
152 | return MenuHelper.onCreateDialog (this, id);
153 | }
154 |
155 | @Override
156 | public void onSharedPreferenceChanged (SharedPreferences prefs,
157 | String key)
158 | {
159 | if (key.equals (SelectedLanguages.PREF))
160 | {
161 | if (stopped)
162 | /* Queue the reload for the next time the activity is started */
163 | reloadQueued = true;
164 | else
165 | {
166 | adapter.reload ();
167 | reloadQueued = false;
168 | }
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/PreferenceActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2013 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Activity;
21 | import android.app.Dialog;
22 | import android.content.Intent;
23 | import android.content.SharedPreferences;
24 | import android.content.res.Resources;
25 | import android.os.Bundle;
26 | import android.os.Looper;
27 | import android.os.MessageQueue.IdleHandler;
28 | import android.support.v7.app.AppCompatActivity;
29 | import android.text.method.LinkMovementMethod;
30 | import android.util.SparseBooleanArray;
31 | import android.view.LayoutInflater;
32 | import android.view.Menu;
33 | import android.view.MenuInflater;
34 | import android.view.MenuItem;
35 | import android.view.View;
36 | import android.widget.AdapterView;
37 | import android.widget.ListView;
38 | import android.widget.TextView;
39 |
40 | public class PreferenceActivity extends AppCompatActivity
41 | {
42 | private SharedPreferences prefs;
43 | private PreferenceAdapter adapter;
44 | private boolean queuedUpdate;
45 | private ListView listView;
46 |
47 | @Override
48 | public void onCreate (Bundle savedInstanceState)
49 | {
50 | super.onCreate (savedInstanceState);
51 | setContentView (R.layout.preferences);
52 |
53 | listView = (ListView) findViewById (R.id.list);
54 |
55 | adapter = new PreferenceAdapter (this);
56 | listView.setAdapter (adapter);
57 |
58 | prefs = getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
59 | MODE_PRIVATE);
60 |
61 | listView.setItemsCanFocus (false);
62 | listView.setChoiceMode (ListView.CHOICE_MODE_MULTIPLE);
63 |
64 | listView.setOnItemClickListener (new AdapterView.OnItemClickListener ()
65 | {
66 | public void onItemClick (AdapterView> parent,
67 | View view,
68 | int position,
69 | long id)
70 | {
71 | if (position == 1)
72 | setAll (true);
73 | else if (position == 2)
74 | setAll (false);
75 |
76 | /* Update the preference on idle so that the system will have time
77 | * to update the checked state after the click */
78 | queueUpdatePreference ();
79 | }
80 | });
81 |
82 | updateCheckedState ();
83 | }
84 |
85 | private void setAll (boolean value)
86 | {
87 | int numPrefs = adapter.getCount ();
88 |
89 | for (int i = PreferenceAdapter.FIRST_LANGUAGE_POSITION; i < numPrefs; i++)
90 | listView.setItemChecked (i, value);
91 | }
92 |
93 | private void updateCheckedState ()
94 | {
95 | SelectedLanguages selectedLanguages = new SelectedLanguages (this);
96 | int numPrefs = adapter.getCount ();
97 |
98 | for (int i = PreferenceAdapter.FIRST_LANGUAGE_POSITION; i < numPrefs; i++)
99 | {
100 | String language = ((Language) adapter.getItem (i)).getCode ();
101 | listView.setItemChecked (i, selectedLanguages.contains (language));
102 | }
103 | }
104 |
105 | private void updatePreference ()
106 | {
107 | SparseBooleanArray checkedItems = listView.getCheckedItemPositions ();
108 | int totalLanguages =
109 | listView.getCount () - PreferenceAdapter.FIRST_LANGUAGE_POSITION;
110 | int numCheckedLanguages = 0;
111 | String value;
112 |
113 | /* Count the total number of set languages. If it's all or none of
114 | * the languages then we'll use a special value for the
115 | * preference */
116 | for (int i = 0; i < checkedItems.size (); i++)
117 | if (checkedItems.keyAt (i) >= PreferenceAdapter.FIRST_LANGUAGE_POSITION &&
118 | checkedItems.valueAt (i))
119 | numCheckedLanguages++;
120 |
121 | if (numCheckedLanguages == 0)
122 | value = "";
123 | else if (numCheckedLanguages >= totalLanguages)
124 | value = null;
125 | else
126 | {
127 | StringBuilder buf = new StringBuilder ();
128 | int checkedItemsSize = checkedItems.size ();
129 |
130 | for (int i = 0; i < checkedItemsSize; i++)
131 | {
132 | if (checkedItems.valueAt (i))
133 | {
134 | int key = checkedItems.keyAt (i);
135 |
136 | if (key >= PreferenceAdapter.FIRST_LANGUAGE_POSITION)
137 | {
138 | Language lang = (Language) adapter.getItem (key);
139 |
140 | if (buf.length () > 0)
141 | buf.append (',');
142 |
143 | buf.append (lang.getCode ());
144 | }
145 | }
146 | }
147 |
148 | value = buf.toString ();
149 | }
150 |
151 | SharedPreferences.Editor editor = prefs.edit ();
152 | if (value == null)
153 | editor.remove (SelectedLanguages.PREF);
154 | else
155 | editor.putString (SelectedLanguages.PREF, value);
156 |
157 | editor.commit ();
158 | }
159 |
160 | private void queueUpdatePreference ()
161 | {
162 | if (!queuedUpdate)
163 | {
164 | Looper.myQueue ().addIdleHandler (new IdleHandler ()
165 | {
166 | public boolean queueIdle ()
167 | {
168 | updatePreference ();
169 | queuedUpdate = false;
170 | /* Remove the handler */
171 | return false;
172 | }
173 | });
174 |
175 | queuedUpdate = true;
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/MenuHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013, 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Activity;
21 | import android.app.AlertDialog;
22 | import android.app.Dialog;
23 | import android.content.Context;
24 | import android.content.DialogInterface;
25 | import android.content.Intent;
26 | import android.content.res.Resources;
27 | import android.content.SharedPreferences;
28 | import android.content.pm.PackageInfo;
29 | import android.content.pm.PackageManager;
30 | import android.text.method.LinkMovementMethod;
31 | import android.text.SpannableStringBuilder;
32 | import android.text.Spanned;
33 | import android.text.style.URLSpan;
34 | import android.view.LayoutInflater;
35 | import android.view.Menu;
36 | import android.view.MenuInflater;
37 | import android.view.MenuItem;
38 | import android.view.View;
39 | import android.widget.TextView;
40 |
41 | public class MenuHelper
42 | {
43 | private static final String LICENSE_URL =
44 | "http://www.gnu.org/licenses/gpl-2.0-standalone.html";
45 | private static final String RETA_VORTARO_URL =
46 | "http://purl.org/net/voko/revo/";
47 | private static final String PRIVACY_POLICY_URL =
48 | "http://busydoingnothing.co.uk/prevo/privacy-policy.html";
49 | public static final String PREVO_PREFERENCES =
50 | "PrevoPreferences";
51 | public static final String PREF_LAST_LANGUAGE =
52 | "lastLanguage";
53 | public static final String PREF_FONT_SIZE =
54 | "fontSize";
55 |
56 | private static final int DIALOG_ABOUT = 0;
57 |
58 | private static void linkifyAboutMessage (Context context,
59 | SpannableStringBuilder string)
60 | {
61 | int pos;
62 |
63 | if ((pos = string.toString ().indexOf ("@VERSION@")) != -1)
64 | {
65 | String packageVersion;
66 |
67 | try
68 | {
69 | PackageManager manager = context.getPackageManager ();
70 | String packageName = context.getPackageName ();
71 | PackageInfo packageInfo = manager.getPackageInfo (packageName, 0);
72 |
73 | packageVersion = packageInfo.versionName;
74 | }
75 | catch (PackageManager.NameNotFoundException e)
76 | {
77 | packageVersion = "?";
78 | }
79 |
80 | string.replace (pos, pos + 9, packageVersion);
81 | }
82 |
83 | if ((pos = string.toString ().indexOf ("Click here for")) != -1)
84 | {
85 | URLSpan span = new URLSpan (LICENSE_URL);
86 | string.setSpan (span, pos + 6, pos + 10, 0 /* flags */);
87 | }
88 |
89 | if ((pos = string.toString ().indexOf ("Reta Vortaro")) != -1)
90 | {
91 | URLSpan span = new URLSpan (RETA_VORTARO_URL);
92 | string.setSpan (span, pos, pos + 12, 0 /* flags */);
93 | }
94 |
95 | if ((pos = string.toString ().indexOf ("Politiko de privateco")) != -1)
96 | {
97 | URLSpan span = new URLSpan (PRIVACY_POLICY_URL);
98 | string.setSpan (span, pos, pos + 21, 0 /* flags */);
99 | }
100 | }
101 |
102 | public static Dialog onCreateDialog (Activity activity,
103 | int id)
104 | {
105 | Dialog dialog;
106 | Resources res = activity.getResources ();
107 |
108 | switch (id)
109 | {
110 | case DIALOG_ABOUT:
111 | {
112 | AlertDialog.Builder builder = new AlertDialog.Builder (activity);
113 | SpannableStringBuilder message =
114 | new SpannableStringBuilder (res.getText (R.string.about_message));
115 |
116 | linkifyAboutMessage (activity, message);
117 |
118 | LayoutInflater layoutInflater = activity.getLayoutInflater ();
119 | TextView tv =
120 | (TextView) layoutInflater.inflate (R.layout.about_view,
121 | null);
122 | tv.setText (message);
123 | tv.setMovementMethod (LinkMovementMethod.getInstance ());
124 |
125 | builder
126 | .setView (tv)
127 | .setCancelable (true)
128 | .setNegativeButton (R.string.close,
129 | new DialogInterface.OnClickListener ()
130 | {
131 | @Override
132 | public void onClick (DialogInterface dialog,
133 | int whichButton)
134 | {
135 | }
136 | });
137 | dialog = builder.create ();
138 | }
139 | break;
140 |
141 | default:
142 | dialog = null;
143 | break;
144 | }
145 |
146 | return dialog;
147 | }
148 |
149 | public static Intent createSearchIntent (Context context,
150 | String language)
151 | {
152 | Intent intent = new Intent (context, SearchActivity.class);
153 |
154 | intent.putExtra (SearchActivity.EXTRA_LANGUAGE, language);
155 | intent.putExtra (SearchActivity.EXTRA_USE_LANGUAGE, true);
156 |
157 | return intent;
158 | }
159 |
160 | public static void goSearch (Context context)
161 | {
162 | SharedPreferences prefs =
163 | context.getSharedPreferences (PREVO_PREFERENCES,
164 | Context.MODE_PRIVATE);
165 | String defaultLanguage = prefs.getString (PREF_LAST_LANGUAGE, "eo");
166 | Intent intent = new Intent (context, SearchActivity.class);
167 | intent.putExtra (SearchActivity.EXTRA_LANGUAGE, defaultLanguage);
168 |
169 | context.startActivity (intent);
170 | }
171 |
172 | public static void goChooseLanguage (Context context)
173 | {
174 | Intent intent = new Intent (context, SelectLanguageActivity.class);
175 | context.startActivity (intent);
176 | }
177 |
178 | public static void showAbout (Activity activity)
179 | {
180 | activity.showDialog (DIALOG_ABOUT);
181 | }
182 |
183 | public static void goPreferences (Context context)
184 | {
185 | Intent intent = new Intent (context, PreferenceActivity.class);
186 | context.startActivity (intent);
187 | }
188 |
189 | public static boolean onOptionsItemSelected (Activity activity,
190 | MenuItem item)
191 | {
192 | switch (item.getItemId ())
193 | {
194 | case R.id.menu_choose_language:
195 | goChooseLanguage (activity);
196 | return true;
197 |
198 | case R.id.menu_search:
199 | goSearch (activity);
200 | return true;
201 |
202 | case R.id.menu_preferences:
203 | goPreferences (activity);
204 | return true;
205 |
206 | case R.id.menu_about:
207 | showAbout (activity);
208 | return true;
209 | }
210 |
211 | return false;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/SearchAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import android.content.res.Resources;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.BaseAdapter;
26 | import android.widget.Filter;
27 | import android.widget.Filterable;
28 | import android.widget.TextView;
29 | import java.util.Locale;
30 |
31 | class SearchResultData
32 | {
33 | public SearchResult[] results;
34 | public int count;
35 | public int languageNum;
36 | }
37 |
38 | public class SearchAdapter extends BaseAdapter
39 | implements Filterable
40 | {
41 | static private final int MAX_RESULTS = 128;
42 |
43 | private Context context;
44 | private SearchFilter filter;
45 |
46 | private SearchResult noteResult;
47 | private SearchResult[] results;
48 | private int languageNum;
49 | private int numResults = 0;
50 |
51 | private String[] languages;
52 |
53 | public SearchAdapter (Context context,
54 | String[] languages)
55 | {
56 | this.context = context;
57 | this.languages = languages;
58 |
59 | setResultData (doSearch (""));
60 | }
61 |
62 | private void setResultData (SearchResultData resultData)
63 | {
64 | results = resultData.results;
65 | languageNum = resultData.languageNum;
66 | numResults = resultData.count;
67 |
68 | if (languageNum > 0)
69 | {
70 | LanguageList languageList = LanguageList.getDefault (context);
71 | String mainLanguage =
72 | languageList.getLanguageName (languages[0],
73 | true /* with article */);
74 | String otherLanguage =
75 | languageList.getLanguageName (languages[languageNum],
76 | true /* with article */);
77 | Resources resources = context.getResources ();
78 | String note = resources.getString (R.string.other_language_note,
79 | mainLanguage,
80 | otherLanguage);
81 | noteResult = new SearchResult (note,
82 | Integer.MAX_VALUE,
83 | Integer.MAX_VALUE);
84 | }
85 | }
86 |
87 | private SearchResultData doSearch(String filterString)
88 | {
89 | SearchResultData resultData = new SearchResultData ();
90 |
91 | resultData.results = new SearchResult[MAX_RESULTS];
92 |
93 | try
94 | {
95 | for (int i = 0; i < languages.length; i++)
96 | {
97 | String language = languages[i];
98 | Trie trie = TrieCache.getTrie (context, language);
99 | int numResults = trie.search (filterString, resultData.results);
100 |
101 | if (numResults > 0)
102 | {
103 | resultData.count = numResults;
104 | resultData.languageNum = i;
105 | return resultData;
106 | }
107 | }
108 |
109 | resultData.languageNum = 0;
110 | resultData.count = 0;
111 | return resultData;
112 | }
113 | catch (java.io.IOException e)
114 | {
115 | throw new IllegalStateException ("Error while loading " +
116 | "an asset");
117 | }
118 | }
119 |
120 | @Override
121 | public int getCount ()
122 | {
123 | if (languageNum > 0)
124 | return numResults + 1;
125 | else
126 | return numResults;
127 | }
128 |
129 | @Override
130 | public SearchResult getItem (int position)
131 | {
132 | if (languageNum > 0)
133 | {
134 | if (position == 0)
135 | return noteResult;
136 |
137 | position--;
138 | }
139 |
140 | return results[position];
141 | }
142 |
143 | @Override
144 | public long getItemId (int position)
145 | {
146 | SearchResult result = getItem (position);
147 | return (result.getArticle () << 32L) | result.getMark ();
148 | }
149 |
150 | @Override
151 | public int getItemViewType (int position)
152 | {
153 | if (languageNum == 0 || position > 0)
154 | return 1;
155 | else
156 | return 0;
157 | }
158 |
159 | @Override
160 | public int getViewTypeCount ()
161 | {
162 | return 2;
163 | }
164 |
165 | @Override
166 | public View getView (int position, View convertView, ViewGroup parent)
167 | {
168 | TextView tv;
169 |
170 | if (convertView == null)
171 | {
172 | LayoutInflater layoutInflater = LayoutInflater.from (context);
173 | int id;
174 | if (languageNum == 0 || position > 0)
175 | id = android.R.layout.simple_list_item_1;
176 | else
177 | id = R.layout.search_note;
178 | tv = (TextView) layoutInflater.inflate (id, parent, false);
179 | }
180 | else
181 | tv = (TextView) convertView;
182 |
183 | tv.setText (getItem (position).toString ());
184 |
185 | return tv;
186 | }
187 |
188 | @Override
189 | public boolean hasStableIds ()
190 | {
191 | return false;
192 | }
193 |
194 | @Override
195 | public boolean isEmpty ()
196 | {
197 | return numResults == 0;
198 | }
199 |
200 | @Override
201 | public boolean isEnabled (int position)
202 | {
203 | /* If a language other than the primary one was used to return
204 | * results then the first item will be a note explaining it and it
205 | * shouldn't be enabled. */
206 | return languageNum == 0 || position > 0;
207 | }
208 |
209 | @Override
210 | public boolean areAllItemsEnabled ()
211 | {
212 | return false;
213 | }
214 |
215 | @Override
216 | public Filter getFilter ()
217 | {
218 | if (filter == null)
219 | filter = new SearchFilter ();
220 |
221 | return filter;
222 | }
223 |
224 | private static CharSequence trimCharSequence (CharSequence seq)
225 | {
226 | int length = seq.length ();
227 |
228 | int start;
229 |
230 | for (start = 0; start < length; start++)
231 | {
232 | if (!Character.isWhitespace (seq.charAt (start)))
233 | break;
234 | }
235 |
236 | int end;
237 |
238 | for (end = length; end > start; end--)
239 | {
240 | if (!Character.isWhitespace (seq.charAt (end - 1)))
241 | break;
242 | }
243 |
244 | return seq.subSequence (start, end);
245 | }
246 |
247 | private class SearchFilter extends Filter
248 | {
249 | @Override
250 | public FilterResults performFiltering (CharSequence filter)
251 | {
252 | FilterResults ret = new FilterResults ();
253 | CharSequence trimmedFilter = trimCharSequence (filter);
254 | String hatlessFilter = Hats.removeHats (trimmedFilter);
255 | String lowercaseFilter = hatlessFilter.toLowerCase (Locale.ROOT);
256 | SearchResultData resultData = doSearch (lowercaseFilter);
257 |
258 | ret.count = resultData.count;
259 | ret.values = resultData;
260 |
261 | return ret;
262 | }
263 |
264 | @Override
265 | public void publishResults (CharSequence filter, FilterResults res)
266 | {
267 | setResultData ((SearchResultData) res.values);
268 |
269 | if (res.count > 0)
270 | notifyDataSetChanged();
271 | else
272 | notifyDataSetInvalidated();
273 | }
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/LanguagesAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.content.Context;
21 | import android.content.SharedPreferences;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.BaseAdapter;
26 | import android.widget.Filter;
27 | import android.widget.Filterable;
28 | import android.widget.TextView;
29 | import java.util.Vector;
30 |
31 | public class LanguagesAdapter extends BaseAdapter
32 | implements Filterable
33 | {
34 | private Language[] mainLanguages =
35 | {
36 | new Language ("esperanto", "eo")
37 | };
38 |
39 | private LanguageList languageList;
40 | private Language[] selectedLanguages;
41 |
42 | private Context context;
43 | private LanguagesFilter filter;
44 | private Language[] filteredLanguages;
45 |
46 | static final int TYPE_HEADER = 0;
47 | static final int TYPE_LANGUAGE = 1;
48 | static final int TYPE_COUNT = 2;
49 |
50 | public LanguagesAdapter (Context context)
51 | {
52 | this.context = context;
53 | this.languageList = LanguageList.getDefault (context);
54 | this.selectedLanguages = getSelectedLanguages ();
55 | }
56 |
57 | private Language[] getSelectedLanguages ()
58 | {
59 | SelectedLanguages selectedLanguages = new SelectedLanguages (context);
60 | Language[] allLanguages = languageList.getAllLanguages ();
61 |
62 | if (selectedLanguages.containsAll ())
63 | return allLanguages;
64 |
65 | Vector languages = new Vector ();
66 |
67 | for (int i = 0; i < allLanguages.length; i++)
68 | if (selectedLanguages.contains (allLanguages[i].getCode ()))
69 | languages.add (allLanguages[i]);
70 |
71 | return languages.toArray (new Language[languages.size ()]);
72 | }
73 |
74 | @Override
75 | public int getCount ()
76 | {
77 | if (filteredLanguages == null)
78 | return mainLanguages.length + selectedLanguages.length + 2;
79 | else
80 | return filteredLanguages.length + 1;
81 | }
82 |
83 | @Override
84 | public Object getItem (int position)
85 | {
86 | if (filteredLanguages == null)
87 | {
88 | if (position == 0)
89 | return context.getString (R.string.main_languages);
90 |
91 | position--;
92 |
93 | if (position < mainLanguages.length)
94 | return mainLanguages[position];
95 |
96 | position -= mainLanguages.length;
97 |
98 | if (position == 0)
99 | return context.getString (R.string.all_languages);
100 |
101 | return selectedLanguages[position - 1];
102 | }
103 | else if (position == 0)
104 | return context.getString (R.string.all_languages);
105 | else
106 | return filteredLanguages[position - 1];
107 | }
108 |
109 | @Override
110 | public long getItemId (int position)
111 | {
112 | return position;
113 | }
114 |
115 | @Override
116 | public int getItemViewType (int position)
117 | {
118 | if (filteredLanguages == null)
119 | {
120 | if (position == 0 || position == mainLanguages.length + 1)
121 | return TYPE_HEADER;
122 | else
123 | return TYPE_LANGUAGE;
124 | }
125 | else if (position == 0)
126 | return TYPE_HEADER;
127 | else
128 | return TYPE_LANGUAGE;
129 | }
130 |
131 | @Override
132 | public int getViewTypeCount ()
133 | {
134 | return TYPE_COUNT;
135 | }
136 |
137 | @Override
138 | public View getView (int position, View convertView, ViewGroup parent)
139 | {
140 | TextView tv;
141 |
142 | if (convertView == null)
143 | {
144 | LayoutInflater layoutInflater = LayoutInflater.from (context);
145 | int id;
146 |
147 | switch (getItemViewType (position))
148 | {
149 | case TYPE_HEADER:
150 | id = android.R.layout.preference_category;
151 | break;
152 |
153 | case TYPE_LANGUAGE:
154 | id = android.R.layout.simple_list_item_1;
155 | break;
156 |
157 | default:
158 | throw new IllegalStateException ();
159 | }
160 |
161 | tv = (TextView) layoutInflater.inflate (id, parent, false);
162 | }
163 | else
164 | {
165 | tv = (TextView) convertView;
166 | }
167 |
168 | tv.setText (getItem (position).toString ());
169 |
170 | return tv;
171 | }
172 |
173 | @Override
174 | public boolean hasStableIds ()
175 | {
176 | return false;
177 | }
178 |
179 | @Override
180 | public boolean isEmpty ()
181 | {
182 | return false;
183 | }
184 |
185 | @Override
186 | public boolean isEnabled (int position)
187 | {
188 | return getItemViewType (position) == TYPE_LANGUAGE;
189 | }
190 |
191 | @Override
192 | public boolean areAllItemsEnabled ()
193 | {
194 | return false;
195 | }
196 |
197 | @Override
198 | public LanguagesFilter getFilter ()
199 | {
200 | if (filter == null)
201 | filter = new LanguagesFilter (selectedLanguages);
202 |
203 | return filter;
204 | }
205 |
206 | public void setMainLanguages (String[] languages)
207 | {
208 | Language[] mainLanguages = new Language[languages.length + 1];
209 |
210 | /* Preserve the 'esperanto' language */
211 | mainLanguages[0] = this.mainLanguages[0];
212 |
213 | for (int i = 0; i < languages.length; i++)
214 | {
215 | String languageName = languageList.getLanguageName (languages[i]);
216 | mainLanguages[i + 1] = new Language (languageName, languages[i]);
217 | }
218 |
219 | this.mainLanguages = mainLanguages;
220 |
221 | notifyDataSetChanged ();
222 | }
223 |
224 | public void reload ()
225 | {
226 | this.selectedLanguages = getSelectedLanguages ();
227 | notifyDataSetChanged ();
228 | }
229 |
230 | private class LanguagesFilter extends Filter
231 | {
232 | private Language[] selectedLanguages;
233 |
234 | public LanguagesFilter (Language[] selectedLanguages)
235 | {
236 | /* We keep a copy of the languages array so that we don't have
237 | * to worry about it being replaced in the main thread */
238 | this.selectedLanguages = selectedLanguages;
239 | }
240 |
241 | @Override
242 | public FilterResults performFiltering (CharSequence filter)
243 | {
244 | FilterResults ret = new FilterResults ();
245 |
246 | if (filter.length () == 0)
247 | {
248 | ret.values = null;
249 | ret.count = 0;
250 | }
251 | else
252 | {
253 | String filterString = Hats.removeHats (filter);
254 | Vector result = new Vector ();
255 |
256 | for (int i = 0; i < selectedLanguages.length; i++)
257 | {
258 | Language language = selectedLanguages[i];
259 |
260 | if (language.getName ().startsWith (filterString))
261 | result.add (language);
262 | }
263 |
264 |
265 | ret.values = result.toArray (new Language[result.size ()]);
266 | ret.count = result.size ();
267 | }
268 |
269 | return ret;
270 | }
271 |
272 | @Override
273 | public void publishResults (CharSequence filter, FilterResults results)
274 | {
275 | filteredLanguages = (Language[]) results.values;
276 | notifyDataSetChanged ();
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/SearchActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013, 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Dialog;
21 | import android.content.Intent;
22 | import android.content.SharedPreferences;
23 | import android.content.res.Resources;
24 | import android.os.Bundle;
25 | import android.support.v7.app.AppCompatActivity;
26 | import android.text.Editable;
27 | import android.text.TextWatcher;
28 | import android.util.Log;
29 | import android.view.inputmethod.InputMethodManager;
30 | import android.view.Menu;
31 | import android.view.MenuInflater;
32 | import android.view.MenuItem;
33 | import android.view.View;
34 | import android.widget.AdapterView;
35 | import android.widget.EditText;
36 | import android.widget.ListView;
37 | import android.widget.TextView;
38 | import java.util.Vector;
39 |
40 | public class SearchActivity extends AppCompatActivity
41 | implements TextWatcher
42 | {
43 | public static final String EXTRA_LANGUAGE =
44 | "uk.co.busydoingnothing.prevo.Language";
45 | public static final String EXTRA_SEARCH_TERM =
46 | "uk.co.busydoingnothing.prevo.SearchTerm";
47 | public static final String EXTRA_USE_LANGUAGE =
48 | "uk.co.busydoingnothing.prevo.UseLanguage";
49 |
50 | public static final String TAG = "prevosearch";
51 |
52 | private SearchAdapter searchAdapter;
53 | private String[] searchLanguages;
54 |
55 | private LanguageDatabaseHelper dbHelper;
56 |
57 | private boolean clearButtonVisible = false;
58 |
59 | @Override
60 | public void onCreate (Bundle savedInstanceState)
61 | {
62 | super.onCreate (savedInstanceState);
63 | setContentView (R.layout.search);
64 |
65 | ListView lv = (ListView) findViewById(R.id.list);
66 | lv.setEmptyView(findViewById(R.id.empty));
67 |
68 | dbHelper = new LanguageDatabaseHelper (this);
69 |
70 | updateSearchLanguages ();
71 | useLanguage ();
72 |
73 | TextView tv = (TextView) findViewById (R.id.search_edit);
74 | tv.addTextChangedListener (this);
75 |
76 | if (searchLanguages.length > 0)
77 | setTitle (getTitle () + " [" + searchLanguages[0] + "]");
78 |
79 | searchAdapter = new SearchAdapter (this, searchLanguages);
80 |
81 | lv.setAdapter (searchAdapter);
82 |
83 | Intent intent = getIntent ();
84 | if (intent != null)
85 | {
86 | String searchTerm = intent.getStringExtra (EXTRA_SEARCH_TERM);
87 |
88 | if (searchTerm != null)
89 | tv.setText (searchTerm);
90 | }
91 |
92 | lv.setOnItemClickListener (new AdapterView.OnItemClickListener ()
93 | {
94 | public void onItemClick (AdapterView> parent,
95 | View view,
96 | int position,
97 | long id)
98 | {
99 | SearchAdapter adapter =
100 | (SearchAdapter) parent.getAdapter ();
101 | SearchResult result = adapter.getItem (position);
102 | Intent intent = new Intent (view.getContext (),
103 | ArticleActivity.class);
104 | intent.putExtra (ArticleActivity.EXTRA_ARTICLE_NUMBER,
105 | result.getArticle ());
106 | intent.putExtra (ArticleActivity.EXTRA_MARK_NUMBER,
107 | result.getMark ());
108 | startActivity (intent);
109 | }
110 | });
111 |
112 | setUpClearSearchButton ();
113 | }
114 |
115 | private void useLanguage ()
116 | {
117 | if (searchLanguages.length <= 0)
118 | return;
119 |
120 | Intent intent = getIntent ();
121 | if (intent == null)
122 | return;
123 |
124 | if (!intent.getBooleanExtra (EXTRA_USE_LANGUAGE, false))
125 | return;
126 |
127 | dbHelper.useLanguage (searchLanguages[0]);
128 |
129 | SharedPreferences prefs =
130 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
131 | MODE_PRIVATE);
132 | SharedPreferences.Editor editor = prefs.edit ();
133 | editor.putString (MenuHelper.PREF_LAST_LANGUAGE, searchLanguages[0]);
134 | editor.commit ();
135 | }
136 |
137 | private void updateSearchLanguages ()
138 | {
139 | Vector searchLanguagesVector = new Vector ();
140 | String mainLanguage = null;
141 | Intent intent = getIntent ();
142 |
143 | if (intent != null)
144 | {
145 | mainLanguage = intent.getStringExtra (EXTRA_LANGUAGE);
146 |
147 | if (mainLanguage != null)
148 | searchLanguagesVector.add (mainLanguage);
149 | }
150 |
151 | if (mainLanguage == null || !mainLanguage.equals ("eo"))
152 | searchLanguagesVector.add ("eo");
153 |
154 | for (String language : dbHelper.getLanguages ())
155 | {
156 | if (mainLanguage == null || !mainLanguage.equals (language))
157 | {
158 | searchLanguagesVector.add (language);
159 | if (searchLanguagesVector.size () >= 3)
160 | break;
161 | }
162 | }
163 |
164 | searchLanguages = new String[searchLanguagesVector.size ()];
165 | searchLanguages = searchLanguagesVector.toArray (searchLanguages);
166 | }
167 |
168 | private void setUpClearSearchButton ()
169 | {
170 | View clearButton = findViewById (R.id.clear_search_button);
171 |
172 | clearButton.setOnClickListener (new View.OnClickListener ()
173 | {
174 | public void onClick (View v)
175 | {
176 | EditText et = findViewById (R.id.search_edit);
177 | et.setText ("");
178 | focusSearchEdit ();
179 | }
180 | });
181 | }
182 |
183 | @Override
184 | public void onStart ()
185 | {
186 | super.onStart ();
187 |
188 | focusSearchEdit ();
189 | }
190 |
191 | private void addLanguageMenuItem (Menu menu,
192 | String language)
193 | {
194 | Resources resources = getResources ();
195 | LanguageList languageList = LanguageList.getDefault (this);
196 | String languageName =
197 | languageList.getLanguageName (language,
198 | true /* with article */);
199 | String label = resources.getString (R.string.menu_search_language,
200 | languageName);
201 | MenuItem item = menu.add (label);
202 |
203 | item.setIntent (MenuHelper.createSearchIntent (this, language));
204 |
205 | item.setShowAsAction (MenuItem.SHOW_AS_ACTION_IF_ROOM |
206 | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
207 | item.setTitleCondensed (language);
208 | }
209 |
210 | @Override
211 | public boolean onCreateOptionsMenu (Menu menu)
212 | {
213 | int i;
214 |
215 | for (i = 1; i < searchLanguages.length; i++)
216 | addLanguageMenuItem (menu, searchLanguages[i]);
217 |
218 | MenuInflater inflater = getMenuInflater ();
219 | inflater.inflate (R.menu.search_menu, menu);
220 |
221 | return true;
222 | }
223 |
224 | @Override
225 | public boolean onOptionsItemSelected (MenuItem item)
226 | {
227 | Intent intent = item.getIntent ();
228 |
229 | if (intent != null &&
230 | intent.getComponent () != null &&
231 | intent.getComponent ().equals (getComponentName ()))
232 | {
233 | TextView tv = (TextView) findViewById (R.id.search_edit);
234 | intent.putExtra (EXTRA_SEARCH_TERM, tv.getText ().toString ());
235 | }
236 |
237 | if (MenuHelper.onOptionsItemSelected (this, item))
238 | return true;
239 |
240 | return super.onOptionsItemSelected (item);
241 | }
242 |
243 | @Override
244 | protected Dialog onCreateDialog (int id)
245 | {
246 | return MenuHelper.onCreateDialog (this, id);
247 | }
248 |
249 | @Override
250 | public void afterTextChanged (Editable s)
251 | {
252 | searchAdapter.getFilter ().filter (s);
253 |
254 | boolean clearButtonShouldBeVisible = s.length () != 0;
255 |
256 | if (clearButtonVisible != clearButtonShouldBeVisible)
257 | {
258 | clearButtonVisible = clearButtonShouldBeVisible;
259 |
260 | View clearButton = findViewById (R.id.clear_search_button);
261 |
262 | clearButton.setVisibility (clearButtonVisible
263 | ? View.VISIBLE
264 | : View.GONE);
265 | }
266 | }
267 |
268 | @Override
269 | public void beforeTextChanged (CharSequence s,
270 | int start,
271 | int count,
272 | int after)
273 | {
274 | }
275 |
276 | @Override
277 | public void onTextChanged (CharSequence s,
278 | int start,
279 | int before,
280 | int count)
281 | {
282 | }
283 |
284 | private void focusSearchEdit ()
285 | {
286 | View tv = findViewById (R.id.search_edit);
287 |
288 | tv.requestFocus ();
289 |
290 | InputMethodManager imm =
291 | (InputMethodManager) getSystemService (INPUT_METHOD_SERVICE);
292 |
293 | if (imm != null)
294 | imm.showSoftInput (tv,
295 | 0, /* flags */
296 | null /* resultReceiver */);
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/Trie.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import java.io.FileInputStream;
21 | import java.io.InputStream;
22 | import java.io.IOException;
23 |
24 | class TrieStack
25 | {
26 | private int[] data;
27 | private int size;
28 |
29 | public TrieStack ()
30 | {
31 | this.data = new int[64];
32 | this.size = 0;
33 | }
34 |
35 | public int getTopStart ()
36 | {
37 | return data[size - 3];
38 | }
39 |
40 | public int getTopEnd ()
41 | {
42 | return data[size - 2];
43 | }
44 |
45 | public int getTopStringLength ()
46 | {
47 | return data[size - 1];
48 | }
49 |
50 | public void pop ()
51 | {
52 | size -= 3;
53 | }
54 |
55 | public boolean isEmpty ()
56 | {
57 | return size <= 0;
58 | }
59 |
60 | public void push (int start,
61 | int end,
62 | int stringLength)
63 | {
64 | /* If there isn't enough space in the array then we'll double its
65 | * size. The size of the array is initially chosen to be quite
66 | * large so this should probably never happen */
67 | if (size + 3 >= data.length)
68 | {
69 | int[] newData = new int[data.length * 2];
70 | System.arraycopy (data, 0, newData, 0, data.length);
71 | data = newData;
72 | }
73 |
74 | data[size++] = start;
75 | data[size++] = end;
76 | data[size++] = stringLength;
77 | }
78 | }
79 |
80 | public class Trie
81 | {
82 | private byte data[];
83 |
84 | private static void readAll (InputStream stream,
85 | byte[] data,
86 | int offset,
87 | int length)
88 | throws IOException
89 | {
90 | while (length > 0)
91 | {
92 | int got = stream.read (data, offset, length);
93 |
94 | if (got == -1)
95 | throw new IOException ("Unexpected end of file");
96 | else
97 | {
98 | offset += got;
99 | length -= got;
100 | }
101 | }
102 | }
103 |
104 | private static final int extractInt (byte[] data,
105 | int offset)
106 | {
107 | return (((data[offset + 0] & 0xff) << 0) |
108 | ((data[offset + 1] & 0xff) << 8) |
109 | ((data[offset + 2] & 0xff) << 16) |
110 | ((data[offset + 3] & 0xff) << 24));
111 | }
112 |
113 | public Trie (InputStream dataStream)
114 | throws IOException
115 | {
116 | byte lengthBytes[] = new byte[4];
117 | int totalLength;
118 |
119 | /* Read 4 bytes to get the length of the file */
120 | readAll (dataStream, lengthBytes, 0, lengthBytes.length);
121 | totalLength = extractInt (lengthBytes, 0) & 0x7fffffff;
122 |
123 | /* Create a byte array big enough to hold the entire file and copy
124 | * the length we just read into the beginning */
125 | data = new byte[totalLength];
126 | System.arraycopy (lengthBytes, 0, data, 0, 4);
127 |
128 | /* Read the rest of the data */
129 | readAll (dataStream, data, 4, totalLength - 4);
130 | }
131 |
132 | /* Gets the number of bytes needed for a UTF-8 sequence which begins
133 | * with the given byte */
134 | private static int getUtf8Length (byte firstByte)
135 | {
136 | if (firstByte >= 0)
137 | return 1;
138 | if ((firstByte & 0xe0) == 0xc0)
139 | return 2;
140 | if ((firstByte & 0xf0) == 0xe0)
141 | return 3;
142 | if ((firstByte & 0xf8) == 0xf0)
143 | return 4;
144 | if ((firstByte & 0xfc) == 0xf8)
145 | return 5;
146 |
147 | return 6;
148 | }
149 |
150 | private static boolean compareArray (byte[] a,
151 | int aOffset,
152 | byte[] b,
153 | int bOffset,
154 | int length)
155 | {
156 | while (length-- > 0)
157 | if (a[aOffset++] != b[bOffset++])
158 | return false;
159 |
160 | return true;
161 | }
162 |
163 | private String getCharacter (int offset)
164 | {
165 | return new String (data, offset, getUtf8Length (data[offset]));
166 | }
167 |
168 | /* Searches the trie for words that begin with 'prefix'. The results
169 | * array is filled with the results. If more results are available
170 | * than the length of the results array then they are ignored. If
171 | * less are available then the remainder of the array is untouched.
172 | * The method returns the number of results found */
173 | public int search (String prefix,
174 | SearchResult[] results)
175 | {
176 | /* Convert the string to unicode to make it easier to compare with
177 | * the unicode characters in the trie. 'getBytes' with no
178 | * parameters converts the string to the default charset. This
179 | * assumes the default charset is always UTF-8 which seems to be
180 | * the case on Android */
181 | byte[] prefixBytes = prefix.getBytes ();
182 |
183 | int trieStart = 0;
184 | int prefixOffset = 0;
185 |
186 | while (prefixOffset < prefixBytes.length)
187 | {
188 | int characterLen = getUtf8Length (prefixBytes[prefixOffset]);
189 | int childStart;
190 |
191 | /* Get the total length of this node */
192 | int offset = extractInt (data, trieStart);
193 |
194 | /* Skip the character for this node */
195 | childStart = trieStart + 4;
196 | childStart += getUtf8Length (data[childStart]);
197 |
198 | /* If the high bit in the offset is set then it is followed by
199 | * the matching articles which we want to skip */
200 | if (offset < 0)
201 | {
202 | offset &= 0x7fffffff;
203 |
204 | boolean hasNext;
205 |
206 | do
207 | {
208 | hasNext = (data[childStart + 1] & 0x80) != 0;
209 | boolean hasDisplayName = (data[childStart + 1] & 0x40) != 0;
210 |
211 | childStart += 3;
212 |
213 | if (hasDisplayName)
214 | childStart += (data[childStart] & 0xff) + 1;
215 | } while (hasNext);
216 | }
217 |
218 | int trieEnd = trieStart + offset;
219 |
220 | trieStart = childStart;
221 |
222 | /* trieStart is now pointing into the children of the
223 | * selected node. We'll scan over these until we either find a
224 | * matching character for the next character of the prefix or
225 | * we hit the end of the node */
226 | while (true)
227 | {
228 | /* If we've reached the end of the node then we haven't
229 | * found a matching character for the prefix so there are
230 | * no results */
231 | if (trieStart >= trieEnd)
232 | return 0;
233 |
234 | /* If we've found a matching character then start scanning
235 | * into this node */
236 | if (compareArray (prefixBytes, prefixOffset,
237 | data, trieStart + 4,
238 | characterLen))
239 | break;
240 | /* Otherwise skip past the node to the next sibling */
241 | else
242 | trieStart += extractInt (data, trieStart) & 0x7fffffff;
243 | }
244 |
245 | prefixOffset += characterLen;
246 | }
247 |
248 | StringBuilder stringBuf = new StringBuilder (prefix);
249 |
250 | /* trieStart is now pointing at the last node with this string.
251 | * Any children of that node are therefore extensions of the
252 | * prefix. We can now depth-first search the tree to get them all
253 | * in sorted order */
254 |
255 | TrieStack stack = new TrieStack ();
256 |
257 | stack.push (trieStart,
258 | trieStart + extractInt (data, trieStart) & 0x7fffffff,
259 | stringBuf.length ());
260 |
261 | int numResults = 0;
262 | boolean firstChar = true;
263 |
264 | while (numResults < results.length &&
265 | !stack.isEmpty ())
266 | {
267 | int searchStart = stack.getTopStart ();
268 | int searchEnd = stack.getTopEnd ();
269 |
270 | stringBuf.setLength (stack.getTopStringLength ());
271 |
272 | stack.pop ();
273 |
274 | int offset = extractInt (data, searchStart);
275 | int characterLen = getUtf8Length (data[searchStart + 4]);
276 | int childrenStart = searchStart + 4 + characterLen;
277 | int oldLength = stringBuf.length ();
278 |
279 | if (firstChar)
280 | firstChar = false;
281 | else
282 | stringBuf.append (new String (data,
283 | searchStart + 4,
284 | characterLen));
285 |
286 | /* If this is a complete word then add it to the results */
287 | if (offset < 0)
288 | {
289 | boolean hasNext = true;
290 |
291 | while (hasNext && numResults < results.length)
292 | {
293 | int article = ((data[childrenStart] & 0xff) |
294 | ((data[childrenStart + 1] & 0xff) << 8));
295 | int mark = data[childrenStart + 2] & 0xff;
296 | hasNext = (article & 0x8000) != 0;
297 | boolean hasDisplayName = (article & 0x4000) != 0;
298 |
299 | childrenStart += 3;
300 |
301 | article &= 0x3fff;
302 |
303 | String word;
304 | if (hasDisplayName)
305 | {
306 | int len = data[childrenStart] & 0xff;
307 | word = new String (data,
308 | childrenStart + 1,
309 | len);
310 | childrenStart += len + 1;
311 | }
312 | else
313 | word = stringBuf.toString ();
314 |
315 | results[numResults++] = new SearchResult (word,
316 | article,
317 | mark);
318 | }
319 |
320 | offset &= 0x7fffffff;
321 | }
322 |
323 | /* If there is a sibling then make sure we continue from that
324 | * after we've descended through the children of this node */
325 | if (searchStart + offset < searchEnd)
326 | stack.push (searchStart + offset, searchEnd, oldLength);
327 |
328 | /* Push a search for the children of this node */
329 | if (childrenStart < searchStart + offset)
330 | stack.push (childrenStart,
331 | searchStart + offset,
332 | stringBuf.length ());
333 | }
334 |
335 | return numResults;
336 | }
337 |
338 | /* Test program */
339 | public static void main (String[] args)
340 | throws IOException
341 | {
342 | if (args.length != 2)
343 | {
344 | System.err.println ("Usage: java Trie ");
345 | System.exit (1);
346 | }
347 |
348 | FileInputStream inputStream = new FileInputStream (args[0]);
349 | Trie trie = new Trie (inputStream);
350 |
351 | SearchResult result[] = new SearchResult[100];
352 |
353 | int numResults = trie.search (args[1], result);
354 |
355 | for (int i = 0; i < numResults; i++)
356 | System.out.println (result[i].getWord () + ": " +
357 | result[i].getArticle () + "," +
358 | result[i].getMark ());
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/app/src/main/java/uk/co/busydoingnothing/prevo/ArticleActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * PReVo - A portable version of ReVo for Android
3 | * Copyright (C) 2012, 2013, 2016 Neil Roberts
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation; version 2 of the License.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package uk.co.busydoingnothing.prevo;
19 |
20 | import android.app.Activity;
21 | import android.app.Dialog;
22 | import android.content.DialogInterface;
23 | import android.content.Intent;
24 | import android.content.res.AssetManager;
25 | import android.content.res.Resources;
26 | import android.content.SharedPreferences;
27 | import android.net.Uri;
28 | import android.view.ContextMenu;
29 | import android.os.Bundle;
30 | import android.os.Handler;
31 | import android.os.Message;
32 | import android.support.v7.app.AlertDialog;
33 | import android.support.v7.app.AppCompatActivity;
34 | import android.text.SpannableString;
35 | import android.text.method.LinkMovementMethod;
36 | import android.text.style.ClickableSpan;
37 | import android.text.style.QuoteSpan;
38 | import android.text.style.RelativeSizeSpan;
39 | import android.text.style.StyleSpan;
40 | import android.text.style.SuperscriptSpan;
41 | import android.util.Log;
42 | import android.util.TypedValue;
43 | import android.view.LayoutInflater;
44 | import android.view.Menu;
45 | import android.view.MenuInflater;
46 | import android.view.MenuItem;
47 | import android.view.KeyEvent;
48 | import android.view.View;
49 | import android.widget.AdapterView;
50 | import android.widget.LinearLayout;
51 | import android.widget.RelativeLayout;
52 | import android.widget.TextView;
53 | import android.widget.ZoomControls;
54 | import java.io.IOException;
55 | import java.util.Vector;
56 | import java.util.Locale;
57 |
58 | public class ArticleActivity extends AppCompatActivity
59 | implements SharedPreferences.OnSharedPreferenceChangeListener
60 | {
61 | public static final String EXTRA_ARTICLE_NUMBER =
62 | "uk.co.busydoingnothing.prevo.ArticleNumber";
63 | public static final String EXTRA_MARK_NUMBER =
64 | "uk.co.busydoingnothing.prevo.MarkNumber";
65 |
66 | /* http://www.openintents.org/en/node/720 */
67 | /* This probably only works with AnkiDroid 2.0 */
68 | private static final String ACTION_CREATE_FLASHCARD =
69 | "org.openintents.action.CREATE_FLASHCARD";
70 |
71 | public static final String SOURCE_TEXT = "SOURCE_TEXT";
72 | public static final String TARGET_TEXT = "TARGET_TEXT";
73 |
74 | public static final String TAG = "prevoarticle";
75 |
76 | public static final int DIALOG_NO_FLASHCARD = 0x10c;
77 |
78 | private Vector sectionHeaders;
79 | private Vector definitions;
80 |
81 | private DelayedScrollView scrollView;
82 | private View articleView;
83 | private int articleNumber;
84 |
85 | private ZoomControls zoomControls;
86 | private RelativeLayout layout;
87 |
88 | /* There are 10 font sizes ranging from 0 to 9. The actual font size
89 | * used is calculated from a logarithmic scale and set in density
90 | * independent pixels */
91 | private static final int N_FONT_SIZES = 10;
92 | private static final float FONT_SIZE_ROOT = 1.2f;
93 |
94 | private int fontSize = N_FONT_SIZES / 2;
95 | private float titleBaseTextSize;
96 | private float definitionBaseTextSize;
97 |
98 | private static final int MSG_HIDE_ZOOM_CONTROLS = 4;
99 |
100 | private Handler handler;
101 |
102 | private boolean stopped;
103 | private boolean reloadQueued;
104 |
105 | private void skipSpannableString (BinaryReader in)
106 | throws IOException
107 | {
108 | int strLength = in.readShort ();
109 |
110 | in.skip (strLength);
111 |
112 | int spanLength;
113 |
114 | while ((spanLength = in.readShort ()) != 0)
115 | in.skip (2 + 2 + 2 + 1);
116 | }
117 |
118 | private SpannableString readSpannableString (BinaryReader in)
119 | throws IOException
120 | {
121 | int strLength = in.readShort ();
122 | byte[] utf8String = new byte[strLength];
123 |
124 | in.readAll (utf8String);
125 |
126 | SpannableString string =
127 | new SpannableString (new String (utf8String));
128 |
129 | int spanLength;
130 |
131 | while ((spanLength = in.readShort ()) != 0)
132 | {
133 | int spanStart = in.readShort ();
134 | int data1 = in.readShort ();
135 | final int data2 = in.readShort ();
136 | int spanType = in.readByte ();
137 |
138 | if (spanStart < 0 || spanLength < 0 ||
139 | spanStart + spanLength > string.length ())
140 | Log.wtf (TAG,
141 | "Invalid span " +
142 | spanStart +
143 | "→" +
144 | (spanLength + spanStart) +
145 | " for string of length " +
146 | string.length ());
147 |
148 | switch (spanType)
149 | {
150 | case 0:
151 | {
152 | ClickableSpan span;
153 |
154 | if (data1 == this.articleNumber)
155 | {
156 | span = (new ClickableSpan ()
157 | {
158 | @Override
159 | public void onClick (View widget)
160 | {
161 | showSection (data2);
162 | }
163 | });
164 | }
165 | else
166 | {
167 | span = new ReferenceSpan (data1, data2);
168 | }
169 |
170 | string.setSpan (span, spanStart, spanStart + spanLength, 0);
171 | }
172 | break;
173 |
174 | case 1:
175 | string.setSpan (new SuperscriptSpan (),
176 | spanStart,
177 | spanStart + spanLength,
178 | 0 /* flags */);
179 | string.setSpan (new RelativeSizeSpan (0.5f),
180 | spanStart,
181 | spanStart + spanLength,
182 | 0 /* flags */);
183 | break;
184 |
185 | case 2:
186 | string.setSpan (new StyleSpan (android.graphics.Typeface.ITALIC),
187 | spanStart,
188 | spanStart + spanLength,
189 | 0 /* flags */);
190 | break;
191 |
192 | case 3:
193 | string.setSpan (new QuoteSpan (),
194 | spanStart,
195 | spanStart + spanLength,
196 | 0 /* flags */);
197 | break;
198 |
199 | case 4:
200 | string.setSpan (new StyleSpan (android.graphics.Typeface.BOLD),
201 | spanStart,
202 | spanStart + spanLength,
203 | 0 /* flags */);
204 | break;
205 | }
206 | }
207 |
208 | return string;
209 | }
210 |
211 | private String readString (BinaryReader in,
212 | int maxLength)
213 | throws IOException
214 | {
215 | byte buf[] = new byte[maxLength];
216 |
217 | in.readAll (buf);
218 |
219 | int len = 3;
220 | while (len > 0 && buf[len - 1] == '\0')
221 | len--;
222 |
223 | return new String (buf, 0, len);
224 | }
225 |
226 | private void skipArticles (BinaryReader in,
227 | int numArticles)
228 | throws IOException
229 | {
230 | char buf[] = null;
231 |
232 | for (int i = 0; i < numArticles; i++)
233 | {
234 | int articleLength = in.readInt ();
235 |
236 | in.skip (articleLength);
237 | }
238 | }
239 |
240 | private LinearLayout loadArticle (int article)
241 | throws IOException
242 | {
243 | AssetManager assetManager = getAssets ();
244 | String filename = String.format (Locale.US,
245 | "articles/article-%03xx.bin",
246 | article >> 4);
247 | BinaryReader in = new BinaryReader (assetManager.open (filename));
248 |
249 | skipArticles (in, article & 0xf);
250 |
251 | int articleLength = in.readInt ();
252 | long articleStart = in.getPosition ();
253 |
254 | setTitle (readSpannableString (in));
255 |
256 | LinearLayout layout = new LinearLayout (this);
257 | layout.setOrientation (LinearLayout.VERTICAL);
258 |
259 | LayoutInflater layoutInflater = getLayoutInflater ();
260 | TextView tv[] = new TextView[2];
261 |
262 | SelectedLanguages selectedLanguages = new SelectedLanguages (this);
263 |
264 | while (in.getPosition () - articleStart < articleLength)
265 | {
266 | String languageCode = readString (in, 3);
267 |
268 | if (selectedLanguages.contains (languageCode))
269 | {
270 | SpannableString header = readSpannableString (in);
271 | SpannableString content = readSpannableString (in);
272 |
273 | tv[0] = (TextView) layoutInflater.inflate (R.layout.section_header,
274 | layout,
275 | false);
276 | sectionHeaders.add (tv[0]);
277 | titleBaseTextSize = tv[0].getTextSize ();
278 | tv[0].setText (header, TextView.BufferType.SPANNABLE);
279 |
280 | DefinitionView definitionView =
281 | (DefinitionView) layoutInflater.inflate (R.layout.definition,
282 | layout,
283 | false);
284 | definitionView.setWord (header, content);
285 | tv[1] = definitionView;
286 | definitions.add (tv[1]);
287 | tv[1].setText (content, TextView.BufferType.SPANNABLE);
288 | registerForContextMenu (tv[1]);
289 | definitionBaseTextSize = tv[1].getTextSize ();
290 |
291 | for (int i = 0; i < tv.length; i++)
292 | {
293 | tv[i].setMovementMethod (LinkMovementMethod.getInstance ());
294 | layout.addView (tv[i]);
295 | }
296 | }
297 | else
298 | {
299 | skipSpannableString (in);
300 | skipSpannableString (in);
301 | }
302 | }
303 |
304 | return layout;
305 | }
306 |
307 | private void showSection (int section)
308 | {
309 | int ypos = 0;
310 |
311 | Log.i (TAG, "Showing section " + section + " of article " + articleNumber);
312 |
313 | scrollView.delayedScrollTo (sectionHeaders.get (section));
314 | }
315 |
316 | private void updateZoomability ()
317 | {
318 | if (zoomControls != null)
319 | {
320 | zoomControls.setIsZoomInEnabled (fontSize < N_FONT_SIZES - 1);
321 | zoomControls.setIsZoomOutEnabled (fontSize > 0);
322 | }
323 | }
324 |
325 | private void setFontSize (int fontSize)
326 | {
327 | if (fontSize < 0)
328 | fontSize = 0;
329 | else if (fontSize >= N_FONT_SIZES)
330 | fontSize = N_FONT_SIZES - 1;
331 |
332 | if (fontSize != this.fontSize)
333 | {
334 | /* There's no point in updating the font size if a reload is
335 | * queued because it will just get set back to the default
336 | * when it is finally reloaded */
337 | if (!reloadQueued)
338 | {
339 | float fontSizeScale =
340 | (float) Math.pow (FONT_SIZE_ROOT,
341 | fontSize - N_FONT_SIZES / 2);
342 | float titleFontSize = titleBaseTextSize * fontSizeScale;
343 | float definitionFontSize = definitionBaseTextSize * fontSizeScale;
344 |
345 | for (TextView tv : sectionHeaders)
346 | tv.setTextSize (TypedValue.COMPLEX_UNIT_PX, titleFontSize);
347 |
348 | for (TextView tv : definitions)
349 | tv.setTextSize (TypedValue.COMPLEX_UNIT_PX, definitionFontSize);
350 | }
351 |
352 | this.fontSize = fontSize;
353 |
354 | updateZoomability ();
355 | }
356 | }
357 |
358 | private void loadIntendedArticle ()
359 | {
360 | Intent intent = getIntent ();
361 |
362 | sectionHeaders.setSize (0);
363 | definitions.setSize (0);
364 |
365 | if (intent != null)
366 | {
367 | int article = intent.getIntExtra (EXTRA_ARTICLE_NUMBER, -1);
368 | int mark = intent.getIntExtra (EXTRA_MARK_NUMBER, -1);
369 |
370 | if (article != -1)
371 | {
372 | try
373 | {
374 | this.articleNumber = article;
375 | if (articleView != null)
376 | scrollView.removeView (articleView);
377 | articleView = loadArticle (article);
378 | scrollView.addView (articleView);
379 | showSection (mark);
380 | }
381 | catch (IOException e)
382 | {
383 | Log.wtf ("Error while loading an asset", e);
384 | }
385 | }
386 | }
387 |
388 | /* The font size will have been reset to the default so we need to
389 | * update it */
390 | int oldFontSize = this.fontSize;
391 | this.fontSize = N_FONT_SIZES / 2;
392 | setFontSize (oldFontSize);
393 | }
394 |
395 | @Override
396 | public void onCreate (Bundle savedInstanceState)
397 | {
398 | super.onCreate (savedInstanceState);
399 |
400 | setContentView (R.layout.article);
401 |
402 | scrollView = (DelayedScrollView) findViewById (R.id.article_scroll_view);
403 | layout = (RelativeLayout) findViewById (R.id.article_layout);
404 |
405 | sectionHeaders = new Vector ();
406 | definitions = new Vector ();
407 |
408 | stopped = true;
409 | reloadQueued = true;
410 |
411 | SharedPreferences prefs =
412 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
413 | Activity.MODE_PRIVATE);
414 |
415 | setFontSize (prefs.getInt (MenuHelper.PREF_FONT_SIZE, fontSize));
416 |
417 | prefs.registerOnSharedPreferenceChangeListener (this);
418 | }
419 |
420 | @Override
421 | public void onStart ()
422 | {
423 | super.onStart ();
424 |
425 | stopped = false;
426 |
427 | if (reloadQueued)
428 | {
429 | reloadQueued = false;
430 | loadIntendedArticle ();
431 | }
432 | }
433 |
434 | @Override
435 | public void onStop ()
436 | {
437 | stopped = true;
438 |
439 | super.onStop ();
440 | }
441 |
442 | @Override
443 | public void onDestroy ()
444 | {
445 | SharedPreferences prefs =
446 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
447 | Activity.MODE_PRIVATE);
448 |
449 | prefs.unregisterOnSharedPreferenceChangeListener (this);
450 |
451 | super.onDestroy ();
452 | }
453 |
454 | @Override
455 | public boolean onCreateOptionsMenu (Menu menu)
456 | {
457 | MenuInflater inflater = getMenuInflater ();
458 |
459 | inflater.inflate (R.menu.article_menu, menu);
460 |
461 | return true;
462 | }
463 |
464 | private void zoom (int direction)
465 | {
466 | int fontSize = this.fontSize + direction;
467 |
468 | if (fontSize >= N_FONT_SIZES)
469 | fontSize = N_FONT_SIZES - 1;
470 | else if (fontSize < 0)
471 | fontSize = 0;
472 |
473 | SharedPreferences prefs =
474 | getSharedPreferences (MenuHelper.PREVO_PREFERENCES,
475 | Activity.MODE_PRIVATE);
476 | SharedPreferences.Editor editor = prefs.edit ();
477 | editor.putInt (MenuHelper.PREF_FONT_SIZE, fontSize);
478 | editor.commit ();
479 |
480 | setHideZoom ();
481 | updateZoomability ();
482 | }
483 |
484 | private void setHideZoom ()
485 | {
486 | handler.removeMessages (MSG_HIDE_ZOOM_CONTROLS);
487 | handler.sendEmptyMessageDelayed (MSG_HIDE_ZOOM_CONTROLS, 10000);
488 | }
489 |
490 | private void showZoomController ()
491 | {
492 | if (zoomControls == null)
493 | {
494 | final int wrap = RelativeLayout.LayoutParams.WRAP_CONTENT;
495 | RelativeLayout.LayoutParams lp =
496 | new RelativeLayout.LayoutParams (wrap, wrap);
497 | final float scale = getResources ().getDisplayMetrics ().density;
498 |
499 | lp.addRule (RelativeLayout.CENTER_HORIZONTAL);
500 | lp.addRule (RelativeLayout.ALIGN_PARENT_BOTTOM);
501 | lp.bottomMargin = (int) (10.0f * scale + 0.5f);
502 |
503 | zoomControls = new ZoomControls (this);
504 | zoomControls.setVisibility (View.GONE);
505 | layout.addView (zoomControls, lp);
506 |
507 | zoomControls.setOnZoomInClickListener (new View.OnClickListener ()
508 | {
509 | @Override
510 | public void onClick (View v)
511 | {
512 | zoom (+1);
513 | }
514 | });
515 | zoomControls.setOnZoomOutClickListener (new View.OnClickListener ()
516 | {
517 | @Override
518 | public void onClick (View v)
519 | {
520 | zoom (-1);
521 | }
522 | });
523 |
524 | handler = new Handler ()
525 | {
526 | @Override
527 | public void handleMessage (Message msg)
528 | {
529 | switch (msg.what)
530 | {
531 | case MSG_HIDE_ZOOM_CONTROLS:
532 | if (zoomControls != null)
533 | zoomControls.hide ();
534 | break;
535 | }
536 | }
537 | };
538 |
539 | updateZoomability ();
540 | }
541 |
542 | if (zoomControls.getVisibility () != View.VISIBLE)
543 | {
544 | zoomControls.show ();
545 | setHideZoom ();
546 | }
547 | }
548 |
549 | @Override
550 | public boolean onOptionsItemSelected (MenuItem item)
551 | {
552 | if (MenuHelper.onOptionsItemSelected (this, item))
553 | return true;
554 |
555 | switch (item.getItemId ())
556 | {
557 | case R.id.menu_zoom:
558 | showZoomController ();
559 | return true;
560 | }
561 |
562 | return super.onOptionsItemSelected (item);
563 | }
564 |
565 | @Override
566 | protected Dialog onCreateDialog (int id)
567 | {
568 | Resources res = getResources ();
569 |
570 | switch (id)
571 | {
572 | case DIALOG_NO_FLASHCARD:
573 | {
574 | AlertDialog.Builder builder = new AlertDialog.Builder (this);
575 |
576 | LayoutInflater layoutInflater = getLayoutInflater ();
577 | TextView tv =
578 | (TextView) layoutInflater.inflate (R.layout.no_flashcard_view,
579 | null);
580 | builder
581 | .setView (tv)
582 | .setCancelable (true)
583 | .setNegativeButton (R.string.close,
584 | new DialogInterface.OnClickListener ()
585 | {
586 | @Override
587 | public void onClick (DialogInterface dialog,
588 | int whichButton)
589 | {
590 | }
591 | });
592 | return builder.create ();
593 | }
594 |
595 | default:
596 | return MenuHelper.onCreateDialog (this, id);
597 | }
598 | }
599 |
600 | @Override
601 | public boolean onKeyDown (int keyCode,
602 | KeyEvent event)
603 | {
604 | if (keyCode == KeyEvent.KEYCODE_SEARCH)
605 | {
606 | MenuHelper.goSearch (this);
607 | return true;
608 | }
609 |
610 | return super.onKeyDown (keyCode, event);
611 | }
612 |
613 | @Override
614 | public void onCreateContextMenu (ContextMenu menu,
615 | View v,
616 | ContextMenu.ContextMenuInfo menuInfo)
617 | {
618 | super.onCreateContextMenu (menu, v, menuInfo);
619 |
620 | if (v instanceof TextView)
621 | {
622 | MenuInflater inflater = getMenuInflater ();
623 | inflater.inflate (R.menu.definition_menu, menu);
624 | }
625 | }
626 |
627 | private void createFlashcard (CharSequence sourceText,
628 | CharSequence targetText)
629 | {
630 | Intent intent = new Intent ();
631 | int i;
632 |
633 | intent.putExtra (SOURCE_TEXT, sourceText.toString ());
634 | intent.putExtra (TARGET_TEXT, targetText.toString ());
635 |
636 | intent.setAction (ACTION_CREATE_FLASHCARD);
637 |
638 | try
639 | {
640 | startActivity (intent);
641 | }
642 | catch (android.content.ActivityNotFoundException e)
643 | {
644 | Log.i (TAG, "Failed to start activity: " + e.getMessage ());
645 | showDialog (DIALOG_NO_FLASHCARD);
646 | }
647 | }
648 |
649 | private void lookUpInPIV (CharSequence word)
650 | {
651 | StringBuilder wordBuilder = new StringBuilder ();
652 |
653 | /* Make a copy of the word with only the alphabetic parts
654 | * converted to lowercase */
655 | for (int i = 0; i < word.length (); i++)
656 | {
657 | char ch = word.charAt (i);
658 |
659 | /* Stop at the first comma because this usually means there is
660 | * more than one word with the same definition and it’s
661 | * useless to try and search both words */
662 | if (ch == ',')
663 | break;
664 |
665 | if (Character.isLetter (ch) || " .-'!’()".indexOf (ch) != -1)
666 | wordBuilder.append (Character.toLowerCase (ch));
667 | }
668 | Uri uri = ((new Uri.Builder ())
669 | .scheme ("https")
670 | .encodedPath ("//vortaro.net/")
671 | .fragment (wordBuilder.toString ())).build ();
672 | Intent intent = new Intent (Intent.ACTION_VIEW, uri);
673 |
674 | try
675 | {
676 | startActivity (intent);
677 | }
678 | catch (android.content.ActivityNotFoundException e)
679 | {
680 | Log.w (TAG, "Failed to start activity: " + e.getMessage ());
681 | }
682 | }
683 |
684 | @Override
685 | public boolean onContextItemSelected (MenuItem item)
686 | {
687 | ContextMenu.ContextMenuInfo info = item.getMenuInfo ();
688 |
689 | if (info instanceof DefinitionView.DefinitionContextMenuInfo)
690 | {
691 | DefinitionView.DefinitionContextMenuInfo defInfo =
692 | (DefinitionView.DefinitionContextMenuInfo) info;
693 |
694 | switch (item.getItemId())
695 | {
696 | case R.id.menu_copy_definition:
697 | CharSequence label =
698 | getResources ().getText (R.string.definition_label);
699 |
700 | SpannedCopy.copyText (this, label, defInfo.definition);
701 |
702 | return true;
703 |
704 | case R.id.menu_create_flashcard_word:
705 | createFlashcard (defInfo.definition, defInfo.word);
706 | return true;
707 |
708 | case R.id.menu_create_flashcard_definition:
709 | createFlashcard (defInfo.word, defInfo.definition);
710 | return true;
711 |
712 | case R.id.menu_look_up_in_piv:
713 | lookUpInPIV (defInfo.word);
714 | return true;
715 | }
716 | }
717 |
718 | return super.onContextItemSelected(item);
719 | }
720 |
721 | @Override
722 | public void onSharedPreferenceChanged (SharedPreferences prefs,
723 | String key)
724 | {
725 | if (key.equals (MenuHelper.PREF_FONT_SIZE))
726 | setFontSize (prefs.getInt (MenuHelper.PREF_FONT_SIZE, fontSize));
727 | else if (key.equals (SelectedLanguages.PREF))
728 | {
729 | if (stopped)
730 | /* Queue the reload for the next time the activity is started */
731 | reloadQueued = true;
732 | else
733 | {
734 | reloadQueued = false;
735 | loadIntendedArticle ();
736 | }
737 | }
738 | }
739 | }
740 |
--------------------------------------------------------------------------------