mirror of
https://github.com/gsantner/dandelion
synced 2024-12-22 02:48:01 +01:00
Update gs/opoc utilities
This commit is contained in:
parent
d923630b42
commit
904f2af20a
13 changed files with 737 additions and 310 deletions
|
@ -15,6 +15,7 @@
|
|||
android:allowBackup="false"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/DiasporaLight">
|
||||
|
||||
<provider
|
||||
|
@ -59,7 +60,8 @@
|
|||
android:theme="@style/DiasporaLight.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<meta-data android:name="android.app.shortcuts"
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
|
||||
<intent-filter>
|
||||
|
@ -68,6 +70,7 @@
|
|||
<action android:name="sc_aspects" />
|
||||
<action android:name="sc_activities" />
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import com.github.dfa.diaspora_android.util.DiasporaUrlHelper;
|
|||
|
||||
import net.gsantner.opoc.util.AdBlock;
|
||||
import net.gsantner.opoc.util.ContextUtils;
|
||||
import net.gsantner.opoc.util.ShareUtil;
|
||||
|
||||
public class App extends Application {
|
||||
private volatile static App app;
|
||||
|
@ -51,6 +52,7 @@ public class App extends Application {
|
|||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
ShareUtil.setFileProviderAuthority(BuildConfig.APPLICATION_ID);
|
||||
app = this;
|
||||
final Context c = getApplicationContext();
|
||||
appSettings = AppSettings.get();
|
||||
|
|
|
@ -128,7 +128,7 @@ 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);
|
||||
ShareUtil shu = new ShareUtil(getContext());
|
||||
PermissionChecker permc = new PermissionChecker(getActivity());
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_reload: {
|
||||
|
@ -185,7 +185,7 @@ public class DiasporaStreamFragment extends BrowserFragment {
|
|||
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);
|
||||
_cu.writeImageToFile(new File(fileSaveDirectory, filename), bmp);
|
||||
Snackbar.make(webView, getString(R.string.saving_screenshot_as)
|
||||
+ " " + filename, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ public class DiasporaStreamFragment extends BrowserFragment {
|
|||
|
||||
case R.id.action_share_screenshot: {
|
||||
if (permc.doIfExtStoragePermissionGranted(getString(R.string.screenshot_permission__appspecific))) {
|
||||
shu.shareImage(ShareUtil.getBitmapFromWebView(webView), Bitmap.CompressFormat.JPEG);
|
||||
shu.shareImage(ShareUtil.getBitmapFromWebView(webView));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ public class ContextMenuWebView extends NestedWebView {
|
|||
public boolean onMenuItemClick(MenuItem item) {
|
||||
HitTestResult result = getHitTestResult();
|
||||
String url = result.getExtra();
|
||||
final ShareUtil shu = new ShareUtil(context).setFileProviderAuthority(BuildConfig.APPLICATION_ID);
|
||||
final ShareUtil shu = new ShareUtil(context);
|
||||
final PermissionChecker permc = new PermissionChecker(parentActivity);
|
||||
final AppSettings appSettings = new AppSettings(context);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.view.MenuInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import net.gsantner.opoc.android.dummy.MenuItemDummy;
|
||||
import net.gsantner.opoc.util.ContextUtils;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
|
@ -37,7 +38,7 @@ public abstract class GsFragmentBase extends Fragment {
|
|||
|
||||
protected ContextUtils _cu;
|
||||
protected Bundle _savedInstanceState = null;
|
||||
protected Menu _fragmentMenu;
|
||||
protected Menu _fragmentMenu = new MenuItemDummy.Menu();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Gregor Santner <https://gsantner.net>
|
||||
* License: Creative Commons Zero (CC0 1.0) / Public Domain
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* You can do whatever you want with this. If we meet some day, and you think it is worth it,
|
||||
* you can buy me a drink in return. Provided as is without any kind of warranty. Do not blame
|
||||
* or ask for support if something goes wrong. - Gregor Santner
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package net.gsantner.opoc.android.dummy;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.ActionProvider;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
|
||||
public class MenuItemDummy implements MenuItem {
|
||||
private final int _itemId;
|
||||
|
||||
public MenuItemDummy(final int itemId) {
|
||||
_itemId = itemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemId() {
|
||||
return _itemId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGroupId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setTitle(CharSequence title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setTitle(int title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setTitleCondensed(CharSequence title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitleCondensed() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setIcon(Drawable icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setIcon(int iconRes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setIntent(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setShortcut(char numericChar, char alphaChar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setNumericShortcut(char numericChar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getNumericShortcut() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setAlphabeticShortcut(char alphaChar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getAlphabeticShortcut() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setCheckable(boolean checkable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCheckable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setChecked(boolean checked) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChecked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setVisible(boolean visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setEnabled(boolean enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSubMenu() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMenu getSubMenu() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener menuItemClickListener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextMenu.ContextMenuInfo getMenuInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowAsAction(int actionEnum) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setShowAsActionFlags(int actionEnum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setActionView(View view) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setActionView(int resId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getActionView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setActionProvider(ActionProvider actionProvider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionProvider getActionProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean expandActionView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collapseActionView() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActionViewExpanded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class Menu implements android.view.Menu {
|
||||
@Override
|
||||
public MenuItem add(CharSequence title) {
|
||||
return add(0, 0, 0, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem add(int titleRes) {
|
||||
return add(0, 0, 0, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
|
||||
return new MenuItemDummy(itemId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem add(int groupId, int itemId, int order, int titleRes) {
|
||||
return add(0, 0, 0, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMenu addSubMenu(CharSequence title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMenu addSubMenu(int titleRes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeItem(int id) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeGroup(int groupId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupVisible(int group, boolean visible) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupEnabled(int group, boolean enabled) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVisibleItems() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem findItem(int id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MenuItem getItem(int index) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShortcutKey(int keyCode, KeyEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performIdentifierAction(int id, int flags) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQwertyMode(boolean isQwerty) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Gregor Santner <https://gsantner.net>
|
||||
* License: Creative Commons Zero (CC0 1.0) / Public Domain
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* You can do whatever you want with this. If we meet some day, and you think it is worth it,
|
||||
* you can buy me a drink in return. Provided as is without any kind of warranty. Do not blame
|
||||
* or ask for support if something goes wrong. - Gregor Santner
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
package net.gsantner.opoc.android.dummy;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
|
||||
import net.gsantner.opoc.util.Callback;
|
||||
|
||||
@SuppressWarnings({"unused", "SpellCheckingInspection"})
|
||||
public class TextWatcherDummy implements TextWatcher {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
}
|
||||
|
||||
public static TextWatcher before(final Callback.a4<CharSequence, Integer, Integer, Integer> impl) {
|
||||
return new TextWatcherDummy() {
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
impl.callback(s, start, count, after);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static TextWatcher on(final Callback.a4<CharSequence, Integer, Integer, Integer> impl) {
|
||||
return new TextWatcherDummy() {
|
||||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||
impl.callback(s, start, before, count);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static TextWatcher after(final Callback.a1<Editable> impl) {
|
||||
return new TextWatcherDummy() {
|
||||
public void afterTextChanged(final Editable s) {
|
||||
impl.callback(s);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ import android.support.annotation.StringRes;
|
|||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
@ -201,11 +202,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
}
|
||||
|
||||
public String getString(String key, String defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getString(key, defaultValue);
|
||||
try {
|
||||
return gp(pref).getString(key, defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public String getString(@StringRes int keyResourceId, String defaultValue, @StringRes int keyResourceIdDefaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getString(rstr(keyResourceId), rstr(keyResourceIdDefaultValue));
|
||||
return getString(rstr(keyResourceId), rstr(keyResourceIdDefaultValue), pref);
|
||||
}
|
||||
|
||||
private void setStringListOne(String key, List<String> values, final SharedPreferences pref) {
|
||||
|
@ -219,9 +224,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
|
||||
private ArrayList<String> getStringListOne(String key, final SharedPreferences pref) {
|
||||
ArrayList<String> ret = new ArrayList<>();
|
||||
String value = pref
|
||||
.getString(key, ARRAY_SEPARATOR)
|
||||
.replace(ARRAY_SEPARATOR_SUBSTITUTE, ARRAY_SEPARATOR);
|
||||
String value = getString(key, ARRAY_SEPARATOR).replace(ARRAY_SEPARATOR_SUBSTITUTE, ARRAY_SEPARATOR);
|
||||
if (value.equals(ARRAY_SEPARATOR) || TextUtils.isEmpty(value)) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -277,11 +280,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
}
|
||||
|
||||
public int getInt(@StringRes int keyResourceId, int defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getInt(rstr(keyResourceId), defaultValue);
|
||||
return getInt(rstr(keyResourceId), defaultValue, pref);
|
||||
}
|
||||
|
||||
public int getInt(String key, int defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getInt(key, defaultValue);
|
||||
try {
|
||||
return gp(pref).getInt(key, defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public int getIntOfStringPref(@StringRes int keyResId, int defaultValue, final SharedPreferences... pref) {
|
||||
|
@ -304,7 +311,7 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
|
||||
private ArrayList<Integer> getIntListOne(String key, final SharedPreferences pref) {
|
||||
ArrayList<Integer> ret = new ArrayList<>();
|
||||
String value = pref.getString(key, ARRAY_SEPARATOR);
|
||||
String value = getString(key, ARRAY_SEPARATOR);
|
||||
if (value.equals(ARRAY_SEPARATOR)) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -361,11 +368,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
}
|
||||
|
||||
public long getLong(@StringRes int keyResourceId, long defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getLong(rstr(keyResourceId), defaultValue);
|
||||
return getLong(rstr(keyResourceId), defaultValue, pref);
|
||||
}
|
||||
|
||||
public long getLong(String key, long defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getLong(key, defaultValue);
|
||||
try {
|
||||
return gp(pref).getLong(key, defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -380,11 +391,15 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
}
|
||||
|
||||
public float getFloat(@StringRes int keyResourceId, float defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getFloat(rstr(keyResourceId), defaultValue);
|
||||
return getFloat(rstr(keyResourceId), defaultValue);
|
||||
}
|
||||
|
||||
public float getFloat(String key, float defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getFloat(key, defaultValue);
|
||||
try {
|
||||
return gp(pref).getFloat(key, defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -418,22 +433,26 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
}
|
||||
|
||||
public boolean getBool(@StringRes int keyResourceId, boolean defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getBoolean(rstr(keyResourceId), defaultValue);
|
||||
return getBool(rstr(keyResourceId), defaultValue);
|
||||
}
|
||||
|
||||
public boolean getBool(String key, boolean defaultValue, final SharedPreferences... pref) {
|
||||
return gp(pref).getBoolean(key, defaultValue);
|
||||
try {
|
||||
return gp(pref).getBoolean(key, defaultValue);
|
||||
} catch (ClassCastException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Getter & Setter for Color
|
||||
//
|
||||
public int getColor(String key, @ColorRes int defaultColor, final SharedPreferences... pref) {
|
||||
return gp(pref).getInt(key, rcolor(defaultColor));
|
||||
return getInt(key, rcolor(defaultColor));
|
||||
}
|
||||
|
||||
public int getColor(@StringRes int keyResourceId, @ColorRes int defaultColor, final SharedPreferences... pref) {
|
||||
return gp(pref).getInt(rstr(keyResourceId), rcolor(defaultColor));
|
||||
return getColor(rstr(keyResourceId), defaultColor);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -588,4 +607,12 @@ public class SharedPreferencesPropertyBackend implements PropertyBackend<String,
|
|||
public static synchronized void appendDebugLog(String text) {
|
||||
_debugLog += "[" + new Date().toString() + "] " + text + "\n";
|
||||
}
|
||||
|
||||
public static boolean ne(final String str) {
|
||||
return str != null && !str.trim().isEmpty();
|
||||
}
|
||||
|
||||
public static boolean fexists(final String fp) {
|
||||
return ne(fp) && (new File(fp)).exists();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,28 +10,27 @@
|
|||
#########################################################*/
|
||||
package net.gsantner.opoc.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.AppCompatEditText;
|
||||
import android.text.Editable;
|
||||
import android.support.v7.widget.TooltipCompat;
|
||||
import android.text.InputType;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Pair;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -40,34 +39,43 @@ import android.view.ViewGroup;
|
|||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.gsantner.opoc.util.ActivityUtils;
|
||||
import net.gsantner.opoc.android.dummy.TextWatcherDummy;
|
||||
import net.gsantner.opoc.util.Callback;
|
||||
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.Iterator;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@SuppressLint("SetTextI18n")
|
||||
public class SearchOrCustomTextDialog {
|
||||
|
||||
public static class DialogOptions {
|
||||
public Callback.a1<String> callback;
|
||||
public Callback.a2<String, Integer> withPositionCallback;
|
||||
public List<? extends CharSequence> data;
|
||||
public List<? extends CharSequence> highlightData;
|
||||
|
||||
// Callback for search text or text of single item
|
||||
@Nullable
|
||||
public Callback.a1<String> callback = null;
|
||||
|
||||
// Callback for indices of selected items.
|
||||
// List will contain single item if isMultiSelectEnabled == false;
|
||||
@Nullable
|
||||
public Callback.a1<List<Integer>> positionCallback = null;
|
||||
|
||||
public boolean isMultiSelectEnabled = false;
|
||||
public List<? extends CharSequence> data = null;
|
||||
public List<? extends CharSequence> highlightData = null;
|
||||
public List<Integer> iconsForData;
|
||||
public String messageText = "";
|
||||
public String defaultText = "";
|
||||
|
@ -78,8 +86,9 @@ public class SearchOrCustomTextDialog {
|
|||
public int gravity = Gravity.NO_GRAVITY;
|
||||
public int searchInputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
public boolean searchIsRegex = false;
|
||||
public Callback.a1<Spannable> highlighter;
|
||||
public Callback.a1<Spannable> highlighter = null;
|
||||
public String extraFilter = null;
|
||||
public List<Integer> preSelected = null;
|
||||
|
||||
public Callback.a0 neutralButtonCallback = null;
|
||||
|
||||
|
@ -97,41 +106,57 @@ public class SearchOrCustomTextDialog {
|
|||
public int titleText = 0;
|
||||
@StringRes
|
||||
public int searchHintText = android.R.string.search_go;
|
||||
@DrawableRes
|
||||
public int clearInputIcon = android.R.drawable.ic_input_delete;
|
||||
}
|
||||
|
||||
private static class WithPositionAdapter extends ArrayAdapter<Pair<String, Integer>> {
|
||||
private static class Adapter extends ArrayAdapter<Integer> {
|
||||
@LayoutRes
|
||||
final int _layout;
|
||||
final LayoutInflater _inflater;
|
||||
final DialogOptions _dopt;
|
||||
final List<Pair<String, Integer>> _filteredItems;
|
||||
final Pattern _extraPattern;
|
||||
private final int _layout;
|
||||
private final int _layoutHeight;
|
||||
private final LayoutInflater _inflater;
|
||||
private final DialogOptions _dopt;
|
||||
private final List<Integer> _filteredItems;
|
||||
private final Set<Integer> _selectedItems;
|
||||
private final Pattern _extraPattern;
|
||||
|
||||
WithPositionAdapter(Context c_context, @LayoutRes int c_layout, List<Pair<String, Integer>> c_filteredItems, DialogOptions c_dopt) {
|
||||
super(c_context, c_layout, c_filteredItems);
|
||||
_inflater = LayoutInflater.from(c_context);
|
||||
_layout = c_layout;
|
||||
_dopt = c_dopt;
|
||||
_filteredItems = c_filteredItems;
|
||||
_extraPattern = (c_dopt.extraFilter == null ? null : Pattern.compile(c_dopt.extraFilter));
|
||||
public static Adapter create(final Context context, final DialogOptions dopt) {
|
||||
return new Adapter(context, dopt, dopt.isMultiSelectEnabled ? android.R.layout.simple_list_item_multiple_choice : android.R.layout.simple_list_item_1, new ArrayList<>());
|
||||
}
|
||||
|
||||
private Adapter(final Context context, final DialogOptions dopt, final int layout, final List<Integer> filteredItems) {
|
||||
super(context, layout, filteredItems);
|
||||
_layout = layout;
|
||||
_filteredItems = filteredItems;
|
||||
_inflater = LayoutInflater.from(context);
|
||||
_dopt = dopt;
|
||||
_extraPattern = (_dopt.extraFilter == null ? null : Pattern.compile(_dopt.extraFilter));
|
||||
_selectedItems = new HashSet<>(_dopt.preSelected != null ? _dopt.preSelected : Collections.emptyList());
|
||||
ContextUtils cu = new ContextUtils(context);
|
||||
_layoutHeight = (int) cu.convertDpToPx(36);
|
||||
cu.freeContextRef();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int pos, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
final Pair<String, Integer> item = getItem(pos);
|
||||
final String text = item.first;
|
||||
final int posInOriginalList = item.second;
|
||||
final int index = getItem(pos);
|
||||
|
||||
final TextView textView;
|
||||
if (convertView == null) {
|
||||
textView = (TextView) _inflater.inflate(_layout, parent, false);
|
||||
textView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
textView.setMinHeight(_layoutHeight);
|
||||
} else {
|
||||
textView = (TextView) convertView;
|
||||
}
|
||||
|
||||
if (posInOriginalList >= 0 && _dopt.iconsForData != null && posInOriginalList < _dopt.iconsForData.size() && _dopt.iconsForData.get(posInOriginalList) != 0) {
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(_dopt.iconsForData.get(posInOriginalList), 0, 0, 0);
|
||||
if (textView instanceof Checkable) {
|
||||
((Checkable) textView).setChecked(_selectedItems.contains(index));
|
||||
}
|
||||
|
||||
if (index >= 0 && _dopt.iconsForData != null && index < _dopt.iconsForData.size() && _dopt.iconsForData.get(index) != 0) {
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(_dopt.iconsForData.get(index), 0, 0, 0);
|
||||
textView.setCompoundDrawablePadding(32);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
textView.setCompoundDrawableTintList(ColorStateList.valueOf(_dopt.isDarkDialog ? Color.WHITE : Color.BLACK));
|
||||
|
@ -140,6 +165,7 @@ public class SearchOrCustomTextDialog {
|
|||
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
final CharSequence text = _dopt.data.get(index).toString();
|
||||
if (_dopt.highlightData != null) {
|
||||
final boolean hl = _dopt.highlightData.contains(text);
|
||||
textView.setTextColor(hl ? _dopt.highlightColor : _dopt.textColor);
|
||||
|
@ -157,6 +183,7 @@ public class SearchOrCustomTextDialog {
|
|||
return textView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return new Filter() {
|
||||
|
@ -164,24 +191,25 @@ public class SearchOrCustomTextDialog {
|
|||
@Override
|
||||
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
||||
_filteredItems.clear();
|
||||
_filteredItems.addAll((List<Pair<String, Integer>>) results.values);
|
||||
_filteredItems.addAll((List<Integer>) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(final CharSequence constraint) {
|
||||
final ArrayList<Pair<CharSequence, Integer>> resList = new ArrayList<>();
|
||||
final List<Integer> resList = new ArrayList<>();
|
||||
|
||||
if (_dopt.data != null) {
|
||||
final String fil = constraint.toString();
|
||||
final boolean emptySearch = fil.isEmpty();
|
||||
for (int i = 0; i < _dopt.data.size(); i++) {
|
||||
final CharSequence str = _dopt.data.get(i);
|
||||
final String str = _dopt.data.get(i).toString();
|
||||
final boolean matchExtra = (_extraPattern == null) || _extraPattern.matcher(str).find();
|
||||
final boolean matchNormal = str.toString().toLowerCase(Locale.getDefault()).contains(fil.toLowerCase(Locale.getDefault()));
|
||||
final boolean matchRegex = _dopt.searchIsRegex && (str.toString().matches(fil));
|
||||
final Locale locale = Locale.getDefault();
|
||||
final boolean matchNormal = str.toLowerCase(locale).contains(fil.toLowerCase(locale));
|
||||
final boolean matchRegex = _dopt.searchIsRegex && (str.matches(fil));
|
||||
if (matchExtra && (matchNormal || matchRegex || emptySearch)) {
|
||||
resList.add(new Pair<>(str, i));
|
||||
resList.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,12 +224,11 @@ public class SearchOrCustomTextDialog {
|
|||
}
|
||||
|
||||
public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activity, final DialogOptions dopt) {
|
||||
final List<Pair<String, Integer>> filteredItems = new ArrayList<>();
|
||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, dopt.isDarkDialog
|
||||
? android.support.v7.appcompat.R.style.Theme_AppCompat_Dialog
|
||||
: android.support.v7.appcompat.R.style.Theme_AppCompat_Light_Dialog
|
||||
);
|
||||
final WithPositionAdapter listAdapter = new WithPositionAdapter(activity, android.R.layout.simple_list_item_1, filteredItems, dopt);
|
||||
final Adapter listAdapter = Adapter.create(activity, dopt);
|
||||
|
||||
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
||||
searchEditText.setText(dopt.defaultText);
|
||||
|
@ -211,21 +238,30 @@ public class SearchOrCustomTextDialog {
|
|||
searchEditText.setHintTextColor((dopt.textColor & 0x00FFFFFF) | 0x99000000);
|
||||
searchEditText.setHint(dopt.searchHintText);
|
||||
searchEditText.setInputType(dopt.searchInputType == 0 ? searchEditText.getInputType() : dopt.searchInputType);
|
||||
searchEditText.addTextChangedListener(TextWatcherDummy.after((cbEditable) -> listAdapter.getFilter().filter(cbEditable)));
|
||||
|
||||
searchEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(final Editable arg0) {
|
||||
listAdapter.getFilter().filter(searchEditText.getText());
|
||||
}
|
||||
final ContextUtils cu = new ContextUtils(activity);
|
||||
final int margin = (int) cu.convertDpToPx(8);
|
||||
cu.freeContextRef();
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence arg0, final int arg1, final int arg2, final int arg3) {
|
||||
}
|
||||
final LinearLayout searchLayout = new LinearLayout(activity);
|
||||
searchLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence arg0, final int arg1, final int arg2, final int arg3) {
|
||||
}
|
||||
});
|
||||
LinearLayout.LayoutParams lp;
|
||||
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT, 1);
|
||||
lp.gravity = Gravity.START | Gravity.BOTTOM;
|
||||
searchLayout.addView(searchEditText, lp);
|
||||
|
||||
// 'Button to clear the search box'
|
||||
final ImageView clearButton = new ImageView(activity);
|
||||
clearButton.setImageResource(dopt.clearInputIcon);
|
||||
TooltipCompat.setTooltipText(clearButton, activity.getString(android.R.string.cancel));
|
||||
clearButton.setColorFilter(dopt.isDarkDialog ? Color.WHITE : Color.parseColor("#ff505050"));
|
||||
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT, 0);
|
||||
lp.gravity = Gravity.END | Gravity.CENTER_VERTICAL;
|
||||
lp.setMargins(margin, 0, (int) (margin * 1.5), 0);
|
||||
searchLayout.addView(clearButton, lp);
|
||||
clearButton.setOnClickListener((v) -> searchEditText.setText(""));
|
||||
|
||||
final ListView listView = new ListView(activity);
|
||||
final LinearLayout linearLayout = new LinearLayout(activity);
|
||||
|
@ -234,10 +270,9 @@ public class SearchOrCustomTextDialog {
|
|||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
if (dopt.isSearchEnabled) {
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
int px = (int) (new ContextUtils(listView.getContext()).convertDpToPx(8));
|
||||
lp.setMargins(px, px / 2, px, px / 2);
|
||||
linearLayout.addView(searchEditText, lp);
|
||||
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
lp.setMargins(margin, margin / 2, margin, margin / 2);
|
||||
linearLayout.addView(searchLayout, lp);
|
||||
}
|
||||
|
||||
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
|
||||
|
@ -251,36 +286,32 @@ public class SearchOrCustomTextDialog {
|
|||
.setOnCancelListener(null)
|
||||
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
|
||||
|
||||
if (dopt.titleText != 0) {
|
||||
dialogBuilder.setTitle(dopt.titleText);
|
||||
}
|
||||
|
||||
// Ok button action
|
||||
if ((dopt.isSearchEnabled && dopt.callback != null) || (dopt.isMultiSelectEnabled)) {
|
||||
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
||||
final String searchText = dopt.isSearchEnabled ? searchEditText.getText().toString() : null;
|
||||
if (dopt.positionCallback != null && !listAdapter._selectedItems.isEmpty()) {
|
||||
final List<Integer> sel = new ArrayList<>(listAdapter._selectedItems);
|
||||
Collections.sort(sel);
|
||||
dopt.positionCallback.callback(sel);
|
||||
} else if (dopt.callback != null && !TextUtils.isEmpty(searchText)) {
|
||||
dopt.callback.callback(searchText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Setup neutralbutton
|
||||
if (dopt.neutralButtonCallback != null && dopt.neutralButtonText != 0) {
|
||||
dialogBuilder.setNeutralButton(dopt.neutralButtonText, (dialogInterface, i) -> {
|
||||
dopt.neutralButtonCallback.callback();
|
||||
});
|
||||
}
|
||||
|
||||
if (dopt.titleText != 0) {
|
||||
dialogBuilder.setTitle(dopt.titleText);
|
||||
}
|
||||
|
||||
if (dopt.isSearchEnabled) {
|
||||
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
||||
dialogInterface.dismiss();
|
||||
if (dopt.callback != null && !TextUtils.isEmpty(searchEditText.getText().toString())) {
|
||||
dopt.callback.callback(searchEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final AlertDialog dialog = dialogBuilder.create();
|
||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
dialog.dismiss();
|
||||
if (dopt.callback != null) {
|
||||
dopt.callback.callback(filteredItems.get(position).first);
|
||||
}
|
||||
if (dopt.withPositionCallback != null) {
|
||||
final Pair<String, Integer> item = filteredItems.get(position);
|
||||
dopt.withPositionCallback.callback(item.first, item.second);
|
||||
}
|
||||
});
|
||||
|
||||
searchEditText.setOnKeyListener((keyView, keyCode, keyEvent) -> {
|
||||
if ((keyEvent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
|
@ -316,117 +347,49 @@ public class SearchOrCustomTextDialog {
|
|||
if (dopt.defaultText != null) {
|
||||
listAdapter.getFilter().filter(searchEditText.getText());
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to trigger callback with single item
|
||||
final Callback.b1<Integer> directActivate = (position) -> {
|
||||
final int index = listAdapter._filteredItems.get(position);
|
||||
dialog.dismiss();
|
||||
if (dopt.callback != null) {
|
||||
dopt.callback.callback(dopt.data.get(index).toString());
|
||||
}
|
||||
if (dopt.positionCallback != null) {
|
||||
dopt.positionCallback.callback(Collections.singletonList(index));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
// Helper function to append selection count to OK button
|
||||
final Button okButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
final String okText = activity.getString(dopt.okButtonText) + (dopt.isMultiSelectEnabled ? " (%d)" : "");
|
||||
final Callback.a0 setOkButtonState = () -> {
|
||||
okButton.setText(okText.replace("%d", Integer.toString(listAdapter._selectedItems.size())));
|
||||
};
|
||||
|
||||
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;
|
||||
// Set ok button text initially
|
||||
setOkButtonState.callback();
|
||||
|
||||
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;
|
||||
// Item click action
|
||||
listView.setOnItemClickListener((parent, textView, pos, id) -> {
|
||||
if (dopt.isMultiSelectEnabled) {
|
||||
final int index = listAdapter._filteredItems.get(pos);
|
||||
if (listAdapter._selectedItems.contains(index)) {
|
||||
listAdapter._selectedItems.remove(index);
|
||||
} else {
|
||||
listAdapter._selectedItems.add(index);
|
||||
}
|
||||
if (alwaysMatchDir) {
|
||||
return true;
|
||||
if (textView instanceof Checkable) {
|
||||
((Checkable) textView).setChecked(listAdapter._selectedItems.contains(index));
|
||||
}
|
||||
setOkButtonState.callback();
|
||||
} else {
|
||||
directActivate.callback(pos);
|
||||
}
|
||||
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 = null;
|
||||
try {
|
||||
iter = FileUtils.iterateFilesAndDirs(_searchDir, this, this);
|
||||
} catch (Exception ex) {
|
||||
// Iterator may throw an error at creation
|
||||
return ret;
|
||||
}
|
||||
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();
|
||||
}
|
||||
// long click always activates
|
||||
listView.setOnItemLongClickListener((parent, view, pos, id) -> directActivate.callback(pos));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,10 +78,10 @@ import net.gsantner.opoc.format.markdown.SimpleMarkdownParser;
|
|||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
|
@ -146,11 +146,11 @@ public class ContextUtils {
|
|||
/**
|
||||
* Get String by given string ressource identifier (textual)
|
||||
*/
|
||||
public String rstr(final String strResKey) {
|
||||
public String rstr(final String strResKey, Object... a0getResKeyAsFallback) {
|
||||
try {
|
||||
return rstr(getResId(ResType.STRING, strResKey));
|
||||
} catch (Resources.NotFoundException e) {
|
||||
return null;
|
||||
return a0getResKeyAsFallback != null && a0getResKeyAsFallback.length > 0 ? strResKey : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,14 +295,27 @@ public class ContextUtils {
|
|||
* Falls back to applicationId of the app which may differ from manifest.
|
||||
*/
|
||||
public Object getBuildConfigValue(final String fieldName) {
|
||||
String pkg = getPackageIdManifest() + ".BuildConfig";
|
||||
final String pkg = getPackageIdManifest() + ".BuildConfig";
|
||||
try {
|
||||
Class<?> c = Class.forName(pkg);
|
||||
return c.getField(fieldName).get(null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<String> getBuildConfigFields() {
|
||||
final String pkg = getPackageIdManifest() + ".BuildConfig";
|
||||
final List<String> fields = new ArrayList<>();
|
||||
try {
|
||||
for (Field f : Class.forName(pkg).getFields()) {
|
||||
fields.add(f.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -745,44 +758,27 @@ public class ContextUtils {
|
|||
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given {@link Bitmap} to {@code imageFile}, in {@link CompressFormat#JPEG} format
|
||||
*/
|
||||
public boolean writeImageToFileJpeg(final File imageFile, final Bitmap image) {
|
||||
return writeImageToFile(imageFile, image, Bitmap.CompressFormat.JPEG, 95);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given {@link Bitmap} to filesystem
|
||||
*
|
||||
* @param targetFile The file to be written in
|
||||
* @param image The image as android {@link Bitmap}
|
||||
* @param format One format of {@link CompressFormat}, null will determine based on filename
|
||||
* @param quality Quality level, defaults to 95
|
||||
* @param image Android {@link Bitmap}
|
||||
* @return True if writing was successful
|
||||
*/
|
||||
public boolean writeImageToFile(final File targetFile, final Bitmap image, CompressFormat format, Integer quality) {
|
||||
public boolean writeImageToFile(final File targetFile, final Bitmap image, Integer... a0quality) {
|
||||
final int quality = (a0quality != null && a0quality.length > 0 && a0quality[0] >= 0 && a0quality[0] <= 100) ? a0quality[0] : 70;
|
||||
final String lc = targetFile.getAbsolutePath().toLowerCase(Locale.ROOT);
|
||||
final CompressFormat format = lc.endsWith(".webp") ? CompressFormat.WEBP : (lc.endsWith(".png") ? CompressFormat.PNG : CompressFormat.JPEG);
|
||||
|
||||
boolean ok = false;
|
||||
File folder = new File(targetFile.getParent());
|
||||
if (quality == null || quality < 0 || quality > 100) {
|
||||
quality = 95;
|
||||
}
|
||||
if (format == null) {
|
||||
format = CompressFormat.JPEG;
|
||||
String lc = targetFile.getAbsolutePath().toLowerCase(Locale.ROOT);
|
||||
if (lc.endsWith(".png")) {
|
||||
format = CompressFormat.PNG;
|
||||
}
|
||||
if (lc.endsWith(".webp")) {
|
||||
format = CompressFormat.WEBP;
|
||||
}
|
||||
}
|
||||
if (folder.exists() || folder.mkdirs()) {
|
||||
FileOutputStream stream = null;
|
||||
try {
|
||||
stream = new FileOutputStream(targetFile); // overwrites this image every time
|
||||
stream = new FileOutputStream(targetFile);
|
||||
image.compress(format, quality, stream);
|
||||
return true;
|
||||
} catch (FileNotFoundException ignored) {
|
||||
ok = true;
|
||||
} catch (Exception ignored) {
|
||||
} finally {
|
||||
try {
|
||||
if (stream != null) {
|
||||
|
@ -792,7 +788,11 @@ public class ContextUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
try {
|
||||
image.recycle();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
package net.gsantner.opoc.util;
|
||||
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
|
@ -37,7 +39,7 @@ import java.util.UUID;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation"})
|
||||
@SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "SpellCheckingInspection", "deprecation", "TryFinallyCanBeTryWithResources"})
|
||||
public class FileUtils {
|
||||
// Used on methods like copyFile(src, dst)
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
@ -391,45 +393,58 @@ public class FileUtils {
|
|||
*/
|
||||
public static String getMimeType(File file) {
|
||||
String guess = null;
|
||||
if (file != null && file.exists() && file.isFile()) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(new FileInputStream(file));
|
||||
guess = URLConnection.guessContentTypeFromStream(is);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
if (file != null) {
|
||||
if (file.exists() && file.isFile()) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(new FileInputStream(file));
|
||||
guess = URLConnection.guessContentTypeFromStream(is);
|
||||
} catch (Exception ignored) {
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (guess == null || guess.isEmpty()) {
|
||||
guess = "*/*";
|
||||
String filename = file.getName().replace(".jenc", "");
|
||||
int dot = filename.lastIndexOf(".") + 1;
|
||||
if (dot > 0 && dot < filename.length()) {
|
||||
switch (filename.substring(dot)) {
|
||||
case "md":
|
||||
case "markdown":
|
||||
case "mkd":
|
||||
case "mdown":
|
||||
case "mkdn":
|
||||
case "mdwn":
|
||||
case "rmd":
|
||||
guess = "text/markdown";
|
||||
break;
|
||||
case "txt":
|
||||
guess = "text/plain";
|
||||
break;
|
||||
}
|
||||
String filename = file.getName().replace(".jenc", "");
|
||||
int dot = filename.lastIndexOf(".") + 1;
|
||||
if (dot > 0 && dot < filename.length()) {
|
||||
switch (filename.substring(dot)) {
|
||||
case "md":
|
||||
case "markdown":
|
||||
case "mkd":
|
||||
case "mdown":
|
||||
case "mkdn":
|
||||
case "mdwn":
|
||||
case "rmd":
|
||||
guess = "text/markdown";
|
||||
break;
|
||||
case "txt":
|
||||
guess = "text/plain";
|
||||
break;
|
||||
case "webp":
|
||||
guess = "image/webp";
|
||||
break;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
guess = "image/jpeg";
|
||||
break;
|
||||
case "png":
|
||||
guess = "image/png";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(guess)) {
|
||||
guess = URLConnection.guessContentTypeFromName(filename);
|
||||
}
|
||||
}
|
||||
return guess;
|
||||
|
||||
return TextUtils.isEmpty(guess) ? "*/*" : guess;
|
||||
}
|
||||
|
||||
public static boolean isTextFile(File file) {
|
||||
|
@ -496,9 +511,11 @@ public class FileUtils {
|
|||
} else if (bytes < 1000000) {
|
||||
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000f), "KB");
|
||||
} else if (bytes < 1000000000) {
|
||||
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000f), "GB");
|
||||
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000f), "MB");
|
||||
} else if (bytes < 1000000000000L) {
|
||||
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000000f), "GB");
|
||||
} else {
|
||||
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000000f), "TB");
|
||||
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000000000f), "TB");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ 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-ss", Locale.getDefault());
|
||||
public final static SimpleDateFormat SDF_SHORT = new SimpleDateFormat("yyMMdd-HHmmss", Locale.getDefault());
|
||||
public final static SimpleDateFormat SDF_IMAGES = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.getDefault()); //20190511-230845
|
||||
public final static String MIME_TEXT_PLAIN = "text/plain";
|
||||
public final static String PREF_KEY__SAF_TREE_URI = "pref_key__saf_tree_uri";
|
||||
|
||||
|
@ -93,9 +94,9 @@ public class ShareUtil {
|
|||
public final static int MIN_OVERWRITE_LENGTH = 5;
|
||||
|
||||
protected static String _lastCameraPictureFilepath;
|
||||
protected static String _fileProviderAuthority;
|
||||
|
||||
protected Context _context;
|
||||
protected String _fileProviderAuthority;
|
||||
protected String _chooserTitle;
|
||||
|
||||
public ShareUtil(final Context context) {
|
||||
|
@ -118,9 +119,8 @@ public class ShareUtil {
|
|||
return _fileProviderAuthority;
|
||||
}
|
||||
|
||||
public ShareUtil setFileProviderAuthority(final String fileProviderAuthority) {
|
||||
public static void setFileProviderAuthority(final String fileProviderAuthority) {
|
||||
_fileProviderAuthority = fileProviderAuthority;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
@ -230,9 +230,9 @@ public class ShareUtil {
|
|||
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
|
||||
showChooser(intent, null);
|
||||
return true;
|
||||
} catch (Exception e) { // FileUriExposed(API24) / IllegalArgument
|
||||
return false;
|
||||
} catch (Exception ignored) { // FileUriExposed(API24) / IllegalArgument
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -320,17 +320,6 @@ public class ShareUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(final Bitmap bitmap, final Bitmap.CompressFormat format) {
|
||||
return shareImage(bitmap, format, 95, "SharedImage");
|
||||
}
|
||||
|
||||
/**
|
||||
* Share the given bitmap with given format
|
||||
*
|
||||
|
@ -340,20 +329,36 @@ public class ShareUtil {
|
|||
* @param quality Quality of the exported image [0-100]
|
||||
* @return if success, true
|
||||
*/
|
||||
public boolean shareImage(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String imageName) {
|
||||
public boolean shareImage(final Bitmap bitmap, final Integer... quality) {
|
||||
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);
|
||||
File file = new File(_context.getCacheDir(), getFilenameWithTimestamp());
|
||||
if (bitmap != null && new ContextUtils(_context).writeImageToFile(file, bitmap, quality)) {
|
||||
String x = FileUtils.getMimeType(file);
|
||||
shareStream(file, FileUtils.getMimeType(file));
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a filename based off current datetime in filename (year, month, day, hour, minute, second)
|
||||
* Examples: Screenshot_20210208-184301_Trebuchet.png IMG_20190511-230845.jpg
|
||||
*
|
||||
* @param A0prefixA1postfixA2ext All arguments are optional and default values are taken for null
|
||||
* [0] = Prefix [Screenshot/IMG]
|
||||
* [1] = Postfix [Trebuchet]
|
||||
* [2] = File extensions [jpg/png/txt]
|
||||
* @return Filename
|
||||
*/
|
||||
public static String getFilenameWithTimestamp(String... A0prefixA1postfixA2ext) {
|
||||
final String prefix = (((A0prefixA1postfixA2ext != null && A0prefixA1postfixA2ext.length > 0 && !TextUtils.isEmpty(A0prefixA1postfixA2ext[0])) ? A0prefixA1postfixA2ext[0] : "Screenshot") + "_").trim().replaceFirst("^_$", "");
|
||||
final String postfix = ("_" + ((A0prefixA1postfixA2ext != null && A0prefixA1postfixA2ext.length > 1 && !TextUtils.isEmpty(A0prefixA1postfixA2ext[1])) ? A0prefixA1postfixA2ext[1] : "")).trim().replaceFirst("^_$", "");
|
||||
final String ext = (A0prefixA1postfixA2ext != null && A0prefixA1postfixA2ext.length > 2 && !TextUtils.isEmpty(A0prefixA1postfixA2ext[2])) ? A0prefixA1postfixA2ext[2] : "jpg";
|
||||
return String.format("%s%s%s.%s", prefix.trim(), SDF_IMAGES.format(new Date()), postfix.trim(), ext.toLowerCase().replace(".", "").replace("jpeg", "jpg"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a {@link WebView}'s contents, also allows to create a PDF
|
||||
*
|
||||
|
@ -405,21 +410,21 @@ public class ShareUtil {
|
|||
* @return A {@link Bitmap} or null
|
||||
*/
|
||||
@Nullable
|
||||
public static Bitmap getBitmapFromWebView(final WebView webView) {
|
||||
public static Bitmap getBitmapFromWebView(final WebView webView, final boolean... a0fullpage) {
|
||||
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());
|
||||
if (a0fullpage != null && a0fullpage.length > 0 && a0fullpage[0]) {
|
||||
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);
|
||||
Bitmap bitmap = Bitmap.createBitmap(webView.getMeasuredWidth(), webView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawBitmap(bitmap, 0, bitmap.getHeight(), new Paint());
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ buildscript {
|
|||
version_plugin_kotlin = "1.3.72"
|
||||
enable_plugin_kotlin = false
|
||||
|
||||
version_compileSdk = 28
|
||||
version_buildTools = "28.0.3"
|
||||
version_compileSdk = 29
|
||||
version_buildTools = "29.0.3"
|
||||
version_minSdk = 17
|
||||
|
||||
// https://developer.android.com/topic/libraries/support-library/
|
||||
|
|
Loading…
Reference in a new issue