mirror of
https://github.com/gsantner/dandelion
synced 2024-11-26 22:32:11 +01:00
Compare commits
2 commits
fa7e37ccba
...
ff62aa5a07
Author | SHA1 | Date | |
---|---|---|---|
|
ff62aa5a07 | ||
|
6fbd399a4b |
13 changed files with 350 additions and 115 deletions
69
.github/workflows/build-android-project.yml
vendored
Normal file
69
.github/workflows/build-android-project.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
name: "CI"
|
||||||
|
|
||||||
|
on: [push, pull_request_target]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: "!contains(github.event.head_commit.message, 'ci skip') && (!contains(github.event_name, 'pull_request') || (contains(github.event_name, 'pull_request') && github.event.pull_request.head.repo.full_name != github.repository))"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: "Checkout: Code"
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
|
||||||
|
- name: "Checkout: Code (PR)"
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
if: "contains(github.event_name, 'pull_request')"
|
||||||
|
with:
|
||||||
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
|
- name: "Setup: Java"
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
|
||||||
|
- name: "Cache: Gradle"
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle
|
||||||
|
.gradle
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.*') }}
|
||||||
|
|
||||||
|
- name: "Build: Project with make"
|
||||||
|
run: make clean all
|
||||||
|
|
||||||
|
- name: "Build: List dist files"
|
||||||
|
if: always()
|
||||||
|
run: find dist -type f -maxdepth 2
|
||||||
|
|
||||||
|
- name: "Artifacts: All"
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v2.2.1
|
||||||
|
with:
|
||||||
|
name: "all"
|
||||||
|
path: dist
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
- name: "Artifacts: Android APK"
|
||||||
|
uses: actions/upload-artifact@v2.2.1
|
||||||
|
with:
|
||||||
|
name: "android-apk"
|
||||||
|
path: |
|
||||||
|
dist/*.apk
|
||||||
|
|
||||||
|
- name: "Test: JUnit report"
|
||||||
|
if: always()
|
||||||
|
uses: mikepenz/action-junit-report@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
report_paths: 'dist/tests/TEST-*.xml'
|
||||||
|
check_name: "JUnit"
|
||||||
|
|
||||||
|
- name: "Test: Android Lint"
|
||||||
|
if: always()
|
||||||
|
uses: yutailang0119/action-android-lint@v1.0.2
|
||||||
|
with:
|
||||||
|
xml_path: 'dist/lint/lint-results-flavorDefaultDebug.xml'
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -36,6 +36,7 @@ tmp/
|
||||||
### Gradle ###
|
### Gradle ###
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
gradle-app.setting
|
gradle-app.setting
|
||||||
|
|
||||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||||
|
|
55
Makefile
Normal file
55
Makefile
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# License of Makefile: Public Domain / CC0
|
||||||
|
.PHONY: $(shell sed -n -e '/^$$/ { n ; /^[^ .\#][^ ]*:/ { s/:.*$$// ; p ; } ; }' $(MAKEFILE_LIST))
|
||||||
|
.NOTPARALLEL: clean
|
||||||
|
.DEFAULT_GOAL := all
|
||||||
|
|
||||||
|
env-%:
|
||||||
|
@: $(if ${${*}},,$(error Environment variable $* not set))
|
||||||
|
####################################################################################
|
||||||
|
|
||||||
|
DIST_DIR = dist
|
||||||
|
MOVE = mv
|
||||||
|
|
||||||
|
all: $(DIST_DIR) lint test build
|
||||||
|
|
||||||
|
####################################################################################
|
||||||
|
|
||||||
|
$(DIST_DIR):
|
||||||
|
mkdir -p ${DIST_DIR}
|
||||||
|
|
||||||
|
.NOTPARALLEL: gradle gradle-check-error
|
||||||
|
gradle: env-ANDROID_SDK_ROOT
|
||||||
|
mkdir -p $(DIST_DIR)/log/
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew --no-daemon --parallel --stacktrace $A 2>&1 | tee "$(DIST_DIR)/log/gradle.log"
|
||||||
|
@echo "-----------------------------------------------------------------------------------"
|
||||||
|
|
||||||
|
gradle-check-error:
|
||||||
|
mv "$(DIST_DIR)/log/gradle.log" "$(DIST_DIR)/log/gradle$A.log"
|
||||||
|
cat "$(DIST_DIR)/log/gradle$A.log" | grep "BUILD " | tail -n1 | grep -q "BUILD SUCCESSFUL in"
|
||||||
|
|
||||||
|
build:
|
||||||
|
rm -f $(DIST_DIR)/*.apk
|
||||||
|
$(MAKE) A="clean assembleFlavorAtest -x lint" gradle
|
||||||
|
find app -type f -iname '*.apk' | grep -v 'unsigned.apk' | xargs cp -R -t $(DIST_DIR)/
|
||||||
|
$(MAKE) A="-build" gradle-check-error
|
||||||
|
|
||||||
|
lint:
|
||||||
|
rm -Rf $(DIST_DIR)/lint
|
||||||
|
mkdir -p $(DIST_DIR)/lint/
|
||||||
|
$(MAKE) A="lintFlavorDefaultDebug" gradle
|
||||||
|
find app -type f -iname 'lint-results-*' | xargs cp -R -t $(DIST_DIR)/lint
|
||||||
|
$(MAKE) A="-lint" gradle-check-error
|
||||||
|
|
||||||
|
test:
|
||||||
|
rm -Rf $(DIST_DIR)/tests
|
||||||
|
$(MAKE) A="testFlavorDefaultDebugUnitTest -x lint" gradle
|
||||||
|
mkdir -p app/build/test-results/testFlavorDefaultDebugUnitTest && echo 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHRlc3RzdWl0ZSBuYW1lPSJkdW1teSIgdGVzdHM9IjEiIHNraXBwZWQ9IjAiIGZhaWx1cmVzPSIwIiBlcnJvcnM9IjAiIHRpbWVzdGFtcD0iMjAyMC0xMi0wOFQwMDowMDowMCIgaG9zdG5hbWU9ImxvY2FsaG9zdCIgdGltZT0iMC4wMSI+CiAgPHByb3BlcnRpZXMvPgogIDx0ZXN0Y2FzZSBuYW1lPSJkdW1teSIgY2xhc3NuYW1lPSJkdW1teSIgdGltZT0iMC4wMSIvPgogIDxzeXN0ZW0tb3V0PjwhW0NEQVRBW11dPjwvc3lzdGVtLW91dD4KICA8c3lzdGVtLWVycj48IVtDREFUQVtdXT48L3N5c3RlbS1lcnI+CjwvdGVzdHN1aXRlPgo=' | base64 -d > 'app/build/test-results/testFlavorDefaultDebugUnitTest/TEST-dummy.xml'
|
||||||
|
find app -type d -iname 'testFlavorDefaultDebugUnitTest' | xargs cp -R -t $(DIST_DIR)/
|
||||||
|
mv ${DIST_DIR}/testFlavorDefaultDebugUnitTest $(DIST_DIR)/tests
|
||||||
|
$(MAKE) A="-test" gradle-check-error
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(MAKE) A="clean" gradle
|
||||||
|
rm -Rf $(DIST_DIR) app/build app/flavor*
|
||||||
|
$(MAKE) $(DIST_DIR)
|
|
@ -21,6 +21,7 @@ android {
|
||||||
buildConfigField "boolean", "IS_TEST_BUILD", "false"
|
buildConfigField "boolean", "IS_TEST_BUILD", "false"
|
||||||
buildConfigField "boolean", "IS_GPLAY_BUILD", "false"
|
buildConfigField "boolean", "IS_GPLAY_BUILD", "false"
|
||||||
buildConfigField "String[]", "DETECTED_ANDROID_LOCALES", "${findUsedAndroidLocales()}"
|
buildConfigField "String[]", "DETECTED_ANDROID_LOCALES", "${findUsedAndroidLocales()}"
|
||||||
|
buildConfigField "String", "BUILD_DATE", "\"${getBuildDate()}\""
|
||||||
buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
|
buildConfigField "String", "GITHASH", "\"${getGitHash()}\""
|
||||||
setProperty("archivesBaseName", applicationId + "-v" + versionCode + "-" + versionName)
|
setProperty("archivesBaseName", applicationId + "-v" + versionCode + "-" + versionName)
|
||||||
}
|
}
|
||||||
|
@ -89,8 +90,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'MissingTranslation'
|
disable 'MissingTranslation', 'InvalidPackage', 'ObsoleteLintCustomCheck', 'DefaultLocale', 'UnusedAttribute', 'VectorRaster', 'InflateParams', 'IconLocation', 'UnusedResources', 'TypographyEllipsis'
|
||||||
disable 'InvalidPackage'
|
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,9 +118,10 @@ dependencies {
|
||||||
implementation 'commons-io:commons-io:2.6'
|
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}"
|
||||||
|
//noinspection AnnotationProcessorOnCompilePath
|
||||||
implementation "com.jakewharton:butterknife:${version_library_butterknife}"
|
implementation "com.jakewharton:butterknife:${version_library_butterknife}"
|
||||||
if (enable_plugin_kotlin) {
|
if (enable_plugin_kotlin) {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$version_plugin_kotlin"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${version_plugin_kotlin}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processors
|
// Processors
|
||||||
|
|
|
@ -125,6 +125,20 @@ public class SimpleMarkdownParser {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
public final static SmpFilter FILTER_H_TO_SUP = new SmpFilter() {
|
||||||
|
@Override
|
||||||
|
public String filter(String text) {
|
||||||
|
text = text
|
||||||
|
.replace("<h1>", "<sup><sup><sup>")
|
||||||
|
.replace("</h1>", "</sup></sup></sup>")
|
||||||
|
.replace("<h2>", "<sup><sup>")
|
||||||
|
.replace("</h2>", "</sup></sup>")
|
||||||
|
.replace("<h3>", "<sup>")
|
||||||
|
.replace("</h3>", "</sup>")
|
||||||
|
;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
};
|
||||||
public final static SmpFilter FILTER_NONE = new SmpFilter() {
|
public final static SmpFilter FILTER_NONE = new SmpFilter() {
|
||||||
@Override
|
@Override
|
||||||
public String filter(String text) {
|
public String filter(String text) {
|
||||||
|
|
|
@ -11,12 +11,14 @@
|
||||||
package net.gsantner.opoc.ui;
|
package net.gsantner.opoc.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.annotation.ColorInt;
|
import android.support.annotation.ColorInt;
|
||||||
|
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.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
|
@ -24,10 +26,14 @@ 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;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableString;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
@ -58,16 +64,23 @@ public class SearchOrCustomTextDialog {
|
||||||
|
|
||||||
public static class DialogOptions {
|
public static class DialogOptions {
|
||||||
public Callback.a1<String> callback;
|
public Callback.a1<String> callback;
|
||||||
public List<? extends CharSequence> data = new ArrayList<>();
|
public Callback.a2<String, Integer> withPositionCallback;
|
||||||
public List<? extends CharSequence> highlightData = new ArrayList<>();
|
public List<? extends CharSequence> data;
|
||||||
public List<Integer> iconsForData = new ArrayList<>();
|
public List<? extends CharSequence> highlightData;
|
||||||
|
public List<Integer> iconsForData;
|
||||||
public String messageText = "";
|
public String messageText = "";
|
||||||
|
public String defaultText = "";
|
||||||
public boolean isSearchEnabled = true;
|
public boolean isSearchEnabled = true;
|
||||||
public boolean isDarkDialog = false;
|
public boolean isDarkDialog = false;
|
||||||
public int dialogWidthDp = WindowManager.LayoutParams.MATCH_PARENT;
|
public int dialogWidthDp = WindowManager.LayoutParams.MATCH_PARENT;
|
||||||
public int dialogHeightDp = WindowManager.LayoutParams.WRAP_CONTENT;
|
public int dialogHeightDp = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
public int gravity = Gravity.NO_GRAVITY;
|
public int gravity = Gravity.NO_GRAVITY;
|
||||||
public int searchInputType = 0;
|
public int searchInputType = 0;
|
||||||
|
public boolean searchIsRegex = false;
|
||||||
|
public Callback.a1<Spannable> highlighter;
|
||||||
|
public String extraFilter = null;
|
||||||
|
|
||||||
|
public Callback.a0 neutralButtonCallback = null;
|
||||||
|
|
||||||
@ColorInt
|
@ColorInt
|
||||||
public int textColor = 0xFF000000;
|
public int textColor = 0xFF000000;
|
||||||
|
@ -76,29 +89,47 @@ public class SearchOrCustomTextDialog {
|
||||||
@StringRes
|
@StringRes
|
||||||
public int cancelButtonText = android.R.string.cancel;
|
public int cancelButtonText = android.R.string.cancel;
|
||||||
@StringRes
|
@StringRes
|
||||||
|
public int neutralButtonText = 0;
|
||||||
|
@StringRes
|
||||||
public int okButtonText = android.R.string.ok;
|
public int okButtonText = android.R.string.ok;
|
||||||
@StringRes
|
@StringRes
|
||||||
public int titleText = android.R.string.untitled;
|
public int titleText = 0;
|
||||||
@StringRes
|
@StringRes
|
||||||
public int searchHintText = android.R.string.search_go;
|
public int searchHintText = android.R.string.search_go;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showMultiChoiceDialogWithSearchFilterUI(final Activity activity, final DialogOptions dopt) {
|
private static class WithPositionAdapter extends ArrayAdapter<Pair<String, Integer>> {
|
||||||
final List<CharSequence> allItems = new ArrayList<>(dopt.data);
|
|
||||||
final List<CharSequence> filteredItems = new ArrayList<>(allItems);
|
final LayoutInflater mInflater;
|
||||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, dopt.isDarkDialog
|
final @LayoutRes
|
||||||
? android.support.v7.appcompat.R.style.Theme_AppCompat_Dialog
|
int mLayout;
|
||||||
: android.support.v7.appcompat.R.style.Theme_AppCompat_Light_Dialog
|
final DialogOptions dopt;
|
||||||
);
|
final List<Pair<String, Integer>> filteredItems;
|
||||||
|
final Pattern extraPattern;
|
||||||
|
|
||||||
|
WithPositionAdapter(Context context, @LayoutRes int layout, List<Pair<String, Integer>> filteredItems, DialogOptions dopt) {
|
||||||
|
super(context, layout, filteredItems);
|
||||||
|
mInflater = LayoutInflater.from(context);
|
||||||
|
mLayout = layout;
|
||||||
|
this.dopt = dopt;
|
||||||
|
this.filteredItems = filteredItems;
|
||||||
|
extraPattern = dopt.extraFilter == null ? null : Pattern.compile(dopt.extraFilter);
|
||||||
|
}
|
||||||
|
|
||||||
final ArrayAdapter<CharSequence> listAdapter = new ArrayAdapter<CharSequence>(activity, android.R.layout.simple_list_item_1, filteredItems) {
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public View getView(int pos, @Nullable View convertView, @NonNull ViewGroup parent) {
|
public View getView(int pos, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||||
TextView textView = (TextView) super.getView(pos, convertView, parent);
|
final Pair<String, Integer> item = getItem(pos);
|
||||||
String text = textView.getText().toString();
|
final String text = item.first;
|
||||||
|
final int posInOriginalList = item.second;
|
||||||
|
|
||||||
|
final TextView textView;
|
||||||
|
if (convertView == null) {
|
||||||
|
textView = (TextView) mInflater.inflate(mLayout, parent, false);
|
||||||
|
} else {
|
||||||
|
textView = (TextView) convertView;
|
||||||
|
}
|
||||||
|
|
||||||
int posInOriginalList = dopt.data.indexOf(text);
|
|
||||||
if (posInOriginalList >= 0 && dopt.iconsForData != null && posInOriginalList < dopt.iconsForData.size() && dopt.iconsForData.get(posInOriginalList) != 0) {
|
if (posInOriginalList >= 0 && dopt.iconsForData != null && posInOriginalList < dopt.iconsForData.size() && dopt.iconsForData.get(posInOriginalList) != 0) {
|
||||||
textView.setCompoundDrawablesWithIntrinsicBounds(dopt.iconsForData.get(posInOriginalList), 0, 0, 0);
|
textView.setCompoundDrawablesWithIntrinsicBounds(dopt.iconsForData.get(posInOriginalList), 0, 0, 0);
|
||||||
textView.setCompoundDrawablePadding(32);
|
textView.setCompoundDrawablePadding(32);
|
||||||
|
@ -109,9 +140,19 @@ public class SearchOrCustomTextDialog {
|
||||||
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hl = dopt.highlightData.contains(text);
|
if (dopt.highlightData != null) {
|
||||||
|
final boolean hl = dopt.highlightData.contains(text);
|
||||||
textView.setTextColor(hl ? dopt.highlightColor : dopt.textColor);
|
textView.setTextColor(hl ? dopt.highlightColor : dopt.textColor);
|
||||||
textView.setTypeface(null, hl ? Typeface.BOLD : Typeface.NORMAL);
|
textView.setTypeface(null, hl ? Typeface.BOLD : Typeface.NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dopt.highlighter != null) {
|
||||||
|
Spannable s = new SpannableString(text);
|
||||||
|
dopt.highlighter.callback(s);
|
||||||
|
textView.setText(s);
|
||||||
|
} else {
|
||||||
|
textView.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
return textView;
|
return textView;
|
||||||
}
|
}
|
||||||
|
@ -123,30 +164,47 @@ public class SearchOrCustomTextDialog {
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
||||||
filteredItems.clear();
|
filteredItems.clear();
|
||||||
filteredItems.addAll((List<String>) results.values);
|
filteredItems.addAll((List<Pair<String, Integer>>) results.values);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected FilterResults performFiltering(final CharSequence constraint) {
|
protected FilterResults performFiltering(final CharSequence constraint) {
|
||||||
final FilterResults res = new FilterResults();
|
final ArrayList<Pair<CharSequence, Integer>> resList = new ArrayList<>();
|
||||||
final ArrayList<CharSequence> resList = new ArrayList<>();
|
|
||||||
final String fil = constraint.toString();
|
|
||||||
|
|
||||||
for (final CharSequence str : allItems) {
|
if (dopt.data != null) {
|
||||||
if ("".equals(fil) || str.toString().toLowerCase(Locale.getDefault()).contains(fil.toLowerCase(Locale.getDefault()))) {
|
final String fil = constraint.toString();
|
||||||
resList.add(str);
|
final boolean emptySearch = fil.isEmpty();
|
||||||
|
for (int i = 0; i < dopt.data.size(); i++) {
|
||||||
|
final CharSequence str = dopt.data.get(i);
|
||||||
|
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));
|
||||||
|
if (matchExtra && (matchNormal || matchRegex || emptySearch)) {
|
||||||
|
resList.add(new Pair<>(str, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final FilterResults res = new FilterResults();
|
||||||
res.values = resList;
|
res.values = resList;
|
||||||
res.count = resList.size();
|
res.count = resList.size();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
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 AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
final AppCompatEditText searchEditText = new AppCompatEditText(activity);
|
||||||
|
searchEditText.setText(dopt.defaultText);
|
||||||
searchEditText.setSingleLine(true);
|
searchEditText.setSingleLine(true);
|
||||||
searchEditText.setMaxLines(1);
|
searchEditText.setMaxLines(1);
|
||||||
searchEditText.setTextColor(dopt.textColor);
|
searchEditText.setTextColor(dopt.textColor);
|
||||||
|
@ -174,24 +232,35 @@ public class SearchOrCustomTextDialog {
|
||||||
listView.setAdapter(listAdapter);
|
listView.setAdapter(listAdapter);
|
||||||
listView.setVisibility(dopt.data != null && !dopt.data.isEmpty() ? View.VISIBLE : View.GONE);
|
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);
|
||||||
int px = (int) (new ContextUtils(listView.getContext()).convertDpToPx(8));
|
int px = (int) (new ContextUtils(listView.getContext()).convertDpToPx(8));
|
||||||
lp.setMargins(px, px / 2, px, px / 2);
|
lp.setMargins(px, px / 2, px, px / 2);
|
||||||
linearLayout.addView(searchEditText, lp);
|
linearLayout.addView(searchEditText, lp);
|
||||||
}
|
}
|
||||||
|
|
||||||
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
|
final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0);
|
||||||
layoutParams.weight = 1;
|
layoutParams.weight = 1;
|
||||||
linearLayout.addView(listView, layoutParams);
|
linearLayout.addView(listView, layoutParams);
|
||||||
if (!TextUtils.isEmpty(dopt.messageText)) {
|
if (!TextUtils.isEmpty(dopt.messageText)) {
|
||||||
dialogBuilder.setMessage(dopt.messageText);
|
dialogBuilder.setMessage(dopt.messageText);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogBuilder.setView(linearLayout)
|
dialogBuilder.setView(linearLayout)
|
||||||
.setOnCancelListener(null)
|
.setOnCancelListener(null)
|
||||||
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
|
.setNegativeButton(dopt.cancelButtonText, (dialogInterface, i) -> dialogInterface.dismiss());
|
||||||
|
|
||||||
|
if (dopt.neutralButtonCallback != null && dopt.neutralButtonText != 0) {
|
||||||
|
dialogBuilder.setNeutralButton(dopt.neutralButtonText, (dialogInterface, i) -> {
|
||||||
|
dopt.neutralButtonCallback.callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (dopt.titleText != 0) {
|
if (dopt.titleText != 0) {
|
||||||
dialogBuilder.setTitle(dopt.titleText);
|
dialogBuilder.setTitle(dopt.titleText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dopt.isSearchEnabled) {
|
if (dopt.isSearchEnabled) {
|
||||||
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
dialogBuilder.setPositiveButton(dopt.okButtonText, (dialogInterface, i) -> {
|
||||||
dialogInterface.dismiss();
|
dialogInterface.dismiss();
|
||||||
|
@ -205,7 +274,11 @@ public class SearchOrCustomTextDialog {
|
||||||
listView.setOnItemClickListener((parent, view, position, id) -> {
|
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
if (dopt.callback != null) {
|
if (dopt.callback != null) {
|
||||||
dopt.callback.callback(filteredItems.get(position).toString());
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -220,7 +293,6 @@ public class SearchOrCustomTextDialog {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Window w;
|
Window w;
|
||||||
if ((w = dialog.getWindow()) != null && dopt.isSearchEnabled) {
|
if ((w = dialog.getWindow()) != null && dopt.isSearchEnabled) {
|
||||||
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
w.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||||
|
@ -241,6 +313,9 @@ public class SearchOrCustomTextDialog {
|
||||||
if (dopt.isSearchEnabled) {
|
if (dopt.isSearchEnabled) {
|
||||||
searchEditText.requestFocus();
|
searchEditText.requestFocus();
|
||||||
}
|
}
|
||||||
|
if (dopt.defaultText != null) {
|
||||||
|
listAdapter.getFilter().filter(searchEditText.getText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,10 @@ public class Callback {
|
||||||
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 b0 {
|
||||||
|
boolean callback();
|
||||||
|
}
|
||||||
|
|
||||||
public interface b1<A> {
|
public interface b1<A> {
|
||||||
boolean callback(A arg1);
|
boolean callback(A arg1);
|
||||||
}
|
}
|
||||||
|
@ -56,4 +60,28 @@ public class Callback {
|
||||||
public interface b5<A, B, C, D, E> {
|
public interface b5<A, B, C, D, E> {
|
||||||
boolean callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
boolean callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface s0 {
|
||||||
|
String callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface s1<A> {
|
||||||
|
String callback(A arg1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface s2<A, B> {
|
||||||
|
String callback(A arg1, B arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface s3<A, B, C> {
|
||||||
|
String callback(A arg1, B arg2, C arg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface s4<A, B, C, D> {
|
||||||
|
String callback(A arg1, B arg2, C arg3, D arg4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface s5<A, B, C, D, E> {
|
||||||
|
String callback(A arg1, B arg2, C arg3, D arg4, E arg5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -893,7 +893,7 @@ public class ContextUtils {
|
||||||
public CharSequence filter(CharSequence src, int start, int end, Spanned dest, int dstart, int dend) {
|
public CharSequence filter(CharSequence src, int start, int end, Spanned dest, int dstart, int dend) {
|
||||||
if (src.length() < 1) return null;
|
if (src.length() < 1) return null;
|
||||||
char last = src.charAt(src.length() - 1);
|
char last = src.charAt(src.length() - 1);
|
||||||
String illegal = "|\\?*<\":>+[]/'";
|
String illegal = "|\\?*<\":>[]/'";
|
||||||
if (illegal.indexOf(last) > -1) return src.subSequence(0, src.length() - 1);
|
if (illegal.indexOf(last) > -1) return src.subSequence(0, src.length() - 1);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -935,7 +935,11 @@ public class ContextUtils {
|
||||||
ContentResolver cr = _context.getContentResolver();
|
ContentResolver cr = _context.getContentResolver();
|
||||||
mimeType = cr.getType(uri);
|
mimeType = cr.getType(uri);
|
||||||
} else {
|
} else {
|
||||||
String ext = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
|
String filename = uri.toString();
|
||||||
|
if (filename.endsWith(".jenc")) {
|
||||||
|
filename = filename.replace(".jenc", "");
|
||||||
|
}
|
||||||
|
String ext = MimeTypeMap.getFileExtensionFromUrl(filename);
|
||||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase());
|
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase());
|
||||||
|
|
||||||
// Try to guess if the recommended methods fail
|
// Try to guess if the recommended methods fail
|
||||||
|
|
|
@ -409,9 +409,10 @@ public class FileUtils {
|
||||||
|
|
||||||
if (guess == null || guess.isEmpty()) {
|
if (guess == null || guess.isEmpty()) {
|
||||||
guess = "*/*";
|
guess = "*/*";
|
||||||
int dot = file.getName().lastIndexOf(".") + 1;
|
String filename = file.getName().replace(".jenc", "");
|
||||||
if (dot > 0 && dot < file.getName().length()) {
|
int dot = filename.lastIndexOf(".") + 1;
|
||||||
switch (file.getName().substring(dot)) {
|
if (dot > 0 && dot < filename.length()) {
|
||||||
|
switch (filename.substring(dot)) {
|
||||||
case "md":
|
case "md":
|
||||||
case "markdown":
|
case "markdown":
|
||||||
case "mkd":
|
case "mkd":
|
||||||
|
@ -488,4 +489,16 @@ public class FileUtils {
|
||||||
ret[2] = (int) (diff / 1000) % 60; // sec
|
ret[2] = (int) (diff / 1000) % 60; // sec
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getHumanReadableByteCountSI(final long bytes) {
|
||||||
|
if (bytes < 1000) {
|
||||||
|
return String.format(Locale.getDefault(), "%d%s", bytes, "B");
|
||||||
|
} 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");
|
||||||
|
} else {
|
||||||
|
return String.format(Locale.getDefault(), "%.2f%s", (bytes / 1000000000f), "TB");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.net.URL;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -150,6 +151,7 @@ public class NetworkUtils {
|
||||||
return performCall(url, method, data, null);
|
return performCall(url, method, data, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CharsetObjectCanBeUsed")
|
||||||
private static String performCall(final URL url, final String method, final String data, final HttpURLConnection existingConnection) {
|
private static String performCall(final URL url, final String method, final String data, final HttpURLConnection existingConnection) {
|
||||||
try {
|
try {
|
||||||
final HttpURLConnection connection = existingConnection != null
|
final HttpURLConnection connection = existingConnection != null
|
||||||
|
@ -160,7 +162,7 @@ public class NetworkUtils {
|
||||||
if (data != null && !data.isEmpty()) {
|
if (data != null && !data.isEmpty()) {
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
final OutputStream output = connection.getOutputStream();
|
final OutputStream output = connection.getOutputStream();
|
||||||
output.write(data.getBytes(Charset.forName(UTF8)));
|
output.write(data.getBytes(Charset.forName("UTF-8")));
|
||||||
output.flush();
|
output.flush();
|
||||||
output.close();
|
output.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ 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", "JavadocReference"})
|
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "SameParameterValue", "unused", "deprecation", "ConstantConditions", "ObsoleteSdkInt", "SpellCheckingInspection", "JavadocReference", "ConstantLocale"})
|
||||||
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-ss", Locale.getDefault());
|
public final static SimpleDateFormat SDF_RFC3339_ISH = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", Locale.getDefault());
|
||||||
|
@ -90,6 +90,8 @@ public class ShareUtil {
|
||||||
public final static int REQUEST_PICK_PICTURE = 50002;
|
public final static int REQUEST_PICK_PICTURE = 50002;
|
||||||
public final static int REQUEST_SAF = 50003;
|
public final static int REQUEST_SAF = 50003;
|
||||||
|
|
||||||
|
public final static int MIN_OVERWRITE_LENGTH = 5;
|
||||||
|
|
||||||
protected static String _lastCameraPictureFilepath;
|
protected static String _lastCameraPictureFilepath;
|
||||||
|
|
||||||
protected Context _context;
|
protected Context _context;
|
||||||
|
@ -1132,7 +1134,9 @@ public class ShareUtil {
|
||||||
try {
|
try {
|
||||||
FileOutputStream fileOutputStream = null;
|
FileOutputStream fileOutputStream = null;
|
||||||
ParcelFileDescriptor pfd = null;
|
ParcelFileDescriptor pfd = null;
|
||||||
if (file.canWrite() || (!file.exists() && file.getParentFile().canWrite())) {
|
final boolean existingEmptyFile = file.canWrite() && file.length() < MIN_OVERWRITE_LENGTH;
|
||||||
|
final boolean nonExistingCreatableFile = !file.exists() && file.getParentFile().canWrite();
|
||||||
|
if (existingEmptyFile || nonExistingCreatableFile) {
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
file.mkdirs();
|
file.mkdirs();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1144,7 +1148,7 @@ public class ShareUtil {
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
} else {
|
} else {
|
||||||
pfd = _context.getContentResolver().openFileDescriptor(dof.getUri(), "w");
|
pfd = _context.getContentResolver().openFileDescriptor(dof.getUri(), "rw");
|
||||||
fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
|
fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
build.gradle
11
build.gradle
|
@ -13,8 +13,8 @@ import java.text.SimpleDateFormat
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
version_gradle_tools = "3.6.1"
|
version_gradle_tools = "3.6.3"
|
||||||
version_plugin_kotlin = "1.3.71"
|
version_plugin_kotlin = "1.3.72"
|
||||||
enable_plugin_kotlin = false
|
enable_plugin_kotlin = false
|
||||||
|
|
||||||
version_compileSdk = 28
|
version_compileSdk = 28
|
||||||
|
@ -59,6 +59,13 @@ allprojects {
|
||||||
tasks.matching { task -> task.name.matches('.*generate.*Resources') }.all {
|
tasks.matching { task -> task.name.matches('.*generate.*Resources') }.all {
|
||||||
task -> task.dependsOn copyRepoFiles
|
task -> task.dependsOn copyRepoFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.matching {it instanceof Test}.all { // Enable unit test output, html+xml output
|
||||||
|
testLogging.events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||||
|
testLogging.showStandardStreams = true
|
||||||
|
reports.junitXml.enabled = true
|
||||||
|
reports.html.enabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
|
|
38
circle.yml
38
circle.yml
|
@ -1,38 +0,0 @@
|
||||||
###################
|
|
||||||
general:
|
|
||||||
artifacts:
|
|
||||||
- /home/ubuntu/dandelion/app/build/outputs/apk/
|
|
||||||
branches:
|
|
||||||
ignore:
|
|
||||||
- gh-pages
|
|
||||||
- l10n_master
|
|
||||||
- crowdin
|
|
||||||
|
|
||||||
###################
|
|
||||||
machine:
|
|
||||||
java:
|
|
||||||
version: oraclejdk8
|
|
||||||
environment:
|
|
||||||
ANDROID_HOME: /usr/local/android-sdk-linux
|
|
||||||
|
|
||||||
###################
|
|
||||||
dependencies:
|
|
||||||
pre:
|
|
||||||
# Android SDK Platform
|
|
||||||
- if [ ! -d "/usr/local/android-sdk-linux/platforms/android-26" ]; then echo y | android update sdk --no-ui --all --filter "android-26"; fi
|
|
||||||
# Android SDK Build-tools
|
|
||||||
- if [ ! -d "/usr/local/android-sdk-linux/build-tools/26.0.1" ]; then echo y | android update sdk --no-ui --all --filter "build-tools-26.0.1"; fi
|
|
||||||
# Android Support Repository - deprecated
|
|
||||||
#- if [ ! -d "/usr/local/android-sdk-linux/extras/android/m2repository/com/android/support/design/26.2.0" ]; then echo y | android update sdk --no-ui --all --filter "extra-android-m2repository"; fi
|
|
||||||
|
|
||||||
|
|
||||||
cache_directories:
|
|
||||||
- /usr/local/android-sdk-linux/platforms/android-26
|
|
||||||
- /usr/local/android-sdk-linux/build-tools/26.0.1
|
|
||||||
#- /usr/local/android-sdk-linux/extras/android/m2repository
|
|
||||||
|
|
||||||
###################
|
|
||||||
test:
|
|
||||||
override:
|
|
||||||
- (./gradlew assembleFlavorDefault):
|
|
||||||
timeout: 360
|
|
Loading…
Reference in a new issue