mirror of
https://github.com/gsantner/dandelion
synced 2024-11-21 20:02:07 +01:00
Rework screenshot saving and sharing; add new share options:
* Share option: Launcher shortcut (fixes #170) * Share option: Copy link of current page to clipboard * Share otpion: Export as PDF / print
This commit is contained in:
parent
d53128e5cb
commit
51093e0c3d
35 changed files with 1222 additions and 138 deletions
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -315,8 +315,8 @@ public class DiasporaPodList implements Iterable<DiasporaPodList.DiasporaPod>, S
|
|||
}
|
||||
|
||||
/*
|
||||
* Getter & Setter
|
||||
*/
|
||||
* Getter & Setter
|
||||
*/
|
||||
public List<DiasporaPodUrl> getPodUrls() {
|
||||
return _podUrls;
|
||||
}
|
||||
|
|
|
@ -81,7 +81,6 @@ public class ActivityUtils extends net.gsantner.opoc.util.ActivityUtils {
|
|||
* @return
|
||||
*/
|
||||
public static Uri getFileSharingUri(Context context, File file) {
|
||||
|
||||
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID, 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;
|
||||
|
@ -31,6 +32,7 @@ import net.gsantner.opoc.preference.SharedPreferencesPropertyBackend;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -375,6 +377,11 @@ public class AppSettings extends SharedPreferencesPropertyBackend {
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -18,18 +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.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
|
@ -38,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.
|
||||
|
@ -155,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;
|
||||
|
|
|
@ -230,7 +230,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);
|
||||
}
|
||||
}
|
||||
|
|
34
app/src/main/java/net/gsantner/opoc/util/Callback.java
Normal file
34
app/src/main/java/net/gsantner/opoc/util/Callback.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2018-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ 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;
|
||||
|
@ -446,6 +447,25 @@ public class ContextUtils {
|
|||
return dp * _context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -459,7 +479,10 @@ public class ContextUtils {
|
|||
*/
|
||||
public Bitmap drawableToBitmap(Drawable drawable) {
|
||||
Bitmap bitmap = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat || drawable instanceof AdaptiveIconDrawable)) {
|
||||
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();
|
||||
}
|
||||
|
|
340
app/src/main/java/net/gsantner/opoc/util/FileUtils.java
Normal file
340
app/src/main/java/net/gsantner/opoc/util/FileUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
208
app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java
Normal file
208
app/src/main/java/net/gsantner/opoc/util/NetworkUtils.java
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* 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) {
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.connect();
|
||||
input = connection.getInputStream();
|
||||
|
||||
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) {
|
||||
try {
|
||||
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setDoInput(true);
|
||||
|
||||
if (data != null && !data.isEmpty()) {
|
||||
conn.setDoOutput(true);
|
||||
final OutputStream output = conn.getOutputStream();
|
||||
output.write(data.getBytes(Charset.forName(UTF8)));
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
|
||||
return FileUtils.readCloseTextStream(conn.getInputStream());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String encodeQuery(final HashMap<String, String> params) throws UnsupportedEncodingException {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
if (first) first = false;
|
||||
else result.append("&");
|
||||
|
||||
result.append(URLEncoder.encode(entry.getKey(), UTF8));
|
||||
result.append("=");
|
||||
result.append(URLEncoder.encode(entry.getValue(), UTF8));
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static HashMap<String, String> getDataMap(final String query) {
|
||||
final HashMap<String, String> result = new HashMap<>();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String name = "";
|
||||
|
||||
try {
|
||||
for (int i = 0; i < query.length(); i++) {
|
||||
char c = query.charAt(i);
|
||||
switch (c) {
|
||||
case '=':
|
||||
name = URLDecoder.decode(sb.toString(), UTF8);
|
||||
sb.setLength(0);
|
||||
break;
|
||||
case '&':
|
||||
result.put(name, URLDecoder.decode(sb.toString(), UTF8));
|
||||
sb.setLength(0);
|
||||
break;
|
||||
default:
|
||||
sb.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!name.isEmpty())
|
||||
result.put(name, URLDecoder.decode(sb.toString(), UTF8));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*#######################################################
|
||||
*
|
||||
* Maintained by Gregor Santner, 2017-
|
||||
* https://gsantner.net/
|
||||
*
|
||||
* License: Apache 2.0
|
||||
* 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());
|
||||
}
|
||||
}
|
453
app/src/main/java/net/gsantner/opoc/util/ShareUtil.java
Normal file
453
app/src/main/java/net/gsantner/opoc/util/ShareUtil.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<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="context_menu_copy_link">Copier le lien dans le presse-papier</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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">外部ブラウザーで開く…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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">പുറമെയുള്ള ബ്രൗസറിൽ തുറക്കുക…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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">Открыть во внешнем браузере…</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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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…</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>
|
||||
|
|
|
@ -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">Відкрити у зовнішньому браузері…</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>
|
||||
|
|
|
@ -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">用外部瀏覽器開啟…</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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue