1
0
Fork 0
mirror of https://github.com/gsantner/dandelion synced 2024-11-16 01:12:08 +01:00

Merge branch 'master' of https://github.com/arnaud-jacquemin/dandelion.git into stringlate-fr-9719

# Conflicts:
#	app/build.gradle
#	app/src/main/java/net/gsantner/opoc/util/ContextUtils.java
#	app/src/main/res/values-fr/strings.xml
This commit is contained in:
Arnaud Jacquemin 2018-05-08 23:40:10 +02:00
commit 5e8b06f750
75 changed files with 2271 additions and 1159 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/diaspora-for-android/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

@ -2,9 +2,8 @@
[![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&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 Bitcoin](https://img.shields.io/badge/donate-bitcoin-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">
@ -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))
- gsantner ([GitHub](https://github.com/gsantner), [Web](https://gsantner.net), [diaspora*](https://pod.geraspora.de/people/d1cbdd70095301341e834860008dbc6c))
- Bitcoin: [1B9ZyYdQoY9BxMe9dRUEKaZbJWsbQqfXU5](http://gsantner.net/donate/#donate)
- vanitasvitae ([GitHub](https://github.com/vanitasvitae), [diaspora*](https://pod.geraspora.de/people/bbd7af90fbec013213e34860008dbc6c))
- Bitcoin: 1Ao3W6NaQv3xKppviB7RSFKjHo6PGd8RTy

View file

@ -14,13 +14,13 @@ android {
targetSdkVersion version_setup_targetSdk
buildConfigField "boolean", "IS_TEST_BUILD", "false"
buildConfigField "boolean", "IS_GPLAY_BUILD", "false"
//buildConfigField "String[]", "APPLICATION_LANGUAGES", "${getUsedAndroidLanguages()}"
resValue "string", "bc_notr__detected_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.1.3"
versionCode 32
vectorDrawables.useSupportLibrary = true
@ -54,6 +54,7 @@ android {
flavorGplay {
buildConfigField "boolean", "IS_GPLAY_BUILD", "true"
}*/
flavorTest {
applicationId "com.github.dfa.secondlion"
resValue 'string', 'app_name', "secondlion*"

View file

@ -7,6 +7,8 @@
<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"

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

@ -22,6 +22,7 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.AppCompatImageView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@ -36,7 +37,6 @@ 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;
@ -68,13 +68,12 @@ public class AspectListFragment extends ThemedFragment implements OnSomethingCli
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);
protected int getLayoutResId() {
return R.layout.recycler_list__fragment;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
app = (App) getActivity().getApplication();
@ -167,7 +166,7 @@ public class AspectListFragment extends ThemedFragment implements OnSomethingCli
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.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : ContextUtils.get().rcolor(R.color.alternate_row_color));
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
} else {
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : Color.WHITE);

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

@ -71,10 +71,10 @@ 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.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;
@ -199,7 +199,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 +207,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 +229,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 +285,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 {
@ -338,12 +331,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/Diaspora-for-Android/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 +369,9 @@ public class MainActivity extends ThemedActivity
*
* @param fragment Fragment to show
*/
protected void showFragment(CustomFragment fragment) {
protected void showFragment(ThemedFragment fragment) {
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();
@ -591,8 +602,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 +616,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 +694,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 +704,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 +846,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);

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.BLACK : 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));

View file

@ -35,7 +35,6 @@ 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;
@ -67,9 +66,8 @@ public class TagListFragment extends ThemedFragment implements OnSomethingClickL
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);
protected int getLayoutResId() {
return R.layout.recycler_list__fragment;
}
@Override
@ -166,7 +164,7 @@ public class TagListFragment extends ThemedFragment implements OnSomethingClickL
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.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : ContextUtils.get().rcolor(R.color.alternate_row_color));
holder.title.setTextColor(isAmoledColorMode ? Color.GRAY : Color.BLACK);
} else {
holder.root.setBackgroundColor(isAmoledColorMode ? Color.BLACK : Color.WHITE);

View file

@ -315,8 +315,8 @@ public class DiasporaPodList implements Iterable<DiasporaPodList.DiasporaPod>, S
}
/*
* Getter & Setter
*/
* Getter & Setter
*/
public List<DiasporaPodUrl> getPodUrls() {
return _podUrls;
}

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

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

@ -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,10 +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.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;

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,11 +27,12 @@ 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;
/**
@ -38,7 +40,7 @@ import java.util.List;
* Created by gsantner (http://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);
}
@ -367,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);
}

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
* 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/
* ----------------------------------------------------------------------------
*/
* Maintained by Gregor Santner, 2016-
* https://gsantner.net/
*
* License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing
* https://www.apache.org/licenses/LICENSE-2.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
/*
* 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.
*
@ -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

View file

@ -1,19 +1,18 @@
/*
* ------------------------------------------------------------------------------
* 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/
* ----------------------------------------------------------------------------
*/
package net.gsantner.opoc.util;
* Maintained by Gregor Santner, 2018-
* 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.preference;
import java.util.List;
@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection"})
@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection", "unused", "SameParameterValue"})
public interface PropertyBackend<TKEY, TTHIS> {
String getString(TKEY key, String defaultValue);

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
* https://github.com/gsantner/opoc/#licensing
* https://www.apache.org/licenses/LICENSE-2.0
*
#########################################################*/
/*
* This is a wrapper for settings based on SharedPreferences
@ -32,7 +31,7 @@
}
*/
package net.gsantner.opoc.util;
package net.gsantner.opoc.preference;
import android.annotation.SuppressLint;
import android.content.Context;
@ -49,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 implements PropertyBackend<String, 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;
}
@ -137,9 +136,9 @@ public class AppSettingsBase implements PropertyBackend<String, AppSettingsBase>
return (pref != null && pref.length > 0 ? pref[0] : _prefApp);
}
//#################################
//## Getter for resources
//#################################
//
// Getter for resources
//
public String rstr(@StringRes int stringKeyResourceId) {
return _context.getString(stringKeyResourceId);
}
@ -149,9 +148,9 @@ public class AppSettingsBase implements PropertyBackend<String, 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();
}
@ -236,9 +235,9 @@ public class AppSettingsBase implements PropertyBackend<String, AppSettingsBase>
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();
}
@ -320,9 +319,9 @@ public class AppSettingsBase implements PropertyBackend<String, AppSettingsBase>
}
//#################################
//## 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();
}
@ -339,9 +338,9 @@ public class AppSettingsBase implements PropertyBackend<String, 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();
}
@ -358,9 +357,9 @@ public class AppSettingsBase implements PropertyBackend<String, 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));
}
@ -377,9 +376,9 @@ public class AppSettingsBase implements PropertyBackend<String, 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();
}
@ -396,9 +395,9 @@ public class AppSettingsBase implements PropertyBackend<String, 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));
}
@ -451,49 +450,49 @@ public class AppSettingsBase implements PropertyBackend<String, AppSettingsBase>
}
@Override
public AppSettingsBase setString(String key, String value) {
public SharedPreferencesPropertyBackend setString(String key, String value) {
setString(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setInt(String key, int value) {
public SharedPreferencesPropertyBackend setInt(String key, int value) {
setInt(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setLong(String key, long value) {
public SharedPreferencesPropertyBackend setLong(String key, long value) {
setLong(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setBool(String key, boolean value) {
public SharedPreferencesPropertyBackend setBool(String key, boolean value) {
setBool(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setFloat(String key, float value) {
public SharedPreferencesPropertyBackend setFloat(String key, float value) {
setFloat(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setDouble(String key, double value) {
public SharedPreferencesPropertyBackend setDouble(String key, double value) {
setDouble(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setIntList(String key, List<Integer> value) {
public SharedPreferencesPropertyBackend setIntList(String key, List<Integer> value) {
setIntListOne(key, value, _prefApp);
return this;
}
@Override
public AppSettingsBase setStringList(String key, List<String> value) {
public SharedPreferencesPropertyBackend setStringList(String key, List<String> value) {
setStringListOne(key, value, _prefApp);
return this;
}

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, 2017-
* https://gsantner.net/
*
* License: Apache 2.0
* https://github.com/gsantner/opoc/#licensing
* https://www.apache.org/licenses/LICENSE-2.0
*
#########################################################*/
/*
* Requires:
@ -49,8 +48,8 @@ package net.gsantner.opoc.preference.nonsupport;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.Nullable;
import android.preference.ListPreference;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
@ -114,15 +113,13 @@ 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) + ";" + langId);
}
}
String aa = contextUtils.str("build_conf__detected_languages");
String a = aa;
// Sort languages naturally
Collections.sort(languages);

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
* https://github.com/gsantner/opoc/#licensing
* https://www.apache.org/licenses/LICENSE-2.0
*
#########################################################*/
package net.gsantner.opoc.util;
import android.app.Activity;

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
* 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,23 @@
/*
* ------------------------------------------------------------------------------
* 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
* 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.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;
@ -30,9 +27,11 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
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;
@ -46,19 +45,17 @@ 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.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.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.gsantner.opoc.format.markdown.SimpleMarkdownParser;
@ -71,13 +68,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) {
@ -88,57 +86,81 @@ 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 BOOL = "bool";
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 String str(String strResKey) {
return str(getResId(ResType.STRING, strResKey));
/**
* 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;
}
}
public Drawable drawable(@DrawableRes int resId) {
/**
* 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() {
@ -152,10 +174,14 @@ public class ContextUtils {
}
}
/**
* 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(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
try {
_context.startActivity(intent);
} catch (ActivityNotFoundException e) {
@ -164,14 +190,18 @@ public class ContextUtils {
}
/**
* Get field from PackageId.BuildConfig
* 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 = rstr("manifest_package_id");
pkg = (pkg != null ? pkg : _context.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();
@ -179,7 +209,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;
@ -187,27 +220,47 @@ 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;
}
/**
* 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));
}
}
}
@ -240,44 +293,61 @@ public class ContextUtils {
return sb.toString();
}
@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 (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();
@ -285,53 +355,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) {
@ -342,49 +433,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();
}
@ -400,6 +498,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;
@ -428,6 +538,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;
@ -436,31 +550,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 {
@ -471,13 +597,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);
@ -495,26 +625,11 @@ 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);
tintDrawable(item.getIcon(), iconColor);
@ -522,13 +637,18 @@ public class ContextUtils {
tintMenuItems(item.getSubMenu(), recurse, iconColor);
}
}
return this;
}
/**
* Loads {@link Drawable} by given {@link DrawableRes} and applies a color
*/
public Drawable tintDrawable(@DrawableRes int drawableRes, @ColorInt int color) {
return tintDrawable(_context.getResources().getDrawable(drawableRes), 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);
@ -537,16 +657,19 @@ public class ContextUtils {
return drawable;
}
@SuppressLint("PrivateApi")
public ContextUtils setSubMenuIconsVisiblity(Menu menu, boolean visible) {
/**
* 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,340 @@
/*#######################################################
*
* 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.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.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
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 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;
}
}
}

View file

@ -0,0 +1,223 @@
/*#######################################################
*
* 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.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
* 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 {
private static final int CODE_PERMISSION_EXTERNAL_STORAGE = 4000;
private 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,453 @@
/*#######################################################
*
* 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.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());
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 : "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);
}
/**
* 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))) {
// 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"}) {
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;
}
}
}
}
}
return null;
}
}

View file

@ -108,14 +108,14 @@
style="@android:style/TextAppearance.DeviceDefault.Large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fragment_license__license"
android:text="@string/licenses"
android:layout_marginBottom="8dp"/>
<TextView
android:textAppearance="@style/TextAppearance.AppCompat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fragment_license__license_text"/>
android:text="@string/copyright_license_text_official"/>
<Button
android:id="@+id/fragment_license__license_button"

View file

@ -14,9 +14,19 @@
<item
android:id="@+id/action_take_screenshot"
android:title="@string/share__take_screenshot" />
<item
android:id="@+id/action_share_pdf"
android:title="@string/pdf"
android:visible="false" />
<item
android:id="@+id/action_share_link"
android:title="@string/share__share_link_as_text" />
<item
android:id="@+id/action_share_link_to_clipboard"
android:title="@string/copy_link_to_clipboard" />
<item
android:id="@+id/action_create_launcher_shortcut"
android:title="@string/launcher_shortcut" />
</menu>
</item>

View file

@ -30,7 +30,7 @@
<!-- Drawer, Menu, Toolbar, ContextMenu -->
<string name="context_menu_share_image">Del billede</string>
<string name="context_menu_open_external_browser">Åben i ekstern browser&#8230;</string>
<string name="context_menu_copy_link">Kopier link-adresse til udklipsholder</string>
<string name="copy_link_to_clipboard">Kopier link-adresse til udklipsholder</string>
<!-- More from MainActivity -->
<!-- Permissions -->
</resources>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Bild speichern</string>
<string name="context_menu_share_image">Bild teilen</string>
<string name="context_menu_open_external_browser">In externem Browser öffnen&#8230;</string>
<string name="context_menu_copy_link">Link-Adresse kopieren</string>
<string name="copy_link_to_clipboard">Link-Adresse kopieren</string>
<string name="context_menu_copy_image_link">Bild-Adresse kopieren</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Konnte Bild nicht laden…</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Guardar imagen</string>
<string name="context_menu_share_image">Compartir imagen</string>
<string name="context_menu_open_external_browser">Abrir en navegador externo&#8230;</string>
<string name="context_menu_copy_link">Copiar dirección del enlace al portapapeles</string>
<string name="copy_link_to_clipboard">Copiar dirección del enlace al portapapeles</string>
<string name="context_menu_copy_image_link">Copiar dirección de imagen al portapapeles</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">No se pudo cargar la imagen</string>

View file

@ -1,50 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com-->
<resources>
<string name="about_activity__title_about_app">À propos</string>
<string name="about_activity__title_about_license">Licence</string>
<string name="about_activity__title_debug_info">Déboguer</string>
<string name="fragment_debug__section_app">Application</string>
<string name="fragment_debug__section_device">Appareil</string>
<string name="fragment_debug__section_pod">Pod diaspora*</string>
<string name="fragment_debug__section_log">Journal de débogage</string>
<string name="fragment_debug__section_log_spam">Log de débogage (Verbeux)</string>
<string name="fragment_debug__app_version">Version app : %1$s</string>
<string name="fragment_debug__android_version">Version Android : %1$s</string>
<string name="fragment_debug__device_name">Nom de l\'appareil : %1$s</string>
<string name="fragment_debug__app_codename">Nom de code : %1$s</string>
<string name="fragment_debug__pod_profile_name">Pod alias : %1$s</string>
<string name="fragment_debug__pod_profile_url">Adresse du pod : %1$s</string>
<string name="fragment_debug__toast_log_copied">Journal de débogage copié dans le presse-papiers</string>
<string name="fragment_about__about_text">dandelion* est votre application compagnon pour naviguer sur le réseau social diaspora*. Il ajoute des fonctionnalités telles que des barres doutils utiles et la prise en charge pour les serveurs proxy comme le réseau Tor à votre expérience sociale.</string>
<string name="fragment_about__contribute">Contribuez au code !</string>
<string name="fragment_about__contribute_text">dandelion* est libre, et suit les idées du projet diaspora*. Si vous voulez contribuer, allez-y ! Actuellement, nous sommes une toute petite équipe, donc nous apprécierions tout type d\'aide !</string>
<string name="fragment_about__contribute_button">Obtenir la source</string>
<string name="fragment_about__translate">Traduire l\'application !</string>
<string name="fragment_about__translate_text">Lapplication nest pas disponible dans votre langue ? Vous pouvez changer cela ! Pourquoi ne pas nous aider à la traduire ? Nous utilisons la plate-forme crowdin pour permettre à quiconque de traduire lapplication.</string>
<string name="fragment_about__translate_button">Je voudrais traduire</string>
<string name="fragment_about__feedback">Donnez votre avis !</string>
<string name="fragment_about__feedback_text">dandelion* est encore en développement, donc si vous avez des suggestions ou nimporte quel genre de retour, veuillez utiliser notre traqueur de bogues pour nous le faire savoir !</string>
<string name="fragment_about__feedback_button">Signaler un bug</string>
<string name="fragment_about__spread_the_word">Faîtes passer le mot !</string>
<string name="fragment_about__spread_the_word_text">Parlez de diaspora* et #dandelion à vos amis et votre famille ! Pourquoi ne pas bloguer à propos de votre expérience ? Nous aimerions la lire !</string>
<string name="fragment_about__spread_the_word_button">Partager cette application</string>
<string name="fragment_about__spread_the_word_share_text"> ! Découvrez #dandelion ! %1$s</string>
<!-- License & help (large amount of text) -->
<string name="fragment_license__maintainers">Mainteneurs</string>
<string name="fragment_license__maintainers_text">Cette application est actuellement développée et maintenue par &lt;br&gt;&lt;br&gt;%1$s</string>
<string name="fragment_license__contributors">Contributeurs</string>
<string name="fragment_license__contributors_thank_you">%1$s&lt;br&gt;&lt;br&gt;Merci !</string>
<string name="fragment_license__license_button">Licence GNU GPLv3+</string>
<string name="fragment_license__thirdparty_libs">Bibliothèques tierces</string>
<string name="fragment_license__thirdparty_libs_text">Les bibliothèques suivantes sont utilisées :</string>
<string name="fragment_license__misc_leafpic">Nous avons pris des inspirations et du code de LeafPic. Allez voir, c\'est aussi un logiciel libre !</string>
<string name="fragment_license__misc_leafpic_button">En savoir plus</string>
<string name="about_activity__title_about_app">À propos</string>
<string name="about_activity__title_about_license">Licence</string>
<string name="about_activity__title_debug_info">Déboguer</string>
<string name="fragment_debug__section_app">Application</string>
<string name="fragment_debug__section_device">Appareil</string>
<string name="fragment_debug__section_pod">Pod diaspora*</string>
<string name="fragment_debug__section_log">Journal de débogage</string>
<string name="fragment_debug__section_log_spam">Log de débogage (Verbeux)</string>
<string name="fragment_debug__app_version">Version app : %1$s</string>
<string name="fragment_debug__android_version">Version Android : %1$s</string>
<string name="fragment_debug__device_name">Nom de l\'appareil : %1$s</string>
<string name="fragment_debug__app_codename">Nom de code : %1$s</string>
<string name="fragment_debug__pod_profile_name">Pod alias : %1$s</string>
<string name="fragment_debug__pod_profile_url">Adresse du pod : %1$s</string>
<string name="fragment_debug__toast_log_copied">Journal de débogage copié dans le presse-papiers</string>
<string name="fragment_about__about_text">dandelion* est votre application compagnon pour naviguer sur le réseau social diaspora*. Il ajoute des fonctionnalités telles que des barres doutils utiles et la prise en charge pour les serveurs proxy comme le réseau Tor à votre expérience sociale.</string>
<string name="fragment_about__contribute">Contribuez au code !</string>
<string name="fragment_about__contribute_text">dandelion* est libre, et suit les idées du projet diaspora*. Si vous voulez contribuer, allez-y ! Actuellement, nous sommes une toute petite équipe, donc nous apprécierions tout type d\'aide !</string>
<string name="fragment_about__contribute_button">Obtenir la source</string>
<string name="fragment_about__translate">Traduire l\'application !</string>
<string name="fragment_about__translate_text">Lapplication nest pas disponible dans votre langue ? Vous pouvez changer cela ! Pourquoi ne pas nous aider à la traduire ? Nous utilisons la plate-forme crowdin pour permettre à quiconque de traduire lapplication.</string>
<string name="fragment_about__translate_button">Je voudrais traduire</string>
<string name="fragment_about__feedback">Donnez votre avis !</string>
<string name="fragment_about__feedback_text">dandelion* est encore en développement, donc si vous avez des suggestions ou nimporte quel genre de retour, veuillez utiliser notre traqueur de bogues pour nous le faire savoir !</string>
<string name="fragment_about__feedback_button">Signaler un bug</string>
<string name="fragment_about__spread_the_word">Faîtes passer le mot !</string>
<string name="fragment_about__spread_the_word_text">Parlez de diaspora* et #dandelion à vos amis et votre famille ! Pourquoi ne pas bloguer à propos de votre expérience ? Nous aimerions la lire !</string>
<string name="fragment_about__spread_the_word_button">Partager cette application</string>
<string name="fragment_about__spread_the_word_share_text"> ! Découvrez #dandelion ! %1$s</string>
<!-- License & help (large amount of text) -->
<string name="fragment_license__maintainers">Mainteneurs</string>
<string name="fragment_license__maintainers_text">Cette application est actuellement développée et maintenue par &lt;br&gt;&lt;br&gt;%1$s</string>
<string name="fragment_license__contributors">Contributeurs</string>
<string name="fragment_license__contributors_thank_you">%1$s&lt;br&gt;&lt;br&gt;Merci !</string>
<string name="fragment_license__license_button">Licence GNU GPLv3+</string>
<string name="fragment_license__thirdparty_libs">Bibliothèques tierces</string>
<string name="fragment_license__thirdparty_libs_text">Les bibliothèques suivantes sont utilisées :</string>
<string name="fragment_license__misc_leafpic">Nous avons pris des inspirations et du code de LeafPic. Allez voir, c\'est aussi un logiciel libre !</string>
<string name="fragment_license__misc_leafpic_button">En savoir plus</string>
</resources>

View file

@ -1,111 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com-->
<resources>
<!-- Key Names (Untranslatable) -->
<!-- Operability -->
<string name="pref_title__topbar_stream_shortcut">La barre du haut charge le flux</string>
<string name="pref_desc__topbar_stream_shortcut">Cliquez sur un espace vide sur la barre du haut pour ouvrir le flux</string>
<!-- Category Titles -->
<string name="pref_cat__visuals">Apparence</string>
<string name="pref_cat__network">Paramètres du réseau</string>
<string name="pref_cat__pod_settings">Paramètres du pod</string>
<string name="pref_cat__operability">Opérabilité</string>
<!-- Visuals -->
<!-- Navigiation Slider -->
<string name="pref_title__sub_nav_slider">Curseur de navigation</string>
<string name="pref_desc__sub_nav_slider">Visibilité de contrôle des entrées dans le volet de navigation</string>
<string name="pref_cat__visibility_nav_items__user">Utilisateur</string>
<string name="pref_cat__visibility_nav_items__general">Général</string>
<string name="pref_cat__visibility_nav_items__admin">Admin</string>
<!-- Themes -->
<string name="pref_title__themes">Thème et couleurs</string>
<string name="pref_desc__themes">Contrôler les couleurs utilisées pour l\'application</string>
<string name="pref_title__primary_color">Couleur principale</string>
<string name="pref_desc__primary_color">Couleur des barres d\'outils</string>
<string name="pref_title__accent_color">Couleur secondaire</string>
<string name="pref_desc__accent_color">Couleur de la barre de progression</string>
<string name="pref_title__primary_color__amoled_mode">Mode AMOLED</string>
<string name="pref_desc__primary_color__amoled_mode">Remplacez les couleurs avec du noir compatible avec les affichages AMOLED, sur plusieurs parties de l\'application. Pour naviguer dans diaspora* avec un affichage sombre, vous pouvez aussi utiliser le thème Dark, que vous trouverez dans vos paramètres personnels de votre compte diaspora*.</string>
<!-- Notifications dropdown -->
<string name="pref_title__extended_notifications">Notifications étendues</string>
<string name="pref_desc__extended_notifications">Étendre la cloche de notifications avec un menu déroulant qui affiche les catégories de notification</string>
<string name="pref_desc__language">Change la langue de l\'application. Redémarrez l\'application pour que les changements prennent effet</string>
<string name="pref_title__language">Langue</string>
<string name="language_system">Langue du système</string>
<!-- Font size -->
<string name="pref_desc__font_size">Contrôle de la taille du texte pour l\'affichage web</string>
<string name="pref_title__font_size">Taille de la police</string>
<string name="font_size_normal">Normal</string>
<string name="font_size_large">Large</string>
<string name="font_size_huge">Très grand</string>
<!-- Load images -->
<string name="pref_title__load_images">Charger les images</string>
<string name="pref_desc__load_images">Désactiver le chargements des images pour préserver la data mobile</string>
<!-- Screen rotation -->
<string name="pref_title__screen_rotation">Rotation de l\'écran</string>
<string name="pref_desc__screen_rotation">Rotation automatique de l\'écran</string>
<string name="rotation_system">Par défaut</string>
<string name="rotation_sensor">Capteur\n
(ignore les paramètres du système)</string>
<string name="rotation_portrait">Portrait</string>
<string name="rotation_landscape">Paysage</string>
<!-- Proxy -->
<string name="pref_title__http_proxy_load_tor_preset">Charger la pré-configuration Tor</string>
<string name="pref_desc__http_proxy_load_tor_preset">Charger les paramètres proxy pour Tor (Orbot) HTTP Proxy</string>
<string name="pref_title__sub_proxy">Proxy</string>
<string name="pref_title__proxy_enabled">Activer Proxy</string>
<string name="pref_desc__http_proxy_enabled">Proxy pour dandelion* pour contourner les pare-feux.\n
Peut nécessiter un redémarrage</string>
<string name="pref_title__http_proxy_host">Hôte</string>
<string name="pref_title__http_proxy_port">Port</string>
<string name="toast__proxy_disabled__restart_required">L\'application a besoin d\'être redémarrée pour désactiver l\'usage du proxy</string>
<string name="toast__proxy_orbot_preset_loaded">Préréglages proxy Orbot chargés</string>
<!-- Chrome custom tabs -->
<string name="pref_desc__chrome_custom_tabs_enabled">Ouvrez les liens externes avec Chrome Custom Tabs. Chromium ou Google Chrome doit être installé pour cette fonctionnalité. \n
NOTE IMPORTANTE : Chrome Custom Tabs n\'utilisent pas les serveurs proxy configurés !</string>
<!-- Diaspora Settings -->
<string name="pref_title__personal_settings">Paramètres personnels</string>
<string name="pref_desc__personal_settings">Ouvrir vos paramètres de compte diaspora*</string>
<string name="pref_desc__manage_contacts">Gérez vos contacts</string>
<string name="pref_title__manage_tags">Gérer les Hashtags</string>
<string name="pref_desc__manage_tags">Ne plus suivre les tags suivis</string>
<string name="pref_title__change_account">Changer de compte</string>
<string name="pref_desc__change_account">Effacer les données de session locale et passer à un autre pod/compte diaspora*</string>
<string name="pref_warning__change_account">Cette opération va effacer les cookies et données de session. Voulez-vous vraiment changer de compte/pod?</string>
<string name="pref_title__clear_cache">Vider le cache</string>
<string name="pref_desc__clear_cache">Vider le cache</string>
<string name="pref_desc__intellihide_toolbars">Masquer les barres doutils en haut et en bas automatiquement lors du défilement</string>
<string name="pref_title__intellihide_toolbars">Masquer la barre d\'outils</string>
<string name="pref_title__append_shared_via_app">Ajouter partagé par avis-</string>
<string name="pref_desc__append_shared_via_app">Ajouter une référence à cette application aux partages: [via #dandelion]</string>
<!-- More -->
<string name="pref_title__sub_miscelaneous">Divers</string>
<string name="pref_title__wipe_settings">Réinitialisation complète</string>
<string name="pref_desc__wipe_settings">Effacer localement tous les réglages relatifs à l\'application et déconnexion de tous les comptes</string>
<string name="dialog_content__wipe_settings">Ceci va réinitialiser tous les changements à leurs valeurs par défaut et vous déconnecter de tous les pods. Vos images téléchargées resteront intactes. Êtes-vous sûr de vouloir continuer ?</string>
<string name="pref_desc__adblock_enable">Activer un bloqueur de pub. Des pubs peuvent être incluses, dans des vues embarquées par exemple</string>
<string name="pref_title__adblock_enable">Bloquer les publicités</string>
<!-- Key Names (Untranslatable) -->
<!-- Operability -->
<string name="pref_title__topbar_stream_shortcut">La barre du haut charge le flux</string>
<string name="pref_desc__topbar_stream_shortcut">Cliquez sur un espace vide sur la barre du haut pour ouvrir le flux</string>
<!-- Category Titles -->
<string name="pref_cat__visuals">Apparence</string>
<string name="pref_cat__network">Paramètres du réseau</string>
<string name="pref_cat__pod_settings">Paramètres du pod</string>
<string name="pref_cat__operability">Opérabilité</string>
<!-- Visuals -->
<!-- Navigiation Slider -->
<string name="pref_title__sub_nav_slider">Curseur de navigation</string>
<string name="pref_desc__sub_nav_slider">Visibilité de contrôle des entrées dans le volet de navigation</string>
<string name="pref_cat__visibility_nav_items__user">Utilisateur</string>
<string name="pref_cat__visibility_nav_items__general">Général</string>
<string name="pref_cat__visibility_nav_items__admin">Admin</string>
<!-- Themes -->
<string name="pref_title__themes">Thème et couleurs</string>
<string name="pref_desc__themes">Contrôler les couleurs utilisées pour l\'application</string>
<string name="pref_title__primary_color">Couleur principale</string>
<string name="pref_desc__primary_color">Couleur des barres d\'outils</string>
<string name="pref_title__accent_color">Couleur secondaire</string>
<string name="pref_desc__accent_color">Couleur de la barre de progression</string>
<string name="pref_title__primary_color__amoled_mode">Mode AMOLED</string>
<string name="pref_desc__primary_color__amoled_mode">Remplacez les couleurs avec du noir compatible avec les affichages AMOLED, sur plusieurs parties de l\'application. Pour naviguer dans diaspora* avec un affichage sombre, vous pouvez aussi utiliser le thème Dark, que vous trouverez dans vos paramètres personnels de votre compte diaspora*.</string>
<!-- Notifications dropdown -->
<string name="pref_title__extended_notifications">Notifications étendues</string>
<string name="pref_desc__extended_notifications">Étendre la cloche de notifications avec un menu déroulant qui affiche les catégories de notification</string>
<string name="pref_desc__language">Change la langue de l\'application. Redémarrez l\'application pour que les changements prennent effet</string>
<string name="pref_title__language">Langue</string>
<string name="language_system">Langue du système</string>
<!-- Font size -->
<string name="pref_desc__font_size">Contrôle de la taille du texte pour l\'affichage web</string>
<string name="pref_title__font_size">Taille de la police</string>
<string name="font_size_normal">Normal</string>
<string name="font_size_large">Large</string>
<string name="font_size_huge">Très grand</string>
<!-- Load images -->
<string name="pref_title__load_images">Charger les images</string>
<string name="pref_desc__load_images">Désactiver le chargements des images pour préserver la data mobile</string>
<!-- Screen rotation -->
<string name="pref_title__screen_rotation">Rotation de l\'écran</string>
<string name="pref_desc__screen_rotation">Rotation automatique de l\'écran</string>
<string name="rotation_system">Par défaut</string>
<string name="rotation_sensor">Capteur\n(ignore les paramètres du système)</string>
<string name="rotation_portrait">Portrait</string>
<string name="rotation_landscape">Paysage</string>
<!-- Proxy -->
<string name="pref_title__http_proxy_load_tor_preset">Charger la pré-configuration Tor</string>
<string name="pref_desc__http_proxy_load_tor_preset">Charger les paramètres proxy pour Tor (Orbot) HTTP Proxy</string>
<string name="pref_title__sub_proxy">Proxy</string>
<string name="pref_title__proxy_enabled">Activer Proxy</string>
<string name="pref_desc__http_proxy_enabled">Proxy pour dandelion* pour contourner les pare-feux.\nPeut nécessiter un redémarrage</string>
<string name="pref_title__http_proxy_host">Hôte</string>
<string name="pref_title__http_proxy_port">Port</string>
<string name="toast__proxy_disabled__restart_required">L\'application a besoin d\'être redémarrée pour désactiver l\'usage du proxy</string>
<string name="toast__proxy_orbot_preset_loaded">Préréglages proxy Orbot chargés</string>
<!-- Chrome custom tabs -->
<string name="pref_desc__chrome_custom_tabs_enabled">Ouvrez les liens externes avec Chrome Custom Tabs. Chromium ou Google Chrome doit être installé pour cette fonctionnalité. \nNOTE IMPORTANTE : Chrome Custom Tabs n\'utilisent pas les serveurs proxy configurés !</string>
<!-- Diaspora Settings -->
<string name="pref_title__personal_settings">Paramètres personnels</string>
<string name="pref_desc__personal_settings">Ouvrir vos paramètres de compte diaspora*</string>
<string name="pref_desc__manage_contacts">Gérez vos contacts</string>
<string name="pref_title__manage_tags">Gérer les Hashtags</string>
<string name="pref_desc__manage_tags">Ne plus suivre les tags suivis</string>
<string name="pref_title__change_account">Changer de compte</string>
<string name="pref_desc__change_account">Effacer les données de session locale et passer à un autre pod/compte diaspora*</string>
<string name="pref_warning__change_account">Cette opération va effacer les cookies et données de session. Voulez-vous vraiment changer de compte/pod?</string>
<string name="pref_title__clear_cache">Vider le cache</string>
<string name="pref_desc__clear_cache">Vider le cache</string>
<string name="pref_desc__intellihide_toolbars">Masquer les barres doutils en haut et en bas automatiquement lors du défilement</string>
<string name="pref_title__intellihide_toolbars">Masquer la barre d\'outils</string>
<string name="pref_title__append_shared_via_app">Ajouter partagé par avis-</string>
<string name="pref_desc__append_shared_via_app">Ajouter une référence à cette application aux partages: [via #dandelion]</string>
<!-- More -->
<string name="pref_title__sub_miscelaneous">Divers</string>
<string name="pref_title__wipe_settings">Réinitialisation complète</string>
<string name="pref_desc__wipe_settings">Effacer localement tous les réglages relatifs à l\'application et déconnexion de tous les comptes</string>
<string name="dialog_content__wipe_settings">Ceci va réinitialiser tous les changements à leurs valeurs par défaut et vous déconnecter de tous les pods. Vos images téléchargées resteront intactes. Êtes-vous sûr de vouloir continuer ?</string>
<string name="pref_desc__adblock_enable">Activer un bloqueur de pub. Des pubs peuvent être incluses, dans des vues embarquées par exemple</string>
<string name="pref_title__adblock_enable">Bloquer les publicités</string>
</resources>

View file

@ -1,96 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com-->
<resources>
<!-- Application -->
<string name="navigation_drawer_open">Ouvrir le tiroir de navigation</string>
<string name="navigation_drawer_close">Fermer le tiroir de navigation</string>
<string name="reload">Rafraîchir</string>
<!-- Common Words -->
<string name="settings">Paramètres</string>
<string name="notifications">Notifications</string>
<string name="conversations">Discussions</string>
<string name="stream">Flux</string>
<string name="profile">Profil</string>
<string name="aspects">Aspects</string>
<string name="activities">Activités</string>
<string name="liked">Aimés</string>
<string name="commented">Commentés</string>
<string name="mentions">Mentionnés</string>
<string name="public_">Publique</string>
<string name="search">Recherche</string>
<string name="contacts">Contacts</string>
<string name="changelog">Nouveautés</string>
<string name="statistics">Statistiques</string>
<!-- Notifications dropdown menu -->
<string name="notifications__all">Toutes les notifications</string>
<string name="notifications__also_commented">Aussi commenté</string>
<string name="notifications__comment_on_post">Commentaires sur posts</string>
<string name="notifications__liked">Aimés</string>
<string name="notifications__mentioned">Mentionnés</string>
<string name="notifications__reshared">Repartagés</string>
<string name="notifications__started_sharing">A commencé à partager</string>
<!-- Pod Activity -->
<string name="podlist_error">Erreur : impossible de récupérer la liste des pods !</string>
<string name="no_internet">Désolé, vous devez être connecté à Internet pour continuer</string>
<string name="confirmation">Vérification</string>
<string name="confirm_exit">Souhaitez-vous quitter ?</string>
<!-- Drawer, Menu, Toolbar, ContextMenu -->
<string name="nav_menu_more">Plus</string>
<string name="nav_help_license">A propos | Aide</string>
<string name="nav_followed_tags">Hashtags suivis</string>
<string name="nav_public_activities">Activités publiques</string>
<string name="nav_reports">Signalements</string>
<string name="share__share_link_as_text">Partager le lien comme texte</string>
<string name="share__share_screenshot">Partager la capture d\'écran d\'un site web</string>
<string name="share__take_screenshot">Prendre une capture d\'écran d\'un site web</string>
<string name="share__toast_saved_image_to_location">Enregistrer l\'image sous</string>
<string name="share__toast_screenshot">Enregistrer la capture d\'écran sous :</string>
<string name="share__toast_link_address_copied">Lien copié…</string>
<string name="new_post">Nouveau message</string>
<string name="action_go_to_top">Retour en haut</string>
<string name="action_search_by_tags_or_persons">Recherche par tags ou par personnes</string>
<string name="action_exit_app">Quitter l\'application</string>
<string name="action_toggle_desktop_page">Activer/désactiver la vue bureau/mobile</string>
<string name="action_share_dotdotdot">Partager…</string>
<string name="search_alert_tag">par tags</string>
<string name="search_alert_people">par personne</string>
<string name="search_alert_bypeople_validate_needsomedata">Veuillez ajouter un nom</string>
<string name="context_menu_share_link">Partager un lien</string>
<string name="context_menu_save_image">Enregistrer l\'image</string>
<string name="context_menu_share_image">Partager l\'image</string>
<string name="context_menu_open_external_browser">Ouvrir dans un navigateur externe…</string>
<string name="copy_link_to_clipboard">Copier le lien dans le presse-papier</string>
<string name="context_menu_copy_image_link">Copier le lien de l\'image dans le presse-papiers</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Impossible de récupérer l\'image</string>
<!-- Permissions -->
<string name="permissions_screenshot">Vous devez autoriser \"Modifier ou supprimer le contenu de la carte Sd \" pour sauvegarder les captures d\'écrans. Ensuite, vous devriez fermer l\'application ou redémarrer votre téléphone. Si vous n\'avez pas autorisé l\'accès à l\'espace de stockage mais que vous souhaitez utiliser la fonctionnalité de prise de capture d\'écran plus tard, vous pouvez donner la permission plus tard. Veuillez ouvrir : Paramètres - applications - dandelion*. Dans la section Autorisations, vous pouvez autoriser \"Modifier ou supprimer le contenu de la carte Sd \".</string>
<string name="permissions_image">Vous devez autoriser \"Modifier ou supprimer le contenu de la carte Sd \" pour sauvegarder/téléverser les images. Ensuite, vous devriez fermer l\'application ou redémarrer votre téléphone. Si vous n\'avez pas autorisé l\'accès à l\'espace de stockage mais que vous souhaitez sauvegarder des images ultérieurement, vous pouvez donner la permission plus tard. Veuillez ouvrir : Paramètres - applications - dandelion*. Dans la section Autorisations, vous pouvez autoriser \"Modifier ou supprimer le contenu de la carte Sd \".</string>
<string name="permission_denied">Permission refusée.</string>
<string name="permission_granted_try_again">Permission accordée. Veuillez réessayer.</string>
<string name="podselection__custom_pod">Pod personnalisé</string>
<string name="pod_name">Nom du Pod</string>
<string name="http_protocol">Protocole</string>
<string name="pod_address">Adresse du Pod</string>
<string name="missing_value">Valeur manquante</string>
<string name="jump_to_last_visited_timestamp_in_stream">Sauter à la dernière page visitée dans le flux ?</string>
<string name="pref_summary__is_statusbar_hidden">Cacher la barre de statut sur la vue principale</string>
<string name="pref_title__is_statusbar_hidden">Cacher la barre de statut</string>
<string name="pref_summary__show_title">Afficher le titre dans la vue principale</string>
<string name="pref_title__show_title">Afficher le titre</string>
<string name="launcher_shortcut">Raccourci de l\'écran d\'accueil</string>
<!-- Application -->
<string name="navigation_drawer_open">Ouvrir le tiroir de navigation</string>
<string name="navigation_drawer_close">Fermer le tiroir de navigation</string>
<string name="reload">Rafraîchir</string>
<!-- Common Words -->
<string name="settings">Paramètres</string>
<string name="notifications">Notifications</string>
<string name="conversations">Discussions</string>
<string name="stream">Flux</string>
<string name="profile">Profil</string>
<string name="aspects">Aspects</string>
<string name="activities">Activités</string>
<string name="liked">Aimés</string>
<string name="commented">Commentés</string>
<string name="mentions">Mentionnés</string>
<string name="public_">Publique</string>
<string name="search">Recherche</string>
<string name="contacts">Contacts</string>
<string name="changelog">Nouveautés</string>
<string name="statistics">Statistiques</string>
<!-- Notifications dropdown menu -->
<string name="notifications__all">Toutes les notifications</string>
<string name="notifications__also_commented">Aussi commenté</string>
<string name="notifications__comment_on_post">Commentaires sur posts</string>
<string name="notifications__liked">Aimés</string>
<string name="notifications__mentioned">Mentionnés</string>
<string name="notifications__reshared">Repartagés</string>
<string name="notifications__started_sharing">A commencé à partager</string>
<!-- Pod Activity -->
<string name="podlist_error">Erreur : impossible de récupérer la liste des pods !</string>
<string name="no_internet">Désolé, vous devez être connecté à Internet pour continuer</string>
<string name="confirmation">Vérification</string>
<string name="confirm_exit">Souhaitez-vous quitter ?</string>
<!-- Drawer, Menu, Toolbar, ContextMenu -->
<string name="nav_menu_more">Plus</string>
<string name="nav_help_license">A propos | Aide</string>
<string name="nav_followed_tags">Hashtags suivis</string>
<string name="nav_public_activities">Activités publiques</string>
<string name="nav_reports">Signalements</string>
<string name="share__share_link_as_text">Partager le lien comme texte</string>
<string name="share__share_screenshot">Partager la capture d\'écran d\'un site web</string>
<string name="share__take_screenshot">Prendre une capture d\'écran d\'un site web</string>
<string name="share__toast_saved_image_to_location">Enregistrer l\'image sous</string>
<string name="share__toast_screenshot">Enregistrer la capture d\'écran sous :</string>
<string name="share__toast_link_address_copied">Lien copié&#8230;</string>
<string name="new_post">Nouveau message</string>
<string name="action_go_to_top">Retour en haut</string>
<string name="action_search_by_tags_or_persons">Recherche par tags ou par personnes</string>
<string name="action_exit_app">Quitter l\'application</string>
<string name="action_toggle_desktop_page">Activer/désactiver la vue bureau/mobile</string>
<string name="action_share_dotdotdot">Partager&#8230;</string>
<string name="search_alert_tag">par tags</string>
<string name="search_alert_people">par personne</string>
<string name="search_alert_bypeople_validate_needsomedata">Veuillez ajouter un nom</string>
<string name="context_menu_share_link">Partager un lien</string>
<string name="context_menu_save_image">Enregistrer l\'image</string>
<string name="context_menu_share_image">Partager l\'image</string>
<string name="context_menu_open_external_browser">Ouvrir dans un navigateur externe&#8230;</string>
<string name="copy_link_to_clipboard">Copier le lien dans le presse-papier</string>
<string name="context_menu_copy_image_link">Copier le lien de l\'image dans le presse-papiers</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Impossible de récupérer l\'image</string>
<!-- Permissions -->
<string name="permissions_screenshot">Vous devez autoriser \"Modifier ou supprimer le contenu de la carte Sd \" pour sauvegarder les captures d\'écrans. Ensuite, vous devriez fermer l\'application ou redémarrer votre téléphone. Si vous n\'avez pas autorisé l\'accès à l\'espace de stockage mais que vous souhaitez utiliser la fonctionnalité de prise de capture d\'écran plus tard, vous pouvez donner la permission plus tard. Veuillez ouvrir : Paramètres - applications - dandelion*. Dans la section Autorisations, vous pouvez autoriser \"Modifier ou supprimer le contenu de la carte Sd \".</string>
<string name="permissions_image">Vous devez autoriser \"Modifier ou supprimer le contenu de la carte Sd \" pour sauvegarder/téléverser les images. Ensuite, vous devriez fermer l\'application ou redémarrer votre téléphone. Si vous n\'avez pas autorisé l\'accès à l\'espace de stockage mais que vous souhaitez sauvegarder des images ultérieurement, vous pouvez donner la permission plus tard. Veuillez ouvrir : Paramètres - applications - dandelion*. Dans la section Autorisations, vous pouvez autoriser \"Modifier ou supprimer le contenu de la carte Sd \".</string>
<string name="permission_denied">Permission refusée.</string>
<string name="permission_granted_try_again">Permission accordée. Veuillez réessayer.</string>
<string name="podselection__custom_pod">Pod personnalisé</string>
<string name="pod_name">Nom du Pod</string>
<string name="http_protocol">Protocole</string>
<string name="pod_address">Adresse du Pod</string>
<string name="missing_value">Valeur manquante</string>
<string name="jump_to_last_visited_timestamp_in_stream">Sauter à la dernière page visitée dans le flux ?</string>
<string name="pref_summary__is_statusbar_hidden">Cacher la barre de statut sur la vue principale</string>
<string name="pref_title__is_statusbar_hidden">Cacher la barre de statut</string>
<string name="pref_summary__show_title">Afficher le titre dans la vue principale</string>
<string name="pref_title__show_title">Afficher le titre</string>
<string name="launcher_shortcut">Raccourci de l\'écran d\'accueil</string>
</resources>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Gardar imaxe</string>
<string name="context_menu_share_image">Compartir imaxe</string>
<string name="context_menu_open_external_browser">Abrir nun navegador externo&#8230;</string>
<string name="context_menu_copy_link">Copiar ligazón ao portapapeis</string>
<string name="copy_link_to_clipboard">Copiar ligazón ao portapapeis</string>
<string name="context_menu_copy_image_link">Copia enderezo da imaxe ao portapapeis</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Non se cargou a imaxe</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Kép mentése</string>
<string name="context_menu_share_image">Kép megosztása</string>
<string name="context_menu_open_external_browser">Megnyitás külső böngészőben&#8230;</string>
<string name="context_menu_copy_link">Link címének másolása a vágólapra</string>
<string name="copy_link_to_clipboard">Link címének másolása a vágólapra</string>
<string name="context_menu_copy_image_link">Kép címének másolása a vágólapra</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Nem lehet betölteni a képet</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Salva immagine</string>
<string name="context_menu_share_image">Condividi immagine</string>
<string name="context_menu_open_external_browser">Apri nel browser&#8230;</string>
<string name="context_menu_copy_link">Copia link negli appunti</string>
<string name="copy_link_to_clipboard">Copia link negli appunti</string>
<string name="context_menu_copy_image_link">Copia indirizzo dell\'immagine negli appunti</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Impossibile caricare immagine</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">画像を保存</string>
<string name="context_menu_share_image">画像をシェア</string>
<string name="context_menu_open_external_browser">外部ブラウザーで開く&#8230;</string>
<string name="context_menu_copy_link">リンクアドレスをクリップボードへコピー</string>
<string name="copy_link_to_clipboard">リンクアドレスをクリップボードへコピー</string>
<string name="context_menu_copy_image_link">画像のアドレスをクリップボードへコピー</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">画像を読み込むことができません</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Sekles tugna</string>
<string name="context_menu_share_image">Bḍu tugna</string>
<string name="context_menu_open_external_browser">Ldi deg iminig azɣaray&#8230;</string>
<string name="context_menu_copy_link">Nɣel aseɣwen ɣef afus</string>
<string name="copy_link_to_clipboard">Nɣel aseɣwen ɣef afus</string>
<string name="context_menu_copy_image_link">Nɣel tugna ɣef afus</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Ur izmir ara ad d-isali tugna</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">ചിത്രം സംരക്ഷിക്കുക</string>
<string name="context_menu_share_image">ചിത്രം പങ്കുവയ്ക്കുക</string>
<string name="context_menu_open_external_browser">പുറമെയുള്ള ബ്രൗസറിൽ തുറക്കുക&#8230;</string>
<string name="context_menu_copy_link">ലിങ്ക് വിലാസം ക്ലിപ്ബോർഡിലേക്ക് പകർത്തുക</string>
<string name="copy_link_to_clipboard">ലിങ്ക് വിലാസം ക്ലിപ്ബോർഡിലേക്ക് പകർത്തുക</string>
<string name="context_menu_copy_image_link">ചിത്രത്തിന്റെ വിലാസം ക്ലിപ്ബോർഡിലേക്ക് പകർത്തുക</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">ചിത്രം ലോഡ് ചെയ്യാൻ സാധിക്കുന്നില്ല</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Afbeelding opslaan</string>
<string name="context_menu_share_image">Deel afbeelding</string>
<string name="context_menu_open_external_browser">Geopend in externe browser&#8230;</string>
<string name="context_menu_copy_link">Link-adres kopiëren naar Klembord</string>
<string name="copy_link_to_clipboard">Link-adres kopiëren naar Klembord</string>
<string name="context_menu_copy_image_link">Afbeelding kopiëren naar Klembord</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Niet in staat om afbeelding te laden</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Zapisz obraz</string>
<string name="context_menu_share_image">Udostępnij obraz</string>
<string name="context_menu_open_external_browser">Otwórz w zewnętrznej przeglądarce&#8230;</string>
<string name="context_menu_copy_link">Skopiuj adres odnośnika do schowka</string>
<string name="copy_link_to_clipboard">Skopiuj adres odnośnika do schowka</string>
<string name="context_menu_copy_image_link">Skopiuj adres obrazu do schowka</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Nie udało się wczytać obrazu</string>

View file

@ -59,7 +59,7 @@
<string name="context_menu_save_image">Salvar imagem</string>
<string name="context_menu_share_image">Compartilhar imagem</string>
<string name="context_menu_open_external_browser">Abrir em navegador externo&#8230;</string>
<string name="context_menu_copy_link">Copiar endereço à área de transferência</string>
<string name="copy_link_to_clipboard">Copiar endereço à área de transferência</string>
<string name="context_menu_copy_image_link">Copiar endereço de imagem à área de transferência</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Impossível carregar a imagem</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Сохранить изображение</string>
<string name="context_menu_share_image">Поделиться изображением</string>
<string name="context_menu_open_external_browser">Открыть во внешнем браузере&#8230;</string>
<string name="context_menu_copy_link">Скопировать адрес ссылки в буфер обмена</string>
<string name="copy_link_to_clipboard">Скопировать адрес ссылки в буфер обмена</string>
<string name="context_menu_copy_image_link">Скопировать адрес изображения в буфер обмена</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Не удаётся загрузить изображение</string>

View file

@ -71,7 +71,7 @@
<string name="context_menu_save_image">Sarva s\'immàgine</string>
<string name="context_menu_share_image">Cumpartzi s\'immàgine</string>
<string name="context_menu_open_external_browser">Aberi in un\'esploradore (browser) esternu…</string>
<string name="context_menu_copy_link">Còpia su ligàmenes in sos apuntos</string>
<string name="copy_link_to_clipboard">Còpia su ligàmenes in sos apuntos</string>
<string name="context_menu_copy_image_link">Còpia s\'indiritzu de s\'immàgine in sos apuntos</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Spara bild</string>
<string name="context_menu_share_image">Dela bild</string>
<string name="context_menu_open_external_browser">Öppna i en extern webbläsare&#8230;</string>
<string name="context_menu_copy_link">Kopiera länkadress</string>
<string name="copy_link_to_clipboard">Kopiera länkadress</string>
<string name="context_menu_copy_image_link">Kopiera bildadressen</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Kunde inte ladda bilden</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">Зберегти зображення</string>
<string name="context_menu_share_image">Поділитися зображенням</string>
<string name="context_menu_open_external_browser">Відкрити у зовнішньому браузері&#8230;</string>
<string name="context_menu_copy_link">Копіювати адресу посилання у буфер обміну</string>
<string name="copy_link_to_clipboard">Копіювати адресу посилання у буфер обміну</string>
<string name="context_menu_copy_image_link">Копіювати зображення у буфер обміну</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">Не вдалося завантажити зображення</string>

View file

@ -57,7 +57,7 @@
<string name="context_menu_save_image">儲存圖片</string>
<string name="context_menu_share_image">分享圖片</string>
<string name="context_menu_open_external_browser">用外部瀏覽器開啟&#8230;</string>
<string name="context_menu_copy_link">將連結網址複製到剪貼簿</string>
<string name="copy_link_to_clipboard">將連結網址複製到剪貼簿</string>
<string name="context_menu_copy_image_link">將圖片網址複製到剪貼簿</string>
<!-- More from MainActivity -->
<string name="unable_to_load_image">無法載入圖片</string>

View file

@ -46,9 +46,9 @@
<string name="fragment_license__maintainers_text">This app is currently being developed and maintained by &lt;br>&lt;br>%1$s</string>
<string name="fragment_license__contributors">Contributors</string>
<string name="fragment_license__contributors_thank_you">%1$s&lt;br>&lt;br>Thank you!</string>
<string name="fragment_license__license" translatable="false">@string/about_activity__title_about_license</string>
<string name="fragment_license__license_text" translatable="false">
Copyright © 2015-2017
<string name="licenses" translatable="false">@string/about_activity__title_about_license</string>
<string name="copyright_license_text_official" translatable="false">
Copyright © 2015-2018
\nThis program 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.
\n
\nThis program 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.

View file

@ -5,7 +5,7 @@
<string name="shared_via_app" translatable="false">
\n\n\n_________________________\n
**Tags:** #dandelíon \n\n
*via [dandelion*](/people?q=dandelion00%40diasp.org) client / [GitHub](https://github.com/Diaspora-for-Android/dandelion)*</string>
*via [dandelion*](/people?q=dandelion00%40diasp.org) client [(Source)](https://github.com/Diaspora-for-Android/dandelion)*</string>
<string name="tor" translatable="false">Tor</string>
@ -127,4 +127,5 @@
<string name="pref_key__is_overview_statusbar_hidden" translatable="false">pref_key__is_overview_statusbar_hidden</string>
<string name="pref_key__recreate_main_activity" translatable="false">pref_key__recreate_main_activity</string>
<string name="pref_key__show_title" translatable="false">pref_key__show_title</string>
<string name="pdf" translatable="false">PDF</string>
</resources>

View file

@ -71,7 +71,7 @@
<!-- Chrome custom tabs -->
<string name="pref_desc__chrome_custom_tabs_enabled">Open external links with Chrome Custom Tabs. Chromium or Google Chrome needs to be installed in order to use this feature. \nIMPORTANT NOTE: Chrome Custom Tabs do not use configured proxy servers!</string>
<string name="pref_desc__chrome_custom_tabs_enabled">Open external links with Chrome Custom Tabs. Chromium, Firefox or Google Chrome needs to be installed in order to use this feature. \nIMPORTANT NOTE: Chrome Custom Tabs do not use configured proxy servers!</string>
<!-- Diaspora Settings -->
<string name="pref_title__personal_settings">Personal settings</string>

View file

@ -70,7 +70,7 @@
<string name="context_menu_save_image">Save image</string>
<string name="context_menu_share_image">Share image</string>
<string name="context_menu_open_external_browser">Open in external browser…</string>
<string name="context_menu_copy_link">Copy link address to clipboard</string>
<string name="copy_link_to_clipboard">Copy link address to clipboard</string>
<string name="context_menu_copy_image_link">Copy image address to clipboard</string>
@ -98,4 +98,5 @@
<string name="pref_title__is_statusbar_hidden">Hide statusbar</string>
<string name="pref_summary__show_title">Show title in the main view</string>
<string name="pref_title__show_title">Show title</string>
<string name="launcher_shortcut">Launcher shortcut</string>
</resources>

View file

@ -7,13 +7,11 @@ buildscript {
ext.version_setup_buildTools = "27.0.3" // Specifying optional
// https://developer.android.com/studio/releases/gradle-plugin.html
ext.version_gradle_tools = "3.0.1"
ext.version_gradle_tools = "3.1.0"
// https://developer.android.com/topic/libraries/support-library/revisions.html
ext.version_library_appcompat = "27.1.0"
// https://github.com/JakeWharton/butterknife/releases
ext.version_library_butterknife = "8.8.1"
// https://github.com/atlassian/commonmark-java/releases
ext.version_library_commonmark = "0.10.0"
// https://github.com/guardianproject/NetCipher/releases
ext.version_library_netcipher = "2.0.0-alpha1"
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-gradle-plugin#LookAtCentral
@ -47,42 +45,12 @@ allprojects {
tasks.matching { task -> task.name.matches('.*generate.*Resources') }.all {
task -> task.dependsOn copyRepoFiles
}
tasks.matching { task -> task.name.matches('.*generate.*Resources') }.all {
task -> task.doLast { markUntranslatableBuildFields() }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
def markUntranslatableBuildFields() {
Set<Project> proc_projects = new HashSet<>(subprojects)
proc_projects.add(rootProject)
proc_projects.each { proc_project ->
File mergeDir = new File("${proc_project.buildDir}/intermediates/res/merged")
if (!mergeDir.exists()) return
mergeDir.eachDir { dir ->
println("Resources" + dir)
dir.eachFileRecurse { file ->
if (file.name.endsWith(".xml")) {
println(file)
String content = file.getText('UTF-8')
if (content != null && content.contains('<string name="bc_notr__')) {
println("Replacing app name in " + file)
content = content.replace('<string name="bc_notr__', '<string translatable="false" name="bc_notr__')
file.write(content, 'UTF-8')
}
}
}
}
}
}
final String[] ROOT_TO_RAW_COPYFILES = ["README.md", "CHANGELOG.md", "CONTRIBUTORS.md", "LICENSE.txt", "LICENSE.md", "LICENSE"]
task copyRepoFiles(type: Copy) {
from rootProject.files(ROOT_TO_RAW_COPYFILES)
@ -93,7 +61,7 @@ task copyRepoFiles(type: Copy) {
@SuppressWarnings(["UnnecessaryQualifiedReference", "SpellCheckingInspection", "GroovyUnusedDeclaration"])
// Returns used android languages as a buildConfig array: {'de', 'it', ..}"
static String getUsedAndroidLanguages() {
static String findUsedAndroidLocales() {
Set<String> langs = new HashSet<>()
new File('.').eachFileRecurse(groovy.io.FileType.DIRECTORIES) {
final foldername = it.name
@ -110,12 +78,13 @@ static String getUsedAndroidLanguages() {
ext.getGitHash = { ->
try {
def stdout = new ByteArrayOutputStream() exec {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim()
} catch (Exception e) {
return '???'
} catch (Exception ignored) {
return 'unknown'
}
}

View file

@ -1,6 +1,6 @@
#Thu Oct 26 13:46:14 CEST 2017
#Sun Apr 08 08:39:15 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View file

@ -3,16 +3,16 @@ It adds useful features to your networking experience:
⚡ Quick access to most diaspora* features
👉 Share content to and from the app
🌎 Proxy support
🌎 Proxy support (Tor/Orbot supported)
📰 In-app-browser to view articles
🎨 Customizeable colors
🌆 Night/AMOLED mode
🈴 Localized in many languages
✔️ Allows system independent language
<b>Support the project:</b>
✋ <a href="https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2Fdiaspora-for-android%2Fdandelion.git&mail=gro.xobliam@@rentnasg">Translate using Stringlate</a>
✋ <a href="https://matrix.to/#/#dandelion:matrix.org">Join discussion on Matrix</a>
✋ <a href="https://github.com/diaspora-for-android/dandelion#contributions">More information about contributions</a>
✋ <a href="https://gsantner.net/android-contribution-guide/?packageid=com.github.dfa.diaspora_android&name=dandelion&web=https://github.com/diaspora-for-android/dandelion">Android Contribution Guide (gsantner blog)</a>
✋ <a href="http://gsantner.net/supportme?ref=dandelion&source=fdroid">Support main developer</a>