eliminar string preferences e about
32
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,25 +1,27 @@
|
|||
#### General information
|
||||
<!-- App version can be e.g. v0.1.1
|
||||
System e.g. Android 7.0.1, Nexus 5X
|
||||
Pod e.g. pod.geraspora.de, v0.7.1.1 -->
|
||||
|
||||
<!--
|
||||
I have:
|
||||
|
||||
- At least version 1.0.6 installed, see About-> Debug. If it is not visible you have an very old version, and
|
||||
your issue will be closed.
|
||||
- searched open and closed issues for duplicates
|
||||
- read <https://github.com/Diaspora-for-Android/dandelion/blob/master/CONTRIBUTING.md>
|
||||
- not submitted translations - see [Crowdin](https://crowdin.com/project/diaspora-for-android/invite)
|
||||
-->
|
||||
|
||||
* **App version:**
|
||||
* **System:**
|
||||
* **Pod:**
|
||||
|
||||
#### Description
|
||||
What this is about, what happens and how. What needs to be done for it to happen.
|
||||
|
||||
|
||||
#### Log
|
||||
<!-- adb logcat -s com.github.dfa.diaspora_android -->
|
||||
|
||||
<!--
|
||||
Look for already reported issues before posting!
|
||||
Also take a look at documentation and wiki, or write in the project chat.
|
||||
|
||||
App version: The version of the app installed and the installation source. Example: v0.3.5 F-Droid
|
||||
Please keep in mind that only the latest downloadable version is supported and that there are no backports to older versions.
|
||||
System: Information about where the app is running. Give all details you know, but at least the Android OS version.
|
||||
Example: Android 8.0.1, Nexus 5, LineageOS
|
||||
|
||||
Description:
|
||||
What this is about, what happens and what is expected to happen. What needs to be done for it to happen.
|
||||
If a crash is happening a log is needed. Screenshots or demonstration videos are always helpful too.
|
||||
|
||||
About logging:
|
||||
https://gsantner.net/android-contribution-guide/?packageid=com.github.dfa.diaspora_android&name=dandelion&web=https://github.com/gsantner/dandelion#logcat
|
||||
-->
|
||||
|
|
8
.github/PULL_REQUEST_TEMPLATE
vendored
|
@ -6,6 +6,14 @@
|
|||
|
||||
<!--
|
||||
Hello, and thanks for contributing!
|
||||
|
||||
Please always do auto-reformat on code before creating a PR.
|
||||
In Android-Studio do a right-click on java->Reformat and check the first two options.
|
||||
|
||||
After creating the PR please wait patiently till somebody from the team has time to give a review.
|
||||
The top-priority requirement for this to get merged is, that building/tests don't fail.
|
||||
If theres an continious integration system integrated in this project, you should see a colored checkmark in the PR window which tells the status.
|
||||
|
||||
## Contributors document
|
||||
Add yourself! When adding your information to the `CONTRIBUTORS.md` file, please use the following format:
|
||||
|
||||
|
|
10
CHANGELOG.md
|
@ -1,3 +1,13 @@
|
|||
### v1.1.3
|
||||
- Improve sharing *a lot*, add support for multiple filetypes
|
||||
- Support for downloading GIFs ;)
|
||||
- Rework screenshot saving and sharing; add new share options:
|
||||
- Merge license and changelog dialog on first start
|
||||
|
||||
### v1.1.2
|
||||
- Fix: loading non-pod links outside customtab/external browser
|
||||
- Fix: webview-js dialog not dismissing correctly
|
||||
|
||||
### v1.1.0
|
||||
- Added: App shortcuts (Android 7+)
|
||||
- Updated: podlist
|
||||
|
|
21
README.md
|
@ -1,10 +1,9 @@
|
|||
[![GitHub release](https://img.shields.io/github/tag/diaspora-for-android/dandelion.svg)](https://github.com/diaspora-for-android/dandelion/releases)
|
||||
[![Build Status](https://travis-ci.org/Diaspora-for-Android/dandelion.svg?branch=master)](https://travis-ci.org/Diaspora-for-Android/dandelion)
|
||||
[![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fdiaspora-for-android%2Fdandelion.git)
|
||||
[![GitHub release](https://img.shields.io/github/tag/gsantner/dandelion.svg)](https://github.com/gsantner/dandelion/releases)
|
||||
[![Build Status](https://travis-ci.org/gsantner/dandelion.svg?branch=master)](https://travis-ci.org/gsantner/dandelion)
|
||||
[![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fgsantner%2Fdandelion.git&mail=gro.xobliam@@rentnasg)
|
||||
[![Chat - Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#dandelion:matrix.org) [![Chat - FreeNode IRC](https://img.shields.io/badge/chat-on%20irc-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/?nick=dandelion-anon|?##dandelion)
|
||||
[![Donate Bitcoin](https://img.shields.io/badge/donate-bitcoin-orange.svg)](http://gsantner.net/#donate)
|
||||
[![Donate](https://img.shields.io/badge/donate-appreciation-orange.svg)](https://gsantner.net/supportme/?project=dandelion&source=readme)
|
||||
[![Donate LiberaPay](https://img.shields.io/badge/donate-liberapay-orange.svg)](https://liberapay.com/gsantner/donate)
|
||||
[![Donate GratiPay](https://img.shields.io/gratipay/team/dandelion.svg)](https://gratipay.com/dandelion/)
|
||||
|
||||
# dandelion\*
|
||||
<img src="/app/src/main/ic_launcher-web.png" align="left" width="100" hspace="10" vspace="10">
|
||||
|
@ -40,9 +39,9 @@ dandelion\* requires access to the Internet and to external storage to be able t
|
|||
|
||||
## Contributions
|
||||
The project is always open for contributions and accepts pull requests.
|
||||
The project uses [AOSP Java Code Style](https://source.android.com/source/code-style#follow-field-naming-conventions), with one exception: private members are `_camelCase` instead of `mBigCamel`. You may use Android Studios _auto reformat feature_ before sending a PR.
|
||||
The project uses [AOSP Java Code Style](https://source.android.com/source/code-style#follow-field-naming-conventions), with one exception: private members are `_camelCase` instead of `mBigCamel`. You may use Android Studios _auto reformat feature_ before sending a PR. See [gsantner's android contribution guide](https://gsantner.net/android-contribution-guide/?packageid=com.github.dfa.diaspora_android&name=dandelion&web=https://github.com/gsantner/dandelion&source=readme#logcat) for more information.
|
||||
|
||||
Translations can be contributed on GitHub or via [E-Mail](http://gsantner.net/#contact). You can use Stringlate ([![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fdiaspora-for-android%2Fdandelion.git)) to translate the project directly on your Android phone. It allows you to export as E-Mail attachement and to post on GitHub.
|
||||
Translations can be contributed on GitHub or via [E-Mail](https://gsantner.net/#contact). You can use Stringlate ([![Translate - with Stringlate](https://img.shields.io/badge/stringlate-translate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fgsantner%2Fdandelion.git)) to translate the project directly on your Android phone. It allows you to export as E-Mail attachement and to post on GitHub.
|
||||
|
||||
Join our IRC or Matrix channel (bridged) and say hello! Don't be afraid to start talking. [![Chat - Matrix](https://img.shields.io/badge/chat-on%20matrix-blue.svg)](https://matrix.to/#/#dandelion:matrix.org) [![Chat - FreeNode IRC](https://img.shields.io/badge/chat-on%20irc-blue.svg)](https://kiwiirc.com/client/irc.freenode.net/?nick=dandelion-anon|?##dandelion)
|
||||
|
||||
|
@ -50,14 +49,14 @@ Note that the main project members are working on this project for free during l
|
|||
|
||||
|
||||
#### Resources
|
||||
* Project: [Changelog](/CHANGELOG.md) | [Issues level/beginner](https://github.com/diaspora-for-android/dandelion/issues?q=is%3Aissue+is%3Aopen+label%3Alevel%2Fbeginner) | [License](/LICENSE.txt) | [CoC](/CODE_OF_CONDUCT.md)
|
||||
* Project: [Changelog](/CHANGELOG.md) | [Issues level/beginner](https://github.com/gsantner/dandelion/issues?q=is%3Aissue+is%3Aopen+label%3Alevel%2Fbeginner) | [License](/LICENSE.txt) | [CoC](/CODE_OF_CONDUCT.md)
|
||||
* Project diaspora\* account: [dandelion00@diasp.org](https://diasp.org/people/48b78420923501341ef3782bcb452bd5)
|
||||
* diaspora\*: [GitHub](https://github.com/diaspora/diaspora) | [Web](https://diasporafoundation.org) | [d\* HQ account](https://pod.diaspora.software/people/7bca7c80311b01332d046c626dd55703)
|
||||
* App on F-Droid: [Metadata](https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/com.github.dfa.diaspora_android.txt) | [Page](https://f-droid.org/packages/com.github.dfa.diaspora_android/) | [Wiki](https://f-droid.org/wiki/page/com.github.dfa.diaspora_android) | [Build log](https://f-droid.org/wiki/page/com.github.dfa.diaspora_android/lastbuild)
|
||||
|
||||
|
||||
## Licensing
|
||||
dandelion\* is released under GNU GENERAL PUBLIC LICENSE (see [LICENCE](https://github.com/Diaspora-for-Android/dandelion/blob/master/LICENSE.md)).
|
||||
dandelion\* is released under GNU GENERAL PUBLIC LICENSE (see [LICENCE](https://github.com/gsantner/dandelion/blob/master/LICENSE.md)).
|
||||
The app is licensed GPL v3. Localization files and resources (strings\*.xml) are licensed CC0 1.0.
|
||||
For more licensing informations, see [`3rd party licenses`](/app/src/main/res/raw/licenses_3rd_party.md).
|
||||
|
||||
|
@ -79,7 +78,7 @@ For more licensing informations, see [`3rd party licenses`](/app/src/main/res/ra
|
|||
|
||||
### Notice
|
||||
#### Maintainers
|
||||
- gsantner ([GitHub](https://github.com/gsantner), [Web](http://gsantner.net), [diaspora*](https://pod.geraspora.de/people/d1cbdd70095301341e834860008dbc6c))
|
||||
- Bitcoin: [1B9ZyYdQoY9BxMe9dRUEKaZbJWsbQqfXU5](http://gsantner.net/donate/#donate)
|
||||
- gsantner ([GitHub](https://github.com/gsantner), [Web](https://gsantner.net/supportme/?project=dandelion&source=readme), [diaspora*](https://pod.geraspora.de/people/d1cbdd70095301341e834860008dbc6c))
|
||||
- Bitcoin: [1B9ZyYdQoY9BxMe9dRUEKaZbJWsbQqfXU5](https://gsantner.net/supportme/?project=dandelion&source=readme)
|
||||
- vanitasvitae ([GitHub](https://github.com/vanitasvitae), [diaspora*](https://pod.geraspora.de/people/bbd7af90fbec013213e34860008dbc6c))
|
||||
- Bitcoin: 1Ao3W6NaQv3xKppviB7RSFKjHo6PGd8RTy
|
||||
|
|
|
@ -14,17 +14,16 @@ android {
|
|||
targetSdkVersion version_setup_targetSdk
|
||||
buildConfigField "boolean", "IS_TEST_BUILD", "false"
|
||||
buildConfigField "boolean", "IS_GPLAY_BUILD", "false"
|
||||
buildConfigField "String[]", "APPLICATION_LANGUAGES", "${getUsedAndroidLanguages()}"
|
||||
buildConfigField "String[]", "DETECTED_ANDROID_LOCALES", "${findUsedAndroidLocales()}"
|
||||
buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
|
||||
resValue "string", "manifest_package_id", "com.github.dfa.diaspora_android"
|
||||
|
||||
applicationId "com.github.dfa.diaspora_android"
|
||||
versionName "1.1.0"
|
||||
versionCode 29
|
||||
versionName "1.2.0"
|
||||
versionCode 33
|
||||
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
resValue 'string', 'app_name', "dandelion*"
|
||||
manifestPlaceholders = [appIcon: "@drawable/ic_launcher"]
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
@ -53,10 +52,13 @@ android {
|
|||
flavorGplay {
|
||||
buildConfigField "boolean", "IS_GPLAY_BUILD", "true"
|
||||
}*/
|
||||
|
||||
flavorDandelior {
|
||||
applicationId "net.gsantner.dandelior"
|
||||
}
|
||||
|
||||
flavorTest {
|
||||
applicationId "com.github.dfa.secondlion"
|
||||
resValue 'string', 'app_name', "secondlion*"
|
||||
manifestPlaceholders = [appIcon: "@drawable/ic_launcher_test"]
|
||||
applicationId "net.gsantner.secondlion"
|
||||
versionCode = Integer.parseInt(new Date().format('yyMMdd'))
|
||||
versionName = new Date().format('yyMMdd')
|
||||
buildConfigField "boolean", "IS_TEST_BUILD", "true"
|
||||
|
|
BIN
app/src/flavorDandelior/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
app/src/flavorDandelior/res/drawable-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
app/src/flavorDandelior/res/drawable-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
app/src/flavorDandelior/res/drawable-ldpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/flavorDandelior/res/drawable-ldpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
app/src/flavorDandelior/res/drawable-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
app/src/flavorDandelior/res/drawable-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
app/src/flavorDandelior/res/drawable-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
app/src/flavorDandelior/res/drawable-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
app/src/flavorDandelior/res/drawable-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
BIN
app/src/flavorDandelior/res/drawable-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0.025,-0.07h19.95v20.14H0.025z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="36.363636"
|
||||
android:viewportHeight="36.363636">
|
||||
<group android:translateX="8.181818"
|
||||
android:translateY="8.181818">
|
||||
<path
|
||||
android:pathData="M11.337,14.123l-0.963,-1.345c-0.257,-0.36 -0.466,-0.64 -0.477,-0.64 -0.012,0 -0.416,0.544 -0.958,1.287a83.9,83.9 0,0 1,-0.947 1.287c-0.015,0 -1.86,-1.3 -1.865,-1.313 -0.002,-0.007 0.415,-0.62 0.927,-1.361 0.512,-0.742 0.931,-1.36 0.931,-1.375 0,-0.023 -0.166,-0.081 -1.468,-0.515l-1.485,-0.496c-0.013,-0.005 0.063,-0.263 0.327,-1.094 0.19,-0.599 0.349,-1.093 0.354,-1.099 0.005,-0.006 0.707,0.219 1.56,0.5 0.852,0.28 1.556,0.509 1.565,0.509 0.008,0 0.018,-0.013 0.022,-0.03 0.003,-0.015 0.01,-0.74 0.016,-1.612 0.006,-0.87 0.015,-1.59 0.02,-1.6 0.009,-0.012 0.248,-0.015 1.127,-0.015 0.614,0 1.123,0.004 1.13,0.01 0.01,0.006 0.027,0.485 0.056,1.56 0.046,1.766 0.047,1.79 0.075,1.79 0.01,0 0.686,-0.226 1.501,-0.503a50.795,50.795 0,0 1,1.49 -0.492c0.016,0.019 0.685,2.194 0.676,2.202 -0.004,0.005 -0.684,0.237 -1.51,0.517 -1.137,0.386 -1.504,0.515 -1.507,0.531 -0.003,0.012 0.388,0.597 0.886,1.324 0.49,0.716 0.888,1.308 0.886,1.314a96.945,96.945 0,0 1,-1.852 1.364c-0.006,0 -0.239,-0.317 -0.517,-0.705z"
|
||||
android:fillColor="#fafafa"/>
|
||||
</group>
|
||||
</vector>
|
4
app/src/flavorDandelior/res/values/strings-flavor.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">dandelior*</string>
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
BIN
app/src/flavorTest/res/drawable-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/flavorTest/res/drawable-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
app/src/flavorTest/res/drawable-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/flavorTest/res/drawable-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/flavorTest/res/drawable-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/flavorTest/res/drawable-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/flavorTest/res/drawable-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
app/src/flavorTest/res/drawable-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
app/src/flavorTest/res/drawable-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
app/src/flavorTest/res/drawable-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0.025,-0.07h19.95v20.14H0.025z"
|
||||
android:fillColor="#492600"/>
|
||||
</vector>
|
12
app/src/flavorTest/res/drawable/ic_launcher_foreground.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="36.363636"
|
||||
android:viewportHeight="36.363636">
|
||||
<group android:translateX="8.181818"
|
||||
android:translateY="8.181818">
|
||||
<path
|
||||
android:pathData="M11.337,14.123l-0.963,-1.345c-0.257,-0.36 -0.466,-0.64 -0.477,-0.64 -0.012,0 -0.416,0.544 -0.958,1.287a83.9,83.9 0,0 1,-0.947 1.287c-0.015,0 -1.86,-1.3 -1.865,-1.313 -0.002,-0.007 0.415,-0.62 0.927,-1.361 0.512,-0.742 0.931,-1.36 0.931,-1.375 0,-0.023 -0.166,-0.081 -1.468,-0.515l-1.485,-0.496c-0.013,-0.005 0.063,-0.263 0.327,-1.094 0.19,-0.599 0.349,-1.093 0.354,-1.099 0.005,-0.006 0.707,0.219 1.56,0.5 0.852,0.28 1.556,0.509 1.565,0.509 0.008,0 0.018,-0.013 0.022,-0.03 0.003,-0.015 0.01,-0.74 0.016,-1.612 0.006,-0.87 0.015,-1.59 0.02,-1.6 0.009,-0.012 0.248,-0.015 1.127,-0.015 0.614,0 1.123,0.004 1.13,0.01 0.01,0.006 0.027,0.485 0.056,1.56 0.046,1.766 0.047,1.79 0.075,1.79 0.01,0 0.686,-0.226 1.501,-0.503a50.795,50.795 0,0 1,1.49 -0.492c0.016,0.019 0.685,2.194 0.676,2.202 -0.004,0.005 -0.684,0.237 -1.51,0.517 -1.137,0.386 -1.504,0.515 -1.507,0.531 -0.003,0.012 0.388,0.597 0.886,1.324 0.49,0.716 0.888,1.308 0.886,1.314a96.945,96.945 0,0 1,-1.852 1.364c-0.006,0 -0.239,-0.317 -0.517,-0.705z"
|
||||
android:fillColor="#fafafa"/>
|
||||
</group>
|
||||
</vector>
|
BIN
app/src/flavorTest/res/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 27 KiB |
4
app/src/flavorTest/res/values/strings-flavor.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">secondlion*</string>
|
||||
</resources>
|
|
@ -7,11 +7,13 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INSTALL_SHORTCUT" />
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
|
||||
<application
|
||||
android:name="com.github.dfa.diaspora_android.App"
|
||||
android:allowBackup="false"
|
||||
android:icon="${appIcon}"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/DiasporaLight">
|
||||
|
||||
|
@ -51,7 +53,7 @@
|
|||
<activity
|
||||
android:name="com.github.dfa.diaspora_android.activity.MainActivity"
|
||||
android:configChanges="keyboardHidden|locale|orientation|screenSize"
|
||||
android:icon="${appIcon}"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@style/DiasporaLight.NoActionBar"
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 17 KiB |
|
@ -35,6 +35,7 @@ import com.github.dfa.diaspora_android.util.AppSettings;
|
|||
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
|
||||
|
||||
import net.gsantner.opoc.util.AdBlock;
|
||||
import net.gsantner.opoc.util.ContextUtils;
|
||||
|
||||
public class App extends Application {
|
||||
private volatile static App app;
|
||||
|
@ -54,6 +55,13 @@ public class App extends Application {
|
|||
final Context c = getApplicationContext();
|
||||
appSettings = AppSettings.get();
|
||||
|
||||
String a = new ContextUtils(this).bcstr("FLAVOR", "");
|
||||
a += "__";
|
||||
|
||||
if (appSettings.isAppFirstStart() && "flavorDandelior".equals(new ContextUtils(this).bcstr("FLAVOR", ""))){
|
||||
appSettings.setAmoledColorMode(true);
|
||||
}
|
||||
|
||||
// Init app log
|
||||
AppLog.setLoggingEnabled(appSettings.isLoggingEnabled());
|
||||
AppLog.setLoggingSpamEnabled(appSettings.isLoggingSpamEnabled());
|
||||
|
|
|
@ -20,12 +20,13 @@ package com.github.dfa.diaspora_android.activity;
|
|||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
@ -157,10 +158,14 @@ public class AboutActivity extends ThemedActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.about__fragment_about, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.about__fragment_about;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ButterKnife.bind(this, view);
|
||||
if (isAdded()) {
|
||||
try {
|
||||
PackageInfo pInfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0);
|
||||
|
@ -170,7 +175,6 @@ public class AboutActivity extends ThemedActivity
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -244,10 +248,14 @@ public class AboutActivity extends ThemedActivity
|
|||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.about__fragment_license, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
final Context context = rootView.getContext();
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.about__fragment_license;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ButterKnife.bind(this, view);
|
||||
accentColor = ContextUtils.get().colorToHexString(ThemeHelper.getAccentColor());
|
||||
|
||||
maintainers.setTextFormatted(getString(R.string.fragment_license__maintainers_text,
|
||||
|
@ -255,8 +263,7 @@ public class AboutActivity extends ThemedActivity
|
|||
contributors.setTextFormatted(getString(R.string.fragment_license__contributors_thank_you,
|
||||
ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.contributors, "")));
|
||||
thirdPartyLibs.setTextFormatted(
|
||||
ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.license_third_party, ""));
|
||||
return rootView;
|
||||
ContextUtils.get().loadMarkdownForTextViewFromRaw(R.raw.licenses_3rd_party, ""));
|
||||
}
|
||||
|
||||
@OnClick({R.id.fragment_license__leafpic_button, R.id.fragment_license__license_button})
|
||||
|
|
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
This file is part of the dandelion*.
|
||||
|
||||
dandelion* is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
dandelion* is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with the dandelion*.
|
||||
|
||||
If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.github.dfa.diaspora_android.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.data.DiasporaAspect;
|
||||
import com.github.dfa.diaspora_android.listener.OnSomethingClickListener;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
|
||||
import com.github.dfa.diaspora_android.util.AppLog;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
import com.github.dfa.diaspora_android.util.ContextUtils;
|
||||
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
/**
|
||||
* Fragment that shows a list of the Aspects
|
||||
*/
|
||||
public class AspectListFragment extends ThemedFragment implements OnSomethingClickListener<Object> {
|
||||
|
||||
public static final String TAG = "com.github.dfa.diaspora_android.AspectListFragment";
|
||||
|
||||
@BindView(R.id.fragment_list__recycler_view)
|
||||
public RecyclerView aspectsRecyclerView;
|
||||
|
||||
@BindView(R.id.fragment_list__spacer)
|
||||
public View space;
|
||||
|
||||
@BindView(R.id.fragment_list__root)
|
||||
public RelativeLayout rootView;
|
||||
|
||||
protected App app;
|
||||
protected DiasporaUrlHelper urls;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
AppLog.d(this, "onCreateView()");
|
||||
return inflater.inflate(R.layout.recycler_list__fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ButterKnife.bind(this, view);
|
||||
app = (App) getActivity().getApplication();
|
||||
AppSettings appSettings = app.getSettings();
|
||||
urls = new DiasporaUrlHelper(appSettings);
|
||||
|
||||
aspectsRecyclerView.setHasFixedSize(true);
|
||||
aspectsRecyclerView.setNestedScrollingEnabled(false);
|
||||
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
|
||||
aspectsRecyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
final AspectAdapter adapter = new AspectAdapter(appSettings, this);
|
||||
aspectsRecyclerView.setAdapter(adapter);
|
||||
|
||||
//Set window title
|
||||
getActivity().setTitle(R.string.nav_aspects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFragmentTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSomethingClicked(Object null1, Integer null2, String aspectId) {
|
||||
((MainActivity) getActivity()).openDiasporaUrl(urls.getAspectUrl(aspectId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyColorToViews() {
|
||||
aspectsRecyclerView.invalidate();
|
||||
if (getAppSettings().isAmoledColorMode()) {
|
||||
rootView.setBackgroundColor(Color.BLACK);
|
||||
space.setBackgroundColor(Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AspectAdapter extends RecyclerView.Adapter<AspectAdapter.ViewHolder> {
|
||||
private boolean isAmoledColorMode;
|
||||
private final AppSettings appSettings;
|
||||
private final DiasporaAspect[] aspectList;
|
||||
private final List<String> aspectFavsList;
|
||||
private final OnSomethingClickListener<Object> aspectClickedListener;
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
@BindView(R.id.recycler_view__list_item__text)
|
||||
public TextView title;
|
||||
@BindView(R.id.recycler_view__list_item__favourite)
|
||||
AppCompatImageView favouriteImage;
|
||||
@BindView(R.id.recycler_view__list_item__root)
|
||||
RelativeLayout root;
|
||||
|
||||
ViewHolder(View v) {
|
||||
super(v);
|
||||
ButterKnife.bind(this, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AspectAdapter(AppSettings appSettings, OnSomethingClickListener<Object> aspectClickedListener) {
|
||||
this.appSettings = appSettings;
|
||||
this.aspectList = appSettings.getAspects();
|
||||
this.aspectFavsList = new ArrayList<>(Arrays.asList(appSettings.getAspectFavs()));
|
||||
this.aspectClickedListener = aspectClickedListener;
|
||||
this.isAmoledColorMode = appSettings.isAmoledColorMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return aspectList.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AspectAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.recycler_list__list_item_with_fav, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
// Alternating colors
|
||||
final Context c = holder.root.getContext();
|
||||
final DiasporaAspect aspect = aspectList[position];
|
||||
holder.title.setText(aspect.name);
|
||||
if (position % 2 == 1) {
|
||||
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : ContextUtils.get().color(R.color.alternate_row_color));
|
||||
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
|
||||
} else {
|
||||
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : Color.WHITE);
|
||||
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
|
||||
}
|
||||
|
||||
// Favourite (Star) Image
|
||||
applyFavouriteImage(holder.favouriteImage, isAspectFaved(aspect.name));
|
||||
|
||||
// Click on fav button
|
||||
holder.favouriteImage.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (isAspectFaved(aspect.name)) {
|
||||
aspectFavsList.remove(aspectFavsList.indexOf(aspect.name));
|
||||
} else {
|
||||
aspectFavsList.add(aspect.name);
|
||||
}
|
||||
appSettings.setAspectFavs(aspectFavsList);
|
||||
applyFavouriteImage(holder.favouriteImage, isAspectFaved(aspect.name));
|
||||
}
|
||||
});
|
||||
|
||||
holder.root.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
aspectClickedListener.onSomethingClicked(null, null, aspect.id + "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isAspectFaved(String tag) {
|
||||
return aspectFavsList.contains(tag);
|
||||
}
|
||||
|
||||
private void applyFavouriteImage(AppCompatImageView imageView, boolean isFaved) {
|
||||
imageView.setImageResource(isFaved ? R.drawable.ic_star_filled_48px : R.drawable.ic_star_border_black_48px);
|
||||
imageView.setColorFilter(isFaved ? appSettings.getAccentColor() : (isAmoledColorMode ? Color.GRAY : 0), PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import android.app.Activity;
|
|||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -39,8 +40,10 @@ import android.webkit.JavascriptInterface;
|
|||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.BuildConfig;
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.data.DiasporaUserProfile;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedAlertDialogBuilder;
|
||||
|
@ -53,10 +56,14 @@ import com.github.dfa.diaspora_android.web.DiasporaStreamWebChromeClient;
|
|||
import com.github.dfa.diaspora_android.web.FileUploadWebChromeClient;
|
||||
import com.github.dfa.diaspora_android.web.WebHelper;
|
||||
|
||||
import net.gsantner.opoc.util.PermissionChecker;
|
||||
import net.gsantner.opoc.util.ShareUtil;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Fragment that displays the Stream of the diaspora* user
|
||||
|
@ -97,6 +104,9 @@ public class DiasporaStreamFragment extends BrowserFragment {
|
|||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.stream__menu_top, menu);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
menu.findItem(R.id.action_share_pdf).setVisible(true);
|
||||
}
|
||||
|
||||
final boolean darkBg = ContextUtils.get().shouldColorOnTopBeLight(AppSettings.get().getPrimaryColor());
|
||||
ContextUtils.get().tintMenuItems(menu, true, ContextCompat.getColor(getActivity(), darkBg ? R.color.white : R.color.black));
|
||||
|
@ -118,6 +128,8 @@ public class DiasporaStreamFragment extends BrowserFragment {
|
|||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
AppLog.d(this, "StreamFragment.onOptionsItemSelected()");
|
||||
ShareUtil shu = new ShareUtil(getContext()).setFileProviderAuthority(BuildConfig.APPLICATION_ID);
|
||||
PermissionChecker permc = new PermissionChecker(getActivity());
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_reload: {
|
||||
if (WebHelper.isOnline(getContext())) {
|
||||
|
@ -144,13 +156,47 @@ public class DiasporaStreamFragment extends BrowserFragment {
|
|||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_share_pdf: {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
shu.createPdf(webView, "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_share_link_to_clipboard: {
|
||||
shu.setClipboard(webView.getUrl());
|
||||
Toast.makeText(getContext(), R.string.share__toast_link_address_copied, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_create_launcher_shortcut: {
|
||||
if (webView.getUrl() != null) {
|
||||
Intent intent = new Intent(getContext(), MainActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(webView.getUrl()));
|
||||
shu.createLauncherDesktopShortcut(intent, R.drawable.ic_launcher, webView.getTitle());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_take_screenshot: {
|
||||
makeScreenshotOfWebView(false);
|
||||
if (permc.doIfExtStoragePermissionGranted(getString(R.string.permissions_screenshot))) {
|
||||
File fileSaveDirectory = appSettings.getAppSaveDirectory();
|
||||
if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) {
|
||||
Bitmap bmp = ShareUtil.getBitmapFromWebView(webView);
|
||||
String filename = "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()) + ".jpg";
|
||||
_cu.writeImageToFileJpeg(new File(fileSaveDirectory, filename), bmp);
|
||||
Snackbar.make(webView, getString(R.string.share__toast_screenshot)
|
||||
+ " " + filename, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case R.id.action_share_screenshot: {
|
||||
makeScreenshotOfWebView(true);
|
||||
if (permc.doIfExtStoragePermissionGranted(getString(R.string.permissions_screenshot))) {
|
||||
shu.shareImage(ShareUtil.getBitmapFromWebView(webView), Bitmap.CompressFormat.JPEG);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -325,21 +371,30 @@ public class DiasporaStreamFragment extends BrowserFragment {
|
|||
@SuppressWarnings("unused")
|
||||
@JavascriptInterface
|
||||
public void setUserProfile(final String webMessage) throws JSONException {
|
||||
App app = ((App) getActivity().getApplication());
|
||||
final DiasporaUserProfile pup = app.getDiasporaUserProfile();
|
||||
if (pup.isRefreshNeeded()) {
|
||||
try {
|
||||
// Try to very fail-safe check if user information gets really loaded from correct pod
|
||||
if (!webView.getUrl().startsWith(app.getSettings().getPod().getPodUrl().getBaseUrl())) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
AppLog.v(this, "DiasporaUserProfile needs refresh; Try to parse JSON");
|
||||
pup.parseJson(webMessage);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
final Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pup.analyzeUrl(webView.getUrl());
|
||||
App app = ((App) activity.getApplication());
|
||||
final DiasporaUserProfile pup = app.getDiasporaUserProfile();
|
||||
if (pup.isRefreshNeeded()) {
|
||||
try {
|
||||
// Try to very fail-safe check if user information gets really loaded from correct pod
|
||||
if (!webView.getUrl().startsWith(app.getSettings().getPod().getPodUrl().getBaseUrl())) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
return;
|
||||
}
|
||||
AppLog.v(this, "DiasporaUserProfile needs refresh; Try to parse JSON");
|
||||
pup.parseJson(webMessage);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
pup.analyzeUrl(webView.getUrl());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import android.support.customtabs.CustomTabsSession;
|
|||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
@ -61,8 +62,8 @@ import android.widget.TextView;
|
|||
import android.widget.Toast;
|
||||
|
||||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.BuildConfig;
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.data.DiasporaAspect;
|
||||
import com.github.dfa.diaspora_android.data.DiasporaPodList;
|
||||
import com.github.dfa.diaspora_android.data.DiasporaUserProfile;
|
||||
import com.github.dfa.diaspora_android.listener.DiasporaUserProfileChangedListener;
|
||||
|
@ -71,10 +72,11 @@ import com.github.dfa.diaspora_android.receiver.OpenExternalLinkReceiver;
|
|||
import com.github.dfa.diaspora_android.receiver.UpdateTitleReceiver;
|
||||
import com.github.dfa.diaspora_android.ui.BadgeDrawable;
|
||||
import com.github.dfa.diaspora_android.ui.PodSelectionDialog;
|
||||
import com.github.dfa.diaspora_android.ui.theme.CustomFragment;
|
||||
import com.github.dfa.diaspora_android.ui.SearchOrCustomTextDialogCreator;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemeHelper;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedActivity;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedAlertDialogBuilder;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
|
||||
import com.github.dfa.diaspora_android.util.ActivityUtils;
|
||||
import com.github.dfa.diaspora_android.util.AndroidBug5497Workaround;
|
||||
import com.github.dfa.diaspora_android.util.AppLog;
|
||||
|
@ -87,7 +89,7 @@ import com.github.dfa.diaspora_android.web.ProxyHandler;
|
|||
import com.github.dfa.diaspora_android.web.WebHelper;
|
||||
import com.github.dfa.diaspora_android.web.custom_tab.CustomTabActivityHelper;
|
||||
|
||||
import net.gsantner.opoc.util.SimpleMarkdownParser;
|
||||
import net.gsantner.opoc.format.markdown.SimpleMarkdownParser;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -199,7 +201,7 @@ public class MainActivity extends ThemedActivity
|
|||
brOpenExternalLink = new OpenExternalLinkReceiver(this);
|
||||
brSetTitle = new UpdateTitleReceiver(app, urls, new UpdateTitleReceiver.TitleCallback() {
|
||||
public void setTitle(String url, int resId) {
|
||||
CustomFragment top = getTopFragment();
|
||||
ThemedFragment top = getTopFragment();
|
||||
if (top != null && top.getFragmentTag().equals(DiasporaStreamFragment.TAG)) {
|
||||
MainActivity.this.setTitle(resId);
|
||||
showLastVisitedTimestampMessageIfNeeded(url);
|
||||
|
@ -207,7 +209,7 @@ public class MainActivity extends ThemedActivity
|
|||
}
|
||||
|
||||
public void setTitle(String url, String title) {
|
||||
CustomFragment top = getTopFragment();
|
||||
ThemedFragment top = getTopFragment();
|
||||
if (top != null && top.getFragmentTag().equals(DiasporaStreamFragment.TAG)) {
|
||||
MainActivity.this.setTitle(title);
|
||||
}
|
||||
|
@ -229,23 +231,16 @@ public class MainActivity extends ThemedActivity
|
|||
}
|
||||
}
|
||||
|
||||
// Show first start dialog
|
||||
// Show first start / update dialog
|
||||
try {
|
||||
SimpleMarkdownParser mdParser = SimpleMarkdownParser.get().setDefaultSmpFilter(SimpleMarkdownParser.FILTER_ANDROID_TEXTVIEW);
|
||||
if (_appSettings.isAppFirstStart()) {
|
||||
mdParser.parse(
|
||||
getResources().openRawResource(R.raw.license), "");
|
||||
String html = mdParser.getHtml()
|
||||
+ "<br/><br/><br/>"
|
||||
+ "<h1>" + getString(R.string.fragment_license__thirdparty_libs) + "</h1>"
|
||||
+ mdParser.parse(getResources().openRawResource(R.raw.license_third_party), "");
|
||||
html = mdParser.setHtml(html).removeMultiNewlines().getHtml();
|
||||
ActivityUtils.get(this).showDialogWithHtmlTextView(R.string.about_activity__title_about_license, html);
|
||||
_appSettings.isAppCurrentVersionFirstStart();
|
||||
} else if (_appSettings.isAppCurrentVersionFirstStart()) {
|
||||
SimpleMarkdownParser smp = new SimpleMarkdownParser().parse(
|
||||
getResources().openRawResource(R.raw.changelog), "");
|
||||
ActivityUtils.get(this).showDialogWithHtmlTextView(R.string.changelog, smp.getHtml());
|
||||
if (_appSettings.isAppCurrentVersionFirstStart(true)) {
|
||||
SimpleMarkdownParser smp = SimpleMarkdownParser.get().setDefaultSmpFilter(SimpleMarkdownParser.FILTER_ANDROID_TEXTVIEW);
|
||||
String html = "";
|
||||
html += smp.parse(getString(R.string.copyright_license_text_official).replace("\n", " \n"), "").getHtml();
|
||||
html += "<br/><br/><br/><big><big>" + getString(R.string.changelog) + "</big></big><br/>" + smp.parse(getResources().openRawResource(R.raw.changelog), "", SimpleMarkdownParser.FILTER_ANDROID_TEXTVIEW, SimpleMarkdownParser.FILTER_CHANGELOG).getHtml();
|
||||
html += "<br/><br/><br/><big><big>" + getString(R.string.licenses) + "</big></big><br/>" + smp.parse(getResources().openRawResource(R.raw.licenses_3rd_party), "").getHtml();
|
||||
ActivityUtils _au = new ActivityUtils(this);
|
||||
_au.showDialogWithHtmlTextView(R.string.licenses, html);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -292,15 +287,15 @@ public class MainActivity extends ThemedActivity
|
|||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the CustomFragment with the tag fragmentTag.
|
||||
* Get an instance of the ThemedFragment with the tag fragmentTag.
|
||||
* If there was no instance so far, create a new one and add it to the FragmentManagers pool.
|
||||
* If there is no Fragment with the corresponding Tag, return the top fragment.
|
||||
*
|
||||
* @param fragmentTag tag
|
||||
* @return corresponding Fragment
|
||||
*/
|
||||
protected CustomFragment getFragment(String fragmentTag) {
|
||||
CustomFragment fragment = (CustomFragment) fm.findFragmentByTag(fragmentTag);
|
||||
protected ThemedFragment getFragment(String fragmentTag) {
|
||||
ThemedFragment fragment = (ThemedFragment) fm.findFragmentByTag(fragmentTag);
|
||||
if (fragment != null) {
|
||||
return fragment;
|
||||
} else {
|
||||
|
@ -313,14 +308,6 @@ public class MainActivity extends ThemedActivity
|
|||
BrowserFragment bf = new BrowserFragment();
|
||||
fm.beginTransaction().add(bf, fragmentTag).commit();
|
||||
return bf;
|
||||
case TagListFragment.TAG:
|
||||
TagListFragment hlf = new TagListFragment();
|
||||
fm.beginTransaction().add(hlf, fragmentTag).commit();
|
||||
return hlf;
|
||||
case AspectListFragment.TAG:
|
||||
AspectListFragment alf = new AspectListFragment();
|
||||
fm.beginTransaction().add(alf, fragmentTag).commit();
|
||||
return alf;
|
||||
case PodSelectionFragment.TAG:
|
||||
PodSelectionFragment psf = new PodSelectionFragment();
|
||||
fm.beginTransaction().add(psf, fragmentTag).commit();
|
||||
|
@ -338,12 +325,30 @@ public class MainActivity extends ThemedActivity
|
|||
*
|
||||
* @param url URL to load in the DiasporaStreamFragment
|
||||
*/
|
||||
public void openDiasporaUrl(String url) {
|
||||
public void openDiasporaUrl(final String url) {
|
||||
AppLog.v(this, "openDiasporaUrl()");
|
||||
DiasporaStreamFragment streamFragment = (DiasporaStreamFragment) getFragment(DiasporaStreamFragment.TAG);
|
||||
showFragment(streamFragment);
|
||||
showLastVisitedTimestampMessageIfNeeded(url);
|
||||
streamFragment.loadUrl(url);
|
||||
if (url != null && url.startsWith("http://127.0.0.1")) {
|
||||
// This URL seems to be called somehow, but it doesn't make sense ;)
|
||||
toolbarTop.postDelayed(() -> {
|
||||
Intent i = new Intent(ACTION_OPEN_EXTERNAL_URL);
|
||||
i.putExtra(EXTRA_URL, "https://github.com/gsantner/dandelion/blob/master/README.md");
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
if (_appSettings.getPod() != null && _appSettings.getPod().getPodUrl() != null && _appSettings.getPod().getPodUrl().getBaseUrl() != null
|
||||
&& url.startsWith(_appSettings.getPod().getPodUrl().getBaseUrl()) && !url.startsWith("https://dia.so/")) {
|
||||
DiasporaStreamFragment streamFragment = (DiasporaStreamFragment) getFragment(DiasporaStreamFragment.TAG);
|
||||
showFragment(streamFragment);
|
||||
showLastVisitedTimestampMessageIfNeeded(url);
|
||||
streamFragment.loadUrl(url);
|
||||
} else {
|
||||
toolbarTop.postDelayed(() -> {
|
||||
Intent i = new Intent(ACTION_OPEN_EXTERNAL_URL);
|
||||
i.putExtra(EXTRA_URL, url);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public void showLastVisitedTimestampMessageIfNeeded(String url) {
|
||||
|
@ -358,9 +363,16 @@ public class MainActivity extends ThemedActivity
|
|||
*
|
||||
* @param fragment Fragment to show
|
||||
*/
|
||||
protected void showFragment(CustomFragment fragment) {
|
||||
protected void showFragment(ThemedFragment fragment) {
|
||||
if (PodSelectionFragment.TAG.equals(fragment.getTag())) {
|
||||
Fragment fragment1 = fm.findFragmentByTag(DiasporaStreamFragment.TAG);
|
||||
if (fragment1 != null) {
|
||||
new net.gsantner.opoc.util.ContextUtils(this).restartApp(MainActivity.class);
|
||||
}
|
||||
}
|
||||
|
||||
AppLog.v(this, "showFragment()");
|
||||
CustomFragment currentTop = (CustomFragment) fm.findFragmentById(R.id.fragment_container);
|
||||
ThemedFragment currentTop = (ThemedFragment) fm.findFragmentById(R.id.fragment_container);
|
||||
if (currentTop == null || !currentTop.getFragmentTag().equals(fragment.getFragmentTag())) {
|
||||
AppLog.v(this, "Fragment was not visible. Replace it.");
|
||||
fm.beginTransaction().addToBackStack(null).replace(R.id.fragment_container, fragment, fragment.getFragmentTag()).commit();
|
||||
|
@ -420,8 +432,6 @@ public class MainActivity extends ThemedActivity
|
|||
app.getAvatarImageLoader().startImageDownload(navheaderImage, avatarUrl);
|
||||
}
|
||||
}
|
||||
} else if (BuildConfig.IS_TEST_BUILD) {
|
||||
navheaderImage.setImageResource(R.drawable.ic_launcher_test);
|
||||
}
|
||||
updateNavigationViewEntryVisibilities();
|
||||
}
|
||||
|
@ -448,7 +458,7 @@ public class MainActivity extends ThemedActivity
|
|||
navMenu.findItem(R.id.nav_statistics).setVisible(_appSettings.isVisibleInNavStatistics());
|
||||
navMenu.findItem(R.id.nav_reports).setVisible(_appSettings.isVisibleInNavReports());
|
||||
navMenu.findItem(R.id.nav_toggle_desktop_page).setVisible(_appSettings.isVisibleInNavToggleMobileDesktop());
|
||||
navMenu.findItem(R.id.nav_dandelion).setVisible(_appSettings.isVisibleInNavDandelionAccount());
|
||||
navMenu.findItem(R.id.nav_product_support).setVisible(_appSettings.isVisibleInNavGsantnerAccount());
|
||||
|
||||
|
||||
// Hide whole group (for logged in use) if no pod was selected
|
||||
|
@ -552,16 +562,18 @@ public class MainActivity extends ThemedActivity
|
|||
} else if ("sc_new_post".equals(action)) {
|
||||
openDiasporaUrl(urls.getNewPostUrl());
|
||||
return;
|
||||
} else if ("sc_nav_followed_tags".equals(action)) {
|
||||
showFragment(getFragment(TagListFragment.TAG));
|
||||
return;
|
||||
} else if ("sc_aspects".equals(action)) {
|
||||
showFragment(getFragment(AspectListFragment.TAG));
|
||||
return;
|
||||
} else if ("sc_activities".equals(action)) {
|
||||
openDiasporaUrl(urls.getActivityUrl());
|
||||
return;
|
||||
}
|
||||
else if ("sc_contacts".equals(action)) {
|
||||
onNavigationItemSelected(navView.getMenu().findItem(R.id.nav_aspects));
|
||||
return;
|
||||
}
|
||||
else if ("sc_tags".equals(action)) {
|
||||
onNavigationItemSelected(navView.getMenu().findItem(R.id.nav_followed_tags));
|
||||
return;
|
||||
}
|
||||
//Catch split screen recreation
|
||||
if (action != null && action.equals(Intent.ACTION_MAIN) && getTopFragment() != null) {
|
||||
return;
|
||||
|
@ -591,8 +603,8 @@ public class MainActivity extends ThemedActivity
|
|||
*
|
||||
* @return top fragment or null if there is none displayed
|
||||
*/
|
||||
private CustomFragment getTopFragment() {
|
||||
return (CustomFragment) fm.findFragmentById(R.id.fragment_container);
|
||||
private ThemedFragment getTopFragment() {
|
||||
return (ThemedFragment) fm.findFragmentById(R.id.fragment_container);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -605,7 +617,7 @@ public class MainActivity extends ThemedActivity
|
|||
navDrawer.closeDrawer(navView);
|
||||
return;
|
||||
}
|
||||
CustomFragment top = getTopFragment();
|
||||
ThemedFragment top = getTopFragment();
|
||||
if (top != null) {
|
||||
AppLog.v(this, "Top Fragment is not null");
|
||||
if (!top.onBackPressed()) {
|
||||
|
@ -683,7 +695,7 @@ public class MainActivity extends ThemedActivity
|
|||
//Clear the menus
|
||||
menu.clear();
|
||||
|
||||
CustomFragment top = getTopFragment();
|
||||
ThemedFragment top = getTopFragment();
|
||||
if (top != null) {
|
||||
if (!top.getFragmentTag().equals(PodSelectionFragment.TAG)) {
|
||||
cache = _appSettings.isExtendedNotificationsActivated();
|
||||
|
@ -693,10 +705,10 @@ public class MainActivity extends ThemedActivity
|
|||
}
|
||||
}
|
||||
|
||||
final boolean darkBg = ContextUtils.get().shouldColorOnTopBeLight(AppSettings.get().getPrimaryColor());
|
||||
ContextUtils.get()
|
||||
.tintMenuItems(menu, true, ContextCompat.getColor(this, darkBg ? R.color.white : R.color.black))
|
||||
.setSubMenuIconsVisiblity(menu, true);
|
||||
ContextUtils cu = ContextUtils.get();
|
||||
final boolean darkBg = cu.get().shouldColorOnTopBeLight(AppSettings.get().getPrimaryColor());
|
||||
cu.tintMenuItems(menu, true, ContextCompat.getColor(this, darkBg ? R.color.white : R.color.black));
|
||||
cu.setSubMenuIconsVisiblity(menu, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -835,7 +847,7 @@ public class MainActivity extends ThemedActivity
|
|||
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
@SuppressLint("InflateParams") View layout = getLayoutInflater().inflate(R.layout.ui__dialog_search__people_tags, null, false);
|
||||
final EditText input = (EditText) layout.findViewById(R.id.dialog_search__input);
|
||||
final EditText input = layout.findViewById(R.id.dialog_search__input);
|
||||
input.setMaxLines(1);
|
||||
input.setSingleLine(true);
|
||||
ThemeHelper.updateEditTextColor(input);
|
||||
|
@ -1053,12 +1065,39 @@ public class MainActivity extends ThemedActivity
|
|||
break;
|
||||
|
||||
case R.id.nav_followed_tags: {
|
||||
showFragment(getFragment(TagListFragment.TAG));
|
||||
SearchOrCustomTextDialogCreator.showDiasporaTagsDialog(this, arg -> {
|
||||
if (arg.startsWith(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX)) {
|
||||
arg = arg.replace(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX, "").trim();
|
||||
if (arg.equals(getString(R.string.pref_title__manage_tags))) {
|
||||
openDiasporaUrl(urls.getManageTagsUrl());
|
||||
} else {
|
||||
openDiasporaUrl(urls.getAllFollowedTagsUrl());
|
||||
}
|
||||
} else {
|
||||
openDiasporaUrl(urls.getSearchTagsUrl(arg));
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.nav_aspects: {
|
||||
showFragment(getFragment(AspectListFragment.TAG));
|
||||
SearchOrCustomTextDialogCreator.showDiasporaAspectsDialog(this, arg -> {
|
||||
if (arg.startsWith(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX)) {
|
||||
arg = arg.replace(SearchOrCustomTextDialogCreator.SPECIAL_PREFIX, "").trim();
|
||||
if (arg.equals(getString(R.string.pref_desc__manage_contacts))) {
|
||||
openDiasporaUrl(urls.getContactsUrl());
|
||||
} else if (arg.equals(getString(R.string.nav_profile))) {
|
||||
openDiasporaUrl(urls.getProfileUrl());
|
||||
}
|
||||
} else {
|
||||
for (DiasporaAspect daspect : _appSettings.getAspects()) {
|
||||
if (arg.equals(daspect.name)) {
|
||||
openDiasporaUrl(urls.getAspectUrl(Long.toString(daspect.id)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1139,8 +1178,8 @@ public class MainActivity extends ThemedActivity
|
|||
}
|
||||
break;
|
||||
|
||||
case R.id.nav_dandelion: {
|
||||
openDiasporaUrl(urls.getProfileUrl("48b78420923501341ef3782bcb452bd5"));
|
||||
case R.id.nav_product_support: {
|
||||
openDiasporaUrl(urls.getProfileUrl("d1cbdd70095301341e834860008dbc6c"));
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import android.support.v4.content.LocalBroadcastManager;
|
|||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.AppCompatButton;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
@ -53,7 +52,6 @@ import com.github.dfa.diaspora_android.service.FetchPodsService;
|
|||
import com.github.dfa.diaspora_android.ui.PodSelectionDialog;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
|
||||
import com.github.dfa.diaspora_android.util.ActivityUtils;
|
||||
import com.github.dfa.diaspora_android.util.AppLog;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
import com.github.dfa.diaspora_android.util.ContextUtils;
|
||||
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
|
||||
|
@ -92,16 +90,14 @@ public class PodSelectionFragment extends ThemedFragment implements SearchView.O
|
|||
private String filterString = "";
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
AppLog.d(this, "onCreateView()");
|
||||
View view = inflater.inflate(R.layout.podselection__fragment, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.podselection__fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ButterKnife.bind(this, view);
|
||||
app = (App) getActivity().getApplication();
|
||||
appSettings = app.getSettings();
|
||||
|
||||
|
@ -177,11 +173,10 @@ public class PodSelectionFragment extends ThemedFragment implements SearchView.O
|
|||
rootView.setBackgroundColor(appSettings.isAmoledColorMode() ? Color.BLACK : Color.WHITE);
|
||||
listViewPod.setDivider(new ColorDrawable(Color.GRAY));
|
||||
listViewPod.setDividerHeight(dividerHeight);
|
||||
if (appSettings.isAmoledColorMode()) {
|
||||
buttonUseCustomPod.setTextColor(Color.WHITE);
|
||||
} else {
|
||||
buttonUseCustomPod.setTextColor(ContextUtils.get().shouldColorOnTopBeLight(appSettings.getAccentColor()) ? Color.WHITE : Color.BLACK);
|
||||
}
|
||||
int bgcolor = appSettings.isAmoledColorMode() ? Color.DKGRAY : appSettings.getAccentColor();
|
||||
buttonUseCustomPod.setBackgroundColor(bgcolor);
|
||||
buttonUseCustomPod.setTextColor(_cu.shouldColorOnTopBeLight(bgcolor) ? Color.WHITE : Color.BLACK);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -204,7 +199,7 @@ public class PodSelectionFragment extends ThemedFragment implements SearchView.O
|
|||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
TextView textView = (TextView) view.findViewById(android.R.id.text1);
|
||||
TextView textView = view.findViewById(android.R.id.text1);
|
||||
textView.setTextColor(appSettings.isAmoledColorMode() ? Color.GRAY : Color.BLACK);
|
||||
return view;
|
||||
}
|
||||
|
|
|
@ -305,10 +305,10 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
|
|||
final ThemedAlertDialogBuilder builder = new ThemedAlertDialogBuilder(context, appSettings);
|
||||
builder.setView(dialogLayout);
|
||||
|
||||
final FrameLayout titleBackground = (FrameLayout) dialogLayout.findViewById(R.id.color_picker_dialog__title_background);
|
||||
final TextView title = (TextView) dialogLayout.findViewById(R.id.color_picker_dialog__title);
|
||||
final LineColorPicker base = (LineColorPicker) dialogLayout.findViewById(R.id.color_picker_dialog__base_picker);
|
||||
final LineColorPicker shade = (LineColorPicker) dialogLayout.findViewById(R.id.color_picker_dialog__shade_picker);
|
||||
final FrameLayout titleBackground = dialogLayout.findViewById(R.id.color_picker_dialog__title_background);
|
||||
final TextView title = dialogLayout.findViewById(R.id.color_picker_dialog__title);
|
||||
final LineColorPicker base = dialogLayout.findViewById(R.id.color_picker_dialog__base_picker);
|
||||
final LineColorPicker shade = dialogLayout.findViewById(R.id.color_picker_dialog__shade_picker);
|
||||
|
||||
title.setText(type == 1 ? R.string.pref_title__primary_color : R.string.pref_title__accent_color);
|
||||
title.setTextColor(getResources().getColor(R.color.white));
|
||||
|
@ -483,11 +483,7 @@ public class SettingsActivity extends ThemedActivity implements SharedPreference
|
|||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
appSettings.resetAppSettings();
|
||||
appSettings.resetPodSettings();
|
||||
Intent restartActivity = new Intent(getActivity(), MainActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(getActivity(), 12374, restartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
AlarmManager mgr = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
|
||||
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent);
|
||||
System.exit(0);
|
||||
new net.gsantner.opoc.util.ContextUtils(appSettings.getContext()).restartApp(MainActivity.class);
|
||||
}
|
||||
}).setNegativeButton(android.R.string.cancel, null)
|
||||
.create().show();
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
/*
|
||||
This file is part of the dandelion*.
|
||||
|
||||
dandelion* is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
dandelion* is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with the dandelion*.
|
||||
|
||||
If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.github.dfa.diaspora_android.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.AppCompatImageView;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.listener.OnSomethingClickListener;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
|
||||
import com.github.dfa.diaspora_android.util.AppLog;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
import com.github.dfa.diaspora_android.util.ContextUtils;
|
||||
import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
/**
|
||||
* Fragment that shows a list of the HashTags the user follows
|
||||
*/
|
||||
public class TagListFragment extends ThemedFragment implements OnSomethingClickListener<Object> {
|
||||
|
||||
public static final String TAG = "com.github.dfa.diaspora_android.TagListFragment";
|
||||
|
||||
@BindView(R.id.fragment_list__recycler_view)
|
||||
public RecyclerView followedTagsRecyclerView;
|
||||
|
||||
@BindView(R.id.fragment_list__spacer)
|
||||
public View space;
|
||||
|
||||
@BindView(R.id.fragment_list__root)
|
||||
public RelativeLayout rootView;
|
||||
|
||||
protected App app;
|
||||
protected DiasporaUrlHelper urls;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
AppLog.d(this, "onCreateView()");
|
||||
return inflater.inflate(R.layout.recycler_list__fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ButterKnife.bind(this, view);
|
||||
app = (App) getActivity().getApplication();
|
||||
AppSettings appSettings = app.getSettings();
|
||||
urls = new DiasporaUrlHelper(appSettings);
|
||||
|
||||
followedTagsRecyclerView.setHasFixedSize(true);
|
||||
followedTagsRecyclerView.setNestedScrollingEnabled(false);
|
||||
|
||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
|
||||
followedTagsRecyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
final FollowedTagsAdapter adapter = new FollowedTagsAdapter(appSettings, this);
|
||||
followedTagsRecyclerView.setAdapter(adapter);
|
||||
|
||||
//Set window title
|
||||
getActivity().setTitle(R.string.nav_followed_tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFragmentTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSomethingClicked(Object null1, Integer null2, String tag) {
|
||||
((MainActivity) getActivity()).openDiasporaUrl(urls.getSearchTagsUrl(tag));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyColorToViews() {
|
||||
followedTagsRecyclerView.invalidate();
|
||||
if (getAppSettings().isAmoledColorMode()) {
|
||||
rootView.setBackgroundColor(Color.BLACK);
|
||||
space.setBackgroundColor(Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FollowedTagsAdapter extends RecyclerView.Adapter<FollowedTagsAdapter.ViewHolder> {
|
||||
private boolean isAmoledColorMode;
|
||||
private AppSettings appSettings;
|
||||
private String[] followedTagsList;
|
||||
private List<String> followedTagsFavsList;
|
||||
private OnSomethingClickListener<Object> tagClickedListener;
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
@BindView(R.id.recycler_view__list_item__text)
|
||||
public TextView title;
|
||||
@BindView(R.id.recycler_view__list_item__favourite)
|
||||
AppCompatImageView favouriteImage;
|
||||
@BindView(R.id.recycler_view__list_item__root)
|
||||
RelativeLayout root;
|
||||
|
||||
ViewHolder(View v) {
|
||||
super(v);
|
||||
ButterKnife.bind(this, v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FollowedTagsAdapter(AppSettings appSettings, OnSomethingClickListener<Object> tagClickedListener) {
|
||||
this.appSettings = appSettings;
|
||||
this.followedTagsList = appSettings.getFollowedTags();
|
||||
this.followedTagsFavsList = new ArrayList<>(Arrays.asList(appSettings.getFollowedTagsFavs()));
|
||||
this.tagClickedListener = tagClickedListener;
|
||||
this.isAmoledColorMode = appSettings.isAmoledColorMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return followedTagsList.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FollowedTagsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.recycler_list__list_item_with_fav, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, final int position) {
|
||||
// Alternating colors
|
||||
final Context c = holder.root.getContext();
|
||||
final String tag = followedTagsList[position];
|
||||
holder.title.setText(tag);
|
||||
if (position % 2 == 1) {
|
||||
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : ContextUtils.get().color(R.color.alternate_row_color));
|
||||
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
|
||||
} else {
|
||||
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : Color.WHITE);
|
||||
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
|
||||
}
|
||||
|
||||
// Favourite (Star) Image
|
||||
applyFavouriteImage(holder.favouriteImage, isFollowedTagFaved(tag));
|
||||
|
||||
// Click on fav button
|
||||
holder.favouriteImage.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (isFollowedTagFaved(tag)) {
|
||||
followedTagsFavsList.remove(followedTagsFavsList.indexOf(tag));
|
||||
} else {
|
||||
followedTagsFavsList.add(tag);
|
||||
}
|
||||
appSettings.setFollowedTagsFavs(followedTagsFavsList);
|
||||
applyFavouriteImage(holder.favouriteImage, isFollowedTagFaved(tag));
|
||||
}
|
||||
});
|
||||
|
||||
holder.root.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
tagClickedListener.onSomethingClicked(null, null, tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isFollowedTagFaved(String tag) {
|
||||
return followedTagsFavsList.contains(tag);
|
||||
}
|
||||
|
||||
private void applyFavouriteImage(AppCompatImageView imageView, boolean isFaved) {
|
||||
imageView.setImageResource(isFaved ? R.drawable.ic_star_filled_48px : R.drawable.ic_star_border_black_48px);
|
||||
imageView.setColorFilter(isFaved ? appSettings.getAccentColor() : (isAmoledColorMode ? Color.GRAY : 0), PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ import java.util.List;
|
|||
|
||||
|
||||
/**
|
||||
* Created by gsantner (http://gsantner.net/ on 30.09.16.
|
||||
* Created by gsantner (https://gsantner.net/ on 30.09.16.
|
||||
* DiasporaPodList - List container for DiasporaPod's, with methods to merge with other DiasporaPodLists
|
||||
* DiasporaPod - Data container for a Pod, can include N DiasporaPodUrl's
|
||||
* DiasporaPodUrl - A Url of an DiasporaPod
|
||||
|
@ -315,8 +315,8 @@ public class DiasporaPodList implements Iterable<DiasporaPodList.DiasporaPod>, S
|
|||
}
|
||||
|
||||
/*
|
||||
* Getter & Setter
|
||||
*/
|
||||
* Getter & Setter
|
||||
*/
|
||||
public List<DiasporaPodUrl> getPodUrls() {
|
||||
return _podUrls;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.json.JSONObject;
|
|||
|
||||
/**
|
||||
* User profile
|
||||
* Created by gsantner (http://gsantner.net/) on 24.03.16. Part of dandelion*.
|
||||
* Created by gsantner (https://gsantner.net/) on 24.03.16. Part of dandelion*.
|
||||
*/
|
||||
public class DiasporaUserProfile {
|
||||
private static final int MINIMUM_USERPROFILE_LOAD_TIMEDIFF = 5000;
|
||||
|
|
|
@ -21,7 +21,7 @@ package com.github.dfa.diaspora_android.listener;
|
|||
import com.github.dfa.diaspora_android.data.DiasporaUserProfile;
|
||||
|
||||
/**
|
||||
* Created by gsantner (http://gsantner.net/) on 26.03.16.
|
||||
* Created by gsantner (https://gsantner.net/) on 26.03.16.
|
||||
* Interface that needs to be implemented by classes that listen for Profile related changes
|
||||
*/
|
||||
public interface DiasporaUserProfileChangedListener {
|
||||
|
|
|
@ -24,6 +24,8 @@ import android.graphics.Bitmap;
|
|||
import android.graphics.BitmapFactory;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import net.gsantner.opoc.util.DownloadTask;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class AvatarImageLoader {
|
||||
|
@ -52,7 +54,9 @@ public class AvatarImageLoader {
|
|||
|
||||
public void startImageDownload(ImageView imageView, String avatarUrl) {
|
||||
if (!avatarUrl.equals("")) {
|
||||
new ImageDownloadTask(imageView, avatarFile.getAbsolutePath()).execute(avatarUrl);
|
||||
new DownloadTask(new File(avatarFile.getAbsolutePath()), (ok, file) -> {
|
||||
loadToImageView(imageView);
|
||||
}).execute(avatarUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ public class FetchPodsService extends Service {
|
|||
}
|
||||
|
||||
class GetPodsTask extends AsyncTask<Void, Void, DiasporaPodList> {
|
||||
private static final String PODDY_PODLIST_URL = "https://raw.githubusercontent.com/Diaspora-for-Android/dandelion/master/app/src/main/res/raw/podlist.json";
|
||||
private static final String PODDY_PODLIST_URL = "https://raw.githubusercontent.com/gsantner/dandelion/master/app/src/main/res/raw/podlist.json";
|
||||
|
||||
private final Service service;
|
||||
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
This file is part of the dandelion*.
|
||||
|
||||
dandelion* is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
dandelion* is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with the dandelion*.
|
||||
|
||||
If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.github.dfa.diaspora_android.service;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.github.dfa.diaspora_android.util.AppLog;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
|
||||
/**
|
||||
* Task that can be used to download images from URLs and store them in storage
|
||||
* Created by gsantner (http://gsantner.net/) on 24.03.16.
|
||||
*/
|
||||
public class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
|
||||
private final ImageView imageView;
|
||||
private String savePath;
|
||||
|
||||
/**
|
||||
* Download image from URL
|
||||
*
|
||||
* @param imageView ImageView to set image to (null = don't set)
|
||||
* @param savePath Save image to file (null = don't save)
|
||||
*/
|
||||
public ImageDownloadTask(@Nullable ImageView imageView, @Nullable String savePath) {
|
||||
this.imageView = imageView;
|
||||
this.savePath = savePath;
|
||||
}
|
||||
|
||||
protected Bitmap doInBackground(String... urls) {
|
||||
String url = urls[0];
|
||||
Bitmap bitmap = null;
|
||||
FileOutputStream out = null;
|
||||
InputStream inStream;
|
||||
HttpsURLConnection connection;
|
||||
try {
|
||||
connection = NetCipher.getHttpsURLConnection(url);
|
||||
inStream = connection.getInputStream();
|
||||
bitmap = BitmapFactory.decodeStream(inStream);
|
||||
|
||||
// Save to file if not null
|
||||
if (savePath != null) {
|
||||
out = new FileOutputStream(savePath);
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||
}
|
||||
|
||||
try {
|
||||
inStream.close();
|
||||
} catch (IOException e) {/*Nothing*/}
|
||||
|
||||
connection.disconnect();
|
||||
|
||||
} catch (Exception e) {
|
||||
AppLog.e(this, e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Bitmap result) {
|
||||
// Display on imageview if not null
|
||||
if (imageView != null) {
|
||||
imageView.setImageBitmap(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,7 +71,7 @@ public class BadgeDrawable extends Drawable {
|
|||
Rect bounds = getBounds();
|
||||
float width = bounds.right - bounds.left;
|
||||
float height = bounds.bottom - bounds.top;
|
||||
float oneDp = ContextUtils.get().dp2px(1);
|
||||
float oneDp = ContextUtils.get().convertDpToPx(1);
|
||||
|
||||
// Position the badge in the top-right quadrant of the icon.
|
||||
float radius = ((Math.max(width, height) / 2)) / 2;
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package com.github.dfa.diaspora_android.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.data.DiasporaAspect;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
|
||||
import net.gsantner.opoc.ui.SearchOrCustomTextDialog;
|
||||
import net.gsantner.opoc.util.Callback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SearchOrCustomTextDialogCreator {
|
||||
public static final String SPECIAL_PREFIX = "\uD83D\uDCA0";
|
||||
|
||||
public static void showDiasporaTagsDialog(final Activity activity, final Callback.a1<String> callback) {
|
||||
SearchOrCustomTextDialog.DialogOptions dopt = new SearchOrCustomTextDialog.DialogOptions();
|
||||
baseConf(activity, dopt);
|
||||
dopt.callback = callback;
|
||||
dopt.isSearchEnabled = true;
|
||||
dopt.searchHintText = R.string.search;
|
||||
dopt.titleText = R.string.tags;
|
||||
|
||||
new Thread(() -> {
|
||||
AppSettings appSettings = AppSettings.get();
|
||||
ArrayList<String> hl = new ArrayList<>();
|
||||
ArrayList<String> data = new ArrayList<>(Arrays.asList(appSettings.getFollowedTags()));
|
||||
if (data.size() > 0) {
|
||||
String highlighted = surroundString(data.remove(0));
|
||||
data.add(0, highlighted);
|
||||
hl.add(highlighted);
|
||||
}
|
||||
|
||||
for (int strid : new int[]{R.string.pref_title__manage_tags}) {
|
||||
String special = surroundString(appSettings.rstr(strid));
|
||||
data.add(0, special);
|
||||
hl.add(special);
|
||||
}
|
||||
dopt.data = data;
|
||||
dopt.highlightData = hl;
|
||||
activity.runOnUiThread(() -> SearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt));
|
||||
}).start();
|
||||
}
|
||||
|
||||
private static String surroundString(String text) {
|
||||
return SPECIAL_PREFIX + " " + text + " ";
|
||||
}
|
||||
|
||||
|
||||
public static void showDiasporaAspectsDialog(final Activity activity, final Callback.a1<String> callback) {
|
||||
SearchOrCustomTextDialog.DialogOptions dopt = new SearchOrCustomTextDialog.DialogOptions();
|
||||
baseConf(activity, dopt);
|
||||
dopt.callback = callback;
|
||||
dopt.isSearchEnabled = false;
|
||||
dopt.titleText = R.string.contacts;
|
||||
|
||||
new Thread(() -> {
|
||||
AppSettings appSettings = AppSettings.get();
|
||||
ArrayList<String> hl = new ArrayList<>();
|
||||
ArrayList<String> data = new ArrayList<>();
|
||||
for (DiasporaAspect aspect : AppSettings.get().getAspects()) {
|
||||
data.add(aspect.name);
|
||||
}
|
||||
for (int strid : new int[]{R.string.nav_profile, R.string.pref_desc__manage_contacts}) {
|
||||
String special = surroundString(appSettings.rstr(strid));
|
||||
data.add(0, special);
|
||||
hl.add(special);
|
||||
}
|
||||
dopt.data = data;
|
||||
dopt.highlightData = hl;
|
||||
activity.runOnUiThread(() -> SearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, dopt));
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private static void baseConf(Activity activity, SearchOrCustomTextDialog.DialogOptions dopt) {
|
||||
AppSettings as = new AppSettings(activity);
|
||||
dopt.isDarkDialog = as.isAmoledColorMode();
|
||||
dopt.textColor = ContextCompat.getColor(activity, dopt.isDarkDialog ? R.color.white : R.color.primary_text);
|
||||
dopt.highlightColor = as.getAccentColor();
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
This file is part of the dandelion*.
|
||||
|
||||
dandelion* is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
dandelion* is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with the dandelion*.
|
||||
|
||||
If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.github.dfa.diaspora_android.ui.theme;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
/**
|
||||
* Customized abstract Fragment class with some useful methods
|
||||
* Created by vanitas on 21.09.16.
|
||||
*/
|
||||
|
||||
public abstract class CustomFragment extends Fragment {
|
||||
|
||||
public static final String TAG = "com.github.dfa.diaspora_android.ui.theme.CustomFragment";
|
||||
|
||||
/**
|
||||
* We have an optionsMenu
|
||||
*
|
||||
* @param savedInstanceState state
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tag used to identify the Fragment.
|
||||
*
|
||||
* @return tag
|
||||
*/
|
||||
public abstract String getFragmentTag();
|
||||
|
||||
/**
|
||||
* Return true if the fragment reacted to a back button press, false else.
|
||||
* In case the fragment returned false, the parent activity should handle the backPress.
|
||||
*
|
||||
* @return did we react to the back press?
|
||||
*/
|
||||
public abstract boolean onBackPressed();
|
||||
|
||||
public boolean isAllowedIntellihide() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,9 +19,7 @@
|
|||
package com.github.dfa.diaspora_android.ui.theme;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Build;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
|
@ -69,15 +67,7 @@ public abstract class ThemedActivity extends AppCompatActivity {
|
|||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
private void updateRecentAppColor() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
BitmapDrawable drawable = ((BitmapDrawable) getDrawable(R.drawable.ic_launcher));
|
||||
if (drawable != null) {
|
||||
setTaskDescription(new ActivityManager.TaskDescription(
|
||||
getResources().getString(R.string.app_name),
|
||||
drawable.getBitmap(),
|
||||
getAppSettings().getPrimaryColor()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void updateScreenRotation() {
|
||||
|
|
|
@ -41,7 +41,7 @@ public class ThemedCheckBoxPreference extends CheckBoxPreference implements Them
|
|||
|
||||
@Override
|
||||
public void setColors() {
|
||||
CheckBox checkBox = (CheckBox) rootLayout.findViewById(android.R.id.checkbox);
|
||||
CheckBox checkBox = rootLayout.findViewById(android.R.id.checkbox);
|
||||
ThemeHelper.getInstance(AppSettings.get());
|
||||
ThemeHelper.updateCheckBoxColor(checkBox);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public class ThemedColorPickerPreference extends Preference implements Themeable
|
|||
@Override
|
||||
protected void onBindView(View view) {
|
||||
super.onBindView(view);
|
||||
colorPreview = (ImageView) view.findViewById(android.R.id.icon);
|
||||
colorPreview = view.findViewById(android.R.id.icon);
|
||||
setColors();
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class ThemedColorPickerPreference extends Preference implements Themeable
|
|||
AppSettings appSettings = AppSettings.get();
|
||||
String key = getKey();
|
||||
|
||||
int color = ContextUtils.get().color(R.color.primary);
|
||||
int color = ContextUtils.get().rcolor(R.color.primary);
|
||||
if ((appSettings.isKeyEqual(key, R.string.pref_key__primary_color_shade))) {
|
||||
color = appSettings.getPrimaryColor();
|
||||
} else if ((appSettings.isKeyEqual(key, R.string.pref_key__accent_color_shade))) {
|
||||
|
|
|
@ -21,12 +21,14 @@ package com.github.dfa.diaspora_android.ui.theme;
|
|||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
|
||||
import net.gsantner.opoc.activity.GsFragmentBase;
|
||||
|
||||
/**
|
||||
* Fragment that supports color schemes
|
||||
* Created by vanitas on 06.10.16.
|
||||
*/
|
||||
|
||||
public abstract class ThemedFragment extends CustomFragment {
|
||||
public abstract class ThemedFragment extends GsFragmentBase {
|
||||
protected AppSettings getAppSettings() {
|
||||
return ((App) getActivity().getApplication()).getSettings();
|
||||
}
|
||||
|
@ -39,4 +41,9 @@ public abstract class ThemedFragment extends CustomFragment {
|
|||
ThemeHelper.getInstance(getAppSettings());
|
||||
applyColorToViews();
|
||||
}
|
||||
|
||||
|
||||
public boolean isAllowedIntellihide() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public class ThemedPreferenceCategory extends PreferenceCategory implements Them
|
|||
@Override
|
||||
protected View onCreateView(ViewGroup parent) {
|
||||
View rootLayout = super.onCreateView(parent);
|
||||
this.titleTextView = (TextView) rootLayout.findViewById(android.R.id.title);
|
||||
this.titleTextView = rootLayout.findViewById(android.R.id.title);
|
||||
setColors();
|
||||
return rootLayout;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ public class ThemedVisibilityPreference extends ThemedCheckBoxPreference {
|
|||
|
||||
@Override
|
||||
public void setColors() {
|
||||
CheckBox checkBox = (CheckBox) rootLayout.findViewById(android.R.id.checkbox);
|
||||
CheckBox checkBox = rootLayout.findViewById(android.R.id.checkbox);
|
||||
checkBox.setButtonDrawable(R.drawable.ic_visibility_selector);
|
||||
ThemeHelper.getInstance(AppSettings.get());
|
||||
ThemeHelper.updateCheckBoxColor(checkBox);
|
||||
|
|
|
@ -21,7 +21,7 @@ import java.util.Locale;
|
|||
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue"})
|
||||
public class ActivityUtils extends net.gsantner.opoc.util.ActivityUtils {
|
||||
protected ActivityUtils(Activity activity) {
|
||||
public ActivityUtils(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
|
@ -77,10 +77,10 @@ public class ActivityUtils extends net.gsantner.opoc.util.ActivityUtils {
|
|||
|
||||
/**
|
||||
* This method creates file sharing uri by using FileProvider
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Uri getFileSharingUri(Context context,File file) {
|
||||
|
||||
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID,file);
|
||||
public static Uri getFileSharingUri(Context context, File file) {
|
||||
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, file);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ public class AndroidBug5497Workaround {
|
|||
private FrameLayout.LayoutParams frameLayoutParams;
|
||||
|
||||
private AndroidBug5497Workaround(Activity activity) {
|
||||
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
|
||||
FrameLayout content = activity.findViewById(android.R.id.content);
|
||||
mChildOfContent = content.getChildAt(0);
|
||||
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
public void onGlobalLayout() {
|
||||
|
|
|
@ -18,6 +18,7 @@ import android.annotation.SuppressLint;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Environment;
|
||||
|
||||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.BuildConfig;
|
||||
|
@ -26,19 +27,20 @@ import com.github.dfa.diaspora_android.data.DiasporaAspect;
|
|||
import com.github.dfa.diaspora_android.data.DiasporaPodList.DiasporaPod;
|
||||
import com.github.dfa.diaspora_android.web.ProxyHandler;
|
||||
|
||||
import net.gsantner.opoc.util.AppSettingsBase;
|
||||
import net.gsantner.opoc.preference.SharedPreferencesPropertyBackend;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Settings
|
||||
* Created by gsantner (http://gsantner.net/) on 20.03.16. Part of dandelion*.
|
||||
* Created by gsantner (https://gsantner.net/) on 20.03.16. Part of dandelion*.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public class AppSettings extends AppSettingsBase {
|
||||
public class AppSettings extends SharedPreferencesPropertyBackend {
|
||||
private final SharedPreferences _prefPod;
|
||||
private DiasporaPod currentPod0Cached;
|
||||
|
||||
|
@ -46,7 +48,7 @@ public class AppSettings extends AppSettingsBase {
|
|||
return new AppSettings(App.get());
|
||||
}
|
||||
|
||||
private AppSettings(Context context) {
|
||||
public AppSettings(Context context) {
|
||||
super(context);
|
||||
_prefPod = _context.getSharedPreferences("pod0", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
@ -145,7 +147,11 @@ public class AppSettings extends AppSettingsBase {
|
|||
}
|
||||
|
||||
public void setPodAspects(DiasporaAspect[] aspects) {
|
||||
setStringArray(R.string.pref_key__podprofile_aspects, aspects, _prefPod);
|
||||
String[] strs = new String[aspects.length];
|
||||
for (int i = 0; i < strs.length; i++) {
|
||||
strs[i] = aspects[i].toShareAbleText();
|
||||
}
|
||||
setStringArray(R.string.pref_key__podprofile_aspects, strs, _prefPod);
|
||||
}
|
||||
|
||||
public DiasporaAspect[] getAspects() {
|
||||
|
@ -341,8 +347,8 @@ public class AppSettings extends AppSettingsBase {
|
|||
return getBool(R.string.pref_key__visibility_nav__reports, false);
|
||||
}
|
||||
|
||||
public boolean isVisibleInNavDandelionAccount() {
|
||||
return getBool(R.string.pref_key__visibility_nav__dandelion_account, false);
|
||||
public boolean isVisibleInNavGsantnerAccount() {
|
||||
return getBool(R.string.pref_key__visibility_nav__gsantner_account, false);
|
||||
}
|
||||
|
||||
public boolean isVisibleInNavToggleMobileDesktop() {
|
||||
|
@ -363,12 +369,19 @@ public class AppSettings extends AppSettingsBase {
|
|||
return value;
|
||||
}
|
||||
|
||||
public boolean isAppCurrentVersionFirstStart() {
|
||||
public boolean isAppCurrentVersionFirstStart(boolean doSet) {
|
||||
int value = getInt(R.string.pref_key__app_first_start_current_version, -1);
|
||||
setInt(R.string.pref_key__app_first_start_current_version, BuildConfig.VERSION_CODE);
|
||||
if (doSet) {
|
||||
setInt(R.string.pref_key__app_first_start_current_version, BuildConfig.VERSION_CODE);
|
||||
}
|
||||
return value != BuildConfig.VERSION_CODE && !BuildConfig.IS_TEST_BUILD;
|
||||
}
|
||||
|
||||
public File getAppSaveDirectory() {
|
||||
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/dandelion");
|
||||
|
||||
}
|
||||
|
||||
public long getLastVisitedPositionInStream() {
|
||||
return getLong(R.string.pref_key__podprofile_last_stream_position, -1, _prefPod);
|
||||
}
|
||||
|
@ -430,6 +443,9 @@ public class AppSettings extends AppSettingsBase {
|
|||
public boolean isAmoledColorMode() {
|
||||
return getBool(R.string.pref_key__primary_color__amoled_mode, false);
|
||||
}
|
||||
public void setAmoledColorMode(boolean enable) {
|
||||
setBool(R.string.pref_key__primary_color__amoled_mode, enable);
|
||||
}
|
||||
|
||||
public boolean isAdBlockEnabled() {
|
||||
return getBool(R.string.pref_key__adblock_enable, true);
|
||||
|
|
|
@ -229,6 +229,15 @@ public class DiasporaUrlHelper {
|
|||
return getPodUrl() + SUBURL_SEARCH_TAGS + query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a url that queries posts for the given hashtag query
|
||||
*
|
||||
* @return https://(pod-domain.tld)/followed_tags
|
||||
*/
|
||||
public String getAllFollowedTagsUrl() {
|
||||
return getPodUrl() + SUBURL_FOLOWED_TAGS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a url that queries user accounts for query
|
||||
*
|
||||
|
|
|
@ -18,19 +18,9 @@
|
|||
*/
|
||||
package com.github.dfa.diaspora_android.web;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.MutableContextWrapper;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
|
@ -39,22 +29,11 @@ import android.widget.ProgressBar;
|
|||
|
||||
import com.github.dfa.diaspora_android.App;
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.activity.MainActivity;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemeHelper;
|
||||
import com.github.dfa.diaspora_android.ui.theme.ThemedFragment;
|
||||
import com.github.dfa.diaspora_android.util.ActivityUtils;
|
||||
import com.github.dfa.diaspora_android.util.AppLog;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Fragment with a webView and a ProgressBar.
|
||||
* This Fragment retains its instance.
|
||||
|
@ -64,7 +43,6 @@ import java.util.Locale;
|
|||
public class BrowserFragment extends ThemedFragment {
|
||||
public static final String TAG = "com.github.dfa.diaspora_android.BrowserFragment";
|
||||
|
||||
protected View rootLayout;
|
||||
protected ContextMenuWebView webView;
|
||||
protected ProgressBar progressBar;
|
||||
protected AppSettings appSettings;
|
||||
|
@ -74,18 +52,11 @@ public class BrowserFragment extends ThemedFragment {
|
|||
protected String pendingUrl;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
AppLog.d(this, "onCreateView()");
|
||||
if (rootLayout == null) {
|
||||
LayoutInflater inflater1 = inflater.cloneInContext(new MutableContextWrapper(getContext()));
|
||||
rootLayout = inflater1.inflate(R.layout.browser__fragment, container, false);
|
||||
} else {
|
||||
MutableContextWrapper context = (MutableContextWrapper) rootLayout.getContext();
|
||||
context.setBaseContext(getContext());
|
||||
}
|
||||
return rootLayout;
|
||||
protected int getLayoutResId() {
|
||||
return R.layout.browser__fragment;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
AppLog.d(this, "onViewCreated()");
|
||||
|
@ -96,7 +67,7 @@ public class BrowserFragment extends ThemedFragment {
|
|||
}
|
||||
|
||||
if (this.webView == null) {
|
||||
this.webView = (ContextMenuWebView) view.findViewById(R.id.webView);
|
||||
this.webView = view.findViewById(R.id.webView);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -108,7 +79,7 @@ public class BrowserFragment extends ThemedFragment {
|
|||
}
|
||||
|
||||
if (this.progressBar == null) {
|
||||
this.progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
this.progressBar = view.findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
if (pendingUrl != null) {
|
||||
|
@ -125,8 +96,8 @@ public class BrowserFragment extends ThemedFragment {
|
|||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (getRetainInstance() && rootLayout.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) rootLayout.getParent()).removeView(rootLayout);
|
||||
if (getRetainInstance() && getView() != null && getView().getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) getView().getParent()).removeView(getView());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,96 +135,6 @@ public class BrowserFragment extends ThemedFragment {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected boolean makeScreenshotOfWebView(boolean hasToShareScreenshot) {
|
||||
AppLog.i(this, "StreamFragment.makeScreenshotOfWebView()");
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
int hasWRITE_EXTERNAL_STORAGE = getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if (hasWRITE_EXTERNAL_STORAGE != PackageManager.PERMISSION_GRANTED) {
|
||||
if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(R.string.permissions_screenshot)
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23)
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MainActivity.REQUEST_CODE_ASK_PERMISSIONS);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MainActivity.REQUEST_CODE_ASK_PERMISSIONS);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Date dateNow = new Date();
|
||||
DateFormat dateFormat = new SimpleDateFormat("yy_MM_dd--HH_mm_ss", Locale.getDefault());
|
||||
File fileSaveDirectory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/Diaspora");
|
||||
|
||||
String fileSaveName = hasToShareScreenshot ? ".DfA_share.jpg" : String.format("DfA_%s.jpg", dateFormat.format(dateNow));
|
||||
if (!fileSaveDirectory.exists()) {
|
||||
if (!fileSaveDirectory.mkdirs()) {
|
||||
AppLog.w(this, "Could not mkdir " + fileSaveDirectory.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasToShareScreenshot) {
|
||||
Snackbar.make(webView, getString(R.string.share__toast_screenshot) + " " + fileSaveName, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
webView.setDrawingCacheEnabled(true);
|
||||
bitmap = Bitmap.createBitmap(webView.getDrawingCache());
|
||||
webView.setDrawingCacheEnabled(false);
|
||||
|
||||
OutputStream bitmapWriter = null;
|
||||
try {
|
||||
bitmapWriter = new FileOutputStream(new File(fileSaveDirectory, fileSaveName));
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, bitmapWriter);
|
||||
bitmapWriter.flush();
|
||||
bitmap.recycle();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (bitmapWriter != null) {
|
||||
try {
|
||||
bitmapWriter.close();
|
||||
} catch (IOException _ignSaveored) {/* Nothing */}
|
||||
}
|
||||
}
|
||||
|
||||
// Only show share intent when Action Share Screenshot was selected
|
||||
if (hasToShareScreenshot) {
|
||||
|
||||
Uri bmpUri = ActivityUtils.getFileSharingUri(getContext(),new File(fileSaveDirectory, fileSaveName));
|
||||
|
||||
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
|
||||
sharingIntent.setType("image/jpeg");
|
||||
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, webView.getTitle());
|
||||
sharingIntent.putExtra(Intent.EXTRA_TEXT, webView.getUrl());
|
||||
sharingIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
sharingIntent.putExtra(Intent.EXTRA_STREAM, bmpUri);
|
||||
|
||||
PackageManager pm = getActivity().getPackageManager();
|
||||
|
||||
if (sharingIntent.resolveActivity(pm) != null) {
|
||||
startActivity(Intent.createChooser(sharingIntent, getString(R.string.action_share_dotdotdot)));
|
||||
}
|
||||
} else {
|
||||
// Broadcast that this file is indexable
|
||||
File file = new File(fileSaveDirectory, fileSaveName);
|
||||
Uri uri = Uri.fromFile(file);
|
||||
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri);
|
||||
getActivity().sendBroadcast(intent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFragmentTag() {
|
||||
return TAG;
|
||||
|
|
|
@ -18,30 +18,27 @@
|
|||
*/
|
||||
package com.github.dfa.diaspora_android.web;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.dfa.diaspora_android.BuildConfig;
|
||||
import com.github.dfa.diaspora_android.R;
|
||||
import com.github.dfa.diaspora_android.activity.MainActivity;
|
||||
import com.github.dfa.diaspora_android.service.ImageDownloadTask;
|
||||
import com.github.dfa.diaspora_android.util.ActivityUtils;
|
||||
import com.github.dfa.diaspora_android.util.AppSettings;
|
||||
|
||||
import net.gsantner.opoc.util.DownloadTask;
|
||||
import net.gsantner.opoc.util.PermissionChecker;
|
||||
import net.gsantner.opoc.util.ShareUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Subclass of WebView which adds a context menu for long clicks on images or links to share, save
|
||||
|
@ -81,107 +78,46 @@ public class ContextMenuWebView extends NestedWebView {
|
|||
public boolean onMenuItemClick(MenuItem item) {
|
||||
HitTestResult result = getHitTestResult();
|
||||
String url = result.getExtra();
|
||||
final ShareUtil shu = new ShareUtil(context).setFileProviderAuthority(BuildConfig.APPLICATION_ID);
|
||||
final PermissionChecker permc = new PermissionChecker(parentActivity);
|
||||
final AppSettings appSettings = new AppSettings(context);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
//Save image to external memory
|
||||
case ID_SAVE_IMAGE: {
|
||||
boolean writeToStoragePermitted = true;
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
int hasWRITE_EXTERNAL_STORAGE = parentActivity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if (hasWRITE_EXTERNAL_STORAGE != PackageManager.PERMISSION_GRANTED) {
|
||||
writeToStoragePermitted = false;
|
||||
if (!parentActivity.shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
new AlertDialog.Builder(parentActivity)
|
||||
.setMessage(R.string.permissions_image)
|
||||
.setPositiveButton(context.getText(android.R.string.yes), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23)
|
||||
parentActivity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MainActivity.REQUEST_CODE__ACCESS_EXTERNAL_STORAGE);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(context.getText(android.R.string.no), null)
|
||||
.show();
|
||||
}
|
||||
parentActivity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MainActivity.REQUEST_CODE__ACCESS_EXTERNAL_STORAGE);
|
||||
}
|
||||
}
|
||||
if (writeToStoragePermitted) {
|
||||
//Make sure, Diaspora Folder exists
|
||||
File destinationFolder = new File(Environment.getExternalStorageDirectory() + "/Pictures/Diaspora");
|
||||
if (!destinationFolder.exists()) {
|
||||
destinationFolder.mkdirs();
|
||||
}
|
||||
|
||||
if (url != null) {
|
||||
Uri source = Uri.parse(url);
|
||||
if (permc.doIfExtStoragePermissionGranted(context.getString(R.string.permissions_image))) {
|
||||
File fileSaveDirectory = appSettings.getAppSaveDirectory();
|
||||
if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) {
|
||||
String filename = "dandelion-" + ShareUtil.SDF_SHORT.format(new Date()) + url.substring(url.lastIndexOf("."));
|
||||
/*Uri source = Uri.parse(url);
|
||||
DownloadManager.Request request = new DownloadManager.Request(source);
|
||||
File destinationFile = new File(Environment.getExternalStorageDirectory() + "/Pictures/Diaspora/" + System.currentTimeMillis() + ".png");
|
||||
|
||||
request.setDestinationUri(Uri.fromFile(destinationFile));
|
||||
((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);
|
||||
|
||||
Toast.makeText(context, context.getText(R.string.share__toast_saved_image_to_location) + " " +
|
||||
destinationFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
|
||||
request.setDestinationUri(Uri.fromFile(new File(fileSaveDirectory, filename)));
|
||||
((DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE)).enqueue(request);*/
|
||||
new DownloadTask(new File(fileSaveDirectory, filename), (ok, dlfile) -> {
|
||||
if (ok) {
|
||||
Toast.makeText(context, context.getText(R.string.share__toast_saved_image_to_location) + " " + dlfile.getName(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}).execute(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ID_SHARE_IMAGE:
|
||||
if (url != null) {
|
||||
boolean writeToStoragePermitted = true;
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
int hasWRITE_EXTERNAL_STORAGE = parentActivity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if (hasWRITE_EXTERNAL_STORAGE != PackageManager.PERMISSION_GRANTED) {
|
||||
writeToStoragePermitted = false;
|
||||
if (!parentActivity.shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
new AlertDialog.Builder(parentActivity)
|
||||
.setMessage(R.string.permissions_image)
|
||||
.setPositiveButton(context.getText(android.R.string.yes), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23)
|
||||
parentActivity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MainActivity.REQUEST_CODE__ACCESS_EXTERNAL_STORAGE);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(context.getText(android.R.string.no), null)
|
||||
.show();
|
||||
} else {
|
||||
parentActivity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MainActivity.REQUEST_CODE__ACCESS_EXTERNAL_STORAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (writeToStoragePermitted) {
|
||||
//Make sure, Diaspora Folder exists
|
||||
File destinationFolder = new File(Environment.getExternalStorageDirectory() + "/Pictures/Diaspora");
|
||||
if (!destinationFolder.exists()) {
|
||||
destinationFolder.mkdirs();
|
||||
}
|
||||
|
||||
final Uri local = Uri.parse(Environment.getExternalStorageDirectory() + "/Pictures/Diaspora/" + System.currentTimeMillis() + ".png");
|
||||
new ImageDownloadTask(null, local.getPath()) {
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap result) {
|
||||
|
||||
Uri myUri = ActivityUtils.getFileSharingUri(context, new File(local.getPath()));
|
||||
Intent sharingIntent = new Intent();
|
||||
sharingIntent.setAction(Intent.ACTION_SEND);
|
||||
sharingIntent.putExtra(Intent.EXTRA_STREAM, myUri);
|
||||
sharingIntent.setType("image/png");
|
||||
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
context.startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.action_share_dotdotdot)));
|
||||
}
|
||||
}.execute(url);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, "Cannot share image: url is null", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ID_SHARE_IMAGE: {
|
||||
if (permc.doIfExtStoragePermissionGranted(context.getString(R.string.permissions_image))) {
|
||||
File fileSaveDirectory = appSettings.getAppSaveDirectory();
|
||||
if (permc.mkdirIfStoragePermissionGranted(fileSaveDirectory)) {
|
||||
String filename = ".dandelion-shared" + url.substring(url.lastIndexOf("."));
|
||||
new DownloadTask(new File(fileSaveDirectory, filename), (ok, dlfile) -> {
|
||||
if (ok) {
|
||||
Toast.makeText(context, context.getText(R.string.share__toast_saved_image_to_location) + " " + dlfile.getName(), Toast.LENGTH_LONG).show();
|
||||
shu.shareStream(dlfile, "image/" + dlfile.getAbsolutePath().lastIndexOf(".") + 1);
|
||||
}
|
||||
}).execute(url);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ID_IMAGE_EXTERNAL_BROWSER:
|
||||
if (url != null) {
|
||||
|
@ -229,7 +165,7 @@ public class ContextMenuWebView extends NestedWebView {
|
|||
result.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
|
||||
// Menu options for a hyperlink.
|
||||
menu.setHeaderTitle(result.getExtra());
|
||||
menu.add(0, ID_COPY_LINK, 0, context.getString(R.string.context_menu_copy_link)).setOnMenuItemClickListener(handler);
|
||||
menu.add(0, ID_COPY_LINK, 0, context.getString(R.string.copy_link_to_clipboard)).setOnMenuItemClickListener(handler);
|
||||
menu.add(0, ID_SHARE_LINK, 0, context.getString(R.string.context_menu_share_link)).setOnMenuItemClickListener(handler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
package com.github.dfa.diaspora_android.web;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.webkit.JsResult;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.ProgressBar;
|
||||
|
@ -65,20 +64,13 @@ public class DiasporaStreamWebChromeClient extends FileUploadWebChromeClient {
|
|||
ThemedAlertDialogBuilder builder = new ThemedAlertDialogBuilder(view.getContext(), AppSettings.get());
|
||||
builder.setTitle(view.getContext().getString(R.string.confirmation))
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.confirm();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
result.cancel();
|
||||
}
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel())
|
||||
.setOnCancelListener(dialog -> {
|
||||
result.cancel();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.create().show();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@ public class CustomTabsHelper {
|
|||
static final String BETA_PACKAGE = "com.chrome.beta";
|
||||
static final String DEV_PACKAGE = "com.chrome.dev";
|
||||
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
|
||||
static final String CHROMIUM = "org.chromium.chrome";
|
||||
static final String FENNEC = "org.mozilla.fennec_fdroid";
|
||||
static final String KLAR = "org.mozilla.klar";
|
||||
private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
|
||||
"android.support.customtabs.extra.KEEP_ALIVE";
|
||||
|
||||
|
@ -101,6 +104,12 @@ public class CustomTabsHelper {
|
|||
sPackageNameToUse = DEV_PACKAGE;
|
||||
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
|
||||
sPackageNameToUse = LOCAL_PACKAGE;
|
||||
} else if (packagesSupportingCustomTabs.contains(CHROMIUM)) {
|
||||
sPackageNameToUse = CHROMIUM;
|
||||
} else if (packagesSupportingCustomTabs.contains(FENNEC)) {
|
||||
sPackageNameToUse = FENNEC;
|
||||
} else if (packagesSupportingCustomTabs.contains(KLAR)) {
|
||||
sPackageNameToUse = KLAR;
|
||||
}
|
||||
return sPackageNameToUse;
|
||||
}
|
||||
|
@ -137,6 +146,6 @@ public class CustomTabsHelper {
|
|||
* @return All possible chrome package names that provide custom tabs feature.
|
||||
*/
|
||||
public static String[] getPackages() {
|
||||
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
|
||||
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE, CHROMIUM, FENNEC, KLAR};
|
||||
}
|
||||
}
|
||||
|
|
129
app/src/main/java/net/gsantner/opoc/activity/GsFragmentBase.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.gsantner.opoc.util.ContextUtils;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
/**
|
||||
* A common base fragment to extend from
|
||||
*/
|
||||
public abstract class GsFragmentBase extends Fragment {
|
||||
private boolean _fragmentFirstTimeVisible = true;
|
||||
private final Object _fragmentFirstTimeVisibleSync = new Object();
|
||||
|
||||
protected ContextUtils _cu;
|
||||
protected Bundle _savedInstanceState = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the fragments layout. Don't override this method, just supply the needed
|
||||
* {@link LayoutRes} via abstract method {@link #getLayoutResId()}, super does the rest
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
_cu = new ContextUtils(inflater.getContext());
|
||||
_cu.setAppLanguage(getAppLanguage());
|
||||
_savedInstanceState = savedInstanceState;
|
||||
View view = inflater.inflate(getLayoutResId(), container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.postDelayed(() -> {
|
||||
synchronized (_fragmentFirstTimeVisibleSync) {
|
||||
if (getUserVisibleHint() && isVisible() && _fragmentFirstTimeVisible) {
|
||||
_fragmentFirstTimeVisible = false;
|
||||
onFragmentFirstTimeVisible();
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tag from the fragment, allows faster distinction
|
||||
*
|
||||
* @return This fragments tag
|
||||
*/
|
||||
public abstract String getFragmentTag();
|
||||
|
||||
|
||||
/**
|
||||
* Get the layout to be inflated in the fragment
|
||||
*
|
||||
* @return Layout resource id
|
||||
*/
|
||||
@LayoutRes
|
||||
protected abstract int getLayoutResId();
|
||||
|
||||
/**
|
||||
* Event to be called when the back button was pressed
|
||||
* True should be returned when this was handled by the fragment
|
||||
* and no further handling in the view hierarchy is needed
|
||||
*
|
||||
* @return True if back handled by fragment
|
||||
*/
|
||||
public boolean onBackPressed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language to be used in this fragment
|
||||
* Defaults to resolve the language from sharedpreferences: pref_key__language
|
||||
*
|
||||
* @return Empty string for system language, or an android locale code
|
||||
*/
|
||||
public String getAppLanguage() {
|
||||
if (getContext() != null) {
|
||||
return getContext().getSharedPreferences("app", Context.MODE_PRIVATE)
|
||||
.getString("pref_key__language", "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be called when this fragment gets the first time visible
|
||||
*/
|
||||
public void onFragmentFirstTimeVisible() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||
super.setUserVisibleHint(isVisibleToUser);
|
||||
synchronized (_fragmentFirstTimeVisibleSync) {
|
||||
if (isVisibleToUser && _fragmentFirstTimeVisible) {
|
||||
_fragmentFirstTimeVisible = false;
|
||||
onFragmentFirstTimeVisible();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,15 @@
|
|||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Gregor Santner <gsantner.net> wrote this. You can do whatever you want
|
||||
* with it. If we meet some day, and you think it is worth it, you can buy me a
|
||||
* coke in return. Provided as is without any kind of warranty. Do not blame or
|
||||
* sue me if something goes wrong. No attribution required. - Gregor Santner
|
||||
/*#######################################################
|
||||
*
|
||||
* License: Creative Commons Zero (CC0 1.0)
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Get updates:
|
||||
* https://github.com/gsantner/onePieceOfCode/blob/master/java/SimpleMarkdownParser.java
|
||||
* Apply to TextView:
|
||||
* See https://github.com/gsantner/onePieceOfCode/blob/master/android/ContextUtils.java
|
||||
* Maintained by Gregor Santner, 2018-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
|
||||
/*
|
||||
* Parses most common markdown tags. Only inline tags are supported, multiline/block syntax
|
||||
* is not supported (citation, multiline code, ..). This is intended to stay as easy as possible.
|
||||
*
|
||||
|
@ -27,7 +22,7 @@
|
|||
* FILTER_WEB is intended to be used at engines understanding most common HTML tags.
|
||||
*/
|
||||
|
||||
package net.gsantner.opoc.util;
|
||||
package net.gsantner.opoc.format.markdown;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -38,7 +33,7 @@ import java.io.InputStreamReader;
|
|||
/**
|
||||
* Simple Markdown Parser
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "CaughtExceptionImmediatelyRethrown", "SameParameterValue", "unused", "SpellCheckingInspection", "RepeatedSpace", "SingleCharAlternation"})
|
||||
@SuppressWarnings({"WeakerAccess", "CaughtExceptionImmediatelyRethrown", "SameParameterValue", "unused", "SpellCheckingInspection", "RepeatedSpace", "SingleCharAlternation", "Convert2Lambda"})
|
||||
public class SimpleMarkdownParser {
|
||||
//########################
|
||||
//## Statics
|
||||
|
@ -129,6 +124,12 @@ public class SimpleMarkdownParser {
|
|||
return text;
|
||||
}
|
||||
};
|
||||
public final static SmpFilter FILTER_NONE = new SmpFilter() {
|
||||
@Override
|
||||
public String filter(String text) {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
//########################
|
||||
//## Singleton
|
|
@ -0,0 +1,48 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2018-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.preference;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection", "unused", "SameParameterValue"})
|
||||
public interface PropertyBackend<TKEY, TTHIS> {
|
||||
String getString(TKEY key, String defaultValue);
|
||||
|
||||
int getInt(TKEY key, int defaultValue);
|
||||
|
||||
long getLong(TKEY key, long defaultValue);
|
||||
|
||||
boolean getBool(TKEY key, boolean defaultValue);
|
||||
|
||||
float getFloat(TKEY key, float defaultValue);
|
||||
|
||||
double getDouble(TKEY key, double defaultValue);
|
||||
|
||||
List<Integer> getIntList(TKEY key);
|
||||
|
||||
List<String> getStringList(TKEY key);
|
||||
|
||||
TTHIS setString(TKEY key, String value);
|
||||
|
||||
TTHIS setInt(TKEY key, int value);
|
||||
|
||||
TTHIS setLong(TKEY key, long value);
|
||||
|
||||
TTHIS setBool(TKEY key, boolean value);
|
||||
|
||||
TTHIS setFloat(TKEY key, float value);
|
||||
|
||||
TTHIS setDouble(TKEY key, double value);
|
||||
|
||||
TTHIS setIntList(TKEY key, List<Integer> value);
|
||||
|
||||
TTHIS setStringList(TKEY key, List<String> value);
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Gregor Santner <gsantner.net> wrote this. You can do whatever you want
|
||||
* with it. If we meet some day, and you think it is worth it, you can buy me a
|
||||
* coke in return. Provided as is without any kind of warranty. Do not blame or
|
||||
* sue me if something goes wrong. No attribution required. - Gregor Santner
|
||||
/*#######################################################
|
||||
*
|
||||
* License: Creative Commons Zero (CC0 1.0)
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
* Maintained by Gregor Santner, 2016-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
|
||||
/*
|
||||
* This is a wrapper for settings based on SharedPreferences
|
||||
|
@ -16,33 +15,23 @@
|
|||
* getters/setters for the app's settings.
|
||||
* Example:
|
||||
public boolean isAppFirstStart(boolean doSet) {
|
||||
boolean value = getBool(prefApp, R.string.pref_key__app_first_start, true);
|
||||
int value = getInt(R.string.pref_key__app_first_start, -1);
|
||||
if (doSet) {
|
||||
setBool(prefApp, R.string.pref_key__app_first_start, false);
|
||||
setBool(true);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isAppCurrentVersionFirstStart(boolean doSet) {
|
||||
int value = getInt(prefApp, R.string.pref_key__app_first_start_current_version, -1);
|
||||
int value = getInt(R.string.pref_key__app_first_start_current_version, -1);
|
||||
if (doSet) {
|
||||
setInt(prefApp, R.string.pref_key__app_first_start_current_version, BuildConfig.VERSION_CODE);
|
||||
setInt(R.string.pref_key__app_first_start_current_version, BuildConfig.VERSION_CODE);
|
||||
}
|
||||
return value != BuildConfig.VERSION_CODE && !BuildConfig.IS_TEST_BUILD;
|
||||
}
|
||||
|
||||
* Maybe add a singleton for this:
|
||||
* Whereas App.get() is returning ApplicationContext
|
||||
private AppSettings(Context _context) {
|
||||
super(_context);
|
||||
}
|
||||
|
||||
public static AppSettings get() {
|
||||
return new AppSettings(App.get());
|
||||
return value != BuildConfig.VERSION_CODE;
|
||||
}
|
||||
*/
|
||||
|
||||
package net.gsantner.opoc.util;
|
||||
package net.gsantner.opoc.preference;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
@ -59,36 +48,36 @@ import java.util.List;
|
|||
|
||||
|
||||
/**
|
||||
* Wrapper for settings based on SharedPreferences with keys in resources
|
||||
* Wrapper for settings based on SharedPreferences, optionally with keys in resources
|
||||
* Default SharedPreference (_prefApp) will be taken if no SP is specified, else the first one
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SpellCheckingInspection", "SameParameterValue"})
|
||||
public class AppSettingsBase {
|
||||
public class SharedPreferencesPropertyBackend implements PropertyBackend<String, SharedPreferencesPropertyBackend> {
|
||||
protected static final String ARRAY_SEPARATOR = "%%%";
|
||||
protected static final String ARRAY_SEPARATOR_SUBSTITUTE = "§§§";
|
||||
public static final String SHARED_PREF_APP = "app";
|
||||
|
||||
//########################
|
||||
//## Members, Constructors
|
||||
//########################
|
||||
//
|
||||
// Members, Constructors
|
||||
//
|
||||
protected final SharedPreferences _prefApp;
|
||||
protected final String _prefAppName;
|
||||
protected final Context _context;
|
||||
|
||||
public AppSettingsBase(final Context context) {
|
||||
public SharedPreferencesPropertyBackend(final Context context) {
|
||||
this(context, SHARED_PREF_APP);
|
||||
}
|
||||
|
||||
public AppSettingsBase(final Context context, final String prefAppName) {
|
||||
public SharedPreferencesPropertyBackend(final Context context, final String prefAppName) {
|
||||
_context = context.getApplicationContext();
|
||||
_prefAppName = TextUtils.isEmpty(prefAppName) ?
|
||||
_context.getPackageName() + "_preferences" : prefAppName;
|
||||
_prefApp = _context.getSharedPreferences(_prefAppName, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
//#####################
|
||||
//## Methods
|
||||
//#####################
|
||||
//
|
||||
// Methods
|
||||
//
|
||||
public Context getContext() {
|
||||
return _context;
|
||||
}
|
||||
|
@ -147,9 +136,25 @@ public class AppSettingsBase {
|
|||
return (pref != null && pref.length > 0 ? pref[0] : _prefApp);
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## Getter for resources
|
||||
//#################################
|
||||
|
||||
public static void limitListTo(final List<?> list, int maxSize, boolean removeDuplicates) {
|
||||
Object o;
|
||||
int pos;
|
||||
|
||||
for (int i = 0; removeDuplicates && i < list.size(); i++) {
|
||||
o = list.get(i);
|
||||
while ((pos = list.lastIndexOf(o)) != i && pos >= 0) {
|
||||
list.remove(pos);
|
||||
}
|
||||
}
|
||||
while ((pos = list.size()) > maxSize && pos > 0) {
|
||||
list.remove(list.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Getter for resources
|
||||
//
|
||||
public String rstr(@StringRes int stringKeyResourceId) {
|
||||
return _context.getString(stringKeyResourceId);
|
||||
}
|
||||
|
@ -159,9 +164,9 @@ public class AppSettingsBase {
|
|||
}
|
||||
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for String
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for String
|
||||
//
|
||||
public void setString(@StringRes int keyResourceId, String value, final SharedPreferences... pref) {
|
||||
gp(pref).edit().putString(rstr(keyResourceId), value).apply();
|
||||
}
|
||||
|
@ -190,36 +195,33 @@ public class AppSettingsBase {
|
|||
return gp(pref).getString(rstr(keyResourceId), rstr(keyResourceIdDefaultValue));
|
||||
}
|
||||
|
||||
public void setStringArray(@StringRes int keyResourceId, Object[] values, final SharedPreferences... pref) {
|
||||
setStringArray(rstr(keyResourceId), values, gp(pref));
|
||||
}
|
||||
|
||||
public void setStringArray(String key, Object[] values, final SharedPreferences... pref) {
|
||||
setStringArray(key, values, gp(pref));
|
||||
}
|
||||
|
||||
private void setStringArray(String key, Object[] values, final SharedPreferences pref) {
|
||||
private void setStringListOne(String key, List<String> values, final SharedPreferences pref) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object value : values) {
|
||||
for (String value : values) {
|
||||
sb.append(ARRAY_SEPARATOR);
|
||||
sb.append(value.toString().replace(ARRAY_SEPARATOR, ARRAY_SEPARATOR_SUBSTITUTE));
|
||||
sb.append(value.replace(ARRAY_SEPARATOR, ARRAY_SEPARATOR_SUBSTITUTE));
|
||||
}
|
||||
setString(key, sb.toString().replaceFirst(ARRAY_SEPARATOR, ""), pref);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String[] getStringArray(@StringRes int keyResourceId, final SharedPreferences... pref) {
|
||||
return getStringArray(rstr(keyResourceId), gp(pref));
|
||||
}
|
||||
|
||||
private String[] getStringArray(String key, final SharedPreferences... pref) {
|
||||
String value = gp(pref)
|
||||
private ArrayList<String> getStringListOne(String key, final SharedPreferences pref) {
|
||||
ArrayList<String> ret = new ArrayList<>();
|
||||
String value = pref
|
||||
.getString(key, ARRAY_SEPARATOR)
|
||||
.replace(ARRAY_SEPARATOR_SUBSTITUTE, ARRAY_SEPARATOR);
|
||||
if (value.equals(ARRAY_SEPARATOR)) {
|
||||
return new String[0];
|
||||
if (value.equals(ARRAY_SEPARATOR) || TextUtils.isEmpty(value)) {
|
||||
return ret;
|
||||
}
|
||||
return value.split(ARRAY_SEPARATOR);
|
||||
ret.addAll(Arrays.asList(value.split(ARRAY_SEPARATOR)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void setStringArray(@StringRes int keyResourceId, String[] values, final SharedPreferences... pref) {
|
||||
setStringArray(rstr(keyResourceId), values, pref);
|
||||
}
|
||||
|
||||
public void setStringArray(String key, String[] values, final SharedPreferences... pref) {
|
||||
setStringListOne(key, Arrays.asList(values), gp(pref));
|
||||
}
|
||||
|
||||
public void setStringList(@StringRes int keyResourceId, List<String> values, final SharedPreferences... pref) {
|
||||
|
@ -230,17 +232,28 @@ public class AppSettingsBase {
|
|||
setStringArray(key, values.toArray(new String[values.size()]), pref);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String[] getStringArray(@StringRes int keyResourceId, final SharedPreferences... pref) {
|
||||
return getStringArray(rstr(keyResourceId), pref);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String[] getStringArray(String key, final SharedPreferences... pref) {
|
||||
List<String> list = getStringListOne(key, gp(pref));
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
public ArrayList<String> getStringList(@StringRes int keyResourceId, final SharedPreferences... pref) {
|
||||
return new ArrayList<>(Arrays.asList(getStringArray(rstr(keyResourceId), gp(pref))));
|
||||
return getStringListOne(rstr(keyResourceId), gp(pref));
|
||||
}
|
||||
|
||||
public ArrayList<String> getStringList(String key, final SharedPreferences... pref) {
|
||||
return new ArrayList<>(Arrays.asList(getStringArray(key, gp(pref))));
|
||||
return getStringListOne(key, gp(pref));
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for integer
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for integer
|
||||
//
|
||||
public void setInt(@StringRes int keyResourceId, int value, final SharedPreferences... pref) {
|
||||
gp(pref).edit().putInt(rstr(keyResourceId), value).apply();
|
||||
}
|
||||
|
@ -266,62 +279,65 @@ public class AppSettingsBase {
|
|||
return Integer.valueOf(strNum);
|
||||
}
|
||||
|
||||
|
||||
public void setIntArray(@StringRes int keyResourceId, Object[] values, final SharedPreferences... pref) {
|
||||
setIntArray(rstr(keyResourceId), values, gp(pref));
|
||||
}
|
||||
|
||||
public void setIntArray(String key, Object[] values, final SharedPreferences... pref) {
|
||||
setIntArray(key, values, gp(pref));
|
||||
}
|
||||
|
||||
private void setIntArray(String key, Object[] values, final SharedPreferences pref) {
|
||||
private void setIntListOne(String key, List<Integer> values, final SharedPreferences pref) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object value : values) {
|
||||
for (Integer value : values) {
|
||||
sb.append(ARRAY_SEPARATOR);
|
||||
sb.append(value.toString());
|
||||
}
|
||||
setString(key, sb.toString().replaceFirst(ARRAY_SEPARATOR, ""), pref);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Integer[] getIntArray(@StringRes int keyResourceId, final SharedPreferences... pref) {
|
||||
return getIntArray(rstr(keyResourceId), gp(pref));
|
||||
}
|
||||
|
||||
private Integer[] getIntArray(String key, final SharedPreferences... pref) {
|
||||
String value = gp(pref).getString(key, ARRAY_SEPARATOR);
|
||||
private ArrayList<Integer> getIntListOne(String key, final SharedPreferences pref) {
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
String value = pref.getString(key, ARRAY_SEPARATOR);
|
||||
if (value.equals(ARRAY_SEPARATOR)) {
|
||||
return new Integer[0];
|
||||
return ret;
|
||||
}
|
||||
String[] split = value.split(ARRAY_SEPARATOR);
|
||||
Integer[] ret = new Integer[split.length];
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = Integer.parseInt(split[i]);
|
||||
for (String s : value.split(ARRAY_SEPARATOR)) {
|
||||
ret.add(Integer.parseInt(s));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void setIntArray(@StringRes int keyResourceId, Integer[] values, final SharedPreferences... pref) {
|
||||
setIntArray(rstr(keyResourceId), values, gp(pref));
|
||||
}
|
||||
|
||||
public void setIntArray(String key, Integer[] values, final SharedPreferences... pref) {
|
||||
setIntListOne(key, Arrays.asList(values), gp(pref));
|
||||
}
|
||||
|
||||
public Integer[] getIntArray(@StringRes int keyResourceId, final SharedPreferences... pref) {
|
||||
return getIntArray(rstr(keyResourceId), gp(pref));
|
||||
}
|
||||
|
||||
public Integer[] getIntArray(String key, final SharedPreferences... pref) {
|
||||
List<Integer> data = getIntListOne(key, gp(pref));
|
||||
return data.toArray(new Integer[data.size()]);
|
||||
}
|
||||
|
||||
|
||||
public void setIntList(@StringRes int keyResourceId, List<Integer> values, final SharedPreferences... pref) {
|
||||
setIntArray(rstr(keyResourceId), values.toArray(new Integer[values.size()]), pref);
|
||||
setIntListOne(rstr(keyResourceId), values, gp(pref));
|
||||
}
|
||||
|
||||
public void setIntList(String key, List<Integer> values, final SharedPreferences... pref) {
|
||||
setIntArray(key, values.toArray(new Integer[values.size()]), pref);
|
||||
setIntListOne(key, values, gp(pref));
|
||||
}
|
||||
|
||||
public ArrayList<Integer> getIntList(@StringRes int keyResourceId, final SharedPreferences... pref) {
|
||||
return new ArrayList<>(Arrays.asList(getIntArray(rstr(keyResourceId), gp(pref))));
|
||||
return getIntListOne(rstr(keyResourceId), gp(pref));
|
||||
}
|
||||
|
||||
public ArrayList<Integer> getIntList(String key, final SharedPreferences... pref) {
|
||||
return new ArrayList<>(Arrays.asList(getIntArray(key, gp(pref))));
|
||||
return getIntListOne(key, gp(pref));
|
||||
}
|
||||
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for Long
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for Long
|
||||
//
|
||||
public void setLong(@StringRes int keyResourceId, long value, final SharedPreferences... pref) {
|
||||
gp(pref).edit().putLong(rstr(keyResourceId), value).apply();
|
||||
}
|
||||
|
@ -338,9 +354,9 @@ public class AppSettingsBase {
|
|||
return gp(pref).getLong(key, defaultValue);
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for Float
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for Float
|
||||
//
|
||||
public void setFloat(@StringRes int keyResourceId, float value, final SharedPreferences... pref) {
|
||||
gp(pref).edit().putFloat(rstr(keyResourceId), value).apply();
|
||||
}
|
||||
|
@ -357,9 +373,9 @@ public class AppSettingsBase {
|
|||
return gp(pref).getFloat(key, defaultValue);
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for Double
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for Double
|
||||
//
|
||||
public void setDouble(@StringRes int keyResourceId, double value, final SharedPreferences... pref) {
|
||||
setLong(rstr(keyResourceId), Double.doubleToRawLongBits(value));
|
||||
}
|
||||
|
@ -376,9 +392,9 @@ public class AppSettingsBase {
|
|||
return Double.longBitsToDouble(getLong(key, Double.doubleToRawLongBits(defaultValue), gp(pref)));
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for boolean
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for boolean
|
||||
//
|
||||
public void setBool(@StringRes int keyResourceId, boolean value, final SharedPreferences... pref) {
|
||||
gp(pref).edit().putBoolean(rstr(keyResourceId), value).apply();
|
||||
}
|
||||
|
@ -395,9 +411,9 @@ public class AppSettingsBase {
|
|||
return gp(pref).getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
//#################################
|
||||
//## Getter & Setter for Color
|
||||
//#################################
|
||||
//
|
||||
// Getter & Setter for Color
|
||||
//
|
||||
public int getColor(String key, @ColorRes int defaultColor, final SharedPreferences... pref) {
|
||||
return gp(pref).getInt(key, rcolor(defaultColor));
|
||||
}
|
||||
|
@ -405,4 +421,95 @@ public class AppSettingsBase {
|
|||
public int getColor(@StringRes int keyResourceId, @ColorRes int defaultColor, final SharedPreferences... pref) {
|
||||
return gp(pref).getInt(rstr(keyResourceId), rcolor(defaultColor));
|
||||
}
|
||||
|
||||
//
|
||||
// PropertyBackend<String> implementations
|
||||
//
|
||||
@Override
|
||||
public String getString(String key, String defaultValue) {
|
||||
return getString(key, defaultValue, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String key, int defaultValue) {
|
||||
return getInt(key, defaultValue, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defaultValue) {
|
||||
return getLong(key, defaultValue, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBool(String key, boolean defaultValue) {
|
||||
return getBool(key, defaultValue, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String key, float defaultValue) {
|
||||
return getFloat(key, defaultValue, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(String key, double defaultValue) {
|
||||
return getDouble(key, defaultValue, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<Integer> getIntList(String key) {
|
||||
return getIntList(key, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<String> getStringList(String key) {
|
||||
return getStringList(key, _prefApp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setString(String key, String value) {
|
||||
setString(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setInt(String key, int value) {
|
||||
setInt(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setLong(String key, long value) {
|
||||
setLong(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setBool(String key, boolean value) {
|
||||
setBool(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setFloat(String key, float value) {
|
||||
setFloat(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setDouble(String key, double value) {
|
||||
setDouble(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setIntList(String key, List<Integer> value) {
|
||||
setIntListOne(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferencesPropertyBackend setStringList(String key, List<String> value) {
|
||||
setStringListOne(key, value, _prefApp);
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,26 +1,25 @@
|
|||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Gregor Santner <gsantner.net> wrote this. You can do whatever you want
|
||||
* with it. If we meet some day, and you think it is worth it, you can buy me a
|
||||
* coke in return. Provided as is without any kind of warranty. Do not blame or
|
||||
* sue me if something goes wrong. No attribution required. - Gregor Santner
|
||||
/*#######################################################
|
||||
*
|
||||
* License: Creative Commons Zero (CC0 1.0)
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
|
||||
/*
|
||||
* A ListPreference that displays a list of available languages
|
||||
* Requires:
|
||||
* The BuildConfig field "APPLICATION_LANGUAGES" which is a array of all available languages
|
||||
* opoc/ContextUtils
|
||||
The BuildConfig field "APPLICATION_LANGUAGES" which is a array of all available languages
|
||||
opoc/ContextUtils
|
||||
* BuildConfig field can be defined by using the method below
|
||||
|
||||
buildConfigField("String[]", "APPLICATION_LANGUAGES", '{' + getUsedAndroidLanguages().collect {"\"${it}\""}.join(",") + '}')
|
||||
buildConfigField "String[]", "APPLICATION_LANGUAGES", "${getUsedAndroidLanguages()}"
|
||||
|
||||
@SuppressWarnings(["UnnecessaryQualifiedReference", "SpellCheckingInspection", "GroovyUnusedDeclaration"])
|
||||
static String[] getUsedAndroidLanguages() {
|
||||
// Returns used android languages as a buildConfig array: {'de', 'it', ..}"
|
||||
static String getUsedAndroidLanguages() {
|
||||
Set<String> langs = new HashSet<>()
|
||||
new File('.').eachFileRecurse(groovy.io.FileType.DIRECTORIES) {
|
||||
final foldername = it.name
|
||||
|
@ -32,19 +31,19 @@ static String[] getUsedAndroidLanguages() {
|
|||
}
|
||||
}
|
||||
}
|
||||
return langs.toArray(new String[langs.size()])
|
||||
return '{' + langs.collect { "\"${it}\"" }.join(",") + '}'
|
||||
}
|
||||
|
||||
* Summary: Change language of this app. Restart app for changes to take effect
|
||||
|
||||
* Define element in Preferences-XML:
|
||||
<net.gsantner.opoc.ui.LanguagePreference
|
||||
<net.gsantner.opoc.preference.LanguagePreference
|
||||
android:icon="@drawable/ic_language_black_24dp"
|
||||
android:key="@string/pref_key__language"
|
||||
android:summary="@string/pref_desc__language"
|
||||
android:title="@string/pref_title__language"/>
|
||||
*/
|
||||
package net.gsantner.opoc.ui;
|
||||
package net.gsantner.opoc.preference.nonsupport;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
|
@ -69,7 +68,7 @@ public class LanguagePreference extends ListPreference {
|
|||
private static final String SYSTEM_LANGUAGE_CODE = "";
|
||||
|
||||
// The language of res/values/ -> (usually English)
|
||||
public String _systemLanguageName = "★System★";
|
||||
public String _systemLanguageName = "System";
|
||||
public String _defaultLanguageCode = "en";
|
||||
|
||||
public LanguagePreference(Context context) {
|
||||
|
@ -95,7 +94,7 @@ public class LanguagePreference extends ListPreference {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected boolean callChangeListener(Object newValue) {
|
||||
public boolean callChangeListener(Object newValue) {
|
||||
if (newValue instanceof String) {
|
||||
// Does not apply to existing UI, use recreate()
|
||||
new ContextUtils(getContext()).setAppLanguage((String) newValue);
|
||||
|
@ -114,11 +113,11 @@ public class LanguagePreference extends ListPreference {
|
|||
// Fetch readable details
|
||||
ContextUtils contextUtils = new ContextUtils(context);
|
||||
List<String> languages = new ArrayList<>();
|
||||
Object bcof = contextUtils.getBuildConfigValue("APPLICATION_LANGUAGES");
|
||||
Object bcof = contextUtils.getBuildConfigValue("DETECTED_ANDROID_LOCALES");
|
||||
if (bcof instanceof String[]) {
|
||||
for (String langId : (String[]) bcof) {
|
||||
Locale locale = contextUtils.getLocaleByAndroidCode(langId);
|
||||
languages.add(summarizeLocale(locale) + ";" + langId);
|
||||
languages.add(summarizeLocale(locale, langId) + ";" + langId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,9 +132,9 @@ public class LanguagePreference extends ListPreference {
|
|||
entryval[i + 2] = languages.get(i).split(";")[1];
|
||||
}
|
||||
entryval[0] = SYSTEM_LANGUAGE_CODE;
|
||||
entries[0] = _systemLanguageName + "\n[" + summarizeLocale(context.getResources().getConfiguration().locale) + "]";
|
||||
entries[0] = _systemLanguageName + " » " + summarizeLocale(context.getResources().getConfiguration().locale, "");
|
||||
entryval[1] = _defaultLanguageCode;
|
||||
entries[1] = summarizeLocale(contextUtils.getLocaleByAndroidCode(_defaultLanguageCode));
|
||||
entries[1] = summarizeLocale(contextUtils.getLocaleByAndroidCode(_defaultLanguageCode), _defaultLanguageCode);
|
||||
|
||||
setEntries(entries);
|
||||
setEntryValues(entryval);
|
||||
|
@ -143,13 +142,21 @@ public class LanguagePreference extends ListPreference {
|
|||
|
||||
// Concat english and localized language name
|
||||
// Append country if country specific (e.g. Portuguese Brazil)
|
||||
private String summarizeLocale(Locale locale) {
|
||||
private String summarizeLocale(final Locale locale, final String localeAndroidCode) {
|
||||
String country = locale.getDisplayCountry(locale);
|
||||
String language = locale.getDisplayLanguage(locale);
|
||||
return locale.getDisplayLanguage(Locale.ENGLISH)
|
||||
String ret = locale.getDisplayLanguage(Locale.ENGLISH)
|
||||
+ " (" + language.substring(0, 1).toUpperCase(Locale.getDefault()) + language.substring(1)
|
||||
+ ((!country.isEmpty() && !country.toLowerCase(Locale.getDefault()).equals(language.toLowerCase(Locale.getDefault()))) ? (", " + country) : "")
|
||||
+ ")";
|
||||
|
||||
if (localeAndroidCode.equals("zh-rCN")) {
|
||||
ret = ret.substring(0, ret.indexOf(" ") + 1) + "Simplified" + ret.substring(ret.indexOf(" "));
|
||||
} else if (localeAndroidCode.equals("zh-rTW")) {
|
||||
ret = ret.substring(0, ret.indexOf(" ") + 1) + "Traditional" + ret.substring(ret.indexOf(" "));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Add current language to summary
|
||||
|
@ -158,7 +165,7 @@ public class LanguagePreference extends ListPreference {
|
|||
Locale locale = new ContextUtils(getContext()).getLocaleByAndroidCode(getValue());
|
||||
String prefix = TextUtils.isEmpty(super.getSummary())
|
||||
? "" : super.getSummary() + "\n\n";
|
||||
return prefix + summarizeLocale(locale);
|
||||
return prefix + summarizeLocale(locale, getValue());
|
||||
}
|
||||
|
||||
public String getSystemLanguageName() {
|
|
@ -0,0 +1,189 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Filter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.gsantner.opoc.util.Callback;
|
||||
import net.gsantner.opoc.util.ContextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SearchOrCustomTextDialog {
|
||||
|
||||
public static class DialogOptions {
|
||||
public Callback.a1<String> callback;
|
||||
public List<? extends CharSequence> data = new ArrayList<>();
|
||||
public List<? extends CharSequence> highlightData = new ArrayList<>();
|
||||
public String messageText = "";
|
||||
public boolean isSearchEnabled = true;
|
||||
public boolean isDarkDialog = false;
|
||||
|
||||
@ColorInt
|
||||
public int textColor = 0xFF000000;
|
||||
@ColorInt
|
||||
public int highlightColor = 0xFF00FF00;
|
||||
@StringRes
|
||||
public int cancelButtonText = android.R.string.cancel;
|
||||
@StringRes
|
||||
public int okButtonText = android.R.string.ok;
|
||||
@StringRes
|
||||
public int titleText = android.R.string.untitled;
|
||||
@StringRes
|
||||
public int searchHintText = android.R.string.search_go;
|
||||
}
|
||||
|
||||
public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activity, final DialogOptions dopt) {
|
||||
final List<CharSequence> allItems = new ArrayList<>(dopt.data);
|
||||
final List<CharSequence> filteredItems = new ArrayList<>(allItems);
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, dopt.isDarkDialog
|
||||
? android.support.v7.appcompat.R.style.Theme_AppCompat_Dialog
|
||||
: android.support.v7.appcompat.R.style.Theme_AppCompat_Light_Dialog
|
||||
);
|
||||
|
||||
final ArrayAdapter<CharSequence> listAdapter = new ArrayAdapter<CharSequence>(activity, android.R.layout.simple_list_item_1, filteredItems) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int pos, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
TextView textView = (TextView) super.getView(pos, convertView, parent);
|
||||
String text = textView.getText().toString();
|
||||
|
||||
textView.setTextColor(dopt.textColor);
|
||||
if (dopt.highlightData.contains(text)) {
|
||||
textView.setTextColor(dopt.highlightColor);
|
||||
}
|
||||
return textView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return new Filter() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
||||
filteredItems.clear();
|
||||
filteredItems.addAll((List<String>) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(final CharSequence constraint) {
|
||||
final FilterResults res = new FilterResults();
|
||||
final ArrayList<CharSequence> resList = new ArrayList<>();
|
||||
final String fil = constraint.toString();
|
||||
|
||||
for (final CharSequence str : allItems) {
|
||||
if ("".equals(fil) || str.toString().toLowerCase(Locale.getDefault()).contains(fil.toLowerCase(Locale.getDefault()))) {
|
||||
resList.add(str);
|
||||
}
|
||||
}
|
||||
res.values = resList;
|
||||
res.count = resList.size();
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
||||
searchEditText.setSingleLine(true);
|
||||
searchEditText.setMaxLines(1);
|
||||
searchEditText.setTextColor(dopt.textColor);
|
||||
searchEditText.setHintTextColor((dopt.textColor & 0x00FFFFFF) | 0x99000000);
|
||||
searchEditText.setHint(dopt.searchHintText);
|
||||
|
||||
searchEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(final Editable arg0) {
|
||||
listAdapter.getFilter().filter(searchEditText.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence arg0, final int arg1, final int arg2, final int arg3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence arg0, final int arg1, final int arg2, final int arg3) {
|
||||
}
|
||||
});
|
||||
|
||||
final ListView listView = new ListView(activity);
|
||||
final LinearLayout linearLayout = new LinearLayout(activity);
|
||||
listView.setAdapter(listAdapter);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
if (dopt.isSearchEnabled) {
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
ContextUtils cu = new net.gsantner.opoc.util.ContextUtils(listView.getContext());
|
||||
int px = (int) (new net.gsantner.opoc.util.ContextUtils(listView.getContext()).convertDpToPx(8));
|
||||
lp.setMargins(px, px / 2, px, px / 2);
|
||||
linearLayout.addView(searchEditText, lp);
|
||||
}
|
||||
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
|
||||
layoutParams.weight = 1;
|
||||
linearLayout.addView(listView, layoutParams);
|
||||
if (!TextUtils.isEmpty(dopt.messageText)) {
|
||||
dialogBuilder.setMessage(dopt.messageText);
|
||||
}
|
||||
dialogBuilder.setView(linearLayout)
|
||||
.setTitle(dopt.titleText)
|
||||
.setOnCancelListener(null)
|
||||
.setNegativeButton(dopt.cancelButtonText, null);
|
||||
if (dopt.isSearchEnabled) {
|
||||
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
||||
dialogInterface.dismiss();
|
||||
if (dopt.callback != null && !TextUtils.isEmpty(searchEditText.getText().toString())) {
|
||||
dopt.callback.callback(searchEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final AlertDialog dialog = dialogBuilder.create();
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
dialog.dismiss();
|
||||
if (dopt.callback != null) {
|
||||
dopt.callback.callback(filteredItems.get(position).toString());
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnKeyListener((keyView, keyCode, keyEvent) -> {
|
||||
if ((keyEvent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
dialog.dismiss();
|
||||
if (dopt.callback != null && !TextUtils.isEmpty(searchEditText.getText().toString())) {
|
||||
dopt.callback.callback(searchEditText.getText().toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Gregor Santner <gsantner.net> wrote this. You can do whatever you want
|
||||
* with it. If we meet some day, and you think it is worth it, you can buy me a
|
||||
* coke in return. Provided as is without any kind of warranty. Do not blame or
|
||||
* sue me if something goes wrong. No attribution required. - Gregor Santner
|
||||
/*#######################################################
|
||||
*
|
||||
* License: Creative Commons Zero (CC0 1.0)
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
* Maintained by Gregor Santner, 2016-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import android.app.Activity;
|
||||
|
@ -19,6 +18,7 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.AppCompatTextView;
|
||||
import android.text.Html;
|
||||
|
@ -28,6 +28,7 @@ import android.util.TypedValue;
|
|||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.WebView;
|
||||
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection"})
|
||||
|
@ -93,16 +94,16 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
}
|
||||
|
||||
public void hideSoftKeyboard() {
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (_activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||
inputMethodManager.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0);
|
||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||
imm.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void showSoftKeyboard() {
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (_activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||
inputMethodManager.showSoftInput(_activity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);
|
||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||
imm.showSoftInput(_activity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,6 +127,16 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
dialog.show();
|
||||
}
|
||||
|
||||
public void showDialogWithRawFileInWebView(String fileInRaw, @StringRes int resTitleId) {
|
||||
WebView wv = new WebView(_context);
|
||||
wv.loadUrl("file:///android_res/raw/" + fileInRaw);
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(_context)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setTitle(resTitleId)
|
||||
.setView(wv);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// Toggle with no param, else set visibility according to first bool
|
||||
public void toggleStatusbarVisibility(boolean... optionalForceVisible) {
|
||||
WindowManager.LayoutParams attrs = _activity.getWindow().getAttributes();
|
||||
|
@ -140,7 +151,7 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
_activity.getWindow().setAttributes(attrs);
|
||||
}
|
||||
|
||||
public void showRateOnGplayDialog() {
|
||||
public void showGooglePlayEntryForThisApp() {
|
||||
String pkgId = "details?id=" + _activity.getPackageName();
|
||||
Intent goToMarket = new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + pkgId));
|
||||
goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
|
||||
|
@ -153,4 +164,14 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
|||
Uri.parse("http://play.google.com/store/apps/" + pkgId)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatusbarColor(int color, boolean... fromRes) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (fromRes != null && fromRes.length > 0 && fromRes[0]) {
|
||||
color = ContextCompat.getColor(_context, color);
|
||||
}
|
||||
|
||||
_activity.getWindow().setStatusBarColor(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
/*
|
||||
* ---------------------------------------------------------------------------- *
|
||||
* Gregor Santner <gsantner.net> wrote this file. You can do whatever
|
||||
* you want with this stuff. If we meet some day, and you think this stuff is
|
||||
* worth it, you can buy me a coke in return. Provided as is without any kind
|
||||
* of warranty. No attribution required. - Gregor Santner
|
||||
/*#######################################################
|
||||
*
|
||||
* License of this file: Creative Commons Zero (CC0 1.0)
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
|
||||
/*
|
||||
* Place adblock hosts file in raw: src/main/res/raw/adblock_domains__xyz.txt
|
||||
|
|
34
app/src/main/java/net/gsantner/opoc/util/Callback.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2018-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Callback {
|
||||
public interface a1<A> {
|
||||
void callback(A arg1);
|
||||
}
|
||||
|
||||
public interface a2<A, B> {
|
||||
void callback(A arg1, B arg2);
|
||||
}
|
||||
|
||||
public interface a3<A, B, C> {
|
||||
void callback(A arg1, B arg2, C arg3);
|
||||
}
|
||||
|
||||
public interface a4<A, B, C, D> {
|
||||
void callback(A arg1, B arg2, C arg3, D arg4);
|
||||
}
|
||||
|
||||
public interface a5<A, B, C, D, E> {
|
||||
void callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||
}
|
||||
}
|
|
@ -1,26 +1,24 @@
|
|||
/*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Gregor Santner <gsantner.net> wrote this. You can do whatever you want
|
||||
* with it. If we meet some day, and you think it is worth it, you can buy me a
|
||||
* coke in return. Provided as is without any kind of warranty. Do not blame or
|
||||
* sue me if something goes wrong. No attribution required. - Gregor Santner
|
||||
/*#######################################################
|
||||
*
|
||||
* License: Creative Commons Zero (CC0 1.0)
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
* Maintained by Gregor Santner, 2016-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
|
@ -29,11 +27,12 @@ import android.graphics.Canvas;
|
|||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
|
@ -47,21 +46,19 @@ import android.support.annotation.StringRes;
|
|||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.AppCompatButton;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.gsantner.opoc.format.markdown.SimpleMarkdownParser;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
|
@ -72,13 +69,14 @@ import java.io.InputStreamReader;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.Locale;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.graphics.Bitmap.CompressFormat;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation", "ObsoleteSdkInt", "ConstantConditions", "UnusedReturnValue"})
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "ObsoleteSdkInt", "deprecation", "SpellCheckingInspection"})
|
||||
public class ContextUtils {
|
||||
//########################
|
||||
//## Members, Constructors
|
||||
//########################
|
||||
//
|
||||
// Members, Constructors
|
||||
//
|
||||
protected Context _context;
|
||||
|
||||
public ContextUtils(Context context) {
|
||||
|
@ -89,58 +87,87 @@ public class ContextUtils {
|
|||
return _context;
|
||||
}
|
||||
|
||||
//########################
|
||||
//## Resources
|
||||
//########################
|
||||
static class ResType {
|
||||
public static final String DRAWABLE = "drawable";
|
||||
public static final String STRING = "string";
|
||||
public static final String PLURAL = "plural";
|
||||
public static final String COLOR = "color";
|
||||
public static final String STYLE = "style";
|
||||
public static final String ARRAY = "array";
|
||||
public static final String DIMEN = "dimen";
|
||||
public static final String MENU = "menu";
|
||||
public static final String RAW = "raw";
|
||||
|
||||
//
|
||||
// Class Methods
|
||||
//
|
||||
public enum ResType {
|
||||
ID, BOOL, INTEGER, COLOR, STRING, ARRAY, DRAWABLE, PLURALS,
|
||||
ANIM, ATTR, DIMEN, LAYOUT, MENU, RAW, STYLE, XML,
|
||||
}
|
||||
|
||||
public String str(@StringRes int strResId) {
|
||||
/**
|
||||
* Find out the nuermical ressource id by given {@link ResType}
|
||||
*
|
||||
* @return A valid id if the id could be found, else 0
|
||||
*/
|
||||
public int getResId(ResType resType, final String name) {
|
||||
return _context.getResources().getIdentifier(name, resType.name().toLowerCase(), _context.getPackageName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get String by given string ressource id (nuermic)
|
||||
*/
|
||||
public String rstr(@StringRes int strResId) {
|
||||
return _context.getString(strResId);
|
||||
}
|
||||
|
||||
public Drawable drawable(@DrawableRes int resId) {
|
||||
/**
|
||||
* Get String by given string ressource identifier (textual)
|
||||
*/
|
||||
public String rstr(String strResKey) {
|
||||
try {
|
||||
return rstr(getResId(ResType.STRING, strResKey));
|
||||
} catch (Resources.NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get drawable from given ressource identifier
|
||||
*/
|
||||
public Drawable rdrawable(@DrawableRes int resId) {
|
||||
return ContextCompat.getDrawable(_context, resId);
|
||||
}
|
||||
|
||||
public int color(@ColorRes int resId) {
|
||||
/**
|
||||
* Get color by given color ressource id
|
||||
*/
|
||||
public int rcolor(@ColorRes int resId) {
|
||||
return ContextCompat.getColor(_context, resId);
|
||||
}
|
||||
|
||||
public int getResId(final String type, final String name) {
|
||||
return _context.getResources().getIdentifier(name, type, _context.getPackageName());
|
||||
}
|
||||
|
||||
public boolean areResIdsAvailable(final String type, final String... names) {
|
||||
for (String name : names) {
|
||||
if (getResId(type, name) == 0) {
|
||||
/**
|
||||
* Checks if all given (textual) ressource ids are available
|
||||
*
|
||||
* @param resType A {@link ResType}
|
||||
* @param resIdsTextual A (textual) identifier to be awaited at R.restype.resIdsTextual
|
||||
* @return True if all given ids are available
|
||||
*/
|
||||
public boolean areRessourcesAvailable(final ResType resType, final String... resIdsTextual) {
|
||||
for (String name : resIdsTextual) {
|
||||
if (getResId(resType, name) == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//########################
|
||||
//## Methods
|
||||
//########################
|
||||
|
||||
public String colorToHexString(int intColor) {
|
||||
return String.format("#%06X", 0xFFFFFF & intColor);
|
||||
/**
|
||||
* Convert an int color to a hex string. Optionally including alpha value.
|
||||
*
|
||||
* @param intColor The color coded in int
|
||||
* @param withAlpha Optional; Set first bool parameter to true to also include alpha value
|
||||
*/
|
||||
public String colorToHexString(int intColor, boolean... withAlpha) {
|
||||
boolean a = withAlpha != null && withAlpha.length >= 1 && withAlpha[0];
|
||||
return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor);
|
||||
}
|
||||
|
||||
public String getAppVersionName() {
|
||||
try {
|
||||
PackageManager manager = _context.getPackageManager();
|
||||
PackageInfo info = manager.getPackageInfo(_context.getPackageName(), 0);
|
||||
PackageInfo info = manager.getPackageInfo(getPackageName(), 0);
|
||||
return info.versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -148,22 +175,74 @@ public class ContextUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public void openWebpageInExternalBrowser(final String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
_context.startActivity(intent);
|
||||
public String getAppInstallationSource() {
|
||||
String src = null;
|
||||
try {
|
||||
src = _context.getPackageManager().getInstallerPackageName(getPackageName());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (TextUtils.isEmpty(src)) {
|
||||
return "Sideloaded";
|
||||
} else if (src.toLowerCase().contains(".amazon.")) {
|
||||
return "Amazon Appstore";
|
||||
}
|
||||
switch (src) {
|
||||
case "com.android.vending":
|
||||
case "com.google.android.feedback": {
|
||||
return "Google Play Store";
|
||||
}
|
||||
case "org.fdroid.fdroid.privileged":
|
||||
case "org.fdroid.fdroid": {
|
||||
return "F-Droid";
|
||||
}
|
||||
case "com.github.yeriomin.yalpstore": {
|
||||
return "Yalp Store";
|
||||
}
|
||||
case "cm.aptoide.pt": {
|
||||
return "Aptoide";
|
||||
}
|
||||
case "com.android.packageinstaller": {
|
||||
return "Package Installer";
|
||||
}
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field from PackageId.BuildConfig
|
||||
* Send a {@link Intent#ACTION_VIEW} Intent with given paramter
|
||||
* If the parameter is an string a browser will get triggered
|
||||
*/
|
||||
public void openWebpageInExternalBrowser(final String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
_context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this apps package name. The builtin method may fail when used with flavors
|
||||
*/
|
||||
public String getPackageName() {
|
||||
String pkg = rstr("manifest_package_id");
|
||||
return pkg != null ? pkg : _context.getPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field from ${applicationId}.BuildConfig
|
||||
* May be helpful in libraries, where a access to
|
||||
* BuildConfig would only get values of the library
|
||||
* rather than the app ones
|
||||
* rather than the app ones. It awaits a string resource
|
||||
* of the package set in manifest (root element).
|
||||
* Falls back to applicationId of the app which may differ from manifest.
|
||||
*/
|
||||
public Object getBuildConfigValue(String fieldName) {
|
||||
String pkg = getPackageName() + ".BuildConfig";
|
||||
try {
|
||||
Class<?> c = Class.forName(_context.getPackageName() + ".BuildConfig");
|
||||
Class<?> c = Class.forName(pkg);
|
||||
return c.getField(fieldName).get(null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -171,7 +250,10 @@ public class ContextUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean getBuildConfigBoolean(String fieldName, boolean defaultValue) {
|
||||
/**
|
||||
* Get a BuildConfig bool value
|
||||
*/
|
||||
public Boolean bcbool(String fieldName, Boolean defaultValue) {
|
||||
Object field = getBuildConfigValue(fieldName);
|
||||
if (field != null && field instanceof Boolean) {
|
||||
return (Boolean) field;
|
||||
|
@ -179,27 +261,58 @@ public class ContextUtils {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a BuildConfig string value
|
||||
*/
|
||||
public String bcstr(String fieldName, String defaultValue) {
|
||||
Object field = getBuildConfigValue(fieldName);
|
||||
if (field != null && field instanceof String) {
|
||||
return (String) field;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a BuildConfig string value
|
||||
*/
|
||||
public Integer bcint(String fieldName, int defaultValue) {
|
||||
Object field = getBuildConfigValue(fieldName);
|
||||
if (field != null && field instanceof Integer) {
|
||||
return (Integer) field;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a gplay build (requires BuildConfig field)
|
||||
*/
|
||||
public boolean isGooglePlayBuild() {
|
||||
return getBuildConfigBoolean("IS_GPLAY_BUILD", true);
|
||||
return bcbool("IS_GPLAY_BUILD", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a foss build (requires BuildConfig field)
|
||||
*/
|
||||
public boolean isFossBuild() {
|
||||
return getBuildConfigBoolean("IS_FOSS_BUILD", false);
|
||||
return bcbool("IS_FOSS_BUILD", false);
|
||||
}
|
||||
|
||||
// Requires donate__bitcoin_* resources (see below) to be available as string resource
|
||||
public void showDonateBitcoinRequest(@StringRes final int strResBitcoinId, @StringRes final int strResBitcoinAmount, @StringRes final int strResBitcoinMessage, @StringRes final int strResAlternativeDonateUrl) {
|
||||
/**
|
||||
* Request a bitcoin donation with given details.
|
||||
* All parameters are awaited as string resource ids
|
||||
*/
|
||||
public void showDonateBitcoinRequest(@StringRes final int srBitcoinId, @StringRes final int srBitcoinAmount, @StringRes final int srBitcoinMessage, @StringRes final int srAlternativeDonateUrl) {
|
||||
if (!isGooglePlayBuild()) {
|
||||
String btcUri = String.format("bitcoin:%s?amount=%s&label=%s&message=%s",
|
||||
str(strResBitcoinId), str(strResBitcoinAmount),
|
||||
str(strResBitcoinMessage), str(strResBitcoinMessage));
|
||||
rstr(srBitcoinId), rstr(srBitcoinAmount),
|
||||
rstr(srBitcoinMessage), rstr(srBitcoinMessage));
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(btcUri));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
try {
|
||||
_context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
openWebpageInExternalBrowser(str(strResAlternativeDonateUrl));
|
||||
openWebpageInExternalBrowser(rstr(srAlternativeDonateUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -232,54 +345,64 @@ public class ContextUtils {
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
public void showDialogWithRawFileInWebView(String fileInRaw, @StringRes int resTitleId) {
|
||||
WebView wv = new WebView(_context);
|
||||
wv.loadUrl("file:///android_res/raw/" + fileInRaw);
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(_context)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setTitle(resTitleId)
|
||||
.setView(wv);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@SuppressWarnings("RestrictedApi")
|
||||
public void setTintColorOfButton(AppCompatButton button, @ColorRes int resColor) {
|
||||
button.setSupportBackgroundTintList(ColorStateList.valueOf(
|
||||
color(resColor)
|
||||
));
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission") // ACCESS_NETWORK_STATE required
|
||||
/**
|
||||
* Get internet connection state - the permission ACCESS_NETWORK_STATE is required
|
||||
*
|
||||
* @return True if internet connection available
|
||||
*/
|
||||
public boolean isConnectedToInternet() {
|
||||
ConnectivityManager con = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo activeNetInfo = con == null ? null : con.getActiveNetworkInfo();
|
||||
return activeNetInfo != null && activeNetInfo.isConnectedOrConnecting();
|
||||
try {
|
||||
ConnectivityManager con = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
@SuppressLint("MissingPermission") NetworkInfo activeNetInfo =
|
||||
con == null ? null : con.getActiveNetworkInfo();
|
||||
return activeNetInfo != null && activeNetInfo.isConnectedOrConnecting();
|
||||
} catch (Exception ignored) {
|
||||
throw new RuntimeException("Error: Developer forgot to declare a permission");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnectedToInternet(@Nullable @StringRes Integer warnMessageStringRes) {
|
||||
final boolean result = isConnectedToInternet();
|
||||
if (!result && warnMessageStringRes != null)
|
||||
Toast.makeText(_context, _context.getString(warnMessageStringRes), Toast.LENGTH_SHORT).show();
|
||||
|
||||
return result;
|
||||
/**
|
||||
* Check if app with given {@code packageName} is installed
|
||||
*/
|
||||
public boolean isAppInstalled(String packageName) {
|
||||
PackageManager pm = _context.getApplicationContext().getPackageManager();
|
||||
try {
|
||||
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
|
||||
return true;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void restartApp(Class classToStartupWith) {
|
||||
Intent restartIntent = new Intent(_context, classToStartupWith);
|
||||
PendingIntent restartIntentP = PendingIntent.getActivity(_context, 555,
|
||||
restartIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
/**
|
||||
* Restart the current app. Supply the class to start on startup
|
||||
*/
|
||||
public void restartApp(Class classToStart) {
|
||||
Intent inte = new Intent(_context, classToStart);
|
||||
PendingIntent inteP = PendingIntent.getActivity(_context, 555, inte, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
AlarmManager mgr = (AlarmManager) _context.getSystemService(Context.ALARM_SERVICE);
|
||||
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, restartIntentP);
|
||||
System.exit(0);
|
||||
if (_context instanceof Activity) {
|
||||
((Activity) _context).finish();
|
||||
}
|
||||
if (mgr != null) {
|
||||
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, inteP);
|
||||
} else {
|
||||
inte.addFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
_context.startActivity(inte);
|
||||
}
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a markdown file from a {@link RawRes}, prepend each line with {@code prepend} text
|
||||
* and convert markdown to html using {@link SimpleMarkdownParser}
|
||||
*/
|
||||
public String loadMarkdownForTextViewFromRaw(@RawRes int rawMdFile, String prepend) {
|
||||
try {
|
||||
return new SimpleMarkdownParser()
|
||||
.parse(_context.getResources().openRawResource(rawMdFile),
|
||||
prepend, SimpleMarkdownParser.FILTER_ANDROID_TEXTVIEW)
|
||||
.replaceColor("#000001", color(getResId(ResType.COLOR, "accent")))
|
||||
.replaceColor("#000001", rcolor(getResId(ResType.COLOR, "accent")))
|
||||
.removeMultiNewlines().replaceBulletCharacter("*").getHtml();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -287,53 +410,74 @@ public class ContextUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load html into a {@link Spanned} object and set the
|
||||
* {@link TextView}'s text using {@link TextView#setText(CharSequence)}
|
||||
*/
|
||||
public void setHtmlToTextView(TextView textView, String html) {
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
textView.setText(new SpannableString(htmlToSpanned(html)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate this device's screen diagonal size in inches
|
||||
*/
|
||||
public double getEstimatedScreenSizeInches() {
|
||||
DisplayMetrics dm = _context.getResources().getDisplayMetrics();
|
||||
|
||||
double density = dm.density * 160;
|
||||
double x = Math.pow(dm.widthPixels / density, 2);
|
||||
double y = Math.pow(dm.heightPixels / density, 2);
|
||||
double screenInches = Math.sqrt(x + y) * 1.16; // 1.16 = est. Nav/Statusbar
|
||||
screenInches = screenInches < 4.0 ? 4.0 : screenInches;
|
||||
screenInches = screenInches > 12.0 ? 12.0 : screenInches;
|
||||
return screenInches;
|
||||
double calc = dm.density * 160d;
|
||||
double x = Math.pow(dm.widthPixels / calc, 2);
|
||||
double y = Math.pow(dm.heightPixels / calc, 2);
|
||||
calc = Math.sqrt(x + y) * 1.16; // 1.16 = est. Nav/Statusbar
|
||||
return Math.min(12, Math.max(4, calc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the device is currently in portrait orientation
|
||||
*/
|
||||
public boolean isInPortraitMode() {
|
||||
return _context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
|
||||
}
|
||||
|
||||
public Locale getLocaleByAndroidCode(String code) {
|
||||
if (!TextUtils.isEmpty(code)) {
|
||||
return code.contains("-r")
|
||||
? new Locale(code.substring(0, 2), code.substring(4, 6)) // de-rAt
|
||||
: new Locale(code); // de
|
||||
/**
|
||||
* Get an {@link Locale} out of a android language code
|
||||
* The {@code androidLC} may be in any of the forms: de, en, de-rAt
|
||||
*/
|
||||
public Locale getLocaleByAndroidCode(String androidLC) {
|
||||
if (!TextUtils.isEmpty(androidLC)) {
|
||||
return androidLC.contains("-r")
|
||||
? new Locale(androidLC.substring(0, 2), androidLC.substring(4, 6)) // de-rAt
|
||||
: new Locale(androidLC); // de
|
||||
}
|
||||
return Resources.getSystem().getConfiguration().locale;
|
||||
}
|
||||
|
||||
// en/de/de-rAt ; Empty string -> default locale
|
||||
public void setAppLanguage(String androidLocaleString) {
|
||||
Locale locale = getLocaleByAndroidCode(androidLocaleString);
|
||||
/**
|
||||
* Set the apps language
|
||||
* {@code androidLC} may be in any of the forms: en, de, de-rAt
|
||||
* If given an empty string, the default (system) locale gets loaded
|
||||
*/
|
||||
public void setAppLanguage(String androidLC) {
|
||||
Locale locale = getLocaleByAndroidCode(androidLC);
|
||||
Configuration config = _context.getResources().getConfiguration();
|
||||
config.locale = (locale != null && !androidLocaleString.isEmpty())
|
||||
config.locale = (locale != null && !androidLC.isEmpty())
|
||||
? locale : Resources.getSystem().getConfiguration().locale;
|
||||
_context.getResources().updateConfiguration(config, null);
|
||||
}
|
||||
|
||||
// Find out if color above the given color should be light or dark. true if light
|
||||
/**
|
||||
* Try to guess if the color on top of the given {@code colorOnBottomInt}
|
||||
* should be light or dark. Returns true if top color should be light
|
||||
*/
|
||||
public boolean shouldColorOnTopBeLight(@ColorInt int colorOnBottomInt) {
|
||||
return 186 > (((0.299 * Color.red(colorOnBottomInt))
|
||||
+ ((0.587 * Color.green(colorOnBottomInt))
|
||||
+ (0.114 * Color.blue(colorOnBottomInt)))));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
/**
|
||||
* Convert a html string to an android {@link Spanned} object
|
||||
*/
|
||||
public Spanned htmlToSpanned(String html) {
|
||||
Spanned result;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
|
@ -344,49 +488,56 @@ public class ContextUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public void setClipboard(String text) {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)).setText(text);
|
||||
} else {
|
||||
ClipData clip = ClipData.newPlainText(_context.getPackageName(), text);
|
||||
((android.content.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(clip);
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getClipboard() {
|
||||
String[] ret;
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
|
||||
ret = new String[]{((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)).getText().toString()};
|
||||
} else {
|
||||
ClipData data = ((android.content.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)).getPrimaryClip();
|
||||
ret = new String[data.getItemCount()];
|
||||
for (int i = 0; i < data.getItemCount() && i < ret.length; i++) {
|
||||
ret[i] = data.getItemAt(i).getText().toString();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public float px2dp(final float px) {
|
||||
/**
|
||||
* Convert pixel unit do android dp unit
|
||||
*/
|
||||
public float convertPxToDp(final float px) {
|
||||
return px / _context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
public float dp2px(final float dp) {
|
||||
/**
|
||||
* Convert android dp unit to pixel unit
|
||||
*/
|
||||
public float convertDpToPx(final float dp) {
|
||||
return dp * _context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
public void setViewVisible(View view, boolean visible) {
|
||||
view.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
/**
|
||||
* Request the givens paths to be scanned by MediaScanner
|
||||
*
|
||||
* @param files Files and folders to scan
|
||||
*/
|
||||
public void mediaScannerScanFile(File... files) {
|
||||
if (android.os.Build.VERSION.SDK_INT > 19) {
|
||||
String[] paths = new String[files.length];
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
paths[i] = files[i].getAbsolutePath();
|
||||
}
|
||||
MediaScannerConnection.scanFile(_context, paths, null, null);
|
||||
} else {
|
||||
for (File file : files) {
|
||||
_context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an image into a {@link ImageView} and apply a color filter
|
||||
*/
|
||||
public static void setDrawableWithColorToImageView(ImageView imageView, @DrawableRes int drawableResId, @ColorRes int colorResId) {
|
||||
imageView.setImageResource(drawableResId);
|
||||
imageView.setColorFilter(ContextCompat.getColor(imageView.getContext(), colorResId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Bitmap} out of a {@link Drawable}
|
||||
*/
|
||||
public Bitmap drawableToBitmap(Drawable drawable) {
|
||||
Bitmap bitmap = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat)) {
|
||||
if (drawable instanceof VectorDrawableCompat
|
||||
|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable instanceof VectorDrawable)
|
||||
|| ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable instanceof AdaptiveIconDrawable))) {
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
drawable = (DrawableCompat.wrap(drawable)).mutate();
|
||||
}
|
||||
|
@ -402,6 +553,18 @@ public class ContextUtils {
|
|||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Bitmap} out of a {@link DrawableRes}
|
||||
*/
|
||||
public Bitmap drawableToBitmap(@DrawableRes int drawableId) {
|
||||
return drawableToBitmap(ContextCompat.getDrawable(_context, drawableId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link Bitmap} from a given {@code imagePath} on the filesystem
|
||||
* Specifying a {@code maxDimen} is also possible and a value below 2000
|
||||
* is recommended, otherwise a {@link OutOfMemoryError} may occur
|
||||
*/
|
||||
public Bitmap loadImageFromFilesystem(File imagePath, int maxDimen) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
|
@ -430,6 +593,10 @@ public class ContextUtils {
|
|||
return inSampleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale the bitmap so both dimensions are lower or equal to {@code maxDimen}
|
||||
* This keeps the aspect ratio
|
||||
*/
|
||||
public Bitmap scaleBitmap(Bitmap bitmap, int maxDimen) {
|
||||
int picSize = Math.min(bitmap.getHeight(), bitmap.getWidth());
|
||||
float scale = 1.f * maxDimen / picSize;
|
||||
|
@ -438,31 +605,43 @@ public class ContextUtils {
|
|||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
public File writeImageToFileJpeg(File imageFile, Bitmap image) {
|
||||
/**
|
||||
* Write the given {@link Bitmap} to {@code imageFile}, in {@link CompressFormat#JPEG} format
|
||||
*/
|
||||
public boolean writeImageToFileJpeg(File imageFile, Bitmap image) {
|
||||
return writeImageToFile(imageFile, image, Bitmap.CompressFormat.JPEG, 95);
|
||||
}
|
||||
|
||||
|
||||
public File writeImageToFileDetectFormat(File imageFile, Bitmap image, int quality) {
|
||||
CompressFormat format = CompressFormat.JPEG;
|
||||
String lc = imageFile.getAbsolutePath().toLowerCase(Locale.ROOT);
|
||||
if (lc.endsWith(".png")) {
|
||||
format = CompressFormat.PNG;
|
||||
/**
|
||||
* Write the given {@link Bitmap} to filesystem
|
||||
*
|
||||
* @param targetFile The file to be written in
|
||||
* @param image The image as android {@link Bitmap}
|
||||
* @param format One format of {@link CompressFormat}, null will determine based on filename
|
||||
* @param quality Quality level, defaults to 95
|
||||
* @return True if writing was successful
|
||||
*/
|
||||
public boolean writeImageToFile(File targetFile, Bitmap image, CompressFormat format, Integer quality) {
|
||||
File folder = new File(targetFile.getParent());
|
||||
if (quality == null || quality < 0 || quality > 100) {
|
||||
quality = 95;
|
||||
}
|
||||
if (lc.endsWith(".webp")) {
|
||||
format = CompressFormat.WEBP;
|
||||
if (format == null) {
|
||||
format = CompressFormat.JPEG;
|
||||
String lc = targetFile.getAbsolutePath().toLowerCase(Locale.ROOT);
|
||||
if (lc.endsWith(".png")) {
|
||||
format = CompressFormat.PNG;
|
||||
}
|
||||
if (lc.endsWith(".webp")) {
|
||||
format = CompressFormat.WEBP;
|
||||
}
|
||||
}
|
||||
return writeImageToFile(imageFile, image, format, quality);
|
||||
}
|
||||
|
||||
public File writeImageToFile(File imageFile, Bitmap image, CompressFormat format, int quality) {
|
||||
File folder = new File(imageFile.getParent());
|
||||
if (folder.exists() || folder.mkdirs()) {
|
||||
FileOutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(imageFile); // overwrites this image every time
|
||||
stream = new FileOutputStream(targetFile); // overwrites this image every time
|
||||
image.compress(format, quality, stream);
|
||||
return imageFile;
|
||||
return true;
|
||||
} catch (FileNotFoundException ignored) {
|
||||
} finally {
|
||||
try {
|
||||
|
@ -473,13 +652,17 @@ public class ContextUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public Bitmap drawTextToDrawable(@DrawableRes int resId, String text, int textSize) {
|
||||
/**
|
||||
* Draw text in the center of the given {@link DrawableRes}
|
||||
* This may be useful for e.g. badge counts
|
||||
*/
|
||||
public Bitmap drawTextOnDrawable(@DrawableRes int drawableRes, String text, int textSize) {
|
||||
Resources resources = _context.getResources();
|
||||
float scale = resources.getDisplayMetrics().density;
|
||||
Bitmap bitmap = getBitmapFromDrawable(resId);
|
||||
Bitmap bitmap = drawableToBitmap(drawableRes);
|
||||
|
||||
bitmap = bitmap.copy(bitmap.getConfig(), true);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
@ -497,50 +680,51 @@ public class ContextUtils {
|
|||
return bitmap;
|
||||
}
|
||||
|
||||
public Bitmap getBitmapFromDrawable(int drawableId) {
|
||||
Bitmap bitmap = null;
|
||||
Drawable drawable = ContextCompat.getDrawable(_context, drawableId);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat)) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
drawable = (DrawableCompat.wrap(drawable)).mutate();
|
||||
}
|
||||
|
||||
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
|
||||
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
} else if (drawable instanceof BitmapDrawable) {
|
||||
bitmap = ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public ContextUtils tintMenuItems(Menu menu, boolean recurse, @ColorInt int iconColor) {
|
||||
/**
|
||||
* Try to tint all {@link Menu}s {@link MenuItem}s with given color
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public void tintMenuItems(Menu menu, boolean recurse, @ColorInt int iconColor) {
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
Drawable drawable = item.getIcon();
|
||||
if (drawable != null) {
|
||||
drawable.mutate();
|
||||
drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
tintDrawable(item.getIcon(), iconColor);
|
||||
if (item.hasSubMenu() && recurse) {
|
||||
tintMenuItems(item.getSubMenu(), recurse, iconColor);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
public ContextUtils setSubMenuIconsVisiblity(Menu menu, boolean visible) {
|
||||
/**
|
||||
* Loads {@link Drawable} by given {@link DrawableRes} and applies a color
|
||||
*/
|
||||
public Drawable tintDrawable(@DrawableRes int drawableRes, @ColorInt int color) {
|
||||
return tintDrawable(rdrawable(drawableRes), color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tint a {@link Drawable} with given {@code color}
|
||||
*/
|
||||
public Drawable tintDrawable(@Nullable Drawable drawable, @ColorInt int color) {
|
||||
if (drawable != null) {
|
||||
drawable = DrawableCompat.wrap(drawable);
|
||||
DrawableCompat.setTint(drawable.mutate(), color);
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to make icons in Toolbar/ActionBars SubMenus visible
|
||||
* This may not work on some devices and it maybe won't work on future android updates
|
||||
*/
|
||||
public void setSubMenuIconsVisiblity(Menu menu, boolean visible) {
|
||||
if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
|
||||
try {
|
||||
Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
|
||||
@SuppressLint("PrivateApi") Method m = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
|
||||
m.setAccessible(true);
|
||||
m.invoke(menu, visible);
|
||||
} catch (Exception ignored) {
|
||||
Log.d(getClass().getName(), "Error: 'setSubMenuIconsVisiblity' not supported on this device");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
57
app/src/main/java/net/gsantner/opoc/util/DownloadTask.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
This file is part of the dandelion*.
|
||||
|
||||
dandelion* is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
dandelion* is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with the dandelion*.
|
||||
|
||||
If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
|
||||
public class DownloadTask extends AsyncTask<String, Void, Boolean> {
|
||||
private File _targetFile;
|
||||
private Callback.a2<Boolean, File> _callback;
|
||||
|
||||
public DownloadTask(File targetFile, @Nullable Callback.a2<Boolean, File> callback) {
|
||||
_targetFile = targetFile;
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
protected Boolean doInBackground(String... urls) {
|
||||
if (urls != null && urls.length > 0 && urls[0] != null) {
|
||||
try {
|
||||
HttpsURLConnection connection = NetCipher.getHttpsURLConnection(urls[0]);
|
||||
return NetworkUtils.downloadFile(null, _targetFile, connection, null);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Boolean result) {
|
||||
if (_callback != null) {
|
||||
_callback.callback(result, _targetFile);
|
||||
}
|
||||
}
|
||||
}
|
443
app/src/main/java/net/gsantner/opoc/util/FileUtils.java
Normal file
|
@ -0,0 +1,443 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation"})
|
||||
public class FileUtils {
|
||||
// Used on methods like copyFile(src, dst)
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
public static String readTextFileFast(final File file) {
|
||||
try {
|
||||
return new String(readCloseBinaryStream(new FileInputStream(file)));
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println("readTextFileFast: File " + file + " not found.");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String readTextFile(final File file) {
|
||||
try {
|
||||
return readCloseTextStream(new FileInputStream(file));
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println("readTextFile: File " + file + " not found.");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String readCloseTextStream(final InputStream stream) {
|
||||
return readCloseTextStream(stream, true).get(0);
|
||||
}
|
||||
|
||||
public static List<String> readCloseTextStream(final InputStream stream, boolean concatToOneString) {
|
||||
final ArrayList<String> lines = new ArrayList<>();
|
||||
BufferedReader reader = null;
|
||||
String line = "";
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
reader = new BufferedReader(new InputStreamReader(stream));
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (concatToOneString) {
|
||||
sb.append(line).append('\n');
|
||||
} else {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
line = sb.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (concatToOneString) {
|
||||
lines.clear();
|
||||
lines.add(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static byte[] readBinaryFile(final File file) {
|
||||
try {
|
||||
return readCloseBinaryStream(new FileInputStream(file), (int) file.length());
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println("readBinaryFile: File " + file + " not found.");
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public static byte[] readCloseBinaryStream(final InputStream stream, int byteCount) {
|
||||
final ArrayList<String> lines = new ArrayList<>();
|
||||
BufferedInputStream reader = null;
|
||||
byte[] buf = new byte[byteCount];
|
||||
int totalBytesRead = 0;
|
||||
try {
|
||||
reader = new BufferedInputStream(stream);
|
||||
while (totalBytesRead < byteCount) {
|
||||
int bytesRead = reader.read(buf, totalBytesRead, byteCount - totalBytesRead);
|
||||
if (bytesRead > 0) {
|
||||
totalBytesRead = totalBytesRead + bytesRead;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Read binary stream (of unknown conf size)
|
||||
public static byte[] readCloseBinaryStream(final InputStream stream) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int read;
|
||||
while ((read = stream.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, read);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static boolean writeFile(final File file, byte[] data) {
|
||||
try {
|
||||
OutputStream output = null;
|
||||
try {
|
||||
output = new BufferedOutputStream(new FileOutputStream(file));
|
||||
output.write(data);
|
||||
output.flush();
|
||||
return true;
|
||||
} finally {
|
||||
if (output != null) {
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean writeFile(final File file, final String content) {
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
if (!file.getParentFile().isDirectory() && !file.getParentFile().mkdirs())
|
||||
return false;
|
||||
|
||||
writer = new BufferedWriter(new FileWriter(file));
|
||||
writer.write(content);
|
||||
writer.flush();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean copyFile(final File src, final File dst) {
|
||||
// Just touch file if src is empty
|
||||
if (src.length() == 0) {
|
||||
return touch(dst);
|
||||
}
|
||||
|
||||
InputStream is = null;
|
||||
FileOutputStream os = null;
|
||||
try {
|
||||
try {
|
||||
is = new FileInputStream(src);
|
||||
os = new FileOutputStream(dst);
|
||||
byte[] buf = new byte[BUFFER_SIZE];
|
||||
int len;
|
||||
while ((len = is.read(buf)) > 0) {
|
||||
os.write(buf, 0, len);
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns -1 if the file did not contain any of the needles, otherwise,
|
||||
// the index of which needle was found in the contents of the file.
|
||||
//
|
||||
// Needless MUST be in lower-case.
|
||||
public static int fileContains(File file, String... needles) {
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
|
||||
int i;
|
||||
String line;
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
while ((line = reader.readLine()) != null) {
|
||||
for (i = 0; i != needles.length; ++i)
|
||||
if (line.toLowerCase(Locale.ROOT).contains(needles[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static boolean deleteRecursive(final File file) {
|
||||
boolean ok = true;
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
for (File child : file.listFiles())
|
||||
ok &= deleteRecursive(child);
|
||||
}
|
||||
ok &= file.delete();
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Example: Check if this is maybe a conf: (str, "jpg", "png", "jpeg")
|
||||
public static boolean hasExtension(String str, String... extensions) {
|
||||
String lc = str.toLowerCase(Locale.ROOT);
|
||||
for (String extension : extensions) {
|
||||
if (lc.endsWith("." + extension.toLowerCase(Locale.ROOT))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean renameFile(File srcFile, File destFile) {
|
||||
if (srcFile.getAbsolutePath().equals(destFile.getAbsolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// renameTo will fail in case of case-changed filename in same dir.Even on case-sensitive FS!!!
|
||||
if (srcFile.getParent().equals(destFile.getParent()) && srcFile.getName().toLowerCase(Locale.getDefault()).equals(destFile.getName().toLowerCase(Locale.getDefault()))) {
|
||||
File tmpFile = new File(destFile.getParent(), UUID.randomUUID().getLeastSignificantBits() + ".tmp");
|
||||
if (!tmpFile.exists()) {
|
||||
renameFile(srcFile, tmpFile);
|
||||
srcFile = tmpFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (!srcFile.renameTo(destFile)) {
|
||||
if (copyFile(srcFile, destFile) && !srcFile.delete()) {
|
||||
if (!destFile.delete()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static boolean renameFileInSameFolder(File srcFile, String destFilename) {
|
||||
return renameFile(srcFile, new File(srcFile.getParent(), destFilename));
|
||||
}
|
||||
|
||||
public static boolean touch(File file) {
|
||||
try {
|
||||
if (!file.exists()) {
|
||||
new FileOutputStream(file).close();
|
||||
}
|
||||
return file.setLastModified(System.currentTimeMillis());
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get relative path to specified destination
|
||||
public static String relativePath(File src, File dest) {
|
||||
try {
|
||||
String[] srcSplit = (src.isDirectory() ? src : src.getParentFile()).getCanonicalPath().split(Pattern.quote(File.separator));
|
||||
String[] destSplit = dest.getCanonicalPath().split(Pattern.quote(File.separator));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int i = 0;
|
||||
|
||||
for (; i < destSplit.length && i < srcSplit.length; ++i) {
|
||||
if (!destSplit[i].equals(srcSplit[i]))
|
||||
break;
|
||||
}
|
||||
if (i != srcSplit.length) {
|
||||
for (int iUpperDir = i; iUpperDir < srcSplit.length; ++iUpperDir) {
|
||||
sb.append("..");
|
||||
sb.append(File.separator);
|
||||
}
|
||||
}
|
||||
for (; i < destSplit.length; ++i) {
|
||||
sb.append(destSplit[i]);
|
||||
sb.append(File.separator);
|
||||
}
|
||||
if (!dest.getPath().endsWith("/") && !dest.getPath().endsWith("\\")) {
|
||||
sb.delete(sb.length() - File.separator.length(), sb.length());
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (IOException | NullPointerException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect MimeType by backwards compatible methods
|
||||
*/
|
||||
public static String getMimeType(File file) {
|
||||
String guess = null;
|
||||
if (file != null && file.exists() && file.isFile()) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(new FileInputStream(file));
|
||||
guess = URLConnection.guessContentTypeFromStream(is);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (guess == null || guess.isEmpty()) {
|
||||
guess = "*/*";
|
||||
int dot = file.getName().lastIndexOf(".") + 1;
|
||||
if (dot > 0 && dot < file.getName().length()) {
|
||||
switch (file.getName().substring(dot)) {
|
||||
case "md":
|
||||
case "markdown":
|
||||
case "mkd":
|
||||
case "mdown":
|
||||
case "mkdn":
|
||||
case "mdwn":
|
||||
case "rmd":
|
||||
guess = "text/markdown";
|
||||
break;
|
||||
case "txt":
|
||||
guess = "text/plain";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return guess;
|
||||
}
|
||||
|
||||
public static boolean isTextFile(File file) {
|
||||
return getMimeType(file).startsWith("text/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze given textfile and retrieve multiple information from it
|
||||
* Information is written back to the {@link AtomicInteger} parameters
|
||||
*/
|
||||
public static void retrieveTextFileSummary(File file, AtomicInteger numCharacters, AtomicInteger numLines) {
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
br = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
numLines.getAndIncrement();
|
||||
numCharacters.getAndSet(numCharacters.get() + line.length());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
numCharacters.set(-1);
|
||||
numLines.set(-1);
|
||||
} finally {
|
||||
if (br != null) {
|
||||
try {
|
||||
br.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filesize to human readable format
|
||||
* Get size in bytes e.g. from {@link File} using {@code File#length()}
|
||||
*/
|
||||
public static String getReadableFileSize(long size, boolean abbreviation) {
|
||||
if (size <= 0) {
|
||||
return "0B";
|
||||
}
|
||||
String[] units = abbreviation ? new String[]{"B", "kB", "MB", "GB", "TB"} : new String[]{"Bytes", "Kilobytes", "Megabytes", "Gigabytes", "Terabytes"};
|
||||
int unit = (int) (Math.log10(size) / Math.log10(1024));
|
||||
return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, unit))
|
||||
+ " " + units[unit];
|
||||
}
|
||||
}
|
223
app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation"})
|
||||
public class NetworkUtils {
|
||||
private static final String UTF8 = "UTF-8";
|
||||
public static final String GET = "GET";
|
||||
public static final String POST = "POST";
|
||||
public static final String PATCH = "PATCH";
|
||||
|
||||
private final static int BUFFER_SIZE = 4096;
|
||||
|
||||
// Downloads a file from the give url to the output file
|
||||
// Creates the file's parent directory if it doesn't exist
|
||||
public static boolean downloadFile(final String url, final File out) {
|
||||
return downloadFile(url, out, null);
|
||||
}
|
||||
|
||||
public static boolean downloadFile(final String url, final File out, final Callback.a1<Float> progressCallback) {
|
||||
try {
|
||||
return downloadFile(new URL(url), out, progressCallback);
|
||||
} catch (MalformedURLException e) {
|
||||
// Won't happen
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean downloadFile(final URL url, final File outFile, final Callback.a1<Float> progressCallback) {
|
||||
return downloadFile(url, outFile, null, progressCallback);
|
||||
}
|
||||
|
||||
public static boolean downloadFile(final URL url, final File outFile, HttpURLConnection connection, final Callback.a1<Float> progressCallback) {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
try {
|
||||
if (connection == null) {
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
connection.connect();
|
||||
input = connection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
|
||||
? connection.getInputStream() : connection.getErrorStream();
|
||||
|
||||
|
||||
if (!outFile.getParentFile().isDirectory())
|
||||
if (!outFile.getParentFile().mkdirs())
|
||||
return false;
|
||||
output = new FileOutputStream(outFile);
|
||||
|
||||
int count;
|
||||
int written = 0;
|
||||
final float invLength = 1f / connection.getContentLength();
|
||||
|
||||
byte data[] = new byte[BUFFER_SIZE];
|
||||
while ((count = input.read(data)) != -1) {
|
||||
output.write(data, 0, count);
|
||||
if (invLength != -1f && progressCallback != null) {
|
||||
written += count;
|
||||
progressCallback.callback(written * invLength);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (output != null)
|
||||
output.close();
|
||||
if (input != null)
|
||||
input.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
if (connection != null)
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// No parameters, method can be GET, POST, etc.
|
||||
public static String performCall(final String url, final String method) {
|
||||
try {
|
||||
return performCall(new URL(url), method, "");
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String performCall(final String url, final String method, final String data) {
|
||||
try {
|
||||
return performCall(new URL(url), method, data);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// URL encoded parameters
|
||||
public static String performCall(final String url, final String method, final HashMap<String, String> params) {
|
||||
try {
|
||||
return performCall(new URL(url), method, encodeQuery(params));
|
||||
} catch (UnsupportedEncodingException | MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Defaults to POST
|
||||
public static String performCall(final String url, final JSONObject json) {
|
||||
return performCall(url, POST, json);
|
||||
}
|
||||
|
||||
public static String performCall(final String url, final String method, final JSONObject json) {
|
||||
try {
|
||||
return performCall(new URL(url), method, json.toString());
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String performCall(final URL url, final String method, final String data) {
|
||||
return performCall(url, method, data, null);
|
||||
}
|
||||
|
||||
private static String performCall(final URL url, final String method, final String data, final HttpURLConnection existingConnection) {
|
||||
try {
|
||||
final HttpURLConnection connection = existingConnection != null
|
||||
? existingConnection : (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod(method);
|
||||
connection.setDoInput(true);
|
||||
|
||||
if (data != null && !data.isEmpty()) {
|
||||
connection.setDoOutput(true);
|
||||
final OutputStream output = connection.getOutputStream();
|
||||
output.write(data.getBytes(Charset.forName(UTF8)));
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
|
||||
InputStream input = connection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST
|
||||
? connection.getInputStream() : connection.getErrorStream();
|
||||
|
||||
return FileUtils.readCloseTextStream(connection.getInputStream());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String encodeQuery(final HashMap<String, String> params) throws UnsupportedEncodingException {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
if (first) first = false;
|
||||
else result.append("&");
|
||||
|
||||
result.append(URLEncoder.encode(entry.getKey(), UTF8));
|
||||
result.append("=");
|
||||
result.append(URLEncoder.encode(entry.getValue(), UTF8));
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static HashMap<String, String> getDataMap(final String query) {
|
||||
final HashMap<String, String> result = new HashMap<>();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String name = "";
|
||||
|
||||
try {
|
||||
for (int i = 0; i < query.length(); i++) {
|
||||
char c = query.charAt(i);
|
||||
switch (c) {
|
||||
case '=':
|
||||
name = URLDecoder.decode(sb.toString(), UTF8);
|
||||
sb.setLength(0);
|
||||
break;
|
||||
case '&':
|
||||
result.put(name, URLDecoder.decode(sb.toString(), UTF8));
|
||||
sb.setLength(0);
|
||||
break;
|
||||
default:
|
||||
sb.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!name.isEmpty())
|
||||
result.put(name, URLDecoder.decode(sb.toString(), UTF8));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class PermissionChecker {
|
||||
protected static final int CODE_PERMISSION_EXTERNAL_STORAGE = 4000;
|
||||
|
||||
protected Activity _activity;
|
||||
|
||||
public PermissionChecker(Activity activity) {
|
||||
_activity = activity;
|
||||
}
|
||||
|
||||
public boolean doIfExtStoragePermissionGranted(String... optionalToastMessageForKnowingWhyNeeded) {
|
||||
if (ContextCompat.checkSelfPermission(_activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
if (optionalToastMessageForKnowingWhyNeeded != null && optionalToastMessageForKnowingWhyNeeded.length > 0 && optionalToastMessageForKnowingWhyNeeded[0] != null) {
|
||||
new AlertDialog.Builder(_activity)
|
||||
.setMessage(optionalToastMessageForKnowingWhyNeeded[0])
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 23) {
|
||||
ActivityCompat.requestPermissions(_activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_PERMISSION_EXTERNAL_STORAGE);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
ActivityCompat.requestPermissions(_activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, CODE_PERMISSION_EXTERNAL_STORAGE);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (grantResults.length > 0) {
|
||||
switch (requestCode) {
|
||||
case CODE_PERMISSION_EXTERNAL_STORAGE: {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean mkdirIfStoragePermissionGranted(File dir) {
|
||||
return doIfExtStoragePermissionGranted() && (dir.exists() || dir.mkdirs());
|
||||
}
|
||||
}
|
476
app/src/main/java/net/gsantner/opoc/util/ShareUtil.java
Normal file
|
@ -0,0 +1,476 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0 / Commercial
|
||||
* https://github.com/gsantner/opoc/#licensing
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
#########################################################*/
|
||||
package net.gsantner.opoc.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.print.PrintAttributes;
|
||||
import android.print.PrintDocumentAdapter;
|
||||
import android.print.PrintJob;
|
||||
import android.print.PrintManager;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.content.pm.ShortcutInfoCompat;
|
||||
import android.support.v4.content.pm.ShortcutManagerCompat;
|
||||
import android.support.v4.graphics.drawable.IconCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* A utility class to ease information sharing on Android
|
||||
* Also allows to parse/fetch information out of shared information
|
||||
*/
|
||||
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "SameParameterValue", "unused", "deprecation", "ConstantConditions", "ObsoleteSdkInt", "SpellCheckingInspection"})
|
||||
public class ShareUtil {
|
||||
public final static String EXTRA_FILEPATH = "real_file_path_2";
|
||||
public final static SimpleDateFormat SDF_RFC3339_ISH = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm", Locale.getDefault());
|
||||
public final static SimpleDateFormat SDF_SHORT = new SimpleDateFormat("yyMMdd-HHmm", Locale.getDefault());
|
||||
public final static String MIME_TEXT_PLAIN = "text/plain";
|
||||
|
||||
|
||||
protected Context _context;
|
||||
protected String _fileProviderAuthority;
|
||||
protected String _chooserTitle;
|
||||
|
||||
public ShareUtil(Context context) {
|
||||
_context = context;
|
||||
_chooserTitle = "➥";
|
||||
}
|
||||
|
||||
public String getFileProviderAuthority() {
|
||||
if (TextUtils.isEmpty(_fileProviderAuthority)) {
|
||||
throw new RuntimeException("Error at ShareUtil.getFileProviderAuthority(): No FileProvider authority provided");
|
||||
}
|
||||
return _fileProviderAuthority;
|
||||
}
|
||||
|
||||
public ShareUtil setFileProviderAuthority(String fileProviderAuthority) {
|
||||
_fileProviderAuthority = fileProviderAuthority;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ShareUtil setChooserTitle(String title) {
|
||||
_chooserTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link File} to an {@link Uri}
|
||||
*
|
||||
* @param file the file
|
||||
* @return Uri for this file
|
||||
*/
|
||||
public Uri getUriByFileProviderAuthority(File file) {
|
||||
return FileProvider.getUriForFile(_context, getFileProviderAuthority(), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to choose a handling app for given intent
|
||||
*
|
||||
* @param intent Thing to be shared
|
||||
* @param chooserText The title text for the chooser, or null for default
|
||||
*/
|
||||
public void showChooser(Intent intent, String chooserText) {
|
||||
_context.startActivity(Intent.createChooser(intent,
|
||||
chooserText != null ? chooserText : _chooserTitle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a new desktop shortcut on the launcher. Add permissions:
|
||||
* <uses-permission android:name="android.permission.INSTALL_SHORTCUT" />
|
||||
* <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
*
|
||||
* @param intent The intent to be invoked on tap
|
||||
* @param iconRes Icon resource for the item
|
||||
* @param title Title of the item
|
||||
*/
|
||||
public void createLauncherDesktopShortcut(Intent intent, @DrawableRes int iconRes, String title) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
if (intent.getAction() == null) {
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
}
|
||||
|
||||
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(_context, Long.toString(new Random().nextLong()))
|
||||
.setIntent(intent)
|
||||
.setIcon(IconCompat.createWithResource(_context, iconRes))
|
||||
.setShortLabel(title)
|
||||
.setLongLabel(title)
|
||||
.build();
|
||||
ShortcutManagerCompat.requestPinShortcut(_context, shortcut, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a new desktop shortcut on the launcher. This will not work on Api > 25. Add permissions:
|
||||
* <uses-permission android:name="android.permission.INSTALL_SHORTCUT" />
|
||||
* <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||
*
|
||||
* @param intent The intent to be invoked on tap
|
||||
* @param iconRes Icon resource for the item
|
||||
* @param title Title of the item
|
||||
*/
|
||||
public void createLauncherDesktopShortcutLegacy(Intent intent, @DrawableRes int iconRes, String title) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
if (intent.getAction() == null) {
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
}
|
||||
|
||||
Intent creationIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
creationIntent.putExtra("duplicate", true);
|
||||
creationIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
|
||||
creationIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
|
||||
creationIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(_context, iconRes));
|
||||
_context.sendBroadcast(creationIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Share text with given mime-type
|
||||
*
|
||||
* @param text The text to share
|
||||
* @param mimeType MimeType or null (uses text/plain)
|
||||
*/
|
||||
public void shareText(String text, @Nullable String mimeType) {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_TEXT, text);
|
||||
intent.setType(mimeType != null ? mimeType : MIME_TEXT_PLAIN);
|
||||
showChooser(intent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the given file as stream with given mime-type
|
||||
*
|
||||
* @param file The file to share
|
||||
* @param mimeType The files mime type
|
||||
*/
|
||||
public void shareStream(File file, String mimeType) {
|
||||
Uri fileUri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), file);
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath());
|
||||
intent.setType(mimeType);
|
||||
showChooser(intent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a View intent for given file
|
||||
*
|
||||
* @param file The file to share
|
||||
*/
|
||||
public void viewFileInOtherApp(File file, @Nullable String type) {
|
||||
Uri fileUri = FileProvider.getUriForFile(_context, getFileProviderAuthority(), file);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
intent.setData(fileUri);
|
||||
intent.putExtra(EXTRA_FILEPATH, file.getAbsolutePath());
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.setDataAndType(fileUri, type);
|
||||
showChooser(intent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the given bitmap with given format
|
||||
*
|
||||
* @param bitmap Image
|
||||
* @param format A {@link Bitmap.CompressFormat}, supporting JPEG,PNG,WEBP
|
||||
* @return if success, true
|
||||
*/
|
||||
public boolean shareImage(Bitmap bitmap, Bitmap.CompressFormat format) {
|
||||
return shareImage(bitmap, format, 95, "SharedImage");
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the given bitmap with given format
|
||||
*
|
||||
* @param bitmap Image
|
||||
* @param format A {@link Bitmap.CompressFormat}, supporting JPEG,PNG,WEBP
|
||||
* @param imageName Filename without extension
|
||||
* @param quality Quality of the exported image [0-100]
|
||||
* @return if success, true
|
||||
*/
|
||||
public boolean shareImage(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String imageName) {
|
||||
try {
|
||||
String ext = format.name().toLowerCase();
|
||||
File file = File.createTempFile(imageName, "." + ext.replace("jpeg", "jpg"), _context.getExternalCacheDir());
|
||||
if (bitmap != null && new ContextUtils(_context).writeImageToFile(file, bitmap, format, quality)) {
|
||||
shareStream(file, "image/" + ext);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a {@link WebView}'s contents, also allows to create a PDF
|
||||
*
|
||||
* @param webview WebView
|
||||
* @param jobName Name of the job (affects PDF name too)
|
||||
* @return {{@link PrintJob}} or null
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@SuppressWarnings("deprecation")
|
||||
public PrintJob print(WebView webview, String jobName) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
PrintDocumentAdapter printAdapter;
|
||||
PrintManager printManager = (PrintManager) webview.getContext().getSystemService(Context.PRINT_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
printAdapter = webview.createPrintDocumentAdapter(jobName);
|
||||
} else {
|
||||
printAdapter = webview.createPrintDocumentAdapter();
|
||||
}
|
||||
if (printManager != null) {
|
||||
return printManager.print(jobName, printAdapter, new PrintAttributes.Builder().build());
|
||||
}
|
||||
} else {
|
||||
Log.e(getClass().getName(), "ERROR: Method called on too low Android API version");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See {@link #print(WebView, String) print method}
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@SuppressWarnings("deprecation")
|
||||
public PrintJob createPdf(WebView webview, String jobName) {
|
||||
return print(webview, jobName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a picture out of {@link WebView}'s whole content
|
||||
*
|
||||
* @param webView The WebView to get contents from
|
||||
* @return A {@link Bitmap} or null
|
||||
*/
|
||||
@Nullable
|
||||
public static Bitmap getBitmapFromWebView(WebView webView) {
|
||||
try {
|
||||
//Measure WebView's content
|
||||
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
webView.measure(widthMeasureSpec, heightMeasureSpec);
|
||||
webView.layout(0, 0, webView.getMeasuredWidth(), webView.getMeasuredHeight());
|
||||
|
||||
//Build drawing cache and store its size
|
||||
webView.buildDrawingCache();
|
||||
int measuredWidth = webView.getMeasuredWidth();
|
||||
int measuredHeight = webView.getMeasuredHeight();
|
||||
|
||||
//Creates the bitmap and draw WebView's content on in
|
||||
Bitmap bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawBitmap(bitmap, 0, bitmap.getHeight(), new Paint());
|
||||
|
||||
webView.draw(canvas);
|
||||
webView.destroyDrawingCache();
|
||||
|
||||
return bitmap;
|
||||
} catch (Exception | OutOfMemoryError e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* Replace (primary) clipboard contents with given {@code text}
|
||||
* @param text Text to be set
|
||||
*/
|
||||
public boolean setClipboard(CharSequence text) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager cm = ((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE));
|
||||
if (cm != null) {
|
||||
cm.setText(text);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
android.content.ClipboardManager cm = ((android.content.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE));
|
||||
if (cm != null) {
|
||||
ClipData clip = ClipData.newPlainText(_context.getPackageName(), text);
|
||||
cm.setPrimaryClip(clip);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clipboard contents, very failsafe and compat to older android versions
|
||||
*/
|
||||
public List<String> getClipboard() {
|
||||
List<String> clipper = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
||||
android.text.ClipboardManager cm = ((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE));
|
||||
if (cm != null && !TextUtils.isEmpty(cm.getText())) {
|
||||
clipper.add(cm.getText().toString());
|
||||
}
|
||||
} else {
|
||||
android.content.ClipboardManager cm = ((android.content.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE));
|
||||
if (cm != null && cm.hasPrimaryClip()) {
|
||||
ClipData data = cm.getPrimaryClip();
|
||||
for (int i = 0; data != null && i < data.getItemCount() && i < data.getItemCount(); i++) {
|
||||
ClipData.Item item = data.getItemAt(i);
|
||||
if (item != null && !TextUtils.isEmpty(item.getText())) {
|
||||
clipper.add(data.getItemAt(i).getText().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return clipper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Share given text on a hastebin compatible server
|
||||
* (https://github.com/seejohnrun/haste-server)
|
||||
* Permission needed: Internet
|
||||
* Pastes will be deleted after 30 days without access
|
||||
*
|
||||
* @param text The text to paste
|
||||
* @param callback Callback after paste try
|
||||
* @param serverOrNothing Supply one or no hastebin server. If empty, the default gets taken
|
||||
*/
|
||||
public void pasteOnHastebin(final String text, final Callback.a2<Boolean, String> callback, String... serverOrNothing) {
|
||||
final Handler handler = new Handler();
|
||||
final String server = (serverOrNothing != null && serverOrNothing.length > 0 && serverOrNothing[0] != null)
|
||||
? serverOrNothing[0] : "https://hastebin.com";
|
||||
new Thread() {
|
||||
public void run() {
|
||||
// Returns a simple result, handleable without json parser {"key":"feediyujiq"}
|
||||
String ret = NetworkUtils.performCall(server + "/documents", NetworkUtils.POST, text);
|
||||
final String key = (ret.length() > 15) ? ret.split("\"")[3] : "";
|
||||
handler.post(() -> callback.callback(!key.isEmpty(), server + "/" + key));
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draft an email with given data. Unknown data can be supplied as null.
|
||||
* This will open a chooser with installed mail clients where the mail can be sent from
|
||||
*
|
||||
* @param subject Subject (top/title) text to be prefilled in the mail
|
||||
* @param body Body (content) text to be prefilled in the mail
|
||||
* @param to recipients to be prefilled in the mail
|
||||
*/
|
||||
public void draftEmail(String subject, String body, String... to) {
|
||||
Intent intent = new Intent(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse("mailto:"));
|
||||
if (subject != null) {
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
|
||||
}
|
||||
if (body != null) {
|
||||
intent.putExtra(Intent.EXTRA_TEXT, body);
|
||||
}
|
||||
if (to != null && to.length > 0 && to[0] != null) {
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, to);
|
||||
}
|
||||
showChooser(intent, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to force extract a absolute filepath from an intent
|
||||
*
|
||||
* @param receivingIntent The intent from {@link Activity#getIntent()}
|
||||
* @return A file or null if extraction did not succeed
|
||||
*/
|
||||
public File extractFileFromIntent(Intent receivingIntent) {
|
||||
String action = receivingIntent.getAction();
|
||||
String type = receivingIntent.getType();
|
||||
File tmpf;
|
||||
String tmps;
|
||||
String fileStr;
|
||||
|
||||
if ((Intent.ACTION_VIEW.equals(action) || Intent.ACTION_EDIT.equals(action)) || Intent.ACTION_SEND.equals(action)) {
|
||||
// Markor, S.M.T FileManager
|
||||
if (receivingIntent.hasExtra((tmps = EXTRA_FILEPATH))) {
|
||||
return new File(receivingIntent.getStringExtra(tmps));
|
||||
}
|
||||
|
||||
// Analyze data/Uri
|
||||
Uri fileUri = receivingIntent.getData();
|
||||
if (fileUri != null && (fileStr = fileUri.toString()) != null) {
|
||||
// Uri contains file
|
||||
if (fileStr.startsWith("file://")) {
|
||||
return new File(fileUri.getPath());
|
||||
}
|
||||
if (fileStr.startsWith((tmps = "content://"))) {
|
||||
fileStr = fileStr.substring(tmps.length());
|
||||
String fileProvider = fileStr.substring(0, fileStr.indexOf("/"));
|
||||
fileStr = fileStr.substring(fileProvider.length() + 1);
|
||||
|
||||
// Some file managers dont add leading slash
|
||||
if (fileStr.startsWith("storage/")) {
|
||||
fileStr = "/" + fileStr;
|
||||
}
|
||||
// Some do add some custom prefix
|
||||
for (String prefix : new String[]{"file", "document", "root_files", "name"}) {
|
||||
if (fileStr.startsWith(prefix)) {
|
||||
fileStr = fileStr.substring(prefix.length());
|
||||
}
|
||||
}
|
||||
// Next/OwnCloud Fileprovider
|
||||
for (String fp : new String[]{"org.nextcloud.files", "org.nextcloud.beta.files", "org.owncloud.files"}) {
|
||||
if (fileProvider.equals(fp) && fileStr.startsWith(tmps = "external_files/")) {
|
||||
return new File(Uri.decode("/storage/" + fileStr.substring(tmps.length())));
|
||||
}
|
||||
}
|
||||
// AOSP File Manager/Documents
|
||||
if (fileProvider.equals("com.android.externalstorage.documents") && fileStr.startsWith(tmps = "/primary%3A")) {
|
||||
return new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + fileStr.substring(tmps.length())));
|
||||
}
|
||||
// Mi File Explorer
|
||||
if (fileProvider.equals("com.mi.android.globalFileexplorer.myprovider") && fileStr.startsWith(tmps = "external_files")) {
|
||||
return new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + fileStr.substring(tmps.length())));
|
||||
}
|
||||
// URI Encoded paths with full path after content://package/
|
||||
if (fileStr.startsWith("/") || fileStr.startsWith("%2F")) {
|
||||
tmpf = new File(Uri.decode(fileStr));
|
||||
if (tmpf.exists()) {
|
||||
return tmpf;
|
||||
} else if ((tmpf = new File(fileStr)).exists()) {
|
||||
return tmpf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fileUri = receivingIntent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if (fileUri != null && !TextUtils.isEmpty(tmps = fileUri.getPath()) && tmps.startsWith("/") && (tmpf = new File(tmps)).exists()) {
|
||||
return tmpf;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
5
app/src/main/res/drawable-anydpi-v26/ic_launcher.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 929 B After Width: | Height: | Size: 2 KiB |
BIN
app/src/main/res/drawable-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.9 KiB |