1
0
Fork 0
mirror of https://github.com/gsantner/dandelion synced 2024-06-18 09:34:54 +02:00

eliminar string preferences e about

This commit is contained in:
Xosé M 2018-07-29 09:54:34 +02:00
commit 215f67308e
189 changed files with 6821 additions and 5676 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

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

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

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">dandelior*</string>
</resources>

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View file

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">secondlion*</string>
</resources>

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View file

@ -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());

View file

@ -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})

View file

@ -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);
}
}
}

View file

@ -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());
}
});
}
}
});
}

View file

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

View file

@ -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;
}

View file

@ -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();

View file

@ -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);
}
}
}

View file

@ -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;
}

View file

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

View file

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

View file

@ -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);
}
}
}

View file

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

View file

@ -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);
}
}
}

View file

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

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -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);
}

View file

@ -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))) {

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}
}

View 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() {

View file

@ -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);

View file

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

View file

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

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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};
}
}

View 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();
}
}
}
}

View file

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

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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() {

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

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

View 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);
}
}

View file

@ -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;
}
}

View 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);
}
}
}

View 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];
}
}

View 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;
}
}

View file

@ -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());
}
}

View 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;
}
}

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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 929 B

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Some files were not shown because too many files have changed in this diff Show more