mirror of
https://github.com/gsantner/dandelion
synced 2024-11-22 04:12:08 +01:00
Update opoc
This commit is contained in:
parent
3ec8ab89c6
commit
a618da97d8
12 changed files with 701 additions and 39 deletions
|
@ -108,11 +108,13 @@ dependencies {
|
||||||
implementation "com.android.support:support-v4:${version_library_appcompat}"
|
implementation "com.android.support:support-v4:${version_library_appcompat}"
|
||||||
implementation "com.android.support:customtabs:${version_library_appcompat}"
|
implementation "com.android.support:customtabs:${version_library_appcompat}"
|
||||||
implementation "com.android.support:cardview-v7:${version_library_appcompat}"
|
implementation "com.android.support:cardview-v7:${version_library_appcompat}"
|
||||||
|
implementation "com.android.support:preference-v7:${version_library_appcompat}"
|
||||||
|
|
||||||
// UI libraries
|
// UI libraries
|
||||||
implementation "com.github.DASAR:ShiftColorPicker:v0.5"
|
implementation "com.github.DASAR:ShiftColorPicker:v0.5"
|
||||||
|
|
||||||
// Tool libraries
|
// Tool libraries
|
||||||
|
implementation 'commons-io:commons-io:2.6'
|
||||||
implementation "info.guardianproject.netcipher:netcipher:${version_library_netcipher}"
|
implementation "info.guardianproject.netcipher:netcipher:${version_library_netcipher}"
|
||||||
implementation "info.guardianproject.netcipher:netcipher-webkit:${version_library_netcipher}"
|
implementation "info.guardianproject.netcipher:netcipher-webkit:${version_library_netcipher}"
|
||||||
implementation "com.jakewharton:butterknife:${version_library_butterknife}"
|
implementation "com.jakewharton:butterknife:${version_library_butterknife}"
|
||||||
|
|
|
@ -16,7 +16,10 @@ import android.support.annotation.LayoutRes;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
@ -33,6 +36,7 @@ public abstract class GsFragmentBase extends Fragment {
|
||||||
|
|
||||||
protected ContextUtils _cu;
|
protected ContextUtils _cu;
|
||||||
protected Bundle _savedInstanceState = null;
|
protected Bundle _savedInstanceState = null;
|
||||||
|
protected Menu _fragmentMenu;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -51,6 +55,9 @@ public abstract class GsFragmentBase extends Fragment {
|
||||||
_cu = new ContextUtils(inflater.getContext());
|
_cu = new ContextUtils(inflater.getContext());
|
||||||
_cu.setAppLanguage(getAppLanguage());
|
_cu.setAppLanguage(getAppLanguage());
|
||||||
_savedInstanceState = savedInstanceState;
|
_savedInstanceState = savedInstanceState;
|
||||||
|
if (getLayoutResId() == 0) {
|
||||||
|
Log.e(getClass().getCanonicalName(), "Error: GsFragmentbase.onCreateview: Returned 0 for getLayoutResId");
|
||||||
|
}
|
||||||
View view = inflater.inflate(getLayoutResId(), container, false);
|
View view = inflater.inflate(getLayoutResId(), container, false);
|
||||||
ButterKnife.bind(this, view);
|
ButterKnife.bind(this, view);
|
||||||
return view;
|
return view;
|
||||||
|
@ -126,4 +133,14 @@ public abstract class GsFragmentBase extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
_fragmentMenu = menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Menu getFragmentMenu() {
|
||||||
|
return _fragmentMenu;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
#########################################################*/
|
#########################################################*/
|
||||||
package net.gsantner.opoc.preference;
|
package net.gsantner.opoc.preference;
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection", "unused", "SameParameterValue"})
|
@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection", "unused", "SameParameterValue"})
|
||||||
|
|
|
@ -532,7 +532,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
||||||
* A method to determine if current hour is between begin and end.
|
* A method to determine if current hour is between begin and end.
|
||||||
* This is especially useful for time-based light/dark mode
|
* This is especially useful for time-based light/dark mode
|
||||||
*/
|
*/
|
||||||
public boolean isCurrentHourOfDayBetween(int begin, int end) {
|
public static boolean isCurrentHourOfDayBetween(int begin, int end) {
|
||||||
begin = (begin >= 23 || begin < 0) ? 0 : begin;
|
begin = (begin >= 23 || begin < 0) ? 0 : begin;
|
||||||
end = (end >= 23 || end < 0) ? 0 : end;
|
end = (end >= 23 || end < 0) ? 0 : end;
|
||||||
int h = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
|
int h = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
|
||||||
|
|
|
@ -12,10 +12,12 @@ package net.gsantner.opoc.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.support.annotation.ColorInt;
|
import android.support.annotation.ColorInt;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.AppCompatEditText;
|
import android.support.v7.widget.AppCompatEditText;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
@ -24,6 +26,7 @@ import android.text.TextWatcher;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Filter;
|
import android.widget.Filter;
|
||||||
|
@ -31,12 +34,20 @@ import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import net.gsantner.opoc.util.ActivityUtils;
|
||||||
import net.gsantner.opoc.util.Callback;
|
import net.gsantner.opoc.util.Callback;
|
||||||
import net.gsantner.opoc.util.ContextUtils;
|
import net.gsantner.opoc.util.ContextUtils;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.filefilter.IOFileFilter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class SearchOrCustomTextDialog {
|
public class SearchOrCustomTextDialog {
|
||||||
|
@ -115,6 +126,7 @@ public class SearchOrCustomTextDialog {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final ActivityUtils activityUtils = new ActivityUtils(activity);
|
||||||
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
||||||
searchEditText.setSingleLine(true);
|
searchEditText.setSingleLine(true);
|
||||||
searchEditText.setMaxLines(1);
|
searchEditText.setMaxLines(1);
|
||||||
|
@ -140,6 +152,7 @@ public class SearchOrCustomTextDialog {
|
||||||
final ListView listView = new ListView(activity);
|
final ListView listView = new ListView(activity);
|
||||||
final LinearLayout linearLayout = new LinearLayout(activity);
|
final LinearLayout linearLayout = new LinearLayout(activity);
|
||||||
listView.setAdapter(listAdapter);
|
listView.setAdapter(listAdapter);
|
||||||
|
listView.setVisibility(dopt.data != null && !dopt.data.isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
if (dopt.isSearchEnabled) {
|
if (dopt.isSearchEnabled) {
|
||||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||||
|
@ -157,7 +170,7 @@ public class SearchOrCustomTextDialog {
|
||||||
dialogBuilder.setView(linearLayout)
|
dialogBuilder.setView(linearLayout)
|
||||||
.setTitle(dopt.titleText)
|
.setTitle(dopt.titleText)
|
||||||
.setOnCancelListener(null)
|
.setOnCancelListener(null)
|
||||||
.setNegativeButton(dopt.cancelButtonText, null);
|
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
|
||||||
if (dopt.isSearchEnabled) {
|
if (dopt.isSearchEnabled) {
|
||||||
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
||||||
dialogInterface.dismiss();
|
dialogInterface.dismiss();
|
||||||
|
@ -186,9 +199,124 @@ public class SearchOrCustomTextDialog {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (dialog.getWindow() != null) {
|
|
||||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
Window w;
|
||||||
|
if ((w = dialog.getWindow()) != null && dopt.isSearchEnabled) {
|
||||||
|
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
}
|
}
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
if ((w = dialog.getWindow()) != null) {
|
||||||
|
w.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dopt.isSearchEnabled) {
|
||||||
|
searchEditText.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static SearchFilesTask recursiveFileSearch(Activity activity, File searchDir, String query, Callback.a1<List<String>> callback) {
|
||||||
|
query = query.replaceAll("(?<![.])[*]", ".*");
|
||||||
|
SearchFilesTask task = new SearchFilesTask(activity, searchDir, query, callback, query.startsWith("^") || query.contains("*"));
|
||||||
|
task.execute();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SearchFilesTask extends AsyncTask<Void, File, List<String>> implements IOFileFilter {
|
||||||
|
private final Callback.a1<List<String>> _callback;
|
||||||
|
private final File _searchDir;
|
||||||
|
private final String _query;
|
||||||
|
private final boolean _isRegex;
|
||||||
|
private final WeakReference<Activity> _activityRef;
|
||||||
|
|
||||||
|
private final Pattern _regex;
|
||||||
|
private Snackbar _snackBar;
|
||||||
|
|
||||||
|
public SearchFilesTask(Activity activity, File searchDir, String query, Callback.a1<List<String>> callback, boolean isRegex) {
|
||||||
|
_searchDir = searchDir;
|
||||||
|
_query = isRegex ? query : query.toLowerCase();
|
||||||
|
_callback = callback;
|
||||||
|
_isRegex = isRegex;
|
||||||
|
_regex = isRegex ? Pattern.compile(_query) : null;
|
||||||
|
_activityRef = new WeakReference<>(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called for both, file and folder filter
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return isMatching(file, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not called
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return isMatching(new File(dir, name), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In iterateFilesAndDirs, subdirs are only scanned when returning true on it
|
||||||
|
// But those dirs will also occur in iterator
|
||||||
|
// Hence call this aagain with alwaysMatchDir=false
|
||||||
|
public boolean isMatching(File file, boolean alwaysMatchDir) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
// Do never scan .git directories, lots of files, lots of time
|
||||||
|
if (file.getName().equals(".git")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (alwaysMatchDir) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String name = file.getName();
|
||||||
|
file = file.getParentFile();
|
||||||
|
return _isRegex ? _regex.matcher(name).matches() : name.toLowerCase().contains(_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
if (_activityRef.get() != null) {
|
||||||
|
_snackBar = Snackbar.make(_activityRef.get().findViewById(android.R.id.content), _query + "...", Snackbar.LENGTH_INDEFINITE);
|
||||||
|
_snackBar.setAction(android.R.string.cancel, (v) -> {
|
||||||
|
_snackBar.dismiss();
|
||||||
|
cancel(true);
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> doInBackground(Void... voidp) {
|
||||||
|
List<String> ret = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
Iterator<File> iter = FileUtils.iterateFilesAndDirs(_searchDir, this, this);
|
||||||
|
while (iter.hasNext() && !isCancelled()) {
|
||||||
|
File f = iter.next();
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
if (f.equals(_searchDir)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (f.isFile() || (f.isDirectory() && isMatching(f, false))) {
|
||||||
|
ret.add(f.getAbsolutePath().replace(_searchDir.getAbsolutePath() + "/", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(List<String> ret) {
|
||||||
|
super.onPostExecute(ret);
|
||||||
|
if (_snackBar != null) {
|
||||||
|
_snackBar.dismiss();
|
||||||
|
}
|
||||||
|
if (_callback != null) {
|
||||||
|
try {
|
||||||
|
_callback.callback(ret);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new ActivityUtils(_activityRef.get()).hideSoftKeyboard().freeContextRef();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,11 @@ import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.provider.CalendarContract;
|
||||||
|
import android.support.annotation.ColorInt;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
@ -47,6 +50,12 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
_activity = activity;
|
_activity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void freeContextRef() {
|
||||||
|
super.freeContextRef();
|
||||||
|
_activity = null;
|
||||||
|
}
|
||||||
|
|
||||||
//########################
|
//########################
|
||||||
//## Methods
|
//## Methods
|
||||||
//########################
|
//########################
|
||||||
|
@ -85,9 +94,11 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void showSnackBar(@StringRes int stringResId, boolean showLong) {
|
public Snackbar showSnackBar(@StringRes int stringResId, boolean showLong) {
|
||||||
Snackbar.make(_activity.findViewById(android.R.id.content), stringResId,
|
Snackbar s = Snackbar.make(_activity.findViewById(android.R.id.content), stringResId,
|
||||||
showLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show();
|
showLong ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT);
|
||||||
|
s.show();
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showSnackBar(@StringRes int stringResId, boolean showLong, @StringRes int actionResId, View.OnClickListener listener) {
|
public void showSnackBar(@StringRes int stringResId, boolean showLong, @StringRes int actionResId, View.OnClickListener listener) {
|
||||||
|
@ -97,19 +108,59 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideSoftKeyboard() {
|
public ActivityUtils setSoftKeyboardVisibile(boolean visible, View... editView) {
|
||||||
|
final Activity activity = _activity;
|
||||||
|
if (activity != null) {
|
||||||
|
final View v = (editView != null && editView.length > 0) ? (editView[0]) : (activity.getCurrentFocus() != null && activity.getCurrentFocus().getWindowToken() != null ? activity.getCurrentFocus() : null);
|
||||||
|
final InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
if (v != null && imm != null) {
|
||||||
|
Runnable r = () -> {
|
||||||
|
if (visible) {
|
||||||
|
v.requestFocus();
|
||||||
|
imm.showSoftInput(v, InputMethodManager.SHOW_FORCED);
|
||||||
|
} else {
|
||||||
|
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.run();
|
||||||
|
for (int d : new int[]{100, 350}) {
|
||||||
|
v.postDelayed(r, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivityUtils hideSoftKeyboard() {
|
||||||
|
if (_activity != null) {
|
||||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||||
imm.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(_activity.getCurrentFocus().getWindowToken(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public void showSoftKeyboard() {
|
public ActivityUtils showSoftKeyboard() {
|
||||||
|
if (_activity != null) {
|
||||||
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
if (imm != null && _activity.getCurrentFocus() != null && _activity.getCurrentFocus().getWindowToken() != null) {
|
||||||
imm.showSoftInput(_activity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);
|
showSoftKeyboard(_activity.getCurrentFocus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ActivityUtils showSoftKeyboard(View textInputView) {
|
||||||
|
if (_activity != null) {
|
||||||
|
InputMethodManager imm = (InputMethodManager) _activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
if (imm != null && textInputView != null) {
|
||||||
|
imm.showSoftInput(textInputView, InputMethodManager.SHOW_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public void showDialogWithHtmlTextView(@StringRes int resTitleId, String html) {
|
public void showDialogWithHtmlTextView(@StringRes int resTitleId, String html) {
|
||||||
showDialogWithHtmlTextView(resTitleId, html, true, null);
|
showDialogWithHtmlTextView(resTitleId, html, true, null);
|
||||||
|
@ -142,7 +193,7 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle with no param, else set visibility according to first bool
|
// Toggle with no param, else set visibility according to first bool
|
||||||
public void toggleStatusbarVisibility(boolean... optionalForceVisible) {
|
public ActivityUtils toggleStatusbarVisibility(boolean... optionalForceVisible) {
|
||||||
WindowManager.LayoutParams attrs = _activity.getWindow().getAttributes();
|
WindowManager.LayoutParams attrs = _activity.getWindow().getAttributes();
|
||||||
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||||
if (optionalForceVisible.length == 0) {
|
if (optionalForceVisible.length == 0) {
|
||||||
|
@ -153,9 +204,10 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
attrs.flags |= flag;
|
attrs.flags |= flag;
|
||||||
}
|
}
|
||||||
_activity.getWindow().setAttributes(attrs);
|
_activity.getWindow().setAttributes(attrs);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showGooglePlayEntryForThisApp() {
|
public ActivityUtils showGooglePlayEntryForThisApp() {
|
||||||
String pkgId = "details?id=" + _activity.getPackageName();
|
String pkgId = "details?id=" + _activity.getPackageName();
|
||||||
Intent goToMarket = new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + pkgId));
|
Intent goToMarket = new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + pkgId));
|
||||||
goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
|
goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY |
|
||||||
|
@ -167,9 +219,10 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
_activity.startActivity(new Intent(Intent.ACTION_VIEW,
|
_activity.startActivity(new Intent(Intent.ACTION_VIEW,
|
||||||
Uri.parse("https://play.google.com/store/apps/" + pkgId)));
|
Uri.parse("https://play.google.com/store/apps/" + pkgId)));
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStatusbarColor(int color, boolean... fromRes) {
|
public ActivityUtils setStatusbarColor(int color, boolean... fromRes) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
if (fromRes != null && fromRes.length > 0 && fromRes[0]) {
|
if (fromRes != null && fromRes.length > 0 && fromRes[0]) {
|
||||||
color = ContextCompat.getColor(_context, color);
|
color = ContextCompat.getColor(_context, color);
|
||||||
|
@ -177,13 +230,55 @@ public class ActivityUtils extends net.gsantner.opoc.util.ContextUtils {
|
||||||
|
|
||||||
_activity.getWindow().setStatusBarColor(color);
|
_activity.getWindow().setStatusBarColor(color);
|
||||||
}
|
}
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLauncherActivityEnabled(Class activityClass, boolean enable) {
|
public ActivityUtils setLauncherActivityEnabled(Class activityClass, boolean enable) {
|
||||||
Context context = _context.getApplicationContext();
|
Context context = _context.getApplicationContext();
|
||||||
PackageManager pkg = context.getPackageManager();
|
PackageManager pkg = context.getPackageManager();
|
||||||
ComponentName component = new ComponentName(context, activityClass);
|
ComponentName component = new ComponentName(context, activityClass);
|
||||||
pkg.setComponentEnabledSetting(component, enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
pkg.setComponentEnabledSetting(component, enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||||
, PackageManager.DONT_KILL_APP);
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public Integer getCurrentPrimaryColor() {
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorPrimary"), typedValue, true);
|
||||||
|
return typedValue.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public Integer getCurrentPrimaryDarkColor() {
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorPrimaryDark"), typedValue, true);
|
||||||
|
return typedValue.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public Integer getCurrentAccentColor() {
|
||||||
|
TypedValue typedValue = new TypedValue();
|
||||||
|
_context.getTheme().resolveAttribute(getResId(ResType.ATTR, "colorAccent"), typedValue, true);
|
||||||
|
return typedValue.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public Integer getActivityBackgroundColor() {
|
||||||
|
TypedArray array = _activity.getTheme().obtainStyledAttributes(new int[]{
|
||||||
|
android.R.attr.colorBackground,
|
||||||
|
});
|
||||||
|
int c = array.getColor(0, 0xFF0000);
|
||||||
|
array.recycle();
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivityUtils startCalendarApp() {
|
||||||
|
Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
|
||||||
|
builder.appendPath("time");
|
||||||
|
builder.appendPath(Long.toString(System.currentTimeMillis()));
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, builder.build());
|
||||||
|
_activity.startActivity(intent);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,24 @@ public class Callback {
|
||||||
public interface a5<A, B, C, D, E> {
|
public interface a5<A, B, C, D, E> {
|
||||||
void callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
void callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface b1<A> {
|
||||||
|
boolean callback(A arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface b2<A, B> {
|
||||||
|
boolean callback(A arg1, B arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface b3<A, B, C> {
|
||||||
|
boolean callback(A arg1, B arg2, C arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface b4<A, B, C, D> {
|
||||||
|
boolean callback(A arg1, B arg2, C arg3, D arg4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface b5<A, B, C, D, E> {
|
||||||
|
boolean callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.ColorInt;
|
import android.support.annotation.ColorInt;
|
||||||
import android.support.annotation.ColorRes;
|
import android.support.annotation.ColorRes;
|
||||||
|
@ -48,6 +49,7 @@ import android.support.annotation.StringRes;
|
||||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.graphics.drawable.DrawableCompat;
|
import android.support.v4.graphics.drawable.DrawableCompat;
|
||||||
|
import android.support.v4.util.Pair;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
|
@ -74,6 +76,8 @@ import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
@ -94,6 +98,9 @@ public class ContextUtils {
|
||||||
return _context;
|
return _context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void freeContextRef() {
|
||||||
|
_context = null;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Class Methods
|
// Class Methods
|
||||||
|
@ -171,16 +178,24 @@ public class ContextUtils {
|
||||||
return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor);
|
return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAndroidVersion() {
|
||||||
|
return Build.VERSION.RELEASE + " (" + Build.VERSION.SDK_INT + ")";
|
||||||
|
}
|
||||||
|
|
||||||
public String getAppVersionName() {
|
public String getAppVersionName() {
|
||||||
try {
|
|
||||||
PackageManager manager = _context.getPackageManager();
|
PackageManager manager = _context.getPackageManager();
|
||||||
|
try {
|
||||||
PackageInfo info = manager.getPackageInfo(getPackageIdManifest(), 0);
|
PackageInfo info = manager.getPackageInfo(getPackageIdManifest(), 0);
|
||||||
return info.versionName;
|
return info.versionName;
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
e.printStackTrace();
|
try {
|
||||||
return "?";
|
PackageInfo info = manager.getPackageInfo(getPackageIdReal(), 0);
|
||||||
|
return info.versionName;
|
||||||
|
} catch (PackageManager.NameNotFoundException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
public String getAppInstallationSource() {
|
public String getAppInstallationSource() {
|
||||||
String src = null;
|
String src = null;
|
||||||
|
@ -519,12 +534,76 @@ public class ContextUtils {
|
||||||
/**
|
/**
|
||||||
* Get the private directory for the current package (usually /data/data/package.name/)
|
* Get the private directory for the current package (usually /data/data/package.name/)
|
||||||
*/
|
*/
|
||||||
public String getAppDataDir() {
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
public File getAppDataPrivateDir() {
|
||||||
|
File filesDir;
|
||||||
try {
|
try {
|
||||||
return _context.getPackageManager().getPackageInfo(getPackageIdReal(), 0).applicationInfo.dataDir;
|
filesDir = new File(new File(_context.getPackageManager().getPackageInfo(getPackageIdReal(), 0).applicationInfo.dataDir), "files");
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
return _context.getFilesDir().getParent();
|
filesDir = _context.getFilesDir();
|
||||||
}
|
}
|
||||||
|
if (!filesDir.exists() && filesDir.mkdirs()) ;
|
||||||
|
return filesDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get public (accessible) appdata folders
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
public List<Pair<File, String>> getAppDataPublicDirs(boolean internalStorageFolder, boolean sdcardFolders, boolean storageNameWithoutType) {
|
||||||
|
List<Pair<File, String>> dirs = new ArrayList<>();
|
||||||
|
for (File externalFileDir : ContextCompat.getExternalFilesDirs(_context, null)) {
|
||||||
|
if (externalFileDir == null || Environment.getExternalStorageDirectory() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||||
|
boolean add = (internalStorageFolder && isInt) || (sdcardFolders && !isInt);
|
||||||
|
if (add) {
|
||||||
|
dirs.add(new Pair<>(externalFileDir, getStorageName(externalFileDir, storageNameWithoutType)));
|
||||||
|
if (!externalFileDir.exists() && externalFileDir.mkdirs()) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStorageName(File externalFileDir, boolean storageNameWithoutType) {
|
||||||
|
boolean isInt = externalFileDir.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath());
|
||||||
|
|
||||||
|
String[] split = externalFileDir.getAbsolutePath().split("/");
|
||||||
|
if (split.length > 2) {
|
||||||
|
return isInt ? (storageNameWithoutType ? "Internal Storage" : "") : (storageNameWithoutType ? split[2] : ("SD Card (" + split[2] + ")"));
|
||||||
|
} else {
|
||||||
|
return "Storage";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Pair<File, String>> getStorages(boolean internalStorageFolder, boolean sdcardFolders) {
|
||||||
|
List<Pair<File, String>> storages = new ArrayList<>();
|
||||||
|
for (Pair<File, String> pair : getAppDataPublicDirs(internalStorageFolder, sdcardFolders, true)) {
|
||||||
|
if (pair.first != null && pair.first.getAbsolutePath().lastIndexOf("/Android/data") > 0) {
|
||||||
|
try {
|
||||||
|
storages.add(new Pair<>(new File(pair.first.getCanonicalPath().replaceFirst("/Android/data.*", "")), pair.second));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return storages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getStorageRootFolder(File file) {
|
||||||
|
String filepath;
|
||||||
|
try {
|
||||||
|
filepath = file.getCanonicalPath();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (Pair<File, String> storage : getStorages(false, true)) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
if (filepath.startsWith(storage.first.getAbsolutePath())) {
|
||||||
|
return storage.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -854,6 +933,18 @@ public class ContextUtils {
|
||||||
}
|
}
|
||||||
return mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer parseColor(String colorstr) {
|
||||||
|
if (colorstr == null || colorstr.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Color.parseColor(colorstr);
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -405,7 +405,7 @@ public class FileUtils {
|
||||||
* Analyze given textfile and retrieve multiple information from it
|
* Analyze given textfile and retrieve multiple information from it
|
||||||
* Information is written back to the {@link AtomicInteger} parameters
|
* Information is written back to the {@link AtomicInteger} parameters
|
||||||
*/
|
*/
|
||||||
public static void retrieveTextFileSummary(File file, AtomicInteger numCharacters, AtomicInteger numLines) {
|
public static void retrieveTextFileSummary(File file, AtomicInteger numCharacters, AtomicInteger numLines, AtomicInteger numWords) {
|
||||||
BufferedReader br = null;
|
BufferedReader br = null;
|
||||||
try {
|
try {
|
||||||
br = new BufferedReader(new FileReader(file));
|
br = new BufferedReader(new FileReader(file));
|
||||||
|
@ -413,11 +413,15 @@ public class FileUtils {
|
||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
numLines.getAndIncrement();
|
numLines.getAndIncrement();
|
||||||
numCharacters.getAndSet(numCharacters.get() + line.length());
|
numCharacters.getAndSet(numCharacters.get() + line.length());
|
||||||
|
if (!line.equals("")) {
|
||||||
|
numWords.getAndSet(numWords.get() + line.split("\\s+").length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
numCharacters.set(-1);
|
numCharacters.set(-1);
|
||||||
numLines.set(-1);
|
numLines.set(-1);
|
||||||
|
numWords.set(-1);
|
||||||
} finally {
|
} finally {
|
||||||
if (br != null) {
|
if (br != null) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#########################################################*/
|
#########################################################*/
|
||||||
package net.gsantner.opoc.util;
|
package net.gsantner.opoc.util;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
|
@ -37,19 +39,29 @@ import android.provider.MediaStore;
|
||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.content.FileProvider;
|
import android.support.v4.content.FileProvider;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.support.v4.content.pm.ShortcutInfoCompat;
|
import android.support.v4.content.pm.ShortcutInfoCompat;
|
||||||
import android.support.v4.content.pm.ShortcutManagerCompat;
|
import android.support.v4.content.pm.ShortcutManagerCompat;
|
||||||
import android.support.v4.graphics.drawable.IconCompat;
|
import android.support.v4.graphics.drawable.IconCompat;
|
||||||
|
import android.support.v4.provider.DocumentFile;
|
||||||
|
import android.support.v4.util.Pair;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.preference.PreferenceManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -65,15 +77,17 @@ import static android.app.Activity.RESULT_OK;
|
||||||
* Also allows to parse/fetch information out of shared information.
|
* Also allows to parse/fetch information out of shared information.
|
||||||
* (M)Permissions are not checked, wrap ShareUtils methods if neccessary
|
* (M)Permissions are not checked, wrap ShareUtils methods if neccessary
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "SameParameterValue", "unused", "deprecation", "ConstantConditions", "ObsoleteSdkInt", "SpellCheckingInspection"})
|
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "SameParameterValue", "unused", "deprecation", "ConstantConditions", "ObsoleteSdkInt", "SpellCheckingInspection", "JavadocReference"})
|
||||||
public class ShareUtil {
|
public class ShareUtil {
|
||||||
public final static String EXTRA_FILEPATH = "real_file_path_2";
|
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_RFC3339_ISH = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm", Locale.getDefault());
|
||||||
public final static SimpleDateFormat SDF_SHORT = new SimpleDateFormat("yyMMdd-HHmm", Locale.getDefault());
|
public final static SimpleDateFormat SDF_SHORT = new SimpleDateFormat("yyMMdd-HHmm", Locale.getDefault());
|
||||||
public final static String MIME_TEXT_PLAIN = "text/plain";
|
public final static String MIME_TEXT_PLAIN = "text/plain";
|
||||||
|
public final static String PREF_KEY__SAF_TREE_URI = "pref_key__saf_tree_uri";
|
||||||
|
|
||||||
public final static int REQUEST_CAMERA_PICTURE = 50001;
|
public final static int REQUEST_CAMERA_PICTURE = 50001;
|
||||||
public final static int REQUEST_PICK_PICTURE = 50002;
|
public final static int REQUEST_PICK_PICTURE = 50002;
|
||||||
|
public final static int REQUEST_SAF = 50003;
|
||||||
|
|
||||||
protected static String _lastCameraPictureFilepath;
|
protected static String _lastCameraPictureFilepath;
|
||||||
|
|
||||||
|
@ -86,6 +100,10 @@ public class ShareUtil {
|
||||||
_chooserTitle = "➥";
|
_chooserTitle = "➥";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void freeContextRef() {
|
||||||
|
_context = null;
|
||||||
|
}
|
||||||
|
|
||||||
public String getFileProviderAuthority() {
|
public String getFileProviderAuthority() {
|
||||||
if (TextUtils.isEmpty(_fileProviderAuthority)) {
|
if (TextUtils.isEmpty(_fileProviderAuthority)) {
|
||||||
throw new RuntimeException("Error at ShareUtil.getFileProviderAuthority(): No FileProvider authority provided");
|
throw new RuntimeException("Error at ShareUtil.getFileProviderAuthority(): No FileProvider authority provided");
|
||||||
|
@ -513,6 +531,15 @@ public class ShareUtil {
|
||||||
fileStr = fileStr.substring(prefix.length());
|
fileStr = fileStr.substring(prefix.length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// external/ prefix for External storage
|
||||||
|
if (fileStr.startsWith((tmps = "external/"))) {
|
||||||
|
File f = new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + fileStr.substring(tmps.length())));
|
||||||
|
if (f.exists()) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Next/OwnCloud Fileprovider
|
// Next/OwnCloud Fileprovider
|
||||||
for (String fp : new String[]{"org.nextcloud.files", "org.nextcloud.beta.files", "org.owncloud.files"}) {
|
for (String fp : new String[]{"org.nextcloud.files", "org.nextcloud.beta.files", "org.owncloud.files"}) {
|
||||||
if (fileProvider.equals(fp) && fileStr.startsWith(tmps = "external_files/")) {
|
if (fileProvider.equals(fp) && fileStr.startsWith(tmps = "external_files/")) {
|
||||||
|
@ -527,6 +554,7 @@ public class ShareUtil {
|
||||||
if (fileProvider.equals("com.mi.android.globalFileexplorer.myprovider") && fileStr.startsWith(tmps = "external_files")) {
|
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())));
|
return new File(Uri.decode(Environment.getExternalStorageDirectory().getAbsolutePath() + fileStr.substring(tmps.length())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// URI Encoded paths with full path after content://package/
|
// URI Encoded paths with full path after content://package/
|
||||||
if (fileStr.startsWith("/") || fileStr.startsWith("%2F")) {
|
if (fileStr.startsWith("/") || fileStr.startsWith("%2F")) {
|
||||||
tmpf = new File(Uri.decode(fileStr));
|
tmpf = new File(Uri.decode(fileStr));
|
||||||
|
@ -557,7 +585,11 @@ public class ShareUtil {
|
||||||
throw new RuntimeException("Error: ShareUtil.requestGalleryPicture needs an Activity Context.");
|
throw new RuntimeException("Error: ShareUtil.requestGalleryPicture needs an Activity Context.");
|
||||||
}
|
}
|
||||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||||
|
try {
|
||||||
((Activity) _context).startActivityForResult(intent, REQUEST_PICK_PICTURE);
|
((Activity) _context).startActivityForResult(intent, REQUEST_PICK_PICTURE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Toast.makeText(_context, "No gallery app installed!", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -565,7 +597,7 @@ public class ShareUtil {
|
||||||
* Result ({@link String}) will be available from {@link Activity#onActivityResult(int, int, Intent)}.
|
* Result ({@link String}) will be available from {@link Activity#onActivityResult(int, int, Intent)}.
|
||||||
* It has set resultCode to {@link Activity#RESULT_OK} with same requestCode, if successfully
|
* It has set resultCode to {@link Activity#RESULT_OK} with same requestCode, if successfully
|
||||||
* The requested image savepath has to be stored at caller side (not contained in intent),
|
* The requested image savepath has to be stored at caller side (not contained in intent),
|
||||||
* it can be retrieved using {@link #extractResultFromActivityResult(int, int, Intent)},
|
* it can be retrieved using {@link #extractResultFromActivityResult(int, int, Intent, Activity...)}
|
||||||
* returns null if an error happened.
|
* returns null if an error happened.
|
||||||
*
|
*
|
||||||
* @param target Path to file to write to, if folder the filename gets app_name + millis + random filename. If null DCIM folder is used.
|
* @param target Path to file to write to, if folder the filename gets app_name + millis + random filename. If null DCIM folder is used.
|
||||||
|
@ -621,7 +653,9 @@ public class ShareUtil {
|
||||||
* Forward all arguments from activity. Only requestCodes from {@link ShareUtil} get analyzed.
|
* Forward all arguments from activity. Only requestCodes from {@link ShareUtil} get analyzed.
|
||||||
* Also may forward results via local broadcast
|
* Also may forward results via local broadcast
|
||||||
*/
|
*/
|
||||||
public Object extractResultFromActivityResult(int requestCode, int resultCode, Intent data) {
|
@SuppressLint("ApplySharedPref")
|
||||||
|
public Object extractResultFromActivityResult(int requestCode, int resultCode, Intent data, Activity... activityOrNull) {
|
||||||
|
Activity activity = greedyGetActivity(activityOrNull);
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_CAMERA_PICTURE: {
|
case REQUEST_CAMERA_PICTURE: {
|
||||||
String picturePath = (resultCode == RESULT_OK) ? _lastCameraPictureFilepath : null;
|
String picturePath = (resultCode == RESULT_OK) ? _lastCameraPictureFilepath : null;
|
||||||
|
@ -676,6 +710,18 @@ public class ShareUtil {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case REQUEST_SAF: {
|
||||||
|
if (resultCode == RESULT_OK && data != null && data.getData() != null) {
|
||||||
|
Uri treeUri = data.getData();
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(_context).edit().putString(PREF_KEY__SAF_TREE_URI, treeUri.toString()).commit();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
activity.getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
}
|
||||||
|
return treeUri;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -822,4 +868,264 @@ public class ShareUtil {
|
||||||
customTabIntent.setPackage(pkg);
|
customTabIntent.setPackage(pkg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Request storage access. The user needs to press "Select storage" at the correct storage.
|
||||||
|
* @param activity The activity which will receive the result from startActivityForResult
|
||||||
|
*/
|
||||||
|
public void requestStorageAccessFramework(Activity... activity) {
|
||||||
|
Activity a = greedyGetActivity(activity);
|
||||||
|
if (a != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||||
|
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
|
||||||
|
);
|
||||||
|
a.startActivityForResult(intent, REQUEST_SAF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get storage access framework tree uri. The user must have granted access via {@link #requestStorageAccessFramework(Activity...)}
|
||||||
|
*
|
||||||
|
* @return Uri or null if not granted yet
|
||||||
|
*/
|
||||||
|
public Uri getStorageAccessFrameworkTreeUri() {
|
||||||
|
String treeStr = PreferenceManager.getDefaultSharedPreferences(_context).getString(PREF_KEY__SAF_TREE_URI, null);
|
||||||
|
if (!TextUtils.isEmpty(treeStr)) {
|
||||||
|
try {
|
||||||
|
return Uri.parse(treeStr);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get mounted storage folder root (by tree uri). The user must have granted access via {@link #requestStorageAccessFramework(Activity...)}
|
||||||
|
*
|
||||||
|
* @return File or null if SD not mounted
|
||||||
|
*/
|
||||||
|
public File getStorageAccessFolder() {
|
||||||
|
Uri safUri = getStorageAccessFrameworkTreeUri();
|
||||||
|
if (safUri != null) {
|
||||||
|
String safUriStr = safUri.toString();
|
||||||
|
ContextUtils cu = new ContextUtils(_context);
|
||||||
|
for (Pair<File, String> storage : cu.getStorages(false, true)) {
|
||||||
|
@SuppressWarnings("ConstantConditions") String storageFolderName = storage.first.getName();
|
||||||
|
if (safUriStr.contains(storageFolderName)) {
|
||||||
|
return storage.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cu.freeContextRef();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether or not a file is under a storage access folder (external storage / SD)
|
||||||
|
*
|
||||||
|
* @param file The file object (file/folder)
|
||||||
|
* @return Wether or not the file is under storage access folder
|
||||||
|
*/
|
||||||
|
public boolean isUnderStorageAccessFolder(File file) {
|
||||||
|
if (file != null) {
|
||||||
|
ContextUtils cu = new ContextUtils(_context);
|
||||||
|
for (Pair<File, String> storage : cu.getStorages(false, true)) {
|
||||||
|
if (file.getAbsolutePath().startsWith(storage.first.getAbsolutePath())) {
|
||||||
|
cu.freeContextRef();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cu.freeContextRef();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Greedy extract Activity from parameter or convert context if it's a activity
|
||||||
|
*/
|
||||||
|
private Activity greedyGetActivity(Activity... activity) {
|
||||||
|
if (activity != null && activity.length != 0 && activity[0] != null) {
|
||||||
|
return activity[0];
|
||||||
|
}
|
||||||
|
if (_context instanceof Activity) {
|
||||||
|
return (Activity) _context;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether or not a file can be written.
|
||||||
|
* Requires storage access framework permission for external storage (SD)
|
||||||
|
*
|
||||||
|
* @param file The file object (file/folder)
|
||||||
|
* @param isDir Wether or not the given file parameter is a directory
|
||||||
|
* @return Wether or not the file can be written
|
||||||
|
*/
|
||||||
|
public boolean canWriteFile(File file, boolean isDir) {
|
||||||
|
if (file == null) {
|
||||||
|
return false;
|
||||||
|
} else if (file.getAbsolutePath().startsWith(Environment.getExternalStorageDirectory().getAbsolutePath())) {
|
||||||
|
boolean s1 = isDir && file.getParentFile().canWrite();
|
||||||
|
return !isDir && file.getParentFile() != null ? file.getParentFile().canWrite() : file.canWrite();
|
||||||
|
} else {
|
||||||
|
DocumentFile dof = getDocumentFile(file, isDir);
|
||||||
|
return dof != null && dof.canWrite();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link DocumentFile} object out of a normal java {@link File}.
|
||||||
|
* When used on a external storage (SD), use {@link #requestStorageAccessFramework(Activity...)}
|
||||||
|
* first to get access. Otherwise this will fail.
|
||||||
|
*
|
||||||
|
* @param file The file/folder to convert
|
||||||
|
* @param isDir Wether or not file is a directory. For non-existing (to be created) files this info is not known hence required.
|
||||||
|
* @return A {@link DocumentFile} object or null if file cannot be converted
|
||||||
|
*/
|
||||||
|
public DocumentFile getDocumentFile(File file, boolean isDir) {
|
||||||
|
// On older versions use fromFile
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
|
||||||
|
return DocumentFile.fromFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ContextUtils to find storageRootFolder
|
||||||
|
ContextUtils cu = new ContextUtils(_context);
|
||||||
|
File baseFolderFile = cu.getStorageRootFolder(file);
|
||||||
|
cu.freeContextRef();
|
||||||
|
|
||||||
|
String baseFolder = baseFolderFile == null ? null : baseFolderFile.getAbsolutePath();
|
||||||
|
boolean originalDirectory = false;
|
||||||
|
if (baseFolder == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String relPath = null;
|
||||||
|
try {
|
||||||
|
String fullPath = file.getCanonicalPath();
|
||||||
|
if (!baseFolder.equals(fullPath)) {
|
||||||
|
relPath = fullPath.substring(baseFolder.length() + 1);
|
||||||
|
} else {
|
||||||
|
originalDirectory = true;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
originalDirectory = true;
|
||||||
|
}
|
||||||
|
Uri treeUri;
|
||||||
|
if ((treeUri = getStorageAccessFrameworkTreeUri()) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DocumentFile dof = DocumentFile.fromTreeUri(_context, treeUri);
|
||||||
|
if (originalDirectory) {
|
||||||
|
return dof;
|
||||||
|
}
|
||||||
|
String[] parts = relPath.split("\\/");
|
||||||
|
for (int i = 0; i < parts.length; i++) {
|
||||||
|
DocumentFile nextDof = dof.findFile(parts[i]);
|
||||||
|
if (nextDof == null) {
|
||||||
|
nextDof = ((i < parts.length - 1) || isDir) ? dof.createDirectory(parts[i]) : dof.createFile("image", parts[i]);
|
||||||
|
}
|
||||||
|
dof = nextDof;
|
||||||
|
}
|
||||||
|
return dof;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showMountSdDialog(@StringRes int title, @StringRes int description, @DrawableRes int mountDescriptionGraphic, Activity... activityOrNull) {
|
||||||
|
Activity activity = greedyGetActivity(activityOrNull);
|
||||||
|
if (activity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image viewer
|
||||||
|
ImageView imv = new ImageView(activity);
|
||||||
|
imv.setImageResource(mountDescriptionGraphic);
|
||||||
|
imv.setAdjustViewBounds(true);
|
||||||
|
|
||||||
|
AlertDialog.Builder dialog = new AlertDialog.Builder(activity);
|
||||||
|
dialog.setView(imv);
|
||||||
|
dialog.setTitle(title);
|
||||||
|
dialog.setMessage(_context.getString(description) + "\n\n");
|
||||||
|
dialog.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
dialog.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> requestStorageAccessFramework(activity));
|
||||||
|
AlertDialog dialogi = dialog.create();
|
||||||
|
dialogi.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeFile(File file, boolean isDirectory, Callback.a2<Boolean, FileOutputStream> writeFileCallback) {
|
||||||
|
try {
|
||||||
|
FileOutputStream fileOutputStream = null;
|
||||||
|
ParcelFileDescriptor pfd = null;
|
||||||
|
if (file.canWrite()) {
|
||||||
|
if (isDirectory) {
|
||||||
|
file.mkdirs();
|
||||||
|
} else {
|
||||||
|
fileOutputStream = new FileOutputStream(file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DocumentFile dof = getDocumentFile(file, isDirectory);
|
||||||
|
if (dof != null && dof.getUri() != null && dof.canWrite()) {
|
||||||
|
if (isDirectory) {
|
||||||
|
// Nothing to do
|
||||||
|
} else {
|
||||||
|
pfd = _context.getContentResolver().openFileDescriptor(dof.getUri(), "w");
|
||||||
|
fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (writeFileCallback != null) {
|
||||||
|
writeFileCallback.callback(fileOutputStream != null || (isDirectory && file.exists()), fileOutputStream);
|
||||||
|
}
|
||||||
|
if (fileOutputStream != null) {
|
||||||
|
fileOutputStream.close();
|
||||||
|
}
|
||||||
|
if (pfd != null) {
|
||||||
|
pfd.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call telephone number.
|
||||||
|
* Non direct call, opens up the dialer and pre-sets the telephone number. User needs to press manually.
|
||||||
|
* Direct call requires M permission granted, also add permissions to manifest:
|
||||||
|
* <uses-permission android:name="android.permission.CALL_PHONE" />
|
||||||
|
*
|
||||||
|
* @param telNo The telephone number to call
|
||||||
|
* @param directCall Direct call number if possible
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SimplifiableConditionalExpression")
|
||||||
|
public void callTelephoneNumber(String telNo, boolean... directCall) {
|
||||||
|
Activity activity = greedyGetActivity();
|
||||||
|
if (activity == null) {
|
||||||
|
throw new RuntimeException("Error: ShareUtil::callTelephoneNumber needs to be contstructed with activity context");
|
||||||
|
}
|
||||||
|
boolean ldirectCall = (directCall != null && directCall.length > 0) ? directCall[0] : true;
|
||||||
|
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 23 && ldirectCall && activity != null) {
|
||||||
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CALL_PHONE}, 4001);
|
||||||
|
ldirectCall = false;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Intent callIntent = new Intent(Intent.ACTION_CALL);
|
||||||
|
callIntent.setData(Uri.parse("tel:" + telNo));
|
||||||
|
activity.startActivity(callIntent);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
ldirectCall = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Show dialer up with telephone number pre-inserted
|
||||||
|
if (!ldirectCall) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts("tel", telNo, null));
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import java.text.SimpleDateFormat
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
version_gradle_tools = "3.2.1"
|
version_gradle_tools = "3.4.2"
|
||||||
version_plugin_kotlin = "1.3.11"
|
version_plugin_kotlin = "1.3.41"
|
||||||
enable_plugin_kotlin = false
|
enable_plugin_kotlin = false
|
||||||
|
|
||||||
version_compileSdk = 28
|
version_compileSdk = 28
|
||||||
|
@ -79,7 +79,7 @@ static String findUsedAndroidLocales() {
|
||||||
Set<String> langs = new HashSet<>()
|
Set<String> langs = new HashSet<>()
|
||||||
new File('.').eachFileRecurse(groovy.io.FileType.DIRECTORIES) {
|
new File('.').eachFileRecurse(groovy.io.FileType.DIRECTORIES) {
|
||||||
final foldername = it.name
|
final foldername = it.name
|
||||||
if (foldername.startsWith('values-') && !it.canonicalPath.contains("build" + File.separator + "intermediates")) {
|
if (foldername.startsWith('values-') && !it.canonicalPath.contains("build" + File.separator + "intermediates") && !it.canonicalPath.contains("gradle" + File.separator + "daemon")) {
|
||||||
new File(it.toString()).eachFileRecurse(groovy.io.FileType.FILES) {
|
new File(it.toString()).eachFileRecurse(groovy.io.FileType.FILES) {
|
||||||
if (it.name.toLowerCase().endsWith(".xml") && it.getCanonicalFile().getText('UTF-8').contains("<string")) {
|
if (it.name.toLowerCase().endsWith(".xml") && it.getCanonicalFile().getText('UTF-8').contains("<string")) {
|
||||||
langs.add(foldername.replace("values-", ""))
|
langs.add(foldername.replace("values-", ""))
|
||||||
|
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,6 @@
|
||||||
|
#Fri Jul 26 02:59:10 CEST 2019
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
||||||
|
|
Loading…
Reference in a new issue