├── .github
├── ISSUE_TEMPLATE.md
└── assets
│ └── direct-apk-download.png
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── app
├── build.gradle
├── debug.keystore
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── style.css
│ ├── ic_launcher-web.png
│ ├── java
│ │ └── com
│ │ │ ├── github
│ │ │ └── takahirom
│ │ │ │ └── webview_in_coodinator_layout
│ │ │ │ └── NestedWebView.java
│ │ │ └── jtmcn
│ │ │ └── archwiki
│ │ │ └── viewer
│ │ │ ├── ArchWikiViewerApp.kt
│ │ │ ├── Constants.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── PreferencesActivity.kt
│ │ │ ├── SearchResultsAdapter.kt
│ │ │ ├── WikiClient.kt
│ │ │ ├── WikiView.kt
│ │ │ ├── data
│ │ │ ├── SearchResult.kt
│ │ │ ├── SearchResultsBuilder.kt
│ │ │ ├── WikiPage.kt
│ │ │ └── WikiPageBuilder.kt
│ │ │ ├── tasks
│ │ │ ├── Fetch.kt
│ │ │ └── FetchUrl.kt
│ │ │ └── utils
│ │ │ ├── AndroidUtils.kt
│ │ │ ├── NetworkUtils.kt
│ │ │ └── SettingsUtils.kt
│ ├── play
│ │ ├── contact-email.txt
│ │ ├── contact-website.txt
│ │ ├── default-language.txt
│ │ ├── listings
│ │ │ └── en-US
│ │ │ │ ├── full-description.txt
│ │ │ │ ├── graphics
│ │ │ │ ├── feature-graphic
│ │ │ │ │ └── 0.png
│ │ │ │ ├── icon
│ │ │ │ │ └── 0.png
│ │ │ │ └── phone-screenshots
│ │ │ │ │ ├── 0.png
│ │ │ │ │ ├── 1.png
│ │ │ │ │ ├── 2.png
│ │ │ │ │ ├── 3.png
│ │ │ │ │ └── 4.png
│ │ │ │ ├── short-description.txt
│ │ │ │ └── title.txt
│ │ └── release-notes
│ │ │ └── en-US
│ │ │ ├── beta.txt
│ │ │ └── default.txt
│ └── res
│ │ ├── drawable
│ │ ├── ic_launcher.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_search_white_24dp.xml
│ │ └── ic_share_white_24dp.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_preferences.xml
│ │ ├── search_suggestions_list_item.xml
│ │ └── toolbar.xml
│ │ ├── menu
│ │ └── menu.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-es
│ │ └── strings.xml
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values-it
│ │ └── strings.xml
│ │ ├── values-iw
│ │ └── strings.xml
│ │ ├── values-pt-rBR
│ │ └── strings.xml
│ │ ├── values-sk
│ │ └── strings.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ ├── preferences.xml
│ │ └── searchable.xml
│ └── test
│ └── java
│ └── com
│ └── jtmcn
│ └── archwiki
│ └── viewer
│ └── data
│ ├── SearchResultsBuilderTest.kt
│ └── WikiPageBuilderTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── playstore
├── archwiki_feature_graphic.svg
└── ic_launcher.svg
├── settings.gradle
└── upload_config.tar.enc
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
3 |
4 | I have:
5 | - [ ] searched open and closed issues for duplicates
6 | - [ ] provided a reproducible result if applicable
7 |
8 | ----------------------------------------
9 |
10 |
11 | ### Bug description
12 | Describe here the issue that you are experiencing.
13 |
14 | ### Steps to reproduce
15 | - using hyphens as bullet points
16 | - list the steps
17 | - that reproduce the bug
18 |
19 | **Actual result:** Describe here what happens after you run the steps above (i.e. the buggy behaviour)
20 | **Expected result:** Describe here what should happen after you run the steps above (i.e. what would be the correct behaviour)
21 |
22 | ### Device info
23 |
24 | **Device:** Manufacturer Model XVI
25 | **Android version:** 0.0.0
26 | **App version:** 0.0.0
27 |
--------------------------------------------------------------------------------
/.github/assets/direct-apk-download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/.github/assets/direct-apk-download.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | # Local configuration file (sdk path, etc)
22 | local.properties
23 |
24 | # Proguard folder generated by Eclipse
25 | proguard/
26 |
27 | # Log Files
28 | *.log
29 |
30 | # Android Studio Navigation editor temp files
31 | .navigation/
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # IntelliJ
37 | *.iml
38 | .idea/
39 |
40 | # Keystore files
41 | # Uncomment the following lines if you do not want to check your keystore files in.
42 | #*.jks
43 | #*.keystore
44 |
45 | # External native build folder generated in Android Studio 2.2 and later
46 | .externalNativeBuild
47 |
48 | # Google Services (e.g. APIs or Firebase)
49 | google-services.json
50 |
51 | # Freeline
52 | freeline.py
53 | freeline/
54 | freeline_project_description.json
55 |
56 | # fastlane
57 | fastlane/report.xml
58 | fastlane/Preview.html
59 | fastlane/screenshots
60 | fastlane/test_output
61 | fastlane/readme.md
62 |
63 | # Version control
64 | vcs.xml
65 |
66 | # lint
67 | lint/intermediates/
68 | lint/generated/
69 | lint/outputs/
70 | lint/tmp/
71 | # lint/reports/
72 |
73 |
74 | # Deploy files
75 | app/upload.keystore
76 | app/upload.json
77 | upload_config.tar
78 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 | sudo: false
4 | env:
5 | global:
6 | - ANDROID_API=29
7 | - EMULATOR_API=21
8 | - ANDROID_BUILD_TOOLS=29.0.2
9 | android:
10 | components:
11 | - build-tools-$ANDROID_BUILD_TOOLS
12 | - android-$ANDROID_API
13 | - android-$EMULATOR_API
14 | - extra-android-support
15 | - extra-google-google_play_services
16 | - extra-google-m2repository
17 | - extra-android-m2repository
18 | - sys-img-armeabi-v7a-android-$EMULATOR_API
19 |
20 | before_install:
21 | - if [ -n "$TRAVIS_TAG" ]; then echo "Decrypting upload config"; openssl aes-256-cbc
22 | -K $encrypted_da56e498cb22_key -iv $encrypted_da56e498cb22_iv -in upload_config.tar.enc
23 | -out upload_config.tar -d; echo "Successfully decrypted files"; tar xvf upload_config.tar;
24 | mv upload_config/upload.json app/upload.json; mv upload_config/upload.keystore app/upload.keystore;
25 | fi
26 | - "./gradlew lint"
27 |
28 | before_script:
29 | - echo no | android create avd --force -n test -t android-$EMULATOR_API --abi armeabi-v7a
30 | - emulator -avd test -no-skin -no-audio -no-window &
31 | - android-wait-for-emulator
32 | - adb shell input keyevent 82 &
33 |
34 | script: "./gradlew build test connectedAndroidTest"
35 |
36 | before_cache:
37 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
38 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
39 | cache:
40 | directories:
41 | - "$HOME/.gradle/caches/"
42 | - "$HOME/.gradle/wrapper/"
43 | - "$HOME/.android/build-cache"
44 |
45 | before_deploy:
46 | - cd app/build/outputs/apk/release/; ls -la; md5sum archwiki-viewer-*.apk > md5sum.txt;
47 | sha1sum archwiki-viewer-*.apk > sha1sum.txt; cd -
48 |
49 | deploy:
50 | skip_cleanup: true
51 | provider: releases
52 | api_key:
53 | secure: F/LqVUBz5fMT28Av5NT3uGUYMxMepkk1XjL7XD/rtlwh1ApJjQDdwEBDSuFiXIqMv5ru8Gvo6u8HsiEJurnuiJ16wkTdyGDvGQffGmm2OIAko6fnlRpKvboN03eEuHElONqHzX792QMCUcSM6YnkSPJdydxhza1BRHnYsECLPcGQzp6CFNKCkVOsfX0XO7dSuZwVKZJufRDLbInaJgAYuKRjeG5Qj+3tM6WDUqOaVroSxeN8cfW14OSShTgXYYtZBYEcrt3p75/9qftMMPVAd5599/voFYJZHMzvZ321dEB84FJX1woY7MJp/tJMdfTVYuUEtYY9XwoR4H/DW+dvwlFFz+v/bNyCoV30KIZm/L7ohNeH5zbLA8bAbHFOcsOW3gXMxgv4gCquenh5/9xDUOIX8CQmjwcvmNzNcx7SxoIuOe2ghttVBziNQPL6D+Xbnd+W7uzlLLleJGnhVomkomFYhwy29a+/fM1gjKRJ7H/VruqCzfWMoXDektSoD3BLAR5Ckm9WRvEPQW5R1Q8gk4Bt13if43GwtI83qh8UQjleJLiVRm6e7HsGqoLnjCoN6gRDx0oo+h3S2vrp4QBe9vOZuCurWXrHDdf5CtuwSAFTpYoT9EF8IfOZ3sXrwRSdtsJaiactj1h2cUK2FJ0Rgy6gxHqeT1sh8HeRetQJZAQ=
54 | file_glob: true
55 | name: $TRAVIS_TAG
56 | file:
57 | - app/build/outputs/apk/release/archwiki-viewer-*.apk
58 | - app/build/outputs/apk/release/md5sum.txt
59 | - app/build/outputs/apk/release/sha1sum.txt
60 | on:
61 | repo: kevinhinterlong/archwiki-viewer
62 | tags: true
63 |
64 | after_deploy:
65 | - echo "Checking if tag matches v.X.Y.Z"
66 | - if [[ $TRAVIS_TAG =~ v[0-9]+\.[0-9]+\.[0-9]+ ]] ; then ./gradlew publishRelease;
67 | fi
68 |
69 | notifications:
70 | webhooks:
71 | on_success: change
72 | on_failure: always
73 | on_start: never
74 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2019 kevinhinterlong
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ArchWiki Viewer
2 | [](https://travis-ci.org/kevinhinterlong/archwiki-viewer) [](https://github.com/kevinhinterlong/archwiki-viewer/releases)
3 | ===============
4 | A simple viewer for the Arch Linux Wiki. Page content is formatted for optimal mobile viewing.
5 |
6 | [
](https://play.google.com/store/apps/details?id=com.jtmcn.archwiki.viewer) [
](https://f-droid.org/repository/browse/?fdid=com.jtmcn.archwiki.viewer) [
](https://github.com/kevinhinterlong/SwishTicker/releases/latest)
7 |
8 | ## Screenshots
9 |
10 |
11 |
12 | ## Contributions
13 | All contributions are welcome, don't forget to ask if you need help.
14 |
15 | Comments and tests are highly encouraged.
16 |
17 | ## License
18 | This project is licensed under the Apache License, Version 2.0
19 |
20 | Copyright 2019 kevinhinterlong
21 |
22 | See [LICENSE.md](LICENSE.md)
23 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'com.github.triplet.play' version '2.4.2'
4 | }
5 | apply plugin: 'kotlin-android'
6 | apply plugin: 'kotlin-android-extensions'
7 |
8 | repositories {
9 | mavenCentral()
10 | }
11 |
12 | android {
13 | applicationVariants.all { variant ->
14 | variant.outputs.all {
15 | outputFileName = "archwiki-viewer-v${variant.versionName}.apk"
16 | }
17 | }
18 |
19 | compileSdkVersion 29
20 | buildToolsVersion '29.0.2'
21 | defaultConfig {
22 | applicationId "com.jtmcn.archwiki.viewer"
23 | minSdkVersion 21
24 | targetSdkVersion 29
25 | versionCode 15
26 | versionName "1.0.14"
27 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
28 | }
29 |
30 | signingConfigs {
31 | debug {
32 | storeFile file('debug.keystore')
33 | storePassword 'archwiki-viewer'
34 | keyAlias 'archwiki-viewer'
35 | keyPassword 'archwiki-viewer'
36 | }
37 | if (file('upload.keystore').exists()) {
38 | upload {
39 | storeFile file('upload.keystore')
40 | storePassword System.getenv('ARCHWIKI_VIEWER_UPLOAD_STORE_PASSWORD')
41 | keyAlias 'awvalias'
42 | keyPassword System.getenv('ARCHWIKI_VIEWER_UPLOAD_KEY_PASSWORD')
43 | }
44 | }
45 | }
46 |
47 | buildTypes {
48 | debug {
49 | signingConfig signingConfigs.debug
50 | }
51 | release {
52 | if (file('upload.keystore').exists()) {
53 | signingConfig signingConfigs.upload
54 | } else {
55 | signingConfig signingConfigs.debug
56 | }
57 | minifyEnabled true
58 | shrinkResources true
59 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
60 | }
61 | }
62 | }
63 |
64 | dependencies {
65 | implementation fileTree(include: ['*.jar'], dir: 'libs')
66 | testImplementation 'junit:junit:4.12'
67 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
68 | exclude group: 'com.android.support', module: 'support-annotations'
69 | })
70 |
71 | implementation 'com.google.code.gson:gson:2.8.5'
72 | implementation 'com.jakewharton.timber:timber:4.7.1'
73 |
74 | implementation 'com.squareup.okhttp3:okhttp:3.11.0'
75 |
76 | implementation 'com.google.android.material:material:1.0.0'
77 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
78 | implementation 'androidx.appcompat:appcompat:1.1.0'
79 | implementation 'androidx.preference:preference:1.1.0'
80 | implementation 'androidx.core:core-ktx:1.1.0'
81 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
82 | }
83 |
84 | play {
85 | serviceAccountCredentials = file("upload.json")
86 | track = "beta"
87 | }
88 |
--------------------------------------------------------------------------------
/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/debug.keystore
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can edit the include path and order by changing the proguardFiles
3 | # directive in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # Add any project specific keep options here:
9 |
10 | # If your project uses WebView with JS, uncomment the following
11 | # and specify the fully qualified class name to the JavaScript interface
12 | # class:
13 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
14 | # public *;
15 | #}-keepnames class org.glassfish.** { *; }
16 |
17 | -keep class android.support.v7.internal.** { *; }
18 | -keep interface android.support.v7.internal.** { *; }
19 | -keep class android.support.v7.** { *; }
20 | -keep interface android.support.v7.** { *; }
21 |
22 | # OkHttp
23 | # JSR 305 annotations are for embedding nullability information.
24 | -dontwarn javax.annotation.**
25 |
26 | # A resource is loaded with a relative path so the package of this class must be preserved.
27 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
28 |
29 | # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
30 | -dontwarn org.codehaus.mojo.animal_sniffer.*
31 |
32 | # OkHttp platform used only on JVM and when Conscrypt dependency is available.
33 | -dontwarn okhttp3.internal.platform.ConscryptPlatform
34 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
47 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/assets/style.css:
--------------------------------------------------------------------------------
1 | .mw-content-ltr {
2 | direction: ltr
3 | }
4 |
5 | .mw-content-rtl {
6 | direction: rtl
7 | }
8 |
9 | .sitedir-ltr textarea,.sitedir-ltr input {
10 | direction: ltr
11 | }
12 |
13 | .sitedir-rtl textarea,.sitedir-rtl input {
14 | direction: rtl
15 | }
16 |
17 | input[type="submit"],input[type="button"],input[type="reset"],input[type="file"]
18 | {
19 | direction: ltr
20 | }
21 |
22 | textarea[dir="ltr"],input[dir="ltr"] {
23 | direction: ltr
24 | }
25 |
26 | textarea[dir="rtl"],input[dir="rtl"] {
27 | direction: rtl
28 | }
29 |
30 | abbr,acronym,.explain {
31 | border-bottom: 1px dotted;
32 | cursor: help
33 | }
34 |
35 | .mw-plusminus-pos {
36 | color: #006400;
37 | }
38 |
39 | .mw-plusminus-neg {
40 | color: #8b0000;
41 | }
42 |
43 | .mw-plusminus-null {
44 | color: #aaa;
45 | }
46 |
47 | .allpagesredirect,.redirect-in-category,.watchlistredir {
48 | font-style: italic
49 | }
50 |
51 | span.comment {
52 | font-style: italic
53 | }
54 |
55 | span.changedby {
56 | font-size: 95%
57 | }
58 |
59 | .texvc {
60 | direction: ltr;
61 | unicode-bidi: embed
62 | }
63 |
64 | img.tex {
65 | vertical-align: middle
66 | }
67 |
68 | span.texhtml {
69 | font-family: serif
70 | }
71 |
72 | #wikiPreview.ontop {
73 | margin-bottom: 1em
74 | }
75 |
76 | #editform,#toolbar,#wpTextbox1 {
77 | clear: both
78 | }
79 |
80 | #toolbar img {
81 | cursor: pointer
82 | }
83 |
84 | .editsection {
85 | float: right;
86 | margin-left: 5px
87 | }
88 |
89 | .mw-content-ltr .editsection,.mw-content-rtl .mw-content-ltr .editsection
90 | {
91 | float: right
92 | }
93 |
94 | .mw-content-rtl .editsection,.mw-content-ltr .mw-content-rtl .editsection
95 | {
96 | float: left
97 | }
98 |
99 | div.mw-filepage-resolutioninfo {
100 | font-size: smaller
101 | }
102 |
103 | td.mw-label {
104 | text-align: right
105 | }
106 |
107 | td.mw-input {
108 | text-align: left
109 | }
110 |
111 | td.mw-submit {
112 | text-align: left
113 | }
114 |
115 | td.mw-label {
116 | vertical-align: top
117 | }
118 |
119 | .prefsection td.mw-label {
120 | width: 20%
121 | }
122 |
123 | .prefsection table {
124 | width: 100%
125 | }
126 |
127 | td.mw-submit {
128 | white-space: nowrap
129 | }
130 |
131 | table.mw-htmlform-nolabel td.mw-label {
132 | width: 1px
133 | }
134 |
135 | tr.mw-htmlform-vertical-label td.mw-label {
136 | text-align: left !important
137 | }
138 |
139 | .mw-htmlform-invalid-input td.mw-input input {
140 | border-color: red
141 | }
142 |
143 | .mw-htmlform-flatlist div.mw-htmlform-flatlist-item {
144 | display: inline;
145 | margin-right: 1em;
146 | white-space: nowrap
147 | }
148 |
149 | input#wpSummary {
150 | width: 80%
151 | }
152 |
153 | .thumbcaption {
154 | text-align: left
155 | }
156 |
157 | .magnify {
158 | float: right
159 | }
160 |
161 | p.mw-ipb-conveniencelinks,p.mw-protect-editreasons,p.mw-filedelete-editreasons,p.mw-delete-editreasons,p.mw-revdel-editreasons
162 | {
163 | font-size: 90%;
164 | text-align: right
165 | }
166 |
167 | .autocomment {
168 | color: gray
169 | }
170 |
171 | .mw-history-revisiondelete-button,#mw-fileduplicatesearch-icon {
172 | float: right
173 | }
174 |
175 | .newpage,.minoredit,.botedit {
176 | font-weight: bold
177 | }
178 |
179 | #shared-image-dup,#shared-image-conflict {
180 | font-style: italic
181 | }
182 |
183 | div.mw-warning-with-logexcerpt {
184 | padding: 3px;
185 | margin-bottom: 3px;
186 | border: 2px solid #2F6FAB;
187 | clear: both
188 | }
189 |
190 | div.mw-warning-with-logexcerpt ul li {
191 | font-size: 90%
192 | }
193 |
194 | span.mw-revdelundel-link,strong.mw-revdelundel-link {
195 | font-size: 90%
196 | }
197 |
198 | span.mw-revdelundel-hidden,input.mw-revdelundel-hidden {
199 | visibility: hidden
200 | }
201 |
202 | td.mw-revdel-checkbox,th.mw-revdel-checkbox {
203 | padding-right: 10px;
204 | text-align: center
205 | }
206 |
207 | .plainlinks a {
208 | background: none !important;
209 | padding: 0 !important
210 | }
211 |
212 | .rtl a.external.free,.rtl a.external.autonumber {
213 | direction: ltr;
214 | unicode-bidi: embed
215 | }
216 |
217 | table.wikitable {
218 | margin: 1em 1em 1em 0;
219 | background-color: #f9f9f9;
220 | border: 1px #aaa solid;
221 | border-collapse: collapse;
222 | color: black;
223 | font-size: 90%
224 | }
225 |
226 | table.wikitable> ; tr> ; th,table.wikitable> ; tr> ; td,table.wikitable>
227 | ; *> ; tr> ; th,table.wikitable> ; *> ; tr> ; td {
228 | border: 1px #aaa solid;
229 | padding: 0.2em
230 | }
231 |
232 | table.wikitable> ; tr> ; th,table.wikitable> ; *> ; tr> ; th {
233 | background-color: #f2f2f2;
234 | text-align: center
235 | }
236 |
237 | table.wikitable> ; caption {
238 | font-weight: bold
239 | }
240 |
241 | table.collapsed tr.collapsable {
242 | display: none
243 | }
244 |
245 | .success {
246 | color: green;
247 | font-size: larger
248 | }
249 |
250 | .warning {
251 | color: #FFA500;
252 | font-size: larger
253 | }
254 |
255 | .error {
256 | color: red;
257 | font-size: larger
258 | }
259 |
260 | .errorbox,.warningbox,.successbox {
261 | font-size: larger;
262 | border: 2px solid;
263 | padding: .5em 1em;
264 | float: left;
265 | margin-bottom: 2em;
266 | color: #000
267 | }
268 |
269 | .errorbox {
270 | border-color: red;
271 | background-color: #fff2f2
272 | }
273 |
274 | .warningbox {
275 | border-color: #FF8C00;
276 | background-color: #FFFFC0
277 | }
278 |
279 | .successbox {
280 | border-color: green;
281 | background-color: #dfd
282 | }
283 |
284 | .errorbox h2,.warningbox h2,.successbox h2 {
285 | font-size: 1em;
286 | font-weight: bold;
287 | display: inline;
288 | margin: 0 .5em 0 0;
289 | border: none
290 | }
291 |
292 | .mw-infobox {
293 | border: 2px solid #ff7f00;
294 | margin: 0.5em;
295 | clear: left;
296 | overflow: hidden
297 | }
298 |
299 | .mw-infobox-left {
300 | margin: 7px;
301 | float: left;
302 | width: 35px
303 | }
304 |
305 | .mw-infobox-right {
306 | margin: 0.5em 0.5em 0.5em 49px
307 | }
308 |
309 | .previewnote {
310 | color: #c00;
311 | margin-bottom: 1em
312 | }
313 |
314 | .previewnote p {
315 | text-indent: 3em;
316 | margin: 0.8em 0
317 | }
318 |
319 | .visualClear {
320 | clear: both
321 | }
322 |
323 | #mw_trackbacks {
324 | border: solid 1px #bbbbff;
325 | background-color: #eeeeff;
326 | padding: 0.2em
327 | }
328 |
329 | .mw-datatable {
330 | border-collapse: collapse
331 | }
332 |
333 | .mw-datatable,.mw-datatable td,.mw-datatable th {
334 | border: 1px solid #aaaaaa;
335 | padding: 0 0.15em 0 0.15em
336 | }
337 |
338 | .mw-datatable th {
339 | background-color: #ddddff
340 | }
341 |
342 | .mw-datatable td {
343 | background-color: #ffffff
344 | }
345 |
346 | .mw-datatable tr:hover td {
347 | background-color: #eeeeff
348 | }
349 |
350 | .TablePager {
351 | min-width: 80%
352 | }
353 |
354 | .TablePager_nav {
355 | margin: 0 auto
356 | }
357 |
358 | .TablePager_nav td {
359 | padding: 3px;
360 | text-align: center
361 | }
362 |
363 | .TablePager_nav a {
364 | text-decoration: none
365 | }
366 |
367 | .imagelist td,.imagelist th {
368 | white-space: nowrap
369 | }
370 |
371 | .imagelist .TablePager_col_links {
372 | background-color: #eeeeff
373 | }
374 |
375 | .imagelist .TablePager_col_img_description {
376 | white-space: normal
377 | }
378 |
379 | .imagelist th.TablePager_sort {
380 | background-color: #ccccff
381 | }
382 |
383 | ul#filetoc {
384 | text-align: center;
385 | border: 1px solid #aaaaaa;
386 | background-color: #f9f9f9;
387 | padding: 5px;
388 | font-size: 95%;
389 | margin-bottom: 0.5em;
390 | margin-left: 0;
391 | margin-right: 0
392 | }
393 |
394 | #filetoc li {
395 | display: inline;
396 | list-style-type: none;
397 | padding-right: 2em
398 | }
399 |
400 | table.mw_metadata {
401 | font-size: 0.8em;
402 | margin-left: 0.5em;
403 | margin-bottom: 0.5em;
404 | width: 400px
405 | }
406 |
407 | table.mw_metadata caption {
408 | font-weight: bold
409 | }
410 |
411 | table.mw_metadata th {
412 | font-weight: normal
413 | }
414 |
415 | table.mw_metadata td {
416 | padding: 0.1em
417 | }
418 |
419 | table.mw_metadata {
420 | border: none;
421 | border-collapse: collapse
422 | }
423 |
424 | table.mw_metadata td,table.mw_metadata th {
425 | text-align: center;
426 | border: 1px solid #aaaaaa;
427 | padding-left: 5px;
428 | padding-right: 5px
429 | }
430 |
431 | table.mw_metadata th {
432 | background-color: #f9f9f9
433 | }
434 |
435 | table.mw_metadata td {
436 | background-color: #fcfcfc
437 | }
438 |
439 | table.mw_metadata ul.metadata-langlist {
440 | list-style-type: none;
441 | list-style-image: none;
442 | padding-right: 5px;
443 | padding-left: 5px;
444 | margin: 0
445 | }
446 |
447 | .mw-content-ltr ul,.mw-content-rtl .mw-content-ltr ul {
448 | word-wrap: break-word;
449 | margin: 0.3em 0 0 1.6em;
450 | padding: 0
451 | }
452 |
453 | .mw-content-rtl ul,.mw-content-ltr .mw-content-rtl ul {
454 | word-wrap: break-word;
455 | margin: 0.3em 1.6em 0 0;
456 | padding: 0
457 | }
458 |
459 | .mw-content-ltr ol,.mw-content-rtl .mw-content-ltr ol {
460 | margin: 0.3em 0 0 3.2em;
461 | padding: 0
462 | }
463 |
464 | .mw-content-rtl ol,.mw-content-ltr .mw-content-rtl ol {
465 | margin: 0.3em 3.2em 0 0;
466 | padding: 0
467 | }
468 |
469 | .mw-content-ltr dd,.mw-content-rtl .mw-content-ltr dd {
470 | margin-left: 1.6em;
471 | margin-right: 0
472 | }
473 |
474 | .mw-content-rtl dd,.mw-content-ltr .mw-content-rtl dd {
475 | margin-right: 1.6em;
476 | margin-left: 0
477 | }
478 |
479 | li.gallerybox {
480 | vertical-align: top;
481 | border: solid 2px white;
482 | display: -moz-inline-box;
483 | display: inline-block
484 | }
485 |
486 | ul.gallery,li.gallerybox {
487 | zoom: 1;
488 | *display: inline
489 | }
490 |
491 | ul.gallery {
492 | margin: 2px;
493 | padding: 2px;
494 | display: block
495 | }
496 |
497 | li.gallerycaption {
498 | font-weight: bold;
499 | text-align: center;
500 | display: block;
501 | word-wrap: break-word
502 | }
503 |
504 | li.gallerybox div.thumb {
505 | text-align: center;
506 | border: 1px solid #ccc;
507 | background-color: #f9f9f9;
508 | margin: 2px
509 | }
510 |
511 | li.gallerybox div.thumb img {
512 | display: block;
513 | margin: 0 auto
514 | }
515 |
516 | div.gallerytext {
517 | overflow: hidden;
518 | font-size: 94%;
519 | padding: 2px 4px;
520 | word-wrap: break-word
521 | }
522 |
523 | h1:lang(as),h1:lang(bn),h1:lang(gu),h1:lang(hi),h1:lang(kn),h1:lang(ml),h1:lang(mr),h1:lang(or),h1:lang(pa),h1:lang(sa),h1:lang(ta),h1:lang(te)
524 | {
525 | line-height: 1.5em !important
526 | }
527 |
528 | h2:lang(as),h3:lang(as),h4:lang(as),h5:lang(as),h6:lang(as),h2:lang(bn),h3:lang(bn),h4:lang(bn),h5:lang(bn),h6:lang(bn),h2:lang(gu),h3:lang(gu),h4:lang(gu),h5:lang(gu),h6:lang(gu),h2:lang(hi),h3:lang(hi),h4:lang(hi),h5:lang(hi),h6:lang(hi),h2:lang(kn),h3:lang(kn),h4:lang(kn),h5:lang(kn),h6:lang(kn),h2:lang(ml),h3:lang(ml),h4:lang(ml),h5:lang(ml),h6:lang(ml),h2:lang(mr),h3:lang(mr),h4:lang(mr),h5:lang(mr),h6:lang(mr),h2:lang(or),h3:lang(or),h4:lang(or),h5:lang(or),h6:lang(or),h2:lang(pa),h3:lang(pa),h4:lang(pa),h5:lang(pa),h6:lang(pa),h2:lang(sa),h3:lang(sa),h4:lang(sa),h5:lang(sa),h6:lang(sa),h2:lang(ta),h3:lang(ta),h4:lang(ta),h5:lang(ta),h6:lang(ta),h2:lang(te),h3:lang(te),h4:lang(te),h5:lang(te),h6:lang(te)
529 | {
530 | line-height: 1.2em
531 | }
532 |
533 | ol:lang(bcc) li,ol:lang(bqi) li,ol:lang(fa) li,ol:lang(glk) li,ol:lang(kk-arab) li,ol:lang(mzn) li
534 | {
535 | list-style-type: -moz-persian;
536 | list-style-type: persian
537 | }
538 |
539 | ol:lang(ckb) li {
540 | list-style-type: -moz-arabic-indic;
541 | list-style-type: arabic-indic
542 | }
543 |
544 | ol:lang(as) li,ol:lang(bn) li {
545 | list-style-type: -moz-bengali;
546 | list-style-type: bengali
547 | }
548 |
549 | ol:lang(or) li {
550 | list-style-type: -moz-oriya;
551 | list-style-type: oriya
552 | }
553 |
554 | #toc ul,.toc ul {
555 | margin: .3em 0
556 | }
557 |
558 | .mw-content-ltr .toc ul,.mw-content-ltr #toc ul,.mw-content-rtl .mw-content-ltr .toc ul,.mw-content-rtl .mw-content-ltr #toc ul
559 | {
560 | text-align: left
561 | }
562 |
563 | .mw-content-rtl .toc ul,.mw-content-rtl #toc ul,.mw-content-ltr .mw-content-rtl .toc ul,.mw-content-ltr .mw-content-rtl #toc ul
564 | {
565 | text-align: right
566 | }
567 |
568 | .mw-content-ltr .toc ul ul,.mw-content-ltr #toc ul ul,.mw-content-rtl .mw-content-ltr .toc ul ul,.mw-content-rtl .mw-content-ltr #toc ul ul
569 | {
570 | margin: 0 0 0 2em
571 | }
572 |
573 | .mw-content-rtl .toc ul ul,.mw-content-rtl #toc ul ul,.mw-content-ltr .mw-content-rtl .toc ul ul,.mw-content-ltr .mw-content-rtl #toc ul ul
574 | {
575 | margin: 0 2em 0 0
576 | }
577 |
578 | #toc #toctitle,.toc #toctitle,#toc .toctitle,.toc .toctitle {
579 | direction: ltr
580 | }
581 |
582 | .mw-help-field-data {
583 | display: block;
584 | background-color: #d6f3ff;
585 | padding: 5px 8px 4px 8px;
586 | border: 1px solid #5dc9f4;
587 | margin-left: 20px
588 | }
589 |
590 | .tipsy {
591 | padding: 5px 5px 10px;
592 | font-size: 12px;
593 | position: absolute;
594 | z-index: 100000;
595 | overflow: visible
596 | }
597 |
598 | .tipsy-inner {
599 | padding: 5px 8px 4px 8px;
600 | background-color: #d6f3ff;
601 | color: black;
602 | border: 1px solid #5dc9f4;
603 | max-width: 300px;
604 | text-align: left
605 | }
606 |
607 | .tipsy-arrow {
608 | position: absolute;
609 | width: 13px;
610 | height: 13px
611 | }
612 |
613 | .tipsy-se .tipsy-arrow {
614 | bottom: -2px;
615 | right: 10px;
616 | background-position: 0% 100%
617 | }
618 |
619 | #mw-clearyourcache,#mw-sitecsspreview,#mw-sitejspreview,#mw-usercsspreview,#mw-userjspreview
620 | {
621 | direction: ltr;
622 | unicode-bidi: embed
623 | }
624 |
625 | .diff-currentversion-title,.diff {
626 | direction: ltr;
627 | unicode-bidi: embed
628 | }
629 |
630 | .diff-contentalign-right td {
631 | direction: rtl;
632 | unicode-bidi: embed
633 | }
634 |
635 | .diff-contentalign-left td {
636 | direction: ltr;
637 | unicode-bidi: embed
638 | }
639 |
640 | .diff-otitle,.diff-ntitle,.diff-lineno {
641 | direction: ltr !important;
642 | unicode-bidi: embed
643 | }
644 |
645 | #mw-revision-info,#mw-revision-info-current,#mw-revision-nav {
646 | direction: ltr;
647 | display: inline
648 | }
649 |
650 | div.tright,div.floatright,table.floatright {
651 | clear: right;
652 | float: right
653 | }
654 |
655 | div.tleft,div.floatleft,table.floatleft {
656 | float: left;
657 | clear: left
658 | }
659 |
660 | div.floatright,table.floatright,div.floatleft,table.floatleft {
661 | position: relative
662 | }
663 |
664 | #mw-credits a {
665 | unicode-bidi: embed
666 | }
667 |
668 | .xdebug-error {
669 | position: absolute;
670 | z-index: 99
671 | }
672 |
673 | a {
674 | text-decoration: none;
675 | color: #0645ad;
676 | background: none
677 | }
678 |
679 | a:visited {
680 | color: #0b0080
681 | }
682 |
683 | a:active {
684 | color: #faa700
685 | }
686 |
687 | a:hover,a:focus {
688 | text-decoration: underline
689 | }
690 |
691 | a.stub {
692 | color: #772233
693 | }
694 |
695 | a.new {
696 | color: #ba0000
697 | }
698 |
699 | a.new:visited {
700 | color: #a55858
701 | }
702 |
703 | .mw-body a.extiw,.mw-body a.extiw:active {
704 | color: #36b
705 | }
706 |
707 | .mw-body a.extiw:visited {
708 | color: #636
709 | }
710 |
711 | .mw-body a.extiw:active {
712 | color: #b63
713 | }
714 |
715 | .mw-body a.external {
716 | color: #36b
717 | }
718 |
719 | .mw-body a.external:visited {
720 | color: #636;
721 | }
722 |
723 | .mw-body a.external:active {
724 | color: #b63
725 | }
726 |
727 | img {
728 | border: none;
729 | vertical-align: middle
730 | }
731 |
732 | hr {
733 | height: 1px;
734 | color: #aaa;
735 | background-color: #aaa;
736 | border: 0;
737 | margin: .2em 0
738 | }
739 |
740 | h1,h2,h3,h4,h5,h6 {
741 | color: black;
742 | background: none;
743 | font-weight: normal;
744 | margin: 0;
745 | overflow: hidden;
746 | padding-top: .5em;
747 | padding-bottom: .17em;
748 | border-bottom: 1px solid #aaa;
749 | width: auto
750 | }
751 |
752 | h1 {
753 | font-size: 188%
754 | }
755 |
756 | h1 .editsection {
757 | font-size: 53%
758 | }
759 |
760 | h2 {
761 | font-size: 150%
762 | }
763 |
764 | h2 .editsection {
765 | font-size: 67%
766 | }
767 |
768 | h3,h4,h5,h6 {
769 | border-bottom: none;
770 | font-weight: bold
771 | }
772 |
773 | h3 {
774 | font-size: 132%
775 | }
776 |
777 | h3 .editsection {
778 | font-size: 76%;
779 | font-weight: normal
780 | }
781 |
782 | h4 {
783 | font-size: 116%
784 | }
785 |
786 | h4 .editsection {
787 | font-size: 86%;
788 | font-weight: normal
789 | }
790 |
791 | h5 {
792 | font-size: 100%
793 | }
794 |
795 | h5 .editsection {
796 | font-weight: normal
797 | }
798 |
799 | h6 {
800 | font-size: 80%
801 | }
802 |
803 | h6 .editsection {
804 | font-size: 125%;
805 | font-weight: normal
806 | }
807 |
808 | h1,h2 {
809 | margin-bottom: .6em
810 | }
811 |
812 | h3,h4,h5 {
813 | margin-bottom: .3em
814 | }
815 |
816 | p {
817 | margin: .4em 0 .5em 0;
818 | line-height: 1.5em
819 | }
820 |
821 | p img {
822 | margin: 0
823 | }
824 |
825 | ul {
826 | line-height: 1.5em;
827 | list-style-type: square;
828 | margin: .3em 0 0 1.6em;
829 | padding: 0
830 | }
831 |
832 | ol {
833 | line-height: 1.5em;
834 | margin: .3em 0 0 3.2em;
835 | padding: 0;
836 | list-style-image: none
837 | }
838 |
839 | li {
840 | margin-bottom: .1em
841 | }
842 |
843 | dt {
844 | font-weight: bold;
845 | margin-bottom: .1em
846 | }
847 |
848 | dl {
849 | margin-top: .2em;
850 | margin-bottom: .5em
851 | }
852 |
853 | dd {
854 | line-height: 1.5em;
855 | margin-left: 1.6em;
856 | margin-bottom: .1em
857 | }
858 |
859 | q {
860 | font-family: Times, "Times New Roman", serif;
861 | font-style: italic
862 | }
863 |
864 | pre,code,tt,kbd,samp {
865 | font-family: monospace, Courier
866 | }
867 |
868 | code {
869 | background-color: #f9f9f9
870 | }
871 |
872 | pre {
873 | padding: 1em;
874 | white-space: pre-wrap;
875 | border: 1px dashed #2f6fab;
876 | color: black;
877 | background-color: #f9f9f9
878 | }
879 |
880 | table {
881 | font-size: 100%
882 | }
883 |
884 | fieldset {
885 | border: 1px solid #2f6fab;
886 | margin: 1em 0 1em 0;
887 | padding: 0 1em 1em;
888 | line-height: 1.5em
889 | }
890 |
891 | fieldset.nested {
892 | margin: 0 0 0.5em 0;
893 | padding: 0 0.5em 0.5em
894 | }
895 |
896 | legend {
897 | padding: .5em;
898 | font-size: 95%
899 | }
900 |
901 | form {
902 | border: none;
903 | margin: 0
904 | }
905 |
906 | textarea {
907 | width: 100%;
908 | padding: .1em
909 | }
910 |
911 | select {
912 | vertical-align: top
913 | }
914 |
915 | .center {
916 | width: 100%;
917 | text-align: center
918 | }
919 |
920 | *.center * {
921 | margin-left: auto;
922 | margin-right: auto
923 | }
924 |
925 | .small {
926 | font-size: 94%
927 | }
928 |
929 | table.small {
930 | font-size: 100%
931 | }
932 |
933 | #toc,.toc,.mw-warning {
934 | border: 1px solid #aaa;
935 | background-color: #f9f9f9;
936 | padding: 5px;
937 | font-size: 95%
938 | }
939 |
940 | #toc h2,.toc h2 {
941 | display: inline;
942 | border: none;
943 | padding: 0;
944 | font-size: 100%;
945 | font-weight: bold
946 | }
947 |
948 | #toc #toctitle,.toc #toctitle,#toc .toctitle,.toc .toctitle {
949 | text-align: center
950 | }
951 |
952 | #toc ul,.toc ul {
953 | list-style-type: none;
954 | list-style-image: none;
955 | margin-left: 0;
956 | padding: 0;
957 | text-align: left
958 | }
959 |
960 | #toc ul ul,.toc ul ul {
961 | margin: 0 0 0 2em
962 | }
963 |
964 | #toc .toctoggle,.toc .toctoggle {
965 | font-size: 94%
966 | }
967 |
968 | .toccolours {
969 | border: 1px solid #aaa;
970 | background-color: #f9f9f9;
971 | padding: 5px;
972 | font-size: 95%
973 | }
974 |
975 | .mw-warning {
976 | margin-left: 50px;
977 | margin-right: 50px;
978 | text-align: center
979 | }
980 |
981 | div.floatright,table.floatright {
982 | margin: 0 0 .5em .5em;
983 | border: 0
984 | }
985 |
986 | div.floatright p {
987 | font-style: italic
988 | }
989 |
990 | div.floatleft,table.floatleft {
991 | margin: 0 .5em .5em 0;
992 | border: 0
993 | }
994 |
995 | div.floatleft p {
996 | font-style: italic
997 | }
998 |
999 | div.thumb {
1000 | margin-bottom: .5em;
1001 | width: auto;
1002 | background-color: transparent
1003 | }
1004 |
1005 | div.thumbinner {
1006 | border: 1px solid #ccc;
1007 | padding: 3px !important;
1008 | background-color: #f9f9f9;
1009 | font-size: 94%;
1010 | text-align: center;
1011 | overflow: hidden
1012 | }
1013 |
1014 | html .thumbimage {
1015 | border: 1px solid #ccc
1016 | }
1017 |
1018 | html .thumbcaption {
1019 | border: none;
1020 | text-align: left;
1021 | line-height: 1.4em;
1022 | padding: 3px !important;
1023 | font-size: 94%
1024 | }
1025 |
1026 | div.magnify {
1027 | float: right;
1028 | border: none !important;
1029 | background: none !important
1030 | }
1031 |
1032 | div.magnify a,div.magnify img {
1033 | display: block;
1034 | border: none !important;
1035 | background: none !important
1036 | }
1037 |
1038 | div.tright {
1039 | margin: .5em 0 1.3em 1.4em
1040 | }
1041 |
1042 | div.tleft {
1043 | margin: .5em 1.4em 1.3em 0
1044 | }
1045 |
1046 | img.thumbborder {
1047 | border: 1px solid #dddddd
1048 | }
1049 |
1050 | #userlogin,#userloginForm {
1051 | border: solid 1px #cccccc;
1052 | padding: 1.2em;
1053 | margin: .5em;
1054 | float: left
1055 | }
1056 |
1057 | .usermessage {
1058 | background-color: #ffce7b;
1059 | border: 1px solid #ffa500;
1060 | color: black;
1061 | font-weight: bold;
1062 | margin: 2em 0 1em;
1063 | padding: .5em 1em;
1064 | vertical-align: middle
1065 | }
1066 |
1067 | #siteNotice {
1068 | position: relative;
1069 | text-align: center;
1070 | margin: 0
1071 | }
1072 |
1073 | #localNotice {
1074 | margin-bottom: 0.9em
1075 | }
1076 |
1077 | .firstHeading,#firstHeading {
1078 | margin-bottom: .1em;
1079 | line-height: 1.2em;
1080 | padding-bottom: 0
1081 | }
1082 |
1083 | #contentSub,#contentSub2 {
1084 | font-size: 84%;
1085 | line-height: 1.2em;
1086 | margin: 0 0 1.4em 1em;
1087 | color: #7d7d7d;
1088 | width: auto
1089 | }
1090 |
1091 | span.subpages {
1092 | display: block
1093 | }
1094 |
1095 | div#column-content {
1096 | width: 100%;
1097 | /*float: right;
1098 | margin: 0 0 .6em -12.2em;*/
1099 | padding: 0
1100 | }
1101 |
1102 | div#content { /*margin: 2.8em 0 0 12.2em;;
1103 | padding: 0 1em 1em 1em;*/
1104 | margin: 1em;
1105 | position: relative;
1106 | z-index: 2
1107 | }
1108 |
1109 | div#content {
1110 | background: white;
1111 | color: black;
1112 | /*border: 1px solid #aaa;
1113 | border-right: none;*/
1114 | line-height: 1.5em
1115 | }
1116 |
1117 | body {
1118 | font: x-small sans-serif;
1119 | /*background: #FFF888;*/
1120 | color: black;
1121 | margin: 0;
1122 | padding: 0;
1123 | direction: ltr;
1124 | unicode-bidi: embed
1125 | }
1126 |
1127 | div#globalWrapper {
1128 | font-size: 115%; /*default 127%*/
1129 | width: 100%;
1130 | margin: 0;
1131 | padding: 0
1132 | }
1133 |
1134 | a {
1135 | color: #002bb8
1136 | }
1137 |
1138 | a:visited {
1139 | color: #5a3696
1140 | }
1141 |
1142 | a.new {
1143 | color: #cc2200
1144 | }
1145 |
1146 | input.historysubmit {
1147 | padding: 0 .3em .3em .3em !important;
1148 | font-size: 94%;
1149 | cursor: pointer;
1150 | height: 1.7em !important;
1151 | margin-left: 1.6em
1152 | }
1153 |
1154 | pre {
1155 | line-height: 1.1em
1156 | }
1157 |
1158 | #siteNotice {
1159 | font-size: 95%;
1160 | padding: 0 0.9em
1161 | }
1162 |
1163 | #localNotice {
1164 | margin: 0
1165 | }
1166 |
1167 | #siteNotice p {
1168 | margin: 0;
1169 | padding: 0
1170 | }
1171 |
1172 | table.rimage {
1173 | float: right;
1174 | position: relative;
1175 | margin-left: 1em;
1176 | margin-bottom: 1em;
1177 | text-align: center
1178 | }
1179 |
1180 | .special li {
1181 | line-height: 1.4em;
1182 | margin: 0;
1183 | padding: 0
1184 | }
1185 |
1186 | #bodyContent {
1187 | word-wrap: break-word;
1188 | }
1189 |
1190 | #bodyContent a.extiw,#bodyContent a.extiw:active {
1191 | color: #36b
1192 | }
1193 |
1194 | #bodyContent a.external {
1195 | color: #36b
1196 | }
1197 |
1198 | .pBody {
1199 | font-size: 95%;
1200 | background-color: white;
1201 | color: black;
1202 | border-collapse: collapse;
1203 | border: 1px solid #aaa;
1204 | padding: 0 .8em .3em .5em
1205 | }
1206 |
1207 | #p-logo {
1208 | top: 0;
1209 | left: 0;
1210 | position: absolute;
1211 | z-index: 3;
1212 | height: 155px;
1213 | width: 12em;
1214 | overflow: visible
1215 | }
1216 |
1217 | #p-logo h5 {
1218 | display: none
1219 | }
1220 |
1221 | #p-logo a,#p-logo a:hover {
1222 | display: block;
1223 | height: 155px;
1224 | width: 12.2em;
1225 | background-repeat: no-repeat;
1226 | background-position: 35% 50% !important;
1227 | text-decoration: none
1228 | }
1229 |
1230 | #p-search {
1231 | position: relative;
1232 | z-index: 3
1233 | }
1234 |
1235 | input.searchButton {
1236 | margin-top: 1px;
1237 | font-size: 95%
1238 | }
1239 |
1240 | #searchGoButton {
1241 | padding-left: .5em;
1242 | padding-right: .5em;
1243 | font-weight: bold
1244 | }
1245 |
1246 | #searchInput {
1247 | width: 10.9em;
1248 | margin: 0;
1249 | font-size: 95%
1250 | }
1251 |
1252 | #p-search .pBody {
1253 | padding: .5em .4em .4em .4em;
1254 | text-align: center
1255 | }
1256 |
1257 | #p-search #searchform div div {
1258 | margin-top: .4em;
1259 | font-size: 95%
1260 | }
1261 |
1262 | #t-ispermalink,#t-iscite {
1263 | color: #999
1264 | }
1265 |
1266 | td.htmlform-tip {
1267 | font-size: x-small;
1268 | padding: .2em 2em;
1269 | color: #666
1270 | }
1271 |
1272 | *> ; html #bodyContent,*> ; html #bodyContent pre {
1273 | overflow-x: auto;
1274 | width: 100%;
1275 | padding-bottom: 25px
1276 | }
1277 |
1278 | * html div#column-content {
1279 | display: inline;
1280 | margin-bottom: 0
1281 | }
1282 |
1283 | *> ; html div#column-content {
1284 | float: none
1285 | }
1286 |
1287 | .redirectText {
1288 | font-size: 150%;
1289 | margin: 5px
1290 | }
1291 |
1292 | .printfooter {
1293 | display: none
1294 | }
1295 |
1296 | div.patrollink {
1297 | clear: both
1298 | }
1299 |
1300 | .sharedUploadNotice {
1301 | font-style: italic
1302 | }
1303 |
1304 | span.updatedmarker {
1305 | color: black;
1306 | background-color: #0f0
1307 | }
1308 |
1309 | .toggle {
1310 | margin-left: 2em;
1311 | text-indent: -2em
1312 | }
1313 |
1314 | input#wpSave,input#wpDiff {
1315 | margin-right: 0.33em
1316 | }
1317 |
1318 | #wpSave {
1319 | font-weight: bold
1320 | }
1321 |
1322 | div.noarticletext {
1323 | border: 1px solid #ccc;
1324 | background: #fff;
1325 | padding: .2em 1em;
1326 | color: #000
1327 | }
1328 |
1329 | div#searchTargetContainer {
1330 | left: 10px;
1331 | top: 10px;
1332 | width: 90%;
1333 | background: white
1334 | }
1335 |
1336 | div#searchTarget {
1337 | padding: 3px;
1338 | margin: 5px;
1339 | background: #F0F0F0;
1340 | border: solid 1px blue
1341 | }
1342 |
1343 | div#searchTarget ul li {
1344 | list-style: none
1345 | }
1346 |
1347 | div#searchTarget ul li:before {
1348 | color: orange;
1349 | content: "\00BB \0020"
1350 | }
1351 |
1352 | div#searchTargetHide {
1353 | float: right;
1354 | border: solid 1px black;
1355 | background: #DCDCDC;
1356 | padding: 2px
1357 | }
1358 |
1359 | #powersearch p {
1360 | margin-top: 0px
1361 | }
1362 |
1363 | div.multipageimagenavbox {
1364 | border: solid 1px silver;
1365 | padding: 4px;
1366 | margin: 1em;
1367 | background: #f0f0f0
1368 | }
1369 |
1370 | div.multipageimagenavbox div.thumb {
1371 | border: none;
1372 | margin-left: 2em;
1373 | margin-right: 2em
1374 | }
1375 |
1376 | div.multipageimagenavbox hr {
1377 | margin: 6px
1378 | }
1379 |
1380 | table.multipageimage td {
1381 | text-align: center
1382 | }
1383 |
1384 | .templatesUsed {
1385 | margin-top: 1.5em
1386 | }
1387 |
1388 | .mw-summary-preview {
1389 | margin: 0.1em 0
1390 | }
1391 |
1392 | div.mw-lag-warn-normal,div.mw-lag-warn-high {
1393 | padding: 3px;
1394 | text-align: center;
1395 | margin: 3px auto
1396 | }
1397 |
1398 | div.mw-lag-warn-normal {
1399 | border: 1px solid #FFCC66;
1400 | background-color: #FFFFCC
1401 | }
1402 |
1403 | div.mw-lag-warn-high {
1404 | font-weight: bold;
1405 | border: 2px solid #FF0033;
1406 | background-color: #FFCCCC
1407 | }
1408 |
1409 | .MediaTransformError {
1410 | background-color: #ccc;
1411 | padding: 0.1em
1412 | }
1413 |
1414 | .MediaTransformError td {
1415 | text-align: center;
1416 | vertical-align: middle;
1417 | font-size: 90%
1418 | }
1419 |
1420 | .no-text-transform {
1421 | text-transform: none
1422 | }
1423 |
1424 | .tipsy {
1425 | font-size: 127%
1426 | }
1427 |
1428 | body { /*background: #f6f9fc*/
1429 | background: #fff
1430 | }
1431 |
1432 | body,#content,table {
1433 | color: #222
1434 | }
1435 |
1436 | h1,h2,h3,h4,h5 {
1437 | color: #222
1438 | }
1439 |
1440 | h1 {
1441 | font-weight: bold
1442 | }
1443 |
1444 | pre,code,tt {
1445 | background-color: #ebf1f5;
1446 | color: #222;
1447 | font-family: monospace
1448 | }
1449 |
1450 | pre {
1451 | border: 1px solid #bcd;
1452 | overflow: auto
1453 | }
1454 |
1455 | code,tt {
1456 | padding: 0.3em
1457 | }
1458 |
1459 | a {
1460 | text-decoration: none;
1461 | outline: none
1462 | }
1463 |
1464 | a:link,#bodyContent a.external {
1465 | color: #07b
1466 | }
1467 |
1468 | #bodyContent> ; div.mw-content-ltr a,#bodyContent> ; div.mw-content-rtl a,#wikiPreview>
1469 | ; div.mw-content-ltr a,#wikiPreview> ; div.mw-content-rtl a {
1470 | font-weight: bold
1471 | }
1472 |
1473 | #bodyContent #toc a,#bodyContent .special li> ; a,#bodyContent .special li span a,#bodyContent #pagehistory a
1474 | {
1475 | font-weight: normal
1476 | }
1477 |
1478 | a:visited,#bodyContent a:visited.external {
1479 | color: #666
1480 | }
1481 |
1482 | a:focus {
1483 | color: #e90 !important
1484 | }
1485 |
1486 | a:hover,#bodyContent #toc a:hover,#bodyContent a:hover.external {
1487 | text-decoration: underline;
1488 | background-color: transparent;
1489 | color: #999
1490 | }
1491 |
1492 | a:active {
1493 | color: #e90 !important
1494 | }
1495 |
1496 | a.new {
1497 | color: #b00 !important
1498 | }
1499 |
1500 | /*#content {
1501 | top: .8em
1502 | }
1503 |
1504 | #content {
1505 | top: 10px
1506 | }*/
1507 | div#globalWrapper {
1508 | width: 100%
1509 | }
1510 |
1511 | #toc,.toc,.mw-warning {
1512 | background-color: #f9faff;
1513 | border: 1px solid #d7dfe3
1514 | }
1515 |
1516 | .pBody {
1517 | border: 1px solid #ddd
1518 | }
1519 |
1520 | div#content { /*border: 1px solid #ccc*/
1521 |
1522 | }
1523 |
1524 | #p-logo {
1525 | display: none !important
1526 | }
1527 |
1528 | #bodyContent a.external[href^="https://"],.link-https {
1529 | background: none;
1530 | padding: 0
1531 | }
1532 |
1533 | #bodyContent table {
1534 | border-collapse: collapse;
1535 | padding: 2px
1536 | }
1537 |
1538 | #bodyContent td {
1539 | padding: 2px
1540 | }
1541 |
1542 | ul,.portlet ul {
1543 | list-style-image: none
1544 | }
1545 |
1546 | /***hide things***/
1547 | #jump-to-nav,#siteSub,#archnavbar,#footer,#column-one,#catlinks {
1548 | display: none !important;
1549 | }
1550 |
1551 | h1.firstHeading { /*hide title*/
1552 | display: none;
1553 | }
1554 |
1555 | div.printfooter,div.mw-search-formheader,div.mw-search-result-data {
1556 | display: none;
1557 | }
1558 |
1559 | input { /*hide search*/
1560 | display: none;
1561 | }
1562 |
1563 | /***format page***/
1564 | #content { /* width: 99% !important;*/
1565 | background-color: #F6F9FC;
1566 | }
1567 |
1568 | li.toclevel-1 {
1569 | padding: 5px;
1570 | }
1571 |
1572 | li.toclevel-2 {
1573 | padding: 5px;
1574 | }
1575 |
1576 | li.toclevel-3 {
1577 | display: none;
1578 | }
1579 |
1580 | div.mw-search-result-heading {
1581 | padding-top: 10px;
1582 | }
1583 | /*table th{
1584 | border: 1px solid #333
1585 | }
1586 | */
1587 | /*this is needed for "Summary" and "Related" */
1588 | div#mw-content-text.mw-content-ltr table {
1589 | /*background-color: #F97;*/
1590 | width: 100% !important;
1591 | float: none !important;
1592 | clear: none !important;
1593 | position: relative !important;
1594 | margin: 0em;
1595 | left: -5px;
1596 | /*display: inline-block !important;*/
1597 | /*border: 1px solid #BCD*/
1598 | }
1599 |
1600 |
1601 | table#disputed.notice.noprint.toc {
1602 | /*background-color: #F17;*/
1603 | /*position: absolute;
1604 | top: 0;
1605 | left: 0;
1606 | width: 100%;*/
1607 | /*display: block !important;*/
1608 | }
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/github/takahirom/webview_in_coodinator_layout/NestedWebView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 takahirom
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.github.takahirom.webview_in_coodinator_layout;
18 |
19 | import android.content.Context;
20 |
21 | import androidx.core.view.MotionEventCompat;
22 | import androidx.core.view.NestedScrollingChild;
23 | import androidx.core.view.NestedScrollingChildHelper;
24 | import androidx.core.view.ViewCompat;
25 |
26 | import android.util.AttributeSet;
27 | import android.view.MotionEvent;
28 | import android.webkit.WebView;
29 |
30 | public class NestedWebView extends WebView implements NestedScrollingChild {
31 | private final int[] mScrollOffset = new int[2];
32 | private final int[] mScrollConsumed = new int[2];
33 | private int mLastY;
34 | private int mNestedOffsetY;
35 | private NestedScrollingChildHelper mChildHelper;
36 |
37 | public NestedWebView(Context context) {
38 | this(context, null);
39 | }
40 |
41 | public NestedWebView(Context context, AttributeSet attrs) {
42 | this(context, attrs, android.R.attr.webViewStyle);
43 | }
44 |
45 | public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
46 | super(context, attrs, defStyleAttr);
47 | mChildHelper = new NestedScrollingChildHelper(this);
48 | setNestedScrollingEnabled(true);
49 | }
50 |
51 | @Override
52 | public boolean onTouchEvent(MotionEvent ev) {
53 | boolean returnValue = false;
54 |
55 | MotionEvent event = MotionEvent.obtain(ev);
56 | final int action = MotionEventCompat.getActionMasked(event);
57 | if (action == MotionEvent.ACTION_DOWN) {
58 | mNestedOffsetY = 0;
59 | }
60 | int eventY = (int) event.getY();
61 | event.offsetLocation(0, mNestedOffsetY);
62 | switch (action) {
63 | case MotionEvent.ACTION_MOVE:
64 | int deltaY = mLastY - eventY;
65 | // NestedPreScroll
66 | if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
67 | deltaY -= mScrollConsumed[1];
68 | mLastY = eventY - mScrollOffset[1];
69 | event.offsetLocation(0, -mScrollOffset[1]);
70 | mNestedOffsetY += mScrollOffset[1];
71 | }
72 | returnValue = super.onTouchEvent(event);
73 |
74 | // NestedScroll
75 | if (dispatchNestedScroll(0, mScrollOffset[1], 0, deltaY, mScrollOffset)) {
76 | event.offsetLocation(0, mScrollOffset[1]);
77 | mNestedOffsetY += mScrollOffset[1];
78 | mLastY -= mScrollOffset[1];
79 | }
80 | break;
81 | case MotionEvent.ACTION_DOWN:
82 | returnValue = super.onTouchEvent(event);
83 | mLastY = eventY;
84 | // start NestedScroll
85 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
86 | break;
87 | case MotionEvent.ACTION_UP:
88 | case MotionEvent.ACTION_CANCEL:
89 | returnValue = super.onTouchEvent(event);
90 | // end NestedScroll
91 | stopNestedScroll();
92 | break;
93 | }
94 | return returnValue;
95 | }
96 |
97 | @Override
98 | public boolean isNestedScrollingEnabled() {
99 | return mChildHelper.isNestedScrollingEnabled();
100 | }
101 |
102 | // Nested Scroll implements
103 | @Override
104 | public void setNestedScrollingEnabled(boolean enabled) {
105 | mChildHelper.setNestedScrollingEnabled(enabled);
106 | }
107 |
108 | @Override
109 | public boolean startNestedScroll(int axes) {
110 | return mChildHelper.startNestedScroll(axes);
111 | }
112 |
113 | @Override
114 | public void stopNestedScroll() {
115 | mChildHelper.stopNestedScroll();
116 | }
117 |
118 | @Override
119 | public boolean hasNestedScrollingParent() {
120 | return mChildHelper.hasNestedScrollingParent();
121 | }
122 |
123 | @Override
124 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
125 | int[] offsetInWindow) {
126 | return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
127 | }
128 |
129 | @Override
130 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
131 | return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
132 | }
133 |
134 | @Override
135 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
136 | return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
137 | }
138 |
139 | @Override
140 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
141 | return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
142 | }
143 |
144 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/ArchWikiViewerApp.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 | import android.app.Application
4 | import timber.log.Timber
5 | import timber.log.Timber.DebugTree
6 |
7 |
8 | class ArchwikiViewerApp : Application() {
9 | override fun onCreate() {
10 | super.onCreate()
11 | Timber.plant(DebugTree())
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 |
4 | //format types
5 | const val TEXT_HTML_MIME = "text/html"
6 | const val TEXT_PLAIN_MIME = "text/plain"
7 | const val UTF_8 = "UTF-8"
8 |
9 | //arch wiki urls
10 | const val ARCHWIKI_BASE = "https://wiki.archlinux.org"
11 | const val ARCHWIKI_MAIN = "${ARCHWIKI_BASE}/index.php/Main_page"
12 | const val ARCHWIKI_SEARCH_URL = "${ARCHWIKI_BASE}/index.php?&search=%s"
13 |
14 | //local file paths
15 | const val ASSETS_FOLDER = "file:///android_asset"
16 | const val LOCAL_CSS = "${ASSETS_FOLDER}/style.css"
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 | import android.app.SearchManager
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.view.Menu
8 | import android.view.MenuItem
9 | import androidx.appcompat.app.AppCompatActivity
10 | import androidx.appcompat.widget.SearchView
11 | import com.jtmcn.archwiki.viewer.data.SearchResult
12 | import com.jtmcn.archwiki.viewer.data.getSearchQuery
13 | import com.jtmcn.archwiki.viewer.tasks.Fetch
14 | import com.jtmcn.archwiki.viewer.utils.getTextZoom
15 | import kotlinx.android.synthetic.main.activity_main.*
16 | import kotlinx.android.synthetic.main.toolbar.*
17 | import timber.log.Timber
18 | import java.util.*
19 |
20 | class MainActivity : AppCompatActivity() {
21 | private lateinit var searchView: SearchView
22 | private lateinit var searchMenuItem: MenuItem
23 | private var currentSuggestions: List? = null
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(R.layout.activity_main)
28 |
29 | setSupportActionBar(toolbar)
30 |
31 | wikiViewer.buildView(progressBar, supportActionBar)
32 |
33 | handleIntent(intent)
34 | }
35 |
36 | override fun onResume() {
37 | super.onResume()
38 | updateWebSettings()
39 | }
40 |
41 | override fun onNewIntent(intent: Intent) {
42 | super.onNewIntent(intent)
43 | handleIntent(intent)
44 | }
45 |
46 | private fun handleIntent(intent: Intent?) {
47 | if (intent == null) {
48 | return
49 | }
50 |
51 | if (Intent.ACTION_SEARCH == intent.action) {
52 | val query = intent.getStringExtra(SearchManager.QUERY)
53 | wikiViewer.passSearch(query!!)
54 | hideSearchView()
55 | } else if (Intent.ACTION_VIEW == intent.action) {
56 | val url = intent.dataString
57 | wikiViewer.wikiClient.shouldOverrideUrlLoading(wikiViewer, url!!)
58 | }
59 | }
60 |
61 | /**
62 | * Update the font size used in the webview.
63 | */
64 | private fun updateWebSettings() {
65 | wikiViewer.settings.textZoom = getTextZoom(this)
66 | }
67 |
68 | override fun onPrepareOptionsMenu(menu: Menu): Boolean {
69 | val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
70 | searchMenuItem = menu.findItem(R.id.menu_search)
71 | searchView = searchMenuItem.actionView as SearchView
72 | searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
73 | if (!hasFocus) {
74 | hideSearchView()
75 | }
76 | }
77 | searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName))
78 | searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
79 | override fun onQueryTextSubmit(query: String): Boolean {
80 | wikiViewer.passSearch(query)
81 | return false
82 | }
83 |
84 | override fun onQueryTextChange(newText: String): Boolean {
85 | if (newText.isEmpty()) {
86 | setCursorAdapter(ArrayList())
87 | return true
88 | } else {
89 | val searchUrl = getSearchQuery(newText)
90 | Fetch.search({
91 | currentSuggestions = it
92 | setCursorAdapter(currentSuggestions)
93 | }, searchUrl)
94 | return true
95 | }
96 | }
97 | })
98 |
99 | searchView.setOnSuggestionListener(object : SearchView.OnSuggestionListener {
100 | override fun onSuggestionSelect(position: Int): Boolean {
101 | return false
102 | }
103 |
104 | override fun onSuggestionClick(position: Int): Boolean {
105 | val (pageName, pageURL) = currentSuggestions!![position]
106 | Timber.d("Opening '$pageName' from search suggestion.")
107 | wikiViewer.wikiClient.shouldOverrideUrlLoading(wikiViewer, pageURL)
108 | hideSearchView()
109 | return true
110 | }
111 | })
112 | return true
113 | }
114 |
115 | private fun hideSearchView() {
116 | searchMenuItem.collapseActionView()
117 | wikiViewer.requestFocus() //pass control back to the wikiview
118 | }
119 |
120 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
121 | val inflater = menuInflater
122 | inflater.inflate(R.menu.menu, menu)
123 | return true
124 | }
125 |
126 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
127 | when (item.itemId) {
128 | R.id.menu_share -> {
129 | val wikiPage = wikiViewer.currentWebPage
130 | if (wikiPage != null) {
131 | val sharingIntent = Intent()
132 | sharingIntent.type = TEXT_PLAIN_MIME
133 | sharingIntent.action = Intent.ACTION_SEND
134 | sharingIntent.putExtra(Intent.EXTRA_TITLE, wikiPage.pageTitle)
135 | sharingIntent.putExtra(Intent.EXTRA_TEXT, wikiPage.pageUrl)
136 | startActivity(Intent.createChooser(sharingIntent, null))
137 | }
138 | }
139 | R.id.refresh -> wikiViewer.onRefresh()
140 | R.id.menu_settings -> startActivity(Intent(this, PreferencesActivity::class.java))
141 | R.id.exit -> finish()
142 | }
143 | return super.onOptionsItemSelected(item)
144 | }
145 |
146 | private fun setCursorAdapter(currentSuggestions: List?) {
147 | searchView.suggestionsAdapter = SearchResultsAdapter.getCursorAdapter(this, currentSuggestions!!)
148 | }
149 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/PreferencesActivity.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.preference.PreferenceFragmentCompat
6 | import com.jtmcn.archwiki.viewer.utils.getTextZoom
7 | import kotlinx.android.synthetic.main.toolbar.*
8 |
9 | class PreferencesActivity : AppCompatActivity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | setContentView(R.layout.activity_preferences)
14 |
15 | setSupportActionBar(toolbar)
16 |
17 | supportFragmentManager
18 | .beginTransaction()
19 | .replace(R.id.settings, SettingsFragment())
20 | .commit()
21 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
22 |
23 | getTextZoom(this)
24 | }
25 |
26 | class SettingsFragment : PreferenceFragmentCompat() {
27 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
28 | setPreferencesFromResource(R.xml.preferences, rootKey)
29 | }
30 | }
31 | }
32 |
33 | object Prefs {
34 | @Deprecated(message = "Should use textZoom", replaceWith = ReplaceWith("KEY_TEXT_ZOOM"))
35 | const val KEY_TEXT_SIZE = "textSize"
36 | const val KEY_TEXT_ZOOM = "textZoom"
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/SearchResultsAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 | import android.app.SearchManager
4 | import android.content.Context
5 | import android.database.MatrixCursor
6 | import android.provider.BaseColumns
7 | import androidx.cursoradapter.widget.CursorAdapter
8 | import androidx.cursoradapter.widget.SimpleCursorAdapter
9 |
10 | import com.jtmcn.archwiki.viewer.data.SearchResult
11 |
12 | /**
13 | * Helper for creating a [SimpleCursorAdapter] which will
14 | * list the search results for a [android.widget.SearchView]
15 | */
16 | object SearchResultsAdapter {
17 | private val columnNames = arrayOf(BaseColumns._ID, SearchManager.SUGGEST_COLUMN_TEXT_1)
18 | private val from = arrayOf(SearchManager.SUGGEST_COLUMN_TEXT_1)
19 | private val to = intArrayOf(R.id.url)
20 |
21 | /**
22 | * Creates a cursor adapter given a [<].
23 | * https://stackoverflow.com/questions/11628172/converting-an-arrayadapter-to-cursoradapter-for-use-in-a-searchview/11628527#11628527
24 | *
25 | * @param results the results to be placed in the adapter.
26 | * @return the adapter.
27 | */
28 | fun getCursorAdapter(context: Context, results: List): CursorAdapter {
29 | var id = 0
30 | val cursor = MatrixCursor(columnNames)
31 | for ((pageName) in results) {
32 | val temp = arrayOfNulls(2)
33 | temp[0] = id.toString() // "_id"
34 | temp[1] = pageName // "title"
35 |
36 | cursor.addRow(temp)
37 | id++
38 | }
39 |
40 | return SimpleCursorAdapter(
41 | context,
42 | R.layout.search_suggestions_list_item,
43 | cursor,
44 | from,
45 | to,
46 | CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/WikiClient.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 | import android.os.Handler
4 | import android.view.View
5 | import android.webkit.WebView
6 | import android.webkit.WebViewClient
7 | import android.widget.ProgressBar
8 | import androidx.appcompat.app.ActionBar
9 | import com.jtmcn.archwiki.viewer.data.WikiPage
10 | import com.jtmcn.archwiki.viewer.tasks.Fetch
11 | import com.jtmcn.archwiki.viewer.utils.openLink
12 | import timber.log.Timber
13 | import java.util.*
14 |
15 |
16 | class WikiClient(private val progressBar: ProgressBar, private val actionBar: ActionBar?, private val webView: WebView) : WebViewClient() {
17 | private val webPageStack = Stack()
18 | private val loadedUrls = HashSet() // this is used to see if we should restore the scroll position
19 | private var lastLoadedUrl: String? = null //https://stackoverflow.com/questions/11601134/android-webview-function-onpagefinished-is-called-twice
20 |
21 | /**
22 | * Get the number of pages that are in the history.
23 | *
24 | * @return number of pages on the stack.
25 | */
26 | val historyStackSize: Int
27 | get() = webPageStack.size
28 |
29 | /**
30 | * Returns null or the current page.
31 | *
32 | * @return The current page
33 | */
34 | val currentWebPage: WikiPage?
35 | get() = if (webPageStack.size == 0) null else webPageStack.peek()
36 |
37 | /*
38 | * Manage page history
39 | */
40 | private fun addHistory(wikiPage: WikiPage) {
41 | if (webPageStack.size > 0) {
42 | Timber.d("Saving ${currentWebPage?.pageTitle} at ${webView.scrollY}")
43 | currentWebPage!!.scrollPosition = webView.scrollY
44 | }
45 | webPageStack.push(wikiPage)
46 | Timber.i("Adding page ${wikiPage.pageTitle}. Stack size= ${webPageStack.size}")
47 | }
48 |
49 | /**
50 | * Loads the html from a [WikiPage] into the webview.
51 | *
52 | * @param wikiPage the page to be loaded.
53 | */
54 | fun loadWikiHtml(wikiPage: WikiPage) {
55 | webView.loadDataWithBaseURL(
56 | wikiPage.pageUrl,
57 | wikiPage.htmlString,
58 | TEXT_HTML_MIME,
59 | UTF_8,
60 | null
61 | )
62 |
63 | setSubtitle(wikiPage.pageTitle)
64 | }
65 |
66 | /**
67 | * Intercept url when clicked. If it's part of the wiki load it here.
68 | * If not, open the device's default browser.
69 | *
70 | * @param view webview being loaded into
71 | * @param url url being loaded
72 | * @return true if should override url loading
73 | */
74 | override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
75 | // deprecated until min api 21 is used
76 | if (url.startsWith(ARCHWIKI_BASE)) {
77 | webView.stopLoading()
78 | Fetch.page({
79 | addHistory(it)
80 | loadWikiHtml(currentWebPage!!)
81 | }, url)
82 | showProgress()
83 |
84 | return false
85 | } else {
86 | openLink(url, view.context)
87 | return true
88 | }
89 | }
90 |
91 | override fun onPageFinished(view: WebView, url: String) {
92 | super.onPageFinished(view, url)
93 | val currentWebPage = currentWebPage
94 | Timber.d("Calling onPageFinished(view, ${currentWebPage?.pageTitle})")
95 | // make sure we're loading the current page and that
96 | // this page's url doesn't have an anchor (only on first page load)
97 | if (url == currentWebPage?.pageUrl && url != lastLoadedUrl) {
98 | if (!isFirstLoad(currentWebPage)) {
99 | Handler().postDelayed({
100 | val scrollY = currentWebPage.scrollPosition
101 | Timber.d("Restoring ${currentWebPage.pageTitle} at $scrollY")
102 | webView.scrollY = scrollY
103 | }, 25)
104 | }
105 |
106 | lastLoadedUrl = url
107 | hideProgress()
108 | }
109 | }
110 |
111 | private fun isFirstLoad(currentWebPage: WikiPage): Boolean {
112 | return if (loadedUrls.contains(currentWebPage.pageUrl)) {
113 | false
114 | } else {
115 | loadedUrls.add(currentWebPage.pageUrl)
116 | true
117 | }
118 | }
119 |
120 | private fun showProgress() {
121 | progressBar.visibility = View.VISIBLE
122 | }
123 |
124 | private fun hideProgress() {
125 | progressBar.visibility = View.GONE
126 | }
127 |
128 | private fun setSubtitle(title: String?) {
129 | actionBar?.subtitle = title
130 | }
131 |
132 | /**
133 | * Go back to the last loaded page.
134 | */
135 | fun goBackHistory() {
136 | val (pageUrl, pageTitle) = webPageStack.pop()
137 | loadedUrls.remove(pageUrl)
138 | Timber.i("Removing $pageTitle from stack")
139 | val newPage = webPageStack.peek()
140 | loadWikiHtml(newPage)
141 | }
142 |
143 | fun refreshPage() {
144 | lastLoadedUrl = null // set to null if page should restore position, otherwise start at top of page
145 | val currentWebPage = currentWebPage
146 | if (currentWebPage != null) {
147 | val scrollPosition = currentWebPage.scrollPosition
148 |
149 | val url = currentWebPage.pageUrl
150 | showProgress()
151 | Fetch.page({ wikiPage ->
152 | webPageStack.pop()
153 | webPageStack.push(wikiPage)
154 | wikiPage.scrollPosition = scrollPosition
155 | loadWikiHtml(wikiPage)
156 | }, url)
157 | }
158 | }
159 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/WikiView.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.util.AttributeSet
6 | import android.view.KeyEvent
7 | import android.webkit.WebSettings
8 | import android.widget.ProgressBar
9 | import androidx.appcompat.app.ActionBar
10 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
11 | import com.github.takahirom.webview_in_coodinator_layout.NestedWebView
12 | import com.jtmcn.archwiki.viewer.data.WikiPage
13 | import timber.log.Timber
14 |
15 | class WikiView(context: Context, attrs: AttributeSet) : NestedWebView(context, attrs), SwipeRefreshLayout.OnRefreshListener {
16 | lateinit var wikiClient: WikiClient
17 |
18 | /**
19 | * Returns the current [WikiPage] being shown or null.
20 | *
21 | * @return current wiki page being shown.
22 | */
23 | val currentWebPage: WikiPage?
24 | get() = wikiClient.currentWebPage
25 |
26 | init {
27 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !isInEditMode) {
28 | // This allows the webview to inject the css (otherwise it blocks it for security reasons)
29 | settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
30 | }
31 | }
32 |
33 | /**
34 | * Initializes the wiki client and loads the main page.
35 | */
36 | fun buildView(progressBar: ProgressBar, actionBar: ActionBar?) {
37 | wikiClient = WikiClient(progressBar, actionBar, this)
38 | webViewClient = wikiClient
39 | wikiClient.shouldOverrideUrlLoading(this, ARCHWIKI_MAIN)
40 | }
41 |
42 | override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
43 | if (keyCode == KeyEvent.KEYCODE_BACK && wikiClient.historyStackSize > 1) {
44 | Timber.i("Loading previous page.")
45 | Timber.d("Position on page currently at $scrollY")
46 | wikiClient.goBackHistory()
47 | return true
48 | } else {
49 | Timber.d("Passing up button press.")
50 | return super.onKeyDown(keyCode, event)
51 | }
52 | }
53 |
54 | /**
55 | * Performs a search against the wiki.
56 | *
57 | * @param query the text to search for.
58 | */
59 | fun passSearch(query: String) {
60 | Timber.d("Searching for $query")
61 | val searchUrl = String.format(ARCHWIKI_SEARCH_URL, query)
62 | wikiClient.shouldOverrideUrlLoading(this, searchUrl)
63 | }
64 |
65 | override fun onRefresh() {
66 | wikiClient.refreshPage()
67 | stopLoading()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResult.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.data
2 |
3 | /**
4 | * A page on the wiki which only knows the name and url.
5 | */
6 | data class SearchResult(val pageName: String, val pageURL: String)
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.data
2 |
3 | import com.google.gson.JsonParser
4 | import com.jtmcn.archwiki.viewer.ARCHWIKI_BASE
5 |
6 | /**
7 | * Builds a string url to fetch search results.
8 | *
9 | * @param query the text to search for.
10 | * @param limit the maximum number of results to retrieve.
11 | * @return a url to fetch.
12 | */
13 | fun getSearchQuery(query: String, limit: Int = 10): String {
14 | return "${ARCHWIKI_BASE}/api.php?" +
15 | "action=opensearch&format=json&formatversion=2&namespace=0&suggest=true" +
16 | "&search=$query" +
17 | "&limit=$limit"
18 | }
19 |
20 | /**
21 | * Builds a [List] from the result of fetching with [getSearchQuery].
22 | *
23 | * @param jsonResult the string returned from the query.
24 | * @return a parsed list of the results.
25 | */
26 | fun parseSearchResults(jsonResult: String): List {
27 | val jsonRoot = JsonParser().parse(jsonResult)
28 | if (!jsonRoot.isJsonArray || jsonRoot.asJsonArray.size() != 4) return listOf()
29 |
30 | val jsonArray = jsonRoot.asJsonArray
31 |
32 | val listOfPageTitles = jsonArray.get(1).asJsonArray.mapNotNull { it.asString }
33 | val listOfPageUrls = jsonArray.get(3).asJsonArray.mapNotNull { it.asString }
34 |
35 | return listOfPageTitles.zip(listOfPageUrls).map { SearchResult(it.first, it.second) }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPage.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.data
2 |
3 | /**
4 | * Wrapper for a downloaded wiki page which holds the title and html.
5 | */
6 | data class WikiPage(val pageUrl: String, val pageTitle: String?, val htmlString: String) {
7 | var scrollPosition = 0
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.data
2 |
3 | import com.jtmcn.archwiki.viewer.LOCAL_CSS
4 |
5 | /**
6 | * Helps with creating a [WikiPage] by extracting content from the
7 | * html fetched from the ArchWiki.
8 | */
9 |
10 | //NOTE: spaces are allowed in ""/etc, but parsing this way should be fine
11 | const val HTML_HEAD_OPEN = ""
12 | const val HTML_HEAD_CLOSE = ""
13 | const val HTML_TITLE_OPEN = ""
14 | const val HTML_TITLE_CLOSE = ""
15 | private const val HEAD_TO_INJECT = "" +
16 | ""
17 | private const val DEFAULT_TITLE = " - ArchWiki"
18 |
19 | /**
20 | * Builds a page containing the title, url, and injects local css.
21 | *
22 | * @param url url to download.
23 | * @param html [StringBuilder] containing the html of the wikipage
24 | * @return [WikiPage] containing downloaded page.
25 | */
26 | fun buildPage(url: String, html: StringBuilder): WikiPage {
27 | val pageTitle = getPageTitle(html)
28 | injectLocalCSS(html, LOCAL_CSS)
29 | return WikiPage(url, pageTitle, html.toString())
30 | }
31 |
32 | /**
33 | * Finds the name of the page within the title block of the html.
34 | * The returned string removes the " - ArchWiki" if found.
35 | *
36 | * @param htmlString The html of the page as a string.
37 | * @return the extracted title from the page.
38 | */
39 | fun getPageTitle(htmlString: StringBuilder): String? {
40 | val titleStart = htmlString.indexOf(HTML_TITLE_OPEN) + HTML_TITLE_OPEN.length
41 | val titleEnd = htmlString.indexOf(HTML_TITLE_CLOSE, titleStart)
42 | if (titleStart in 1..titleEnd) { // if there is an html title block
43 | val title = htmlString.substring(titleStart, titleEnd)
44 | return title.replace(DEFAULT_TITLE, "") // drop DEFAULT_TITLE from page title
45 | }
46 | return null
47 | }
48 |
49 | /**
50 | * Removes the contents within the head block of the html
51 | * and replaces it with the a reference to a local css file.
52 | *
53 | * @param htmlString The html of the page as a string.
54 | * @param localCSSFilePath The path of the css file to inject.
55 | * @return true if the block was successfully replaced.
56 | */
57 | fun injectLocalCSS(htmlString: StringBuilder, localCSSFilePath: String): Boolean {
58 | val headStart = htmlString.indexOf(HTML_HEAD_OPEN) + HTML_HEAD_OPEN.length
59 | val headEnd = htmlString.indexOf(HTML_HEAD_CLOSE, headStart)
60 |
61 | if (headStart in 1..headEnd) {
62 | val injectedHeadHtml = String.format(HEAD_TO_INJECT, localCSSFilePath)
63 | htmlString.replace(headStart, headEnd, injectedHeadHtml)
64 | return true
65 | }
66 |
67 | return false
68 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/Fetch.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.tasks
2 |
3 | import android.os.AsyncTask
4 | import com.jtmcn.archwiki.viewer.data.SearchResult
5 | import com.jtmcn.archwiki.viewer.data.WikiPage
6 | import com.jtmcn.archwiki.viewer.data.buildPage
7 | import com.jtmcn.archwiki.viewer.data.parseSearchResults
8 |
9 | /**
10 | * Wrapper for [FetchUrl] which gives an easy to use interface
11 | * for fetching [SearchResult] and [WikiPage].
12 | */
13 | object Fetch {
14 | private val SEARCH_RESULTS_MAPPER = { _: String, html: StringBuilder -> parseSearchResults(html.toString()) }
15 |
16 | private val WIKI_PAGE_MAPPER = { url: String, html: StringBuilder -> buildPage(url, html) }
17 |
18 | /**
19 | * Fetches a List from the url.
20 | *
21 | * @param onFinish The listener called when search results are ready.
22 | * @param url The url to fetch the search results from.
23 | * @return the async task fetching the data.
24 | */
25 | fun search(onFinish: (List) -> Unit, url: String): AsyncTask> {
26 | return FetchUrl(onFinish, SEARCH_RESULTS_MAPPER).execute(url)
27 | }
28 |
29 | /**
30 | * Fetches a [WikiPage] from the url.
31 | *
32 | * @param onFinish The listener called when the page is ready.
33 | * @param url The url to fetch the page from.
34 | * @return the async task fetching the data.
35 | */
36 | fun page(onFinish: (WikiPage) -> Unit, url: String): AsyncTask {
37 | return FetchUrl(onFinish, WIKI_PAGE_MAPPER).execute(url)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/tasks/FetchUrl.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.tasks
2 |
3 | import android.os.AsyncTask
4 | import com.jtmcn.archwiki.viewer.utils.fetchURL
5 | import timber.log.Timber
6 | import java.io.IOException
7 |
8 | /**
9 | * Fetches a url, [mapper] maps it to a [Result], and returns it.
10 | * */
11 | class FetchUrl(
12 | private val onFinish: (Result) -> Unit,
13 | private val mapper: (url: String, html: StringBuilder) -> Result
14 | ) : AsyncTask() {
15 |
16 | override fun doInBackground(vararg params: String): Result? {
17 | if (params.isNotEmpty()) {
18 | val url = params[0]
19 | val toAdd = getItem(url)
20 | return mapper(url, toAdd)
21 | }
22 | return null
23 | }
24 |
25 | override fun onPostExecute(values: Result) {
26 | super.onPostExecute(values)
27 | onFinish(values)
28 | }
29 |
30 | /**
31 | * Fetches a url and returns what was downloaded or null
32 | *
33 | * @param url to query
34 | */
35 | private fun getItem(url: String): StringBuilder {
36 | var toReturn: StringBuilder
37 | try {
38 | val response = fetchURL(url).execute().body()?.string() ?: ""
39 | toReturn = StringBuilder(response)
40 | } catch (e: IOException) { //network exception
41 | Timber.w(e,"Could not connect to $url")
42 | toReturn = StringBuilder()
43 | }
44 |
45 | return toReturn
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/utils/AndroidUtils.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.utils
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 |
7 | /**
8 | * Creates an intent to open a link.
9 | *
10 | * @param url The url to be opened.
11 | * @param context The context needed to start the intent.
12 | */
13 | fun openLink(url: String, context: Context) {
14 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
15 | context.startActivity(intent)
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/utils/NetworkUtils.kt:
--------------------------------------------------------------------------------
1 |
2 | package com.jtmcn.archwiki.viewer.utils
3 |
4 | import okhttp3.Call
5 | import okhttp3.OkHttpClient
6 | import okhttp3.Request
7 |
8 |
9 |
10 | private val client = OkHttpClient.Builder().build()
11 | private val builder = Request.Builder()
12 |
13 | /**
14 | * Fetches a url with optional caching.
15 | *
16 | * @param url url to be fetched.
17 | * @param cb callback to handle result
18 | */
19 | fun fetchURL(url: String): Call = client.newCall(builder.url(url).build())
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtmcn/archwiki/viewer/utils/SettingsUtils.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.utils
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.preference.PreferenceManager
6 | import com.jtmcn.archwiki.viewer.Prefs
7 |
8 | @Deprecated("Use getTextZoom", replaceWith = ReplaceWith("getTextZoom(this)"))
9 | fun getTextSize(prefs: SharedPreferences): Int? {
10 | if(!prefs.contains(Prefs.KEY_TEXT_SIZE)) {
11 | return null
12 | }
13 |
14 | // https://stackoverflow.com/questions/11346916/listpreference-use-string-array-as-entry-and-integer-array-as-entry-values-does
15 | // the value of this preference must be parsed as a string
16 | val fontSizePref = prefs.getString(Prefs.KEY_TEXT_SIZE, "2")!!
17 | return Integer.valueOf(fontSizePref)
18 |
19 | }
20 |
21 | @Deprecated("Use getTextZoom")
22 | fun textSizeToTextZoom(fontSize: Int) = when(fontSize) {
23 | 0 -> 50
24 | 1 -> 75
25 | 2 -> 100
26 | 3 -> 150
27 | 4 -> 200
28 | else -> 100
29 | }
30 |
31 | /**
32 | * gets the [Prefs.KEY_TEXT_ZOOM] preference, and if needed migrates from [getTextSize=]
33 | */
34 | fun getTextZoom(context: Context): Int {
35 | val prefs = PreferenceManager.getDefaultSharedPreferences(context)
36 | val textSize = getTextSize(prefs)
37 | if(textSize != null) {
38 | val textZoom = textSizeToTextZoom(textSize)
39 | prefs.edit()
40 | .putInt(Prefs.KEY_TEXT_ZOOM, textZoom)
41 | .remove(Prefs.KEY_TEXT_SIZE)
42 | .apply()
43 |
44 | return textZoom
45 | }
46 |
47 | return prefs.getInt(Prefs.KEY_TEXT_ZOOM, 100)
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/play/contact-email.txt:
--------------------------------------------------------------------------------
1 | kevinhinterlong+archwiki@gmail.com
--------------------------------------------------------------------------------
/app/src/main/play/contact-website.txt:
--------------------------------------------------------------------------------
1 | https://github.com/kevinhinterlong/archwiki-viewer
--------------------------------------------------------------------------------
/app/src/main/play/default-language.txt:
--------------------------------------------------------------------------------
1 | en-US
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/full-description.txt:
--------------------------------------------------------------------------------
1 | A simple viewer for the ArchLinux Wiki online. Page content is formatted for optimal mobile viewing.
2 |
3 |
4 | https://github.com/jtmcn/archwiki-viewer/
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/feature-graphic/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/feature-graphic/0.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/icon/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/icon/0.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/phone-screenshots/0.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/short-description.txt:
--------------------------------------------------------------------------------
1 | A simple viewer for the ArchLinux Wiki online.
--------------------------------------------------------------------------------
/app/src/main/play/listings/en-US/title.txt:
--------------------------------------------------------------------------------
1 | ArchWiki Viewer
--------------------------------------------------------------------------------
/app/src/main/play/release-notes/en-US/beta.txt:
--------------------------------------------------------------------------------
1 | v1.0.13
2 |
3 | It's been awhile, here's what's new
4 | - Spanish translation
5 | - Brazilian Portuguese translation
6 | - Slovak translation
7 |
8 | Backend
9 | - Automated releases
10 | - Updated to androidx
--------------------------------------------------------------------------------
/app/src/main/play/release-notes/en-US/default.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/play/release-notes/en-US/default.txt
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
3 |
5 |
7 |
9 |
11 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_preferences.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/search_suggestions_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
21 |
22 |
27 |
28 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ArchWiki Viewer
4 | Einstellungen
5 | Link teilen
6 | Suchen
7 | Beenden
8 | Schriftgröße
9 | Aktualisieren
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Visor de ArchWiki
4 | Preferencias
5 | Compartir enlace
6 | Buscar
7 | Salir
8 | Tamaño del texto
9 | Refrescar
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ArchWiki Viewer
4 | Paramètres
5 | Partager le lien
6 | Chercher
7 | Quitter
8 | Taille du texte
9 | Rafraîchir
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ArchWiki Viewer
4 | Preferenze
5 | Condividi Link
6 | Cerca
7 | Esci
8 | Grandezza testo
9 | Aggiorna
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-iw/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | קורא ArchWiki
4 | העדפות
5 | שתף קישור
6 | חיפוש
7 | Exit
8 | Text Size
9 | Refresh
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ArchWiki Viewer
4 | Preferências
5 | Compartilhar link
6 | Pesquisar
7 | Sair
8 | Tamanho da fonte
9 | Atualizar
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ArchWiki Viewer
4 | Nastavenia
5 | Zdieľať
6 | Vyhľadať
7 | Ukončiť
8 | Veľkosť textu
9 | Obnoviť
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @color/archBlue
5 | #272727
6 | @color/colorPrimary
7 |
8 | #1793D1
9 | #FFFFFF
10 | #CCCCCC
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/archBlue
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ArchWiki Viewer
4 | Preferences
5 | Share Link
6 | Search
7 | Exit
8 | Text Size
9 | Refresh
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/test/java/com/jtmcn/archwiki/viewer/data/SearchResultsBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.data
2 |
3 | import org.junit.Test
4 | import org.junit.Assert.*
5 |
6 | class SearchResultsBuilderTest {
7 | private val realResult = """["arch", ["Arch-based Distros", "Arch-based distributions", "Arch-chroot", "Arch32", "Arch64 FAQ"],
8 | ["", "", "", "", ""],
9 | ["https://wiki.archlinux.org/index.php/Arch-based_Distros", "https://wiki.archlinux.org/index.php/Arch-based_distributions", "https://wiki.archlinux.org/index.php/Arch-chroot", "https://wiki.archlinux.org/index.php/Arch32", "https://wiki.archlinux.org/index.php/Arch64_FAQ"]
10 | ]"""
11 |
12 | @Test
13 | @Throws(Exception::class)
14 | fun parseSearchResultsTest() {
15 | val searchResults = parseSearchResults(realResult)
16 | assertEquals("Arch-based Distros", searchResults[0].pageName)
17 | assertEquals("https://wiki.archlinux.org/index.php/Arch-based_Distros", searchResults[0].pageURL)
18 | assertEquals("Arch-chroot", searchResults[2].pageName)
19 | assertEquals("https://wiki.archlinux.org/index.php/Arch-chroot", searchResults[2].pageURL)
20 | }
21 |
22 | @Test
23 | fun getSearchQuery() {
24 | val query: String = getSearchQuery("arch")
25 | assertEquals("https://wiki.archlinux.org/api.php?action=opensearch&format=json&formatversion=2&namespace=0&suggest=true&search=arch&limit=10", query)
26 | val queryWithLength: String = getSearchQuery("arch", 9)
27 | assertEquals("https://wiki.archlinux.org/api.php?action=opensearch&format=json&formatversion=2&namespace=0&suggest=true&search=arch&limit=9", queryWithLength)
28 | }
29 |
30 | @Test
31 | @Throws(Exception::class)
32 | fun emptySearchCorrectFormat() {
33 | val fakeResult = "[\"\", [],\n" +
34 | "\t[],\n" +
35 | "\t[]\n" +
36 | "]"
37 | val searchResults = parseSearchResults(fakeResult)
38 | assertEquals(0, searchResults.size)
39 | }
40 |
41 | @Test
42 | @Throws(Exception::class)
43 | fun emptyStringSearch() {
44 | val fakeResult = ""
45 | val searchResults = parseSearchResults(fakeResult)
46 | assertEquals(0, searchResults.size)
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/jtmcn/archwiki/viewer/data/WikiPageBuilderTest.kt:
--------------------------------------------------------------------------------
1 | package com.jtmcn.archwiki.viewer.data
2 |
3 | import com.jtmcn.archwiki.viewer.LOCAL_CSS
4 | import org.junit.Assert.*
5 |
6 | import org.junit.Test
7 |
8 | class WikiPageBuilderTest {
9 | @Test
10 | @Throws(Exception::class)
11 | fun getPageTitle() {
12 | val fakeTitle = "fake title..1!@#!@#!"
13 | assertEquals(fakeTitle, getPageTitle(wrappedTitle(fakeTitle)))
14 |
15 | assertEquals("", getPageTitle(wrappedTitle("")))
16 | }
17 |
18 | private fun wrappedTitle(fakeTitle: String) =
19 | StringBuilder("${HTML_TITLE_OPEN}${fakeTitle}${HTML_TITLE_CLOSE}")
20 |
21 | @Test
22 | @Throws(Exception::class)
23 | fun getEmptyTitle() {
24 | assertNull(getPageTitle(StringBuilder()))
25 | }
26 |
27 | @Test
28 | @Throws(Exception::class)
29 | fun injectLocalCSS() {
30 | val head = StringBuilder("${HTML_HEAD_OPEN}${HTML_HEAD_CLOSE}")
31 | val passed = injectLocalCSS(head, LOCAL_CSS)
32 | assertTrue(passed)
33 | assertEquals("",
34 | head.toString())
35 | }
36 |
37 | @Test
38 | @Throws(Exception::class)
39 | fun injectLocalCSSFail() {
40 | val fakeHead = " "
41 | val head = StringBuilder(fakeHead)
42 | val passed = injectLocalCSS(head, LOCAL_CSS)
43 | assertFalse(passed)
44 | assertEquals(fakeHead, head.toString())
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = '1.3.61'
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | google()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or 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 | # http://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 UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin, switch paths to Windows format before running java
129 | if $cygwin ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/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 http://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 Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/playstore/archwiki_feature_graphic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
216 |
--------------------------------------------------------------------------------
/playstore/ic_launcher.svg:
--------------------------------------------------------------------------------
1 |
2 |
154 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/upload_config.tar.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinhinterlong/archwiki-viewer/7a9a1c96ec0f33adc46231c3f95c586173361a5d/upload_config.tar.enc
--------------------------------------------------------------------------------