mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 01:45:36 +03:00
Merge pull request #8556 from vector-im/feature/bma/noJcenter
Feature/bma/no jcenter
This commit is contained in:
commit
2b4b5f05eb
51 changed files with 4214 additions and 46 deletions
11
build.gradle
11
build.gradle
|
@ -112,16 +112,6 @@ allprojects {
|
|||
groups.google.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
//noinspection JcenterRepositoryObsolete
|
||||
// Do not use `jcenter`, it prevents Dependabot from working properly
|
||||
maven {
|
||||
url 'https://jcenter.bintray.com'
|
||||
content {
|
||||
groups.jcenter.regex.each { includeGroupByRegex it }
|
||||
groups.jcenter.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
|
||||
maven {
|
||||
url 'https://s01.oss.sonatype.org/content/repositories/snapshots'
|
||||
content {
|
||||
|
@ -129,7 +119,6 @@ allprojects {
|
|||
groups.mavenSnapshots.group.each { includeGroup it }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
|
|
1
changelog.d/8556.misc
Normal file
1
changelog.d/8556.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Include some source code in our project to remove our dependency to artifact hosted by bintray (Jcenter).
|
|
@ -115,6 +115,7 @@ ext.groups = [
|
|||
'com.linkedin.dexmaker',
|
||||
'com.mapbox.mapboxsdk',
|
||||
'com.nulab-inc',
|
||||
'com.otaliastudios',
|
||||
'com.otaliastudios.opengl',
|
||||
'com.parse.bolts',
|
||||
'com.pinterest',
|
||||
|
@ -234,18 +235,4 @@ ext.groups = [
|
|||
'xml-apis',
|
||||
]
|
||||
],
|
||||
jcenter : [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
'com.amulyakhare',
|
||||
'com.otaliastudios',
|
||||
'com.yqritc',
|
||||
// https://github.com/cmelchior/realmfieldnameshelper/issues/42
|
||||
'dk.ilios',
|
||||
'im.dlg',
|
||||
'me.dm7.barcodescanner',
|
||||
'me.gujun.android',
|
||||
]
|
||||
]
|
||||
]
|
||||
|
|
32
library/external/autocomplete/build.gradle
vendored
Normal file
32
library/external/autocomplete/build.gradle
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
namespace "com.otaliastudios.autocomplete"
|
||||
|
||||
compileSdk versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.sourceCompat
|
||||
targetCompatibility versions.targetCompat
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.androidx.recyclerview
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.findAll { it.name.startsWith("lint") }.each {
|
||||
it.enabled = false
|
||||
}
|
||||
}
|
434
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/Autocomplete.java
vendored
Normal file
434
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/Autocomplete.java
vendored
Normal file
|
@ -0,0 +1,434 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.Selection;
|
||||
import android.text.SpanWatcher;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
/**
|
||||
* Entry point for adding Autocomplete behavior to a {@link EditText}.
|
||||
*
|
||||
* You can construct a {@code Autocomplete} using the builder provided by {@link Autocomplete#on(EditText)}.
|
||||
* Building is enough, but you can hold a reference to this class to call its public methods.
|
||||
*
|
||||
* Requires:
|
||||
* - {@link EditText}: this is both the anchor for the popup, and the source of text events that we listen to
|
||||
* - {@link AutocompletePresenter}: this presents items in the popup window. See class for more info.
|
||||
* - {@link AutocompleteCallback}: if specified, this listens to click events and visibility changes
|
||||
* - {@link AutocompletePolicy}: if specified, this controls how and when to show the popup based on text events
|
||||
* If not, this defaults to {@link SimplePolicy}: shows the popup when text.length() bigger than 0.
|
||||
*/
|
||||
public final class Autocomplete<T> implements TextWatcher, SpanWatcher {
|
||||
|
||||
private final static String TAG = Autocomplete.class.getSimpleName();
|
||||
private final static boolean DEBUG = false;
|
||||
|
||||
private static void log(String log) {
|
||||
if (DEBUG) Log.e(TAG, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for building {@link Autocomplete}.
|
||||
* The only mandatory item is a presenter, {@link #with(AutocompletePresenter)}.
|
||||
*
|
||||
* @param <T> the data model
|
||||
*/
|
||||
public final static class Builder<T> {
|
||||
private EditText source;
|
||||
private AutocompletePresenter<T> presenter;
|
||||
private AutocompletePolicy policy;
|
||||
private AutocompleteCallback<T> callback;
|
||||
private Drawable backgroundDrawable;
|
||||
private float elevationDp = 6;
|
||||
|
||||
private Builder(EditText source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the {@link AutocompletePresenter} to be used, responsible for showing
|
||||
* items. See the class for info.
|
||||
*
|
||||
* @param presenter desired presenter
|
||||
* @return this for chaining
|
||||
*/
|
||||
public Builder<T> with(AutocompletePresenter<T> presenter) {
|
||||
this.presenter = presenter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the {@link AutocompleteCallback} to be used, responsible for listening to
|
||||
* clicks provided by the presenter, and visibility changes.
|
||||
*
|
||||
* @param callback desired callback
|
||||
* @return this for chaining
|
||||
*/
|
||||
public Builder<T> with(AutocompleteCallback<T> callback) {
|
||||
this.callback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the {@link AutocompletePolicy} to be used, responsible for showing / dismissing
|
||||
* the popup when certain events happen (e.g. certain characters are typed).
|
||||
*
|
||||
* @param policy desired policy
|
||||
* @return this for chaining
|
||||
*/
|
||||
public Builder<T> with(AutocompletePolicy policy) {
|
||||
this.policy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a background drawable for the popup.
|
||||
*
|
||||
* @param backgroundDrawable drawable
|
||||
* @return this for chaining
|
||||
*/
|
||||
public Builder<T> with(Drawable backgroundDrawable) {
|
||||
this.backgroundDrawable = backgroundDrawable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets elevation for the popup. Defaults to 6 dp.
|
||||
*
|
||||
* @param elevationDp popup elevation, in DP
|
||||
* @return this for chaning.
|
||||
*/
|
||||
public Builder<T> with(float elevationDp) {
|
||||
this.elevationDp = elevationDp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an Autocomplete instance. This is enough for autocomplete to be set up,
|
||||
* but you can hold a reference to the object and call its public methods.
|
||||
*
|
||||
* @return an Autocomplete instance, if you need it
|
||||
*
|
||||
* @throws RuntimeException if either EditText or the presenter are null
|
||||
*/
|
||||
public Autocomplete<T> build() {
|
||||
if (source == null) throw new RuntimeException("Autocomplete needs a source!");
|
||||
if (presenter == null) throw new RuntimeException("Autocomplete needs a presenter!");
|
||||
if (policy == null) policy = new SimplePolicy();
|
||||
return new Autocomplete<T>(this);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
source = null;
|
||||
presenter = null;
|
||||
callback = null;
|
||||
policy = null;
|
||||
backgroundDrawable = null;
|
||||
elevationDp = 6;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for building autocomplete on a certain {@link EditText}.
|
||||
* @param anchor the anchor for the popup, and the source of text events
|
||||
* @param <T> your data model
|
||||
* @return a Builder for set up
|
||||
*/
|
||||
public static <T> Builder<T> on(EditText anchor) {
|
||||
return new Builder<T>(anchor);
|
||||
}
|
||||
|
||||
private AutocompletePolicy policy;
|
||||
private AutocompletePopup popup;
|
||||
private AutocompletePresenter<T> presenter;
|
||||
private AutocompleteCallback<T> callback;
|
||||
private EditText source;
|
||||
|
||||
private boolean block;
|
||||
private boolean disabled;
|
||||
private boolean openBefore;
|
||||
private String lastQuery = "null";
|
||||
|
||||
private Autocomplete(Builder<T> builder) {
|
||||
policy = builder.policy;
|
||||
presenter = builder.presenter;
|
||||
callback = builder.callback;
|
||||
source = builder.source;
|
||||
|
||||
// Set up popup
|
||||
popup = new AutocompletePopup(source.getContext());
|
||||
popup.setAnchorView(source);
|
||||
popup.setGravity(Gravity.START);
|
||||
popup.setModal(false);
|
||||
popup.setBackgroundDrawable(builder.backgroundDrawable);
|
||||
popup.setElevation(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, builder.elevationDp,
|
||||
source.getContext().getResources().getDisplayMetrics()));
|
||||
|
||||
// popup dimensions
|
||||
AutocompletePresenter.PopupDimensions dim = this.presenter.getPopupDimensions();
|
||||
popup.setWidth(dim.width);
|
||||
popup.setHeight(dim.height);
|
||||
popup.setMaxWidth(dim.maxWidth);
|
||||
popup.setMaxHeight(dim.maxHeight);
|
||||
|
||||
// Fire visibility events
|
||||
popup.setOnDismissListener(new PopupWindow.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
lastQuery = "null";
|
||||
if (callback != null) callback.onPopupVisibilityChanged(false);
|
||||
boolean saved = block;
|
||||
block = true;
|
||||
policy.onDismiss(source.getText());
|
||||
block = saved;
|
||||
presenter.hideView();
|
||||
}
|
||||
});
|
||||
|
||||
// Set up source
|
||||
source.getText().setSpan(this, 0, source.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
source.addTextChangedListener(this);
|
||||
|
||||
// Set up presenter
|
||||
presenter.registerClickProvider(new AutocompletePresenter.ClickProvider<T>() {
|
||||
@Override
|
||||
public void click(@NonNull T item) {
|
||||
AutocompleteCallback<T> callback = Autocomplete.this.callback;
|
||||
EditText edit = Autocomplete.this.source;
|
||||
if (callback == null) return;
|
||||
boolean saved = block;
|
||||
block = true;
|
||||
boolean dismiss = callback.onPopupItemClicked(edit.getText(), item);
|
||||
if (dismiss) dismissPopup();
|
||||
block = saved;
|
||||
}
|
||||
});
|
||||
|
||||
builder.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls how the popup operates with an input method.
|
||||
*
|
||||
* If the popup is showing, calling this method will take effect only
|
||||
* the next time the popup is shown.
|
||||
*
|
||||
* @param mode a {@link PopupWindow} input method mode
|
||||
*/
|
||||
public void setInputMethodMode(int mode) {
|
||||
popup.setInputMethodMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the operating mode for the soft input area.
|
||||
*
|
||||
* @param mode The desired mode, see {@link WindowManager.LayoutParams#softInputMode}
|
||||
*/
|
||||
public void setSoftInputMode(int mode) {
|
||||
popup.setSoftInputMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the popup with the given query.
|
||||
* There is rarely need to call this externally: it is already triggered by events on the anchor.
|
||||
* To control when this is called, provide a good implementation of {@link AutocompletePolicy}.
|
||||
*
|
||||
* @param query query text.
|
||||
*/
|
||||
public void showPopup(@NonNull CharSequence query) {
|
||||
if (isPopupShowing() && lastQuery.equals(query.toString())) return;
|
||||
lastQuery = query.toString();
|
||||
|
||||
log("showPopup: called with filter "+query);
|
||||
if (!isPopupShowing()) {
|
||||
log("showPopup: showing");
|
||||
presenter.registerDataSetObserver(new Observer()); // Calling new to avoid leaking... maybe...
|
||||
popup.setView(presenter.getView());
|
||||
presenter.showView();
|
||||
popup.show();
|
||||
if (callback != null) callback.onPopupVisibilityChanged(true);
|
||||
}
|
||||
log("showPopup: popup should be showing... "+isPopupShowing());
|
||||
presenter.onQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the popup, if showing.
|
||||
* There is rarely need to call this externally: it is already triggered by events on the anchor.
|
||||
* To control when this is called, provide a good implementation of {@link AutocompletePolicy}.
|
||||
*/
|
||||
public void dismissPopup() {
|
||||
if (isPopupShowing()) {
|
||||
popup.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the popup is showing.
|
||||
* @return whether the popup is currently showing
|
||||
*/
|
||||
public boolean isPopupShowing() {
|
||||
return this.popup.isShowing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to control the autocomplete behavior. When disabled, no popup is shown.
|
||||
* This is useful if you want to do runtime edits to the anchor text, without triggering
|
||||
* the popup.
|
||||
*
|
||||
* @param enabled whether to enable autocompletion
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
disabled = !enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gravity for the popup. Basically only {@link Gravity#START} and {@link Gravity#END}
|
||||
* do work.
|
||||
*
|
||||
* @param gravity gravity for the popup
|
||||
*/
|
||||
public void setGravity(int gravity) {
|
||||
popup.setGravity(gravity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the vertical offset of the popup from the EditText anchor.
|
||||
*
|
||||
* @param offset offset in pixels.
|
||||
*/
|
||||
public void setOffsetFromAnchor(int offset) { popup.setVerticalOffset(offset); }
|
||||
|
||||
/**
|
||||
* Controls whether the popup should listen to clicks outside its boundaries.
|
||||
*
|
||||
* @param outsideTouchable true to listen to outside clicks
|
||||
*/
|
||||
public void setOutsideTouchable(boolean outsideTouchable) { popup.setOutsideTouchable(outsideTouchable); }
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
if (block || disabled) return;
|
||||
openBefore = isPopupShowing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (block || disabled) return;
|
||||
if (openBefore && !isPopupShowing()) {
|
||||
return; // Copied from somewhere.
|
||||
}
|
||||
if (!(s instanceof Spannable)) {
|
||||
source.setText(new SpannableString(s));
|
||||
return;
|
||||
}
|
||||
Spannable sp = (Spannable) s;
|
||||
|
||||
int cursor = source.getSelectionEnd();
|
||||
log("onTextChanged: cursor end position is "+cursor);
|
||||
if (cursor == -1) { // No cursor present.
|
||||
dismissPopup(); return;
|
||||
}
|
||||
if (cursor != source.getSelectionStart()) {
|
||||
// Not sure about this. We should have no problems dealing with multi selections,
|
||||
// we just take the end...
|
||||
// dismissPopup(); return;
|
||||
}
|
||||
|
||||
boolean b = block;
|
||||
block = true; // policy might add spans or other stuff.
|
||||
if (isPopupShowing() && policy.shouldDismissPopup(sp, cursor)) {
|
||||
log("onTextChanged: dismissing");
|
||||
dismissPopup();
|
||||
} else if (isPopupShowing() || policy.shouldShowPopup(sp, cursor)) {
|
||||
// LOG.now("onTextChanged: updating with filter "+policy.getQuery(sp));
|
||||
showPopup(policy.getQuery(sp));
|
||||
}
|
||||
block = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
|
||||
@Override
|
||||
public void onSpanAdded(Spannable text, Object what, int start, int end) {}
|
||||
|
||||
@Override
|
||||
public void onSpanRemoved(Spannable text, Object what, int start, int end) {}
|
||||
|
||||
@Override
|
||||
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
|
||||
if (disabled || block) return;
|
||||
if (what == Selection.SELECTION_END) {
|
||||
// Selection end changed from ostart to nstart. Trigger a check.
|
||||
log("onSpanChanged: selection end moved from "+ostart+" to "+nstart);
|
||||
log("onSpanChanged: block is "+block);
|
||||
boolean b = block;
|
||||
block = true;
|
||||
if (!isPopupShowing() && policy.shouldShowPopup(text, nstart)) {
|
||||
showPopup(policy.getQuery(text));
|
||||
}
|
||||
block = b;
|
||||
}
|
||||
}
|
||||
|
||||
private class Observer extends DataSetObserver implements Runnable {
|
||||
private Handler ui = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
// ??? Not sure this is needed...
|
||||
ui.post(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isPopupShowing()) {
|
||||
// Call show again to revisit width and height.
|
||||
popup.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A very simple {@link AutocompletePolicy} implementation.
|
||||
* Popup is shown when text length is bigger than 0, and hidden when text is empty.
|
||||
* The query string is the whole text.
|
||||
*/
|
||||
public static class SimplePolicy implements AutocompletePolicy {
|
||||
@Override
|
||||
public boolean shouldShowPopup(@NonNull Spannable text, int cursorPos) {
|
||||
return text.length() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDismissPopup(@NonNull Spannable text, int cursorPos) {
|
||||
return text.length() == 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CharSequence getQuery(@NonNull Spannable text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull Spannable text) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.text.Editable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Optional callback to be passed to {@link Autocomplete.Builder}.
|
||||
*/
|
||||
public interface AutocompleteCallback<T> {
|
||||
|
||||
/**
|
||||
* Called when an item inside your list is clicked.
|
||||
* This works if your presenter has dispatched a click event.
|
||||
* At this point you can edit the text, e.g. {@code editable.append(item.toString())}.
|
||||
*
|
||||
* @param editable editable text that you can work on
|
||||
* @param item item that was clicked
|
||||
* @return true if the action is valid and the popup can be dismissed
|
||||
*/
|
||||
boolean onPopupItemClicked(@NonNull Editable editable, @NonNull T item);
|
||||
|
||||
/**
|
||||
* Called when popup visibility state changes.
|
||||
*
|
||||
* @param shown true if the popup was just shown, false if it was just hidden
|
||||
*/
|
||||
void onPopupVisibilityChanged(boolean shown);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.text.Spannable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* This interface controls when to show or hide the popup window, and, in the first case,
|
||||
* what text should be passed to the popup {@link AutocompletePresenter}.
|
||||
*
|
||||
* @see Autocomplete.SimplePolicy for the simplest possible implementation
|
||||
*/
|
||||
public interface AutocompletePolicy {
|
||||
|
||||
/**
|
||||
* Called to understand whether the popup should be shown. Some naive examples:
|
||||
* - Show when there's text: {@code return text.length() > 0}
|
||||
* - Show when last char is @: {@code return text.getCharAt(text.length()-1) == '@'}
|
||||
*
|
||||
* @param text current text, along with its Spans
|
||||
* @param cursorPos the position of the cursor
|
||||
* @return true if popup should be shown
|
||||
*/
|
||||
boolean shouldShowPopup(@NonNull Spannable text, int cursorPos);
|
||||
|
||||
/**
|
||||
* Called to understand whether a currently shown popup should be closed, maybe
|
||||
* because text is invalid. A reasonable implementation is
|
||||
* {@code return !shouldShowPopup(text, cursorPos)}.
|
||||
*
|
||||
* However this is defined so you can add or clear spans.
|
||||
*
|
||||
* @param text current text, along with its Spans
|
||||
* @param cursorPos the position of the cursor
|
||||
* @return true if popup should be hidden
|
||||
*/
|
||||
boolean shouldDismissPopup(@NonNull Spannable text, int cursorPos);
|
||||
|
||||
/**
|
||||
* Called to understand which query should be passed to {@link AutocompletePresenter}
|
||||
* for a showing popup. If this is called, {@link #shouldShowPopup(Spannable, int)} just returned
|
||||
* true, or {@link #shouldDismissPopup(Spannable, int)} just returned false.
|
||||
*
|
||||
* This is useful to understand which part of the text should be passed to presenters.
|
||||
* For example, user might have typed '@john' to select a username, but you just want to
|
||||
* search for 'john'.
|
||||
*
|
||||
* For more complex cases, you can add inclusive Spans in {@link #shouldShowPopup(Spannable, int)},
|
||||
* and get the span position here.
|
||||
*
|
||||
* @param text current text, along with its Spans
|
||||
* @return the query for presenter
|
||||
*/
|
||||
@NonNull
|
||||
CharSequence getQuery(@NonNull Spannable text);
|
||||
|
||||
/**
|
||||
* Called when popup is dismissed. This can be used, for instance, to clear custom Spans
|
||||
* from the text.
|
||||
*
|
||||
* @param text text at the moment of dismissing
|
||||
*/
|
||||
void onDismiss(@NonNull Spannable text);
|
||||
}
|
521
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/AutocompletePopup.java
vendored
Normal file
521
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/AutocompletePopup.java
vendored
Normal file
|
@ -0,0 +1,521 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.widget.PopupWindowCompat;
|
||||
|
||||
/**
|
||||
* A simplified version of andriod.widget.ListPopupWindow, which is the class used by
|
||||
* AutocompleteTextView.
|
||||
*
|
||||
* Other than being simplified, this deals with Views rather than ListViews, so the content
|
||||
* can be whatever. Lots of logic (clicks, selections etc.) has been removed because we manage that
|
||||
* in {@link AutocompletePresenter}.
|
||||
*
|
||||
*/
|
||||
class AutocompletePopup {
|
||||
private Context mContext;
|
||||
private ViewGroup mView;
|
||||
private int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
private int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
private int mMaxHeight = Integer.MAX_VALUE;
|
||||
private int mMaxWidth = Integer.MAX_VALUE;
|
||||
private int mUserMaxHeight = Integer.MAX_VALUE;
|
||||
private int mUserMaxWidth = Integer.MAX_VALUE;
|
||||
private int mHorizontalOffset = 0;
|
||||
private int mVerticalOffset = 0;
|
||||
private boolean mVerticalOffsetSet;
|
||||
private int mGravity = Gravity.NO_GRAVITY;
|
||||
private boolean mAlwaysVisible = false;
|
||||
private boolean mOutsideTouchable = true;
|
||||
private View mAnchorView;
|
||||
private final Rect mTempRect = new Rect();
|
||||
private boolean mModal;
|
||||
private PopupWindow mPopup;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new, empty popup window capable of displaying items from a ListAdapter.
|
||||
* Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
|
||||
*
|
||||
* @param context Context used for contained views.
|
||||
*/
|
||||
AutocompletePopup(@NonNull Context context) {
|
||||
super();
|
||||
mContext = context;
|
||||
mPopup = new PopupWindow(context);
|
||||
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set whether this window should be modal when shown.
|
||||
*
|
||||
* <p>If a popup window is modal, it will receive all touch and key input.
|
||||
* If the user touches outside the popup window's content area the popup window
|
||||
* will be dismissed.
|
||||
* @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
void setModal(boolean modal) {
|
||||
mModal = modal;
|
||||
mPopup.setFocusable(modal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the popup window will be modal when shown.
|
||||
* @return {@code true} if the popup window will be modal, {@code false} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
boolean isModal() {
|
||||
return mModal;
|
||||
}
|
||||
|
||||
void setElevation(float elevationPx) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mPopup.setElevation(elevationPx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the drop-down should remain visible under certain conditions.
|
||||
*
|
||||
* The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
|
||||
* of the size or content of the list. {@link #getBackground()} will fill any space
|
||||
* that is not used by the list.
|
||||
* @param dropDownAlwaysVisible Whether to keep the drop-down visible.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
|
||||
mAlwaysVisible = dropDownAlwaysVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the drop-down is visible under special conditions.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
boolean isDropDownAlwaysVisible() {
|
||||
return mAlwaysVisible;
|
||||
}
|
||||
|
||||
void setOutsideTouchable(boolean outsideTouchable) {
|
||||
mOutsideTouchable = outsideTouchable;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
boolean isOutsideTouchable() {
|
||||
return mOutsideTouchable && !mAlwaysVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the operating mode for the soft input area.
|
||||
* @param mode The desired mode, see
|
||||
* {@link android.view.WindowManager.LayoutParams#softInputMode}
|
||||
* for the full list
|
||||
* @see android.view.WindowManager.LayoutParams#softInputMode
|
||||
* @see #getSoftInputMode()
|
||||
*/
|
||||
void setSoftInputMode(int mode) {
|
||||
mPopup.setSoftInputMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value in {@link #setSoftInputMode(int)}.
|
||||
* @see #setSoftInputMode(int)
|
||||
* @see android.view.WindowManager.LayoutParams#softInputMode
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
int getSoftInputMode() {
|
||||
return mPopup.getSoftInputMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The background drawable for the popup window.
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
@Nullable
|
||||
Drawable getBackground() {
|
||||
return mPopup.getBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a drawable to be the background for the popup window.
|
||||
* @param d A drawable to set as the background.
|
||||
*/
|
||||
void setBackgroundDrawable(@Nullable Drawable d) {
|
||||
mPopup.setBackgroundDrawable(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an animation style to use when the popup window is shown or dismissed.
|
||||
* @param animationStyle Animation style to use.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setAnimationStyle(@StyleRes int animationStyle) {
|
||||
mPopup.setAnimationStyle(animationStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the animation style that will be used when the popup window is
|
||||
* shown or dismissed.
|
||||
* @return Animation style that will be used.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@StyleRes
|
||||
int getAnimationStyle() {
|
||||
return mPopup.getAnimationStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view that will be used to anchor this popup.
|
||||
* @return The popup's anchor view
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
View getAnchorView() {
|
||||
return mAnchorView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the popup's anchor view. This popup will always be positioned relative to
|
||||
* the anchor view when shown.
|
||||
* @param anchor The view to use as an anchor.
|
||||
*/
|
||||
void setAnchorView(@NonNull View anchor) {
|
||||
mAnchorView = anchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal offset of this popup from its anchor view in pixels.
|
||||
* @param offset The horizontal offset of the popup from its anchor.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setHorizontalOffset(int offset) {
|
||||
mHorizontalOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the vertical offset of this popup from its anchor view in pixels.
|
||||
* @param offset The vertical offset of the popup from its anchor.
|
||||
*/
|
||||
void setVerticalOffset(int offset) {
|
||||
mVerticalOffset = offset;
|
||||
mVerticalOffsetSet = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gravity of the dropdown list. This is commonly used to
|
||||
* set gravity to START or END for alignment with the anchor.
|
||||
* @param gravity Gravity value to use
|
||||
*/
|
||||
void setGravity(int gravity) {
|
||||
mGravity = gravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The width of the popup window in pixels.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the width of the popup window in pixels. Can also be MATCH_PARENT
|
||||
* or WRAP_CONTENT.
|
||||
* @param width Width of the popup window.
|
||||
*/
|
||||
void setWidth(int width) {
|
||||
mWidth = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the width of the popup window by the size of its content. The final width may be
|
||||
* larger to accommodate styled window dressing.
|
||||
* @param width Desired width of content in pixels.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setContentWidth(int width) {
|
||||
Drawable popupBackground = mPopup.getBackground();
|
||||
if (popupBackground != null) {
|
||||
popupBackground.getPadding(mTempRect);
|
||||
width += mTempRect.left + mTempRect.right;
|
||||
}
|
||||
setWidth(width);
|
||||
}
|
||||
|
||||
void setMaxWidth(int width) {
|
||||
if (width > 0) {
|
||||
mUserMaxWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The height of the popup window in pixels.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of the popup window in pixels. Can also be MATCH_PARENT.
|
||||
* @param height Height of the popup window.
|
||||
*/
|
||||
void setHeight(int height) {
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the height of the popup window by the size of its content. The final height may be
|
||||
* larger to accommodate styled window dressing.
|
||||
* @param height Desired height of content in pixels.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void setContentHeight(int height) {
|
||||
Drawable popupBackground = mPopup.getBackground();
|
||||
if (popupBackground != null) {
|
||||
popupBackground.getPadding(mTempRect);
|
||||
height += mTempRect.top + mTempRect.bottom;
|
||||
}
|
||||
setHeight(height);
|
||||
}
|
||||
|
||||
void setMaxHeight(int height) {
|
||||
if (height > 0) {
|
||||
mUserMaxHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
void setOnDismissListener(PopupWindow.OnDismissListener listener) {
|
||||
mPopup.setOnDismissListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the popup list. If the list is already showing, this method
|
||||
* will recalculate the popup's size and position.
|
||||
*/
|
||||
void show() {
|
||||
if (!ViewCompat.isAttachedToWindow(getAnchorView())) return;
|
||||
|
||||
int height = buildDropDown();
|
||||
final boolean noInputMethod = isInputMethodNotNeeded();
|
||||
int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
|
||||
PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
|
||||
|
||||
if (mPopup.isShowing()) {
|
||||
// First pass for this special case, don't know why.
|
||||
if (mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
int tempWidth = mWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT : 0;
|
||||
if (noInputMethod) {
|
||||
mPopup.setWidth(tempWidth);
|
||||
mPopup.setHeight(0);
|
||||
} else {
|
||||
mPopup.setWidth(tempWidth);
|
||||
mPopup.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
}
|
||||
}
|
||||
|
||||
// The call to PopupWindow's update method below can accept -1
|
||||
// for any value you do not want to update.
|
||||
|
||||
// Width.
|
||||
int widthSpec;
|
||||
if (mWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
widthSpec = -1;
|
||||
} else if (mWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
||||
widthSpec = getAnchorView().getWidth();
|
||||
} else {
|
||||
widthSpec = mWidth;
|
||||
}
|
||||
widthSpec = Math.min(widthSpec, mMaxWidth);
|
||||
widthSpec = (widthSpec < 0) ? - 1 : widthSpec;
|
||||
|
||||
// Height.
|
||||
int heightSpec;
|
||||
if (mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
} else if (mHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
||||
heightSpec = height;
|
||||
} else {
|
||||
heightSpec = mHeight;
|
||||
}
|
||||
heightSpec = Math.min(heightSpec, mMaxHeight);
|
||||
heightSpec = (heightSpec < 0) ? - 1 : heightSpec;
|
||||
|
||||
// Update.
|
||||
mPopup.setOutsideTouchable(isOutsideTouchable());
|
||||
if (heightSpec == 0) {
|
||||
dismiss();
|
||||
} else {
|
||||
mPopup.update(getAnchorView(), mHorizontalOffset, mVerticalOffset, widthSpec, heightSpec);
|
||||
}
|
||||
|
||||
} else {
|
||||
int widthSpec;
|
||||
if (mWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
} else if (mWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
||||
widthSpec = getAnchorView().getWidth();
|
||||
} else {
|
||||
widthSpec = mWidth;
|
||||
}
|
||||
widthSpec = Math.min(widthSpec, mMaxWidth);
|
||||
|
||||
int heightSpec;
|
||||
if (mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
} else if (mHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
||||
heightSpec = height;
|
||||
} else {
|
||||
heightSpec = mHeight;
|
||||
}
|
||||
heightSpec = Math.min(heightSpec, mMaxHeight);
|
||||
|
||||
// Set width and height.
|
||||
mPopup.setWidth(widthSpec);
|
||||
mPopup.setHeight(heightSpec);
|
||||
mPopup.setClippingEnabled(true);
|
||||
|
||||
// use outside touchable to dismiss drop down when touching outside of it, so
|
||||
// only set this if the dropdown is not always visible
|
||||
mPopup.setOutsideTouchable(isOutsideTouchable());
|
||||
PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mHorizontalOffset, mVerticalOffset, mGravity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the popup window.
|
||||
*/
|
||||
void dismiss() {
|
||||
mPopup.dismiss();
|
||||
mPopup.setContentView(null);
|
||||
mView = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control how the popup operates with an input method: one of
|
||||
* INPUT_METHOD_FROM_FOCUSABLE, INPUT_METHOD_NEEDED,
|
||||
* or INPUT_METHOD_NOT_NEEDED.
|
||||
*
|
||||
* <p>If the popup is showing, calling this method will take effect only
|
||||
* the next time the popup is shown or through a manual call to the {@link #show()}
|
||||
* method.</p>
|
||||
*
|
||||
* @see #show()
|
||||
*/
|
||||
void setInputMethodMode(int mode) {
|
||||
mPopup.setInputMethodMode(mode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
|
||||
*/
|
||||
boolean isShowing() {
|
||||
return mPopup.isShowing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if this popup is configured to assume the user does not need
|
||||
* to interact with the IME while it is showing, {@code false} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
boolean isInputMethodNotNeeded() {
|
||||
return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
|
||||
}
|
||||
|
||||
|
||||
void setView(ViewGroup view) {
|
||||
mView = view;
|
||||
mView.setFocusable(true);
|
||||
mView.setFocusableInTouchMode(true);
|
||||
ViewGroup dropDownView = mView;
|
||||
mPopup.setContentView(dropDownView);
|
||||
ViewGroup.LayoutParams params = mView.getLayoutParams();
|
||||
if (params != null) {
|
||||
if (params.height > 0) setHeight(params.height);
|
||||
if (params.width > 0) setWidth(params.width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Builds the popup window's content and returns the height the popup
|
||||
* should have. Returns -1 when the content already exists.</p>
|
||||
*
|
||||
* @return the content's wrap content height or -1 if content already exists
|
||||
*/
|
||||
private int buildDropDown() {
|
||||
int otherHeights = 0;
|
||||
|
||||
// getMaxAvailableHeight() subtracts the padding, so we put it back
|
||||
// to get the available height for the whole window.
|
||||
final int paddingVert;
|
||||
final int paddingHoriz;
|
||||
final Drawable background = mPopup.getBackground();
|
||||
if (background != null) {
|
||||
background.getPadding(mTempRect);
|
||||
paddingVert = mTempRect.top + mTempRect.bottom;
|
||||
paddingHoriz = mTempRect.left + mTempRect.right;
|
||||
|
||||
// If we don't have an explicit vertical offset, determine one from
|
||||
// the window background so that content will line up.
|
||||
if (!mVerticalOffsetSet) {
|
||||
mVerticalOffset = -mTempRect.top;
|
||||
}
|
||||
} else {
|
||||
mTempRect.setEmpty();
|
||||
paddingVert = 0;
|
||||
paddingHoriz = 0;
|
||||
}
|
||||
|
||||
// Redefine dimensions taking into account maxWidth and maxHeight.
|
||||
final boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
|
||||
final int maxContentHeight = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ?
|
||||
mPopup.getMaxAvailableHeight(getAnchorView(), mVerticalOffset, ignoreBottomDecorations) :
|
||||
mPopup.getMaxAvailableHeight(getAnchorView(), mVerticalOffset);
|
||||
final int maxContentWidth = mContext.getResources().getDisplayMetrics().widthPixels - paddingHoriz;
|
||||
|
||||
mMaxHeight = Math.min(maxContentHeight + paddingVert, mUserMaxHeight);
|
||||
mMaxWidth = Math.min(maxContentWidth + paddingHoriz, mUserMaxWidth);
|
||||
// if (mHeight > 0) mHeight = Math.min(mHeight, maxContentHeight);
|
||||
// if (mWidth > 0) mWidth = Math.min(mWidth, maxContentWidth);
|
||||
|
||||
if (mAlwaysVisible || mHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
|
||||
return mMaxHeight;
|
||||
}
|
||||
|
||||
final int childWidthSpec;
|
||||
switch (mWidth) {
|
||||
case ViewGroup.LayoutParams.WRAP_CONTENT:
|
||||
childWidthSpec = View.MeasureSpec.makeMeasureSpec(maxContentWidth, View.MeasureSpec.AT_MOST); break;
|
||||
case ViewGroup.LayoutParams.MATCH_PARENT:
|
||||
childWidthSpec = View.MeasureSpec.makeMeasureSpec(maxContentWidth, View.MeasureSpec.EXACTLY); break;
|
||||
default:
|
||||
//noinspection Range
|
||||
childWidthSpec = View.MeasureSpec.makeMeasureSpec(mWidth, View.MeasureSpec.EXACTLY); break;
|
||||
}
|
||||
|
||||
// Add padding only if the list has items in it, that way we don't show
|
||||
// the popup if it is not needed. For this reason, we measure as wrap_content.
|
||||
mView.measure(childWidthSpec, View.MeasureSpec.makeMeasureSpec(maxContentHeight, View.MeasureSpec.AT_MOST));
|
||||
final int viewHeight = mView.getMeasuredHeight();
|
||||
if (viewHeight > 0) {
|
||||
otherHeights += paddingVert + mView.getPaddingTop() + mView.getPaddingBottom();
|
||||
}
|
||||
|
||||
return Math.min(viewHeight + otherHeights, mMaxHeight);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Base class for presenting items inside a popup. This is abstract and must be implemented.
|
||||
*
|
||||
* Most important methods are {@link #getView()} and {@link #onQuery(CharSequence)}.
|
||||
*/
|
||||
public abstract class AutocompletePresenter<T> {
|
||||
|
||||
private Context context;
|
||||
private boolean isShowing;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public AutocompletePresenter(@NonNull Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* At this point the presenter is passed the {@link ClickProvider}.
|
||||
* The contract is that {@link ClickProvider#click(Object)} must be called when a list item
|
||||
* is clicked. This ensure that the autocomplete callback will receive the event.
|
||||
*
|
||||
* @param provider a click provider for this presenter.
|
||||
*/
|
||||
protected void registerClickProvider(ClickProvider<T> provider) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful if you wish to change width/height based on content height.
|
||||
* The contract is to call {@link DataSetObserver#onChanged()} when your view has
|
||||
* changes.
|
||||
*
|
||||
* This is called after {@link #getView()}.
|
||||
*
|
||||
* @param observer the observer.
|
||||
*/
|
||||
protected void registerDataSetObserver(@NonNull DataSetObserver observer) {}
|
||||
|
||||
/**
|
||||
* Called each time the popup is shown. You are meant to inflate the view here.
|
||||
* You can get a LayoutInflater using {@link #getContext()}.
|
||||
*
|
||||
* @return a ViewGroup for the popup
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract ViewGroup getView();
|
||||
|
||||
/**
|
||||
* Provide the {@link PopupDimensions} for this popup. Called just once.
|
||||
* You can use fixed dimensions or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and
|
||||
* {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
|
||||
*
|
||||
* @return a PopupDimensions object
|
||||
*/
|
||||
// Called at first to understand which dimensions to use for the popup.
|
||||
@NonNull
|
||||
protected PopupDimensions getPopupDimensions() {
|
||||
return new PopupDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform firther initialization here. Called after {@link #getView()},
|
||||
* each time the popup is shown.
|
||||
*/
|
||||
protected abstract void onViewShown();
|
||||
|
||||
/**
|
||||
* Called to update the view to filter results with the query.
|
||||
* It is called any time the popup is shown, and any time the text changes and query is updated.
|
||||
*
|
||||
* @param query query from the edit text, to filter our results
|
||||
*/
|
||||
protected abstract void onQuery(@Nullable CharSequence query);
|
||||
|
||||
/**
|
||||
* Called when the popup is hidden, to release resources.
|
||||
*/
|
||||
protected abstract void onViewHidden();
|
||||
|
||||
/**
|
||||
* @return this presenter context
|
||||
*/
|
||||
@NonNull
|
||||
protected final Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether we are showing currently
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected final boolean isShowing() {
|
||||
return isShowing;
|
||||
}
|
||||
|
||||
final void showView() {
|
||||
isShowing = true;
|
||||
onViewShown();
|
||||
}
|
||||
|
||||
final void hideView() {
|
||||
isShowing = false;
|
||||
onViewHidden();
|
||||
}
|
||||
|
||||
public interface ClickProvider<T> {
|
||||
void click(@NonNull T item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides width, height, maxWidth and maxHeight for the popup.
|
||||
* @see #getPopupDimensions()
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static class PopupDimensions {
|
||||
public int width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
public int height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
public int maxWidth = Integer.MAX_VALUE;
|
||||
public int maxHeight = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
184
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/CharPolicy.java
vendored
Normal file
184
library/external/autocomplete/src/main/java/com/otaliastudios/autocomplete/CharPolicy.java
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* A special {@link AutocompletePolicy} for cases when you want to trigger the popup when a
|
||||
* certain character is shown.
|
||||
*
|
||||
* For instance, this might be the case for hashtags ('#') or usernames ('@') or whatever you wish.
|
||||
* Passing this to {@link Autocomplete.Builder} ensures the following behavior (assuming '@'):
|
||||
* - text "@john" : presenter will be passed the query "john"
|
||||
* - text "You should see this @j" : presenter will be passed the query "j"
|
||||
* - text "You should see this @john @m" : presenter will be passed the query "m"
|
||||
*/
|
||||
public class CharPolicy implements AutocompletePolicy {
|
||||
|
||||
private final static String TAG = CharPolicy.class.getSimpleName();
|
||||
private final static boolean DEBUG = false;
|
||||
|
||||
private static void log(@NonNull String log) {
|
||||
if (DEBUG) Log.e(TAG, log);
|
||||
}
|
||||
|
||||
private final char CH;
|
||||
private final int[] INT = new int[2];
|
||||
private boolean needSpaceBefore = true;
|
||||
|
||||
/**
|
||||
* Constructs a char policy for the given character.
|
||||
*
|
||||
* @param trigger the triggering character.
|
||||
*/
|
||||
public CharPolicy(char trigger) {
|
||||
CH = trigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a char policy for the given character.
|
||||
* You can choose whether a whitespace is needed before 'trigger'.
|
||||
*
|
||||
* @param trigger the triggering character.
|
||||
* @param needSpaceBefore whether we need a space before trigger
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public CharPolicy(char trigger, boolean needSpaceBefore) {
|
||||
CH = trigger;
|
||||
this.needSpaceBefore = needSpaceBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overriden to understand which characters are valid. The default implementation
|
||||
* returns true for any character except whitespaces.
|
||||
*
|
||||
* @param ch the character
|
||||
* @return whether it's valid part of a query
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected boolean isValidChar(char ch) {
|
||||
return !Character.isWhitespace(ch);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private int[] checkText(@NonNull Spannable text, int cursorPos) {
|
||||
final int spanEnd = cursorPos;
|
||||
char last = 'x';
|
||||
cursorPos -= 1; // If the cursor is at the end, we will have cursorPos = length. Go back by 1.
|
||||
while (cursorPos >= 0 && last != CH) {
|
||||
char ch = text.charAt(cursorPos);
|
||||
log("checkText: char is "+ch);
|
||||
if (isValidChar(ch)) {
|
||||
// We are going back
|
||||
log("checkText: char is valid");
|
||||
cursorPos -= 1;
|
||||
last = ch;
|
||||
} else {
|
||||
// We got a whitespace before getting a CH. This is invalid.
|
||||
log("checkText: char is not valid, returning NULL");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
cursorPos += 1; // + 1 because we end BEHIND the valid selection
|
||||
|
||||
// Start checking.
|
||||
if (cursorPos == 0 && last != CH) {
|
||||
// We got to the start of the string, and no CH was encountered. Nothing to do.
|
||||
log("checkText: got to start but no CH, returning NULL");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Additional checks for cursorPos - 1
|
||||
if (cursorPos > 0 && needSpaceBefore) {
|
||||
char ch = text.charAt(cursorPos-1);
|
||||
if (!Character.isWhitespace(ch)) {
|
||||
log("checkText: char before is not whitespace, returning NULL");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// All seems OK.
|
||||
final int spanStart = cursorPos + 1; // + 1 because we want to exclude CH from the query
|
||||
INT[0] = spanStart;
|
||||
INT[1] = spanEnd;
|
||||
log("checkText: found! cursorPos="+cursorPos);
|
||||
log("checkText: found! spanStart="+spanStart);
|
||||
log("checkText: found! spanEnd="+spanEnd);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldShowPopup(@NonNull Spannable text, int cursorPos) {
|
||||
// Returning true if, right before cursorPos, we have a word starting with @.
|
||||
log("shouldShowPopup: text is "+text);
|
||||
log("shouldShowPopup: cursorPos is "+cursorPos);
|
||||
int[] show = checkText(text, cursorPos);
|
||||
if (show != null) {
|
||||
text.setSpan(new QuerySpan(), show[0], show[1], Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
return true;
|
||||
}
|
||||
log("shouldShowPopup: returning false");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDismissPopup(@NonNull Spannable text, int cursorPos) {
|
||||
log("shouldDismissPopup: text is "+text);
|
||||
log("shouldDismissPopup: cursorPos is "+cursorPos);
|
||||
boolean dismiss = checkText(text, cursorPos) == null;
|
||||
log("shouldDismissPopup: returning "+dismiss);
|
||||
return dismiss;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CharSequence getQuery(@NonNull Spannable text) {
|
||||
QuerySpan[] span = text.getSpans(0, text.length(), QuerySpan.class);
|
||||
if (span == null || span.length == 0) {
|
||||
// Should never happen.
|
||||
log("getQuery: there's no span!");
|
||||
return "";
|
||||
}
|
||||
log("getQuery: found spans: "+span.length);
|
||||
QuerySpan sp = span[0];
|
||||
log("getQuery: span start is "+text.getSpanStart(sp));
|
||||
log("getQuery: span end is "+text.getSpanEnd(sp));
|
||||
CharSequence seq = text.subSequence(text.getSpanStart(sp), text.getSpanEnd(sp));
|
||||
log("getQuery: returning "+seq);
|
||||
return seq;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull Spannable text) {
|
||||
// Remove any span added by shouldShow. Should be useless, but anyway.
|
||||
QuerySpan[] span = text.getSpans(0, text.length(), QuerySpan.class);
|
||||
for (QuerySpan s : span) {
|
||||
text.removeSpan(s);
|
||||
}
|
||||
}
|
||||
|
||||
private static class QuerySpan {}
|
||||
|
||||
/**
|
||||
* Returns the current query out of the given Spannable.
|
||||
* @param text the anchor text
|
||||
* @return an int[] with query start and query end positions
|
||||
*/
|
||||
@Nullable
|
||||
public static int[] getQueryRange(@NonNull Spannable text) {
|
||||
QuerySpan[] span = text.getSpans(0, text.length(), QuerySpan.class);
|
||||
if (span == null || span.length == 0) return null;
|
||||
if (span.length > 1) {
|
||||
// Won't happen
|
||||
log("getQueryRange: ERR: MORE THAN ONE QuerySpan.");
|
||||
}
|
||||
QuerySpan sp = span[0];
|
||||
return new int[]{text.getSpanStart(sp), text.getSpanEnd(sp)};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package com.otaliastudios.autocomplete;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Simple {@link AutocompletePresenter} implementation that hosts a {@link RecyclerView}.
|
||||
* Supports {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} natively.
|
||||
* The only contract is to
|
||||
*
|
||||
* - provide a {@link RecyclerView.Adapter} in {@link #instantiateAdapter()}
|
||||
* - call {@link #dispatchClick(Object)} when an object is clicked
|
||||
* - update your data during {@link #onQuery(CharSequence)}
|
||||
*
|
||||
* @param <T> your model object (the object displayed by the list)
|
||||
*/
|
||||
public abstract class RecyclerViewPresenter<T> extends AutocompletePresenter<T> {
|
||||
|
||||
private RecyclerView recycler;
|
||||
private ClickProvider<T> clicks;
|
||||
private Observer observer;
|
||||
|
||||
public RecyclerViewPresenter(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void registerClickProvider(@NonNull ClickProvider<T> provider) {
|
||||
this.clicks = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void registerDataSetObserver(@NonNull DataSetObserver observer) {
|
||||
this.observer = new Observer(observer);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected ViewGroup getView() {
|
||||
recycler = new RecyclerView(getContext());
|
||||
RecyclerView.Adapter adapter = instantiateAdapter();
|
||||
recycler.setAdapter(adapter);
|
||||
recycler.setLayoutManager(instantiateLayoutManager());
|
||||
if (observer != null) {
|
||||
adapter.registerAdapterDataObserver(observer);
|
||||
observer = null;
|
||||
}
|
||||
return recycler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewShown() {}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
protected void onViewHidden() {
|
||||
recycler = null;
|
||||
observer = null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Nullable
|
||||
protected final RecyclerView getRecyclerView() {
|
||||
return recycler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch click event to {@link AutocompleteCallback}.
|
||||
* Should be called when items are clicked.
|
||||
*
|
||||
* @param item the clicked item.
|
||||
*/
|
||||
protected final void dispatchClick(@NonNull T item) {
|
||||
if (clicks != null) clicks.click(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the popup should recompute its dimensions based on a recent change in
|
||||
* the view being displayed.
|
||||
*
|
||||
* This is already managed internally for {@link RecyclerView} events.
|
||||
* Only use it for changes in other views that you have added to the popup,
|
||||
* and only if one of the dimensions for the popup is WRAP_CONTENT .
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected final void dispatchLayoutChange() {
|
||||
if (observer != null) observer.onChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an adapter for the recycler.
|
||||
* This should be a fresh instance every time this is called.
|
||||
*
|
||||
* @return a new adapter.
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract RecyclerView.Adapter instantiateAdapter();
|
||||
|
||||
/**
|
||||
* Provides a layout manager for the recycler.
|
||||
* This should be a fresh instance every time this is called.
|
||||
* Defaults to a vertical LinearLayoutManager, which is guaranteed to work well.
|
||||
*
|
||||
* @return a new layout manager.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@NonNull
|
||||
protected RecyclerView.LayoutManager instantiateLayoutManager() {
|
||||
return new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
|
||||
}
|
||||
|
||||
private final static class Observer extends RecyclerView.AdapterDataObserver {
|
||||
|
||||
private DataSetObserver root;
|
||||
|
||||
Observer(@NonNull DataSetObserver root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged() {
|
||||
root.onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
root.onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
root.onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
root.onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
root.onChanged();
|
||||
}
|
||||
}
|
||||
}
|
26
library/external/barcodescanner/core/build.gradle
vendored
Normal file
26
library/external/barcodescanner/core/build.gradle
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
namespace "me.dm7.barcodescanner.core"
|
||||
compileSdk versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.sourceCompat
|
||||
targetCompatibility versions.targetCompat
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.annotation:annotation-jvm:1.6.0'
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.findAll { it.name.startsWith("lint") }.each {
|
||||
it.enabled = false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
public abstract class BarcodeScannerView extends FrameLayout implements Camera.PreviewCallback {
|
||||
|
||||
private CameraWrapper mCameraWrapper;
|
||||
private CameraPreview mPreview;
|
||||
private IViewFinder mViewFinderView;
|
||||
private Rect mFramingRectInPreview;
|
||||
private CameraHandlerThread mCameraHandlerThread;
|
||||
private Boolean mFlashState;
|
||||
private boolean mAutofocusState = true;
|
||||
private boolean mShouldScaleToFill = true;
|
||||
|
||||
private boolean mIsLaserEnabled = true;
|
||||
@ColorInt private int mLaserColor = getResources().getColor(R.color.viewfinder_laser);
|
||||
@ColorInt private int mBorderColor = getResources().getColor(R.color.viewfinder_border);
|
||||
private int mMaskColor = getResources().getColor(R.color.viewfinder_mask);
|
||||
private int mBorderWidth = getResources().getInteger(R.integer.viewfinder_border_width);
|
||||
private int mBorderLength = getResources().getInteger(R.integer.viewfinder_border_length);
|
||||
private boolean mRoundedCorner = false;
|
||||
private int mCornerRadius = 0;
|
||||
private boolean mSquaredFinder = false;
|
||||
private float mBorderAlpha = 1.0f;
|
||||
private int mViewFinderOffset = 0;
|
||||
private float mAspectTolerance = 0.1f;
|
||||
|
||||
public BarcodeScannerView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BarcodeScannerView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(
|
||||
attributeSet,
|
||||
R.styleable.BarcodeScannerView,
|
||||
0, 0);
|
||||
|
||||
try {
|
||||
setShouldScaleToFill(a.getBoolean(R.styleable.BarcodeScannerView_shouldScaleToFill, true));
|
||||
mIsLaserEnabled = a.getBoolean(R.styleable.BarcodeScannerView_laserEnabled, mIsLaserEnabled);
|
||||
mLaserColor = a.getColor(R.styleable.BarcodeScannerView_laserColor, mLaserColor);
|
||||
mBorderColor = a.getColor(R.styleable.BarcodeScannerView_borderColor, mBorderColor);
|
||||
mMaskColor = a.getColor(R.styleable.BarcodeScannerView_maskColor, mMaskColor);
|
||||
mBorderWidth = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_borderWidth, mBorderWidth);
|
||||
mBorderLength = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_borderLength, mBorderLength);
|
||||
|
||||
mRoundedCorner = a.getBoolean(R.styleable.BarcodeScannerView_roundedCorner, mRoundedCorner);
|
||||
mCornerRadius = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_cornerRadius, mCornerRadius);
|
||||
mSquaredFinder = a.getBoolean(R.styleable.BarcodeScannerView_squaredFinder, mSquaredFinder);
|
||||
mBorderAlpha = a.getFloat(R.styleable.BarcodeScannerView_borderAlpha, mBorderAlpha);
|
||||
mViewFinderOffset = a.getDimensionPixelSize(R.styleable.BarcodeScannerView_finderOffset, mViewFinderOffset);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mViewFinderView = createViewFinderView(getContext());
|
||||
}
|
||||
|
||||
public final void setupLayout(CameraWrapper cameraWrapper) {
|
||||
removeAllViews();
|
||||
|
||||
mPreview = new CameraPreview(getContext(), cameraWrapper, this);
|
||||
mPreview.setAspectTolerance(mAspectTolerance);
|
||||
mPreview.setShouldScaleToFill(mShouldScaleToFill);
|
||||
if (!mShouldScaleToFill) {
|
||||
RelativeLayout relativeLayout = new RelativeLayout(getContext());
|
||||
relativeLayout.setGravity(Gravity.CENTER);
|
||||
relativeLayout.setBackgroundColor(Color.BLACK);
|
||||
relativeLayout.addView(mPreview);
|
||||
addView(relativeLayout);
|
||||
} else {
|
||||
addView(mPreview);
|
||||
}
|
||||
|
||||
if (mViewFinderView instanceof View) {
|
||||
addView((View) mViewFinderView);
|
||||
} else {
|
||||
throw new IllegalArgumentException("IViewFinder object returned by " +
|
||||
"'createViewFinderView()' should be instance of android.view.View");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Method that creates view that represents visual appearance of a barcode scanner</p>
|
||||
* <p>Override it to provide your own view for visual appearance of a barcode scanner</p>
|
||||
*
|
||||
* @param context {@link Context}
|
||||
* @return {@link android.view.View} that implements {@link ViewFinderView}
|
||||
*/
|
||||
protected IViewFinder createViewFinderView(Context context) {
|
||||
ViewFinderView viewFinderView = new ViewFinderView(context);
|
||||
viewFinderView.setBorderColor(mBorderColor);
|
||||
viewFinderView.setLaserColor(mLaserColor);
|
||||
viewFinderView.setLaserEnabled(mIsLaserEnabled);
|
||||
viewFinderView.setBorderStrokeWidth(mBorderWidth);
|
||||
viewFinderView.setBorderLineLength(mBorderLength);
|
||||
viewFinderView.setMaskColor(mMaskColor);
|
||||
|
||||
viewFinderView.setBorderCornerRounded(mRoundedCorner);
|
||||
viewFinderView.setBorderCornerRadius(mCornerRadius);
|
||||
viewFinderView.setSquareViewFinder(mSquaredFinder);
|
||||
viewFinderView.setViewFinderOffset(mViewFinderOffset);
|
||||
return viewFinderView;
|
||||
}
|
||||
|
||||
public void setLaserColor(int laserColor) {
|
||||
mLaserColor = laserColor;
|
||||
mViewFinderView.setLaserColor(mLaserColor);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setMaskColor(int maskColor) {
|
||||
mMaskColor = maskColor;
|
||||
mViewFinderView.setMaskColor(mMaskColor);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setBorderColor(int borderColor) {
|
||||
mBorderColor = borderColor;
|
||||
mViewFinderView.setBorderColor(mBorderColor);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setBorderStrokeWidth(int borderStrokeWidth) {
|
||||
mBorderWidth = borderStrokeWidth;
|
||||
mViewFinderView.setBorderStrokeWidth(mBorderWidth);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setBorderLineLength(int borderLineLength) {
|
||||
mBorderLength = borderLineLength;
|
||||
mViewFinderView.setBorderLineLength(mBorderLength);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setLaserEnabled(boolean isLaserEnabled) {
|
||||
mIsLaserEnabled = isLaserEnabled;
|
||||
mViewFinderView.setLaserEnabled(mIsLaserEnabled);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setIsBorderCornerRounded(boolean isBorderCornerRounded) {
|
||||
mRoundedCorner = isBorderCornerRounded;
|
||||
mViewFinderView.setBorderCornerRounded(mRoundedCorner);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setBorderCornerRadius(int borderCornerRadius) {
|
||||
mCornerRadius = borderCornerRadius;
|
||||
mViewFinderView.setBorderCornerRadius(mCornerRadius);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setSquareViewFinder(boolean isSquareViewFinder) {
|
||||
mSquaredFinder = isSquareViewFinder;
|
||||
mViewFinderView.setSquareViewFinder(mSquaredFinder);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
public void setBorderAlpha(float borderAlpha) {
|
||||
mBorderAlpha = borderAlpha;
|
||||
mViewFinderView.setBorderAlpha(mBorderAlpha);
|
||||
mViewFinderView.setupViewFinder();
|
||||
}
|
||||
|
||||
public void startCamera(int cameraId) {
|
||||
if(mCameraHandlerThread == null) {
|
||||
mCameraHandlerThread = new CameraHandlerThread(this);
|
||||
}
|
||||
mCameraHandlerThread.startCamera(cameraId);
|
||||
}
|
||||
|
||||
public void setupCameraPreview(CameraWrapper cameraWrapper) {
|
||||
mCameraWrapper = cameraWrapper;
|
||||
if(mCameraWrapper != null) {
|
||||
setupLayout(mCameraWrapper);
|
||||
mViewFinderView.setupViewFinder();
|
||||
if(mFlashState != null) {
|
||||
setFlash(mFlashState);
|
||||
}
|
||||
setAutoFocus(mAutofocusState);
|
||||
}
|
||||
}
|
||||
|
||||
public void startCamera() {
|
||||
startCamera(CameraUtils.getDefaultCameraId());
|
||||
}
|
||||
|
||||
public void stopCamera() {
|
||||
if(mCameraWrapper != null) {
|
||||
mPreview.stopCameraPreview();
|
||||
mPreview.setCamera(null, null);
|
||||
mCameraWrapper.mCamera.release();
|
||||
mCameraWrapper = null;
|
||||
}
|
||||
if(mCameraHandlerThread != null) {
|
||||
mCameraHandlerThread.quit();
|
||||
mCameraHandlerThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopCameraPreview() {
|
||||
if(mPreview != null) {
|
||||
mPreview.stopCameraPreview();
|
||||
}
|
||||
}
|
||||
|
||||
protected void resumeCameraPreview() {
|
||||
if(mPreview != null) {
|
||||
mPreview.showCameraPreview();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Rect getFramingRectInPreview(int previewWidth, int previewHeight) {
|
||||
if (mFramingRectInPreview == null) {
|
||||
Rect framingRect = mViewFinderView.getFramingRect();
|
||||
int viewFinderViewWidth = mViewFinderView.getWidth();
|
||||
int viewFinderViewHeight = mViewFinderView.getHeight();
|
||||
if (framingRect == null || viewFinderViewWidth == 0 || viewFinderViewHeight == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Rect rect = new Rect(framingRect);
|
||||
|
||||
if(previewWidth < viewFinderViewWidth) {
|
||||
rect.left = rect.left * previewWidth / viewFinderViewWidth;
|
||||
rect.right = rect.right * previewWidth / viewFinderViewWidth;
|
||||
}
|
||||
|
||||
if(previewHeight < viewFinderViewHeight) {
|
||||
rect.top = rect.top * previewHeight / viewFinderViewHeight;
|
||||
rect.bottom = rect.bottom * previewHeight / viewFinderViewHeight;
|
||||
}
|
||||
|
||||
mFramingRectInPreview = rect;
|
||||
}
|
||||
return mFramingRectInPreview;
|
||||
}
|
||||
|
||||
public void setFlash(boolean flag) {
|
||||
mFlashState = flag;
|
||||
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
|
||||
|
||||
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
|
||||
if(flag) {
|
||||
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
|
||||
return;
|
||||
}
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
|
||||
} else {
|
||||
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_OFF)) {
|
||||
return;
|
||||
}
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
|
||||
}
|
||||
mCameraWrapper.mCamera.setParameters(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getFlash() {
|
||||
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
|
||||
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
|
||||
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void toggleFlash() {
|
||||
if(mCameraWrapper != null && CameraUtils.isFlashSupported(mCameraWrapper.mCamera)) {
|
||||
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
|
||||
if(parameters.getFlashMode().equals(Camera.Parameters.FLASH_MODE_TORCH)) {
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
|
||||
} else {
|
||||
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
|
||||
}
|
||||
mCameraWrapper.mCamera.setParameters(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoFocus(boolean state) {
|
||||
mAutofocusState = state;
|
||||
if(mPreview != null) {
|
||||
mPreview.setAutoFocus(state);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShouldScaleToFill(boolean shouldScaleToFill) {
|
||||
mShouldScaleToFill = shouldScaleToFill;
|
||||
}
|
||||
|
||||
public void setAspectTolerance(float aspectTolerance) {
|
||||
mAspectTolerance = aspectTolerance;
|
||||
}
|
||||
|
||||
public byte[] getRotatedData(byte[] data, Camera camera) {
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
Camera.Size size = parameters.getPreviewSize();
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
int rotationCount = getRotationCount();
|
||||
|
||||
if(rotationCount == 1 || rotationCount == 3) {
|
||||
for (int i = 0; i < rotationCount; i++) {
|
||||
byte[] rotatedData = new byte[data.length];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++)
|
||||
rotatedData[x * height + height - y - 1] = data[x + y * width];
|
||||
}
|
||||
data = rotatedData;
|
||||
int tmp = width;
|
||||
width = height;
|
||||
height = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getRotationCount() {
|
||||
int displayOrientation = mPreview.getDisplayOrientation();
|
||||
return displayOrientation / 90;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
|
||||
import android.hardware.Camera;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
|
||||
// This code is mostly based on the top answer here: http://stackoverflow.com/questions/18149964/best-use-of-handlerthread-over-other-similar-classes
|
||||
public class CameraHandlerThread extends HandlerThread {
|
||||
private static final String LOG_TAG = "CameraHandlerThread";
|
||||
|
||||
private BarcodeScannerView mScannerView;
|
||||
|
||||
public CameraHandlerThread(BarcodeScannerView scannerView) {
|
||||
super("CameraHandlerThread");
|
||||
mScannerView = scannerView;
|
||||
start();
|
||||
}
|
||||
|
||||
public void startCamera(final int cameraId) {
|
||||
Handler localHandler = new Handler(getLooper());
|
||||
localHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Camera camera = CameraUtils.getCameraInstance(cameraId);
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mScannerView.setupCameraPreview(CameraWrapper.getWrapper(camera, cameraId));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
312
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java
vendored
Normal file
312
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraPreview.java
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
|
||||
private static final String TAG = "CameraPreview";
|
||||
|
||||
private CameraWrapper mCameraWrapper;
|
||||
private Handler mAutoFocusHandler;
|
||||
private boolean mPreviewing = true;
|
||||
private boolean mAutoFocus = true;
|
||||
private boolean mSurfaceCreated = false;
|
||||
private boolean mShouldScaleToFill = true;
|
||||
private Camera.PreviewCallback mPreviewCallback;
|
||||
private float mAspectTolerance = 0.1f;
|
||||
|
||||
public CameraPreview(Context context, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
||||
super(context);
|
||||
init(cameraWrapper, previewCallback);
|
||||
}
|
||||
|
||||
public CameraPreview(Context context, AttributeSet attrs, CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
||||
super(context, attrs);
|
||||
init(cameraWrapper, previewCallback);
|
||||
}
|
||||
|
||||
public void init(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
||||
setCamera(cameraWrapper, previewCallback);
|
||||
mAutoFocusHandler = new Handler();
|
||||
getHolder().addCallback(this);
|
||||
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
||||
}
|
||||
|
||||
public void setCamera(CameraWrapper cameraWrapper, Camera.PreviewCallback previewCallback) {
|
||||
mCameraWrapper = cameraWrapper;
|
||||
mPreviewCallback = previewCallback;
|
||||
}
|
||||
|
||||
public void setShouldScaleToFill(boolean scaleToFill) {
|
||||
mShouldScaleToFill = scaleToFill;
|
||||
}
|
||||
|
||||
public void setAspectTolerance(float aspectTolerance) {
|
||||
mAspectTolerance = aspectTolerance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
||||
mSurfaceCreated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
|
||||
if(surfaceHolder.getSurface() == null) {
|
||||
return;
|
||||
}
|
||||
stopCameraPreview();
|
||||
showCameraPreview();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
||||
mSurfaceCreated = false;
|
||||
stopCameraPreview();
|
||||
}
|
||||
|
||||
public void showCameraPreview() {
|
||||
if(mCameraWrapper != null) {
|
||||
try {
|
||||
getHolder().addCallback(this);
|
||||
mPreviewing = true;
|
||||
setupCameraParameters();
|
||||
mCameraWrapper.mCamera.setPreviewDisplay(getHolder());
|
||||
mCameraWrapper.mCamera.setDisplayOrientation(getDisplayOrientation());
|
||||
mCameraWrapper.mCamera.setOneShotPreviewCallback(mPreviewCallback);
|
||||
mCameraWrapper.mCamera.startPreview();
|
||||
if(mAutoFocus) {
|
||||
if (mSurfaceCreated) { // check if surface created before using autofocus
|
||||
safeAutoFocus();
|
||||
} else {
|
||||
scheduleAutoFocus(); // wait 1 sec and then do check again
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void safeAutoFocus() {
|
||||
try {
|
||||
mCameraWrapper.mCamera.autoFocus(autoFocusCB);
|
||||
} catch (RuntimeException re) {
|
||||
// Horrible hack to deal with autofocus errors on Sony devices
|
||||
// See https://github.com/dm77/barcodescanner/issues/7 for example
|
||||
scheduleAutoFocus(); // wait 1 sec and then do check again
|
||||
}
|
||||
}
|
||||
|
||||
public void stopCameraPreview() {
|
||||
if(mCameraWrapper != null) {
|
||||
try {
|
||||
mPreviewing = false;
|
||||
getHolder().removeCallback(this);
|
||||
mCameraWrapper.mCamera.cancelAutoFocus();
|
||||
mCameraWrapper.mCamera.setOneShotPreviewCallback(null);
|
||||
mCameraWrapper.mCamera.stopPreview();
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setupCameraParameters() {
|
||||
Camera.Size optimalSize = getOptimalPreviewSize();
|
||||
Camera.Parameters parameters = mCameraWrapper.mCamera.getParameters();
|
||||
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
|
||||
mCameraWrapper.mCamera.setParameters(parameters);
|
||||
adjustViewSize(optimalSize);
|
||||
}
|
||||
|
||||
private void adjustViewSize(Camera.Size cameraSize) {
|
||||
Point previewSize = convertSizeToLandscapeOrientation(new Point(getWidth(), getHeight()));
|
||||
float cameraRatio = ((float) cameraSize.width) / cameraSize.height;
|
||||
float screenRatio = ((float) previewSize.x) / previewSize.y;
|
||||
|
||||
if (screenRatio > cameraRatio) {
|
||||
setViewSize((int) (previewSize.y * cameraRatio), previewSize.y);
|
||||
} else {
|
||||
setViewSize(previewSize.x, (int) (previewSize.x / cameraRatio));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
private Point convertSizeToLandscapeOrientation(Point size) {
|
||||
if (getDisplayOrientation() % 180 == 0) {
|
||||
return size;
|
||||
} else {
|
||||
return new Point(size.y, size.x);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
private void setViewSize(int width, int height) {
|
||||
ViewGroup.LayoutParams layoutParams = getLayoutParams();
|
||||
int tmpWidth;
|
||||
int tmpHeight;
|
||||
if (getDisplayOrientation() % 180 == 0) {
|
||||
tmpWidth = width;
|
||||
tmpHeight = height;
|
||||
} else {
|
||||
tmpWidth = height;
|
||||
tmpHeight = width;
|
||||
}
|
||||
|
||||
if (mShouldScaleToFill) {
|
||||
int parentWidth = ((View) getParent()).getWidth();
|
||||
int parentHeight = ((View) getParent()).getHeight();
|
||||
float ratioWidth = (float) parentWidth / (float) tmpWidth;
|
||||
float ratioHeight = (float) parentHeight / (float) tmpHeight;
|
||||
|
||||
float compensation;
|
||||
|
||||
if (ratioWidth > ratioHeight) {
|
||||
compensation = ratioWidth;
|
||||
} else {
|
||||
compensation = ratioHeight;
|
||||
}
|
||||
|
||||
tmpWidth = Math.round(tmpWidth * compensation);
|
||||
tmpHeight = Math.round(tmpHeight * compensation);
|
||||
}
|
||||
|
||||
layoutParams.width = tmpWidth;
|
||||
layoutParams.height = tmpHeight;
|
||||
setLayoutParams(layoutParams);
|
||||
}
|
||||
|
||||
public int getDisplayOrientation() {
|
||||
if (mCameraWrapper == null) {
|
||||
//If we don't have a camera set there is no orientation so return dummy value
|
||||
return 0;
|
||||
}
|
||||
|
||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||
if(mCameraWrapper.mCameraId == -1) {
|
||||
Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
|
||||
} else {
|
||||
Camera.getCameraInfo(mCameraWrapper.mCameraId, info);
|
||||
}
|
||||
|
||||
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
|
||||
int rotation = display.getRotation();
|
||||
int degrees = 0;
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0: degrees = 0; break;
|
||||
case Surface.ROTATION_90: degrees = 90; break;
|
||||
case Surface.ROTATION_180: degrees = 180; break;
|
||||
case Surface.ROTATION_270: degrees = 270; break;
|
||||
}
|
||||
|
||||
int result;
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
result = (info.orientation + degrees) % 360;
|
||||
result = (360 - result) % 360; // compensate the mirror
|
||||
} else { // back-facing
|
||||
result = (info.orientation - degrees + 360) % 360;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Camera.Size getOptimalPreviewSize() {
|
||||
if(mCameraWrapper == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Camera.Size> sizes = mCameraWrapper.mCamera.getParameters().getSupportedPreviewSizes();
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
|
||||
int portraitWidth = h;
|
||||
h = w;
|
||||
w = portraitWidth;
|
||||
}
|
||||
|
||||
double targetRatio = (double) w / h;
|
||||
if (sizes == null) return null;
|
||||
|
||||
Camera.Size optimalSize = null;
|
||||
double minDiff = Double.MAX_VALUE;
|
||||
|
||||
int targetHeight = h;
|
||||
|
||||
// Try to find an size match aspect ratio and size
|
||||
for (Camera.Size size : sizes) {
|
||||
double ratio = (double) size.width / size.height;
|
||||
if (Math.abs(ratio - targetRatio) > mAspectTolerance) continue;
|
||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
||||
optimalSize = size;
|
||||
minDiff = Math.abs(size.height - targetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot find the one match the aspect ratio, ignore the requirement
|
||||
if (optimalSize == null) {
|
||||
minDiff = Double.MAX_VALUE;
|
||||
for (Camera.Size size : sizes) {
|
||||
if (Math.abs(size.height - targetHeight) < minDiff) {
|
||||
optimalSize = size;
|
||||
minDiff = Math.abs(size.height - targetHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
return optimalSize;
|
||||
}
|
||||
|
||||
public void setAutoFocus(boolean state) {
|
||||
if(mCameraWrapper != null && mPreviewing) {
|
||||
if(state == mAutoFocus) {
|
||||
return;
|
||||
}
|
||||
mAutoFocus = state;
|
||||
if(mAutoFocus) {
|
||||
if (mSurfaceCreated) { // check if surface created before using autofocus
|
||||
Log.v(TAG, "Starting autofocus");
|
||||
safeAutoFocus();
|
||||
} else {
|
||||
scheduleAutoFocus(); // wait 1 sec and then do check again
|
||||
}
|
||||
} else {
|
||||
Log.v(TAG, "Cancelling autofocus");
|
||||
mCameraWrapper.mCamera.cancelAutoFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable doAutoFocus = new Runnable() {
|
||||
public void run() {
|
||||
if(mCameraWrapper != null && mPreviewing && mAutoFocus && mSurfaceCreated) {
|
||||
safeAutoFocus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Mimic continuous auto-focusing
|
||||
Camera.AutoFocusCallback autoFocusCB = new Camera.AutoFocusCallback() {
|
||||
public void onAutoFocus(boolean success, Camera camera) {
|
||||
scheduleAutoFocus();
|
||||
}
|
||||
};
|
||||
|
||||
private void scheduleAutoFocus() {
|
||||
mAutoFocusHandler.postDelayed(doAutoFocus, 1000);
|
||||
}
|
||||
}
|
63
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java
vendored
Normal file
63
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraUtils.java
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CameraUtils {
|
||||
/** A safe way to get an instance of the Camera object. */
|
||||
public static Camera getCameraInstance() {
|
||||
return getCameraInstance(getDefaultCameraId());
|
||||
}
|
||||
|
||||
/** Favor back-facing camera by default. If none exists, fallback to whatever camera is available **/
|
||||
public static int getDefaultCameraId() {
|
||||
int numberOfCameras = Camera.getNumberOfCameras();
|
||||
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
|
||||
int defaultCameraId = -1;
|
||||
for (int i = 0; i < numberOfCameras; i++) {
|
||||
defaultCameraId = i;
|
||||
Camera.getCameraInfo(i, cameraInfo);
|
||||
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return defaultCameraId;
|
||||
}
|
||||
|
||||
/** A safe way to get an instance of the Camera object. */
|
||||
public static Camera getCameraInstance(int cameraId) {
|
||||
Camera c = null;
|
||||
try {
|
||||
if(cameraId == -1) {
|
||||
c = Camera.open(); // attempt to get a Camera instance
|
||||
} else {
|
||||
c = Camera.open(cameraId); // attempt to get a Camera instance
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Camera is not available (in use or does not exist)
|
||||
}
|
||||
return c; // returns null if camera is unavailable
|
||||
}
|
||||
|
||||
public static boolean isFlashSupported(Camera camera) {
|
||||
/* Credits: Top answer at http://stackoverflow.com/a/19599365/868173 */
|
||||
if (camera != null) {
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
|
||||
if (parameters.getFlashMode() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> supportedFlashModes = parameters.getSupportedFlashModes();
|
||||
if (supportedFlashModes == null || supportedFlashModes.isEmpty() || supportedFlashModes.size() == 1 && supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
23
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java
vendored
Normal file
23
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class CameraWrapper {
|
||||
public final Camera mCamera;
|
||||
public final int mCameraId;
|
||||
|
||||
private CameraWrapper(@NonNull Camera camera, int cameraId) {
|
||||
this.mCamera = camera;
|
||||
this.mCameraId = cameraId;
|
||||
}
|
||||
|
||||
public static CameraWrapper getWrapper(Camera camera, int cameraId) {
|
||||
if (camera == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new CameraWrapper(camera, cameraId);
|
||||
}
|
||||
}
|
||||
}
|
41
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java
vendored
Normal file
41
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/DisplayUtils.java
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public class DisplayUtils {
|
||||
public static Point getScreenResolution(Context context) {
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
Point screenResolution = new Point();
|
||||
if (android.os.Build.VERSION.SDK_INT >= 13) {
|
||||
display.getSize(screenResolution);
|
||||
} else {
|
||||
screenResolution.set(display.getWidth(), display.getHeight());
|
||||
}
|
||||
|
||||
return screenResolution;
|
||||
}
|
||||
|
||||
public static int getScreenOrientation(Context context)
|
||||
{
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
|
||||
int orientation = Configuration.ORIENTATION_UNDEFINED;
|
||||
if(display.getWidth()==display.getHeight()){
|
||||
orientation = Configuration.ORIENTATION_SQUARE;
|
||||
} else{
|
||||
if(display.getWidth() < display.getHeight()){
|
||||
orientation = Configuration.ORIENTATION_PORTRAIT;
|
||||
}else {
|
||||
orientation = Configuration.ORIENTATION_LANDSCAPE;
|
||||
}
|
||||
}
|
||||
return orientation;
|
||||
}
|
||||
|
||||
}
|
53
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java
vendored
Normal file
53
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/IViewFinder.java
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
public interface IViewFinder {
|
||||
|
||||
void setLaserColor(int laserColor);
|
||||
void setMaskColor(int maskColor);
|
||||
void setBorderColor(int borderColor);
|
||||
void setBorderStrokeWidth(int borderStrokeWidth);
|
||||
void setBorderLineLength(int borderLineLength);
|
||||
void setLaserEnabled(boolean isLaserEnabled);
|
||||
|
||||
void setBorderCornerRounded(boolean isBorderCornersRounded);
|
||||
void setBorderAlpha(float alpha);
|
||||
void setBorderCornerRadius(int borderCornersRadius);
|
||||
void setViewFinderOffset(int offset);
|
||||
void setSquareViewFinder(boolean isSquareViewFinder);
|
||||
/**
|
||||
* Method that executes when Camera preview is starting.
|
||||
* It is recommended to update framing rect here and invalidate view after that. <br/>
|
||||
* For example see: {@link ViewFinderView#setupViewFinder()}
|
||||
*/
|
||||
void setupViewFinder();
|
||||
|
||||
/**
|
||||
* Provides {@link Rect} that identifies area where barcode scanner can detect visual codes
|
||||
* <p>Note: This rect is a area representation in absolute pixel values. <br/>
|
||||
* For example: <br/>
|
||||
* If View's size is 1024x800 so framing rect might be 500x400</p>
|
||||
*
|
||||
* @return {@link Rect} that identifies barcode scanner area
|
||||
*/
|
||||
Rect getFramingRect();
|
||||
|
||||
/**
|
||||
* Width of a {@link android.view.View} that implements this interface
|
||||
* <p>Note: this is already implemented in {@link android.view.View},
|
||||
* so you don't need to override method and provide your implementation</p>
|
||||
*
|
||||
* @return width of a view
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* Height of a {@link android.view.View} that implements this interface
|
||||
* <p>Note: this is already implemented in {@link android.view.View},
|
||||
* so you don't need to override method and provide your implementation</p>
|
||||
*
|
||||
* @return height of a view
|
||||
*/
|
||||
int getHeight();
|
||||
}
|
259
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java
vendored
Normal file
259
library/external/barcodescanner/core/src/main/java/me/dm7/barcodescanner/core/ViewFinderView.java
vendored
Normal file
|
@ -0,0 +1,259 @@
|
|||
package me.dm7.barcodescanner.core;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.CornerPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
public class ViewFinderView extends View implements IViewFinder {
|
||||
private static final String TAG = "ViewFinderView";
|
||||
|
||||
private Rect mFramingRect;
|
||||
|
||||
private static final float PORTRAIT_WIDTH_RATIO = 6f/8;
|
||||
private static final float PORTRAIT_WIDTH_HEIGHT_RATIO = 0.75f;
|
||||
|
||||
private static final float LANDSCAPE_HEIGHT_RATIO = 5f/8;
|
||||
private static final float LANDSCAPE_WIDTH_HEIGHT_RATIO = 1.4f;
|
||||
private static final int MIN_DIMENSION_DIFF = 50;
|
||||
|
||||
private static final float DEFAULT_SQUARE_DIMENSION_RATIO = 5f / 8;
|
||||
|
||||
private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
|
||||
private int scannerAlpha;
|
||||
private static final int POINT_SIZE = 10;
|
||||
private static final long ANIMATION_DELAY = 80l;
|
||||
|
||||
private final int mDefaultLaserColor = getResources().getColor(R.color.viewfinder_laser);
|
||||
private final int mDefaultMaskColor = getResources().getColor(R.color.viewfinder_mask);
|
||||
private final int mDefaultBorderColor = getResources().getColor(R.color.viewfinder_border);
|
||||
private final int mDefaultBorderStrokeWidth = getResources().getInteger(R.integer.viewfinder_border_width);
|
||||
private final int mDefaultBorderLineLength = getResources().getInteger(R.integer.viewfinder_border_length);
|
||||
|
||||
protected Paint mLaserPaint;
|
||||
protected Paint mFinderMaskPaint;
|
||||
protected Paint mBorderPaint;
|
||||
protected int mBorderLineLength;
|
||||
protected boolean mSquareViewFinder;
|
||||
private boolean mIsLaserEnabled;
|
||||
private float mBordersAlpha;
|
||||
private int mViewFinderOffset = 0;
|
||||
|
||||
public ViewFinderView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public ViewFinderView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
//set up laser paint
|
||||
mLaserPaint = new Paint();
|
||||
mLaserPaint.setColor(mDefaultLaserColor);
|
||||
mLaserPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
//finder mask paint
|
||||
mFinderMaskPaint = new Paint();
|
||||
mFinderMaskPaint.setColor(mDefaultMaskColor);
|
||||
|
||||
//border paint
|
||||
mBorderPaint = new Paint();
|
||||
mBorderPaint.setColor(mDefaultBorderColor);
|
||||
mBorderPaint.setStyle(Paint.Style.STROKE);
|
||||
mBorderPaint.setStrokeWidth(mDefaultBorderStrokeWidth);
|
||||
mBorderPaint.setAntiAlias(true);
|
||||
|
||||
mBorderLineLength = mDefaultBorderLineLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLaserColor(int laserColor) {
|
||||
mLaserPaint.setColor(laserColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaskColor(int maskColor) {
|
||||
mFinderMaskPaint.setColor(maskColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderColor(int borderColor) {
|
||||
mBorderPaint.setColor(borderColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderStrokeWidth(int borderStrokeWidth) {
|
||||
mBorderPaint.setStrokeWidth(borderStrokeWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderLineLength(int borderLineLength) {
|
||||
mBorderLineLength = borderLineLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLaserEnabled(boolean isLaserEnabled) { mIsLaserEnabled = isLaserEnabled; }
|
||||
|
||||
@Override
|
||||
public void setBorderCornerRounded(boolean isBorderCornersRounded) {
|
||||
if (isBorderCornersRounded) {
|
||||
mBorderPaint.setStrokeJoin(Paint.Join.ROUND);
|
||||
} else {
|
||||
mBorderPaint.setStrokeJoin(Paint.Join.BEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderAlpha(float alpha) {
|
||||
int colorAlpha = (int) (255 * alpha);
|
||||
mBordersAlpha = alpha;
|
||||
mBorderPaint.setAlpha(colorAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBorderCornerRadius(int borderCornersRadius) {
|
||||
mBorderPaint.setPathEffect(new CornerPathEffect(borderCornersRadius));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setViewFinderOffset(int offset) {
|
||||
mViewFinderOffset = offset;
|
||||
}
|
||||
|
||||
// TODO: Need a better way to configure this. Revisit when working on 2.0
|
||||
@Override
|
||||
public void setSquareViewFinder(boolean set) {
|
||||
mSquareViewFinder = set;
|
||||
}
|
||||
|
||||
public void setupViewFinder() {
|
||||
updateFramingRect();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public Rect getFramingRect() {
|
||||
return mFramingRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
if(getFramingRect() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawViewFinderMask(canvas);
|
||||
drawViewFinderBorder(canvas);
|
||||
|
||||
if (mIsLaserEnabled) {
|
||||
drawLaser(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawViewFinderMask(Canvas canvas) {
|
||||
int width = canvas.getWidth();
|
||||
int height = canvas.getHeight();
|
||||
Rect framingRect = getFramingRect();
|
||||
|
||||
canvas.drawRect(0, 0, width, framingRect.top, mFinderMaskPaint);
|
||||
canvas.drawRect(0, framingRect.top, framingRect.left, framingRect.bottom + 1, mFinderMaskPaint);
|
||||
canvas.drawRect(framingRect.right + 1, framingRect.top, width, framingRect.bottom + 1, mFinderMaskPaint);
|
||||
canvas.drawRect(0, framingRect.bottom + 1, width, height, mFinderMaskPaint);
|
||||
}
|
||||
|
||||
public void drawViewFinderBorder(Canvas canvas) {
|
||||
Rect framingRect = getFramingRect();
|
||||
|
||||
// Top-left corner
|
||||
Path path = new Path();
|
||||
path.moveTo(framingRect.left, framingRect.top + mBorderLineLength);
|
||||
path.lineTo(framingRect.left, framingRect.top);
|
||||
path.lineTo(framingRect.left + mBorderLineLength, framingRect.top);
|
||||
canvas.drawPath(path, mBorderPaint);
|
||||
|
||||
// Top-right corner
|
||||
path.moveTo(framingRect.right, framingRect.top + mBorderLineLength);
|
||||
path.lineTo(framingRect.right, framingRect.top);
|
||||
path.lineTo(framingRect.right - mBorderLineLength, framingRect.top);
|
||||
canvas.drawPath(path, mBorderPaint);
|
||||
|
||||
// Bottom-right corner
|
||||
path.moveTo(framingRect.right, framingRect.bottom - mBorderLineLength);
|
||||
path.lineTo(framingRect.right, framingRect.bottom);
|
||||
path.lineTo(framingRect.right - mBorderLineLength, framingRect.bottom);
|
||||
canvas.drawPath(path, mBorderPaint);
|
||||
|
||||
// Bottom-left corner
|
||||
path.moveTo(framingRect.left, framingRect.bottom - mBorderLineLength);
|
||||
path.lineTo(framingRect.left, framingRect.bottom);
|
||||
path.lineTo(framingRect.left + mBorderLineLength, framingRect.bottom);
|
||||
canvas.drawPath(path, mBorderPaint);
|
||||
}
|
||||
|
||||
public void drawLaser(Canvas canvas) {
|
||||
Rect framingRect = getFramingRect();
|
||||
|
||||
// Draw a red "laser scanner" line through the middle to show decoding is active
|
||||
mLaserPaint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
|
||||
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
|
||||
int middle = framingRect.height() / 2 + framingRect.top;
|
||||
canvas.drawRect(framingRect.left + 2, middle - 1, framingRect.right - 1, middle + 2, mLaserPaint);
|
||||
|
||||
postInvalidateDelayed(ANIMATION_DELAY,
|
||||
framingRect.left - POINT_SIZE,
|
||||
framingRect.top - POINT_SIZE,
|
||||
framingRect.right + POINT_SIZE,
|
||||
framingRect.bottom + POINT_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
|
||||
updateFramingRect();
|
||||
}
|
||||
|
||||
public synchronized void updateFramingRect() {
|
||||
Point viewResolution = new Point(getWidth(), getHeight());
|
||||
int width;
|
||||
int height;
|
||||
int orientation = DisplayUtils.getScreenOrientation(getContext());
|
||||
|
||||
if(mSquareViewFinder) {
|
||||
if(orientation != Configuration.ORIENTATION_PORTRAIT) {
|
||||
height = (int) (getHeight() * DEFAULT_SQUARE_DIMENSION_RATIO);
|
||||
width = height;
|
||||
} else {
|
||||
width = (int) (getWidth() * DEFAULT_SQUARE_DIMENSION_RATIO);
|
||||
height = width;
|
||||
}
|
||||
} else {
|
||||
if(orientation != Configuration.ORIENTATION_PORTRAIT) {
|
||||
height = (int) (getHeight() * LANDSCAPE_HEIGHT_RATIO);
|
||||
width = (int) (LANDSCAPE_WIDTH_HEIGHT_RATIO * height);
|
||||
} else {
|
||||
width = (int) (getWidth() * PORTRAIT_WIDTH_RATIO);
|
||||
height = (int) (PORTRAIT_WIDTH_HEIGHT_RATIO * width);
|
||||
}
|
||||
}
|
||||
|
||||
if(width > getWidth()) {
|
||||
width = getWidth() - MIN_DIMENSION_DIFF;
|
||||
}
|
||||
|
||||
if(height > getHeight()) {
|
||||
height = getHeight() - MIN_DIMENSION_DIFF;
|
||||
}
|
||||
|
||||
int leftOffset = (viewResolution.x - width) / 2;
|
||||
int topOffset = (viewResolution.y - height) / 2;
|
||||
mFramingRect = new Rect(leftOffset + mViewFinderOffset, topOffset + mViewFinderOffset, leftOffset + width - mViewFinderOffset, topOffset + height - mViewFinderOffset);
|
||||
}
|
||||
}
|
||||
|
5
library/external/barcodescanner/core/src/main/res/values-hdpi/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values-hdpi/integers.xml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="viewfinder_border_width">4</integer>
|
||||
<integer name="viewfinder_border_length">60</integer>
|
||||
</resources>
|
5
library/external/barcodescanner/core/src/main/res/values-xhdpi/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values-xhdpi/integers.xml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="viewfinder_border_width">5</integer>
|
||||
<integer name="viewfinder_border_length">80</integer>
|
||||
</resources>
|
5
library/external/barcodescanner/core/src/main/res/values-xxhdpi/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values-xxhdpi/integers.xml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="viewfinder_border_width">6</integer>
|
||||
<integer name="viewfinder_border_length">100</integer>
|
||||
</resources>
|
17
library/external/barcodescanner/core/src/main/res/values/attrs.xml
vendored
Normal file
17
library/external/barcodescanner/core/src/main/res/values/attrs.xml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="BarcodeScannerView">
|
||||
<attr name="shouldScaleToFill" format="boolean" />
|
||||
<attr name="laserEnabled" format="boolean" />
|
||||
<attr name="laserColor" format="color" />
|
||||
<attr name="borderColor" format="color" />
|
||||
<attr name="maskColor" format="color" />
|
||||
<attr name="borderWidth" format="dimension" />
|
||||
<attr name="borderLength" format="dimension" />
|
||||
<attr name="roundedCorner" format="boolean" />
|
||||
<attr name="cornerRadius" format="dimension" />
|
||||
<attr name="squaredFinder" format="boolean" />
|
||||
<attr name="borderAlpha" format="float" />
|
||||
<attr name="finderOffset" format="dimension" />
|
||||
</declare-styleable>
|
||||
</resources>
|
6
library/external/barcodescanner/core/src/main/res/values/colors.xml
vendored
Normal file
6
library/external/barcodescanner/core/src/main/res/values/colors.xml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="viewfinder_mask">#60000000</color>
|
||||
<color name="viewfinder_laser">#ffcc0000</color>
|
||||
<color name="viewfinder_border">#ffafed44</color>
|
||||
</resources>
|
5
library/external/barcodescanner/core/src/main/res/values/integers.xml
vendored
Normal file
5
library/external/barcodescanner/core/src/main/res/values/integers.xml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="viewfinder_border_width">4</integer>
|
||||
<integer name="viewfinder_border_length">60</integer>
|
||||
</resources>
|
29
library/external/barcodescanner/zxing/build.gradle
vendored
Normal file
29
library/external/barcodescanner/zxing/build.gradle
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
namespace "me.dm7.barcodescanner.zxing"
|
||||
|
||||
compileSdk versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.sourceCompat
|
||||
targetCompatibility versions.targetCompat
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(":library:external:barcodescanner:core")
|
||||
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
|
||||
api 'com.google.zxing:core:3.3.3'
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.findAll { it.name.startsWith("lint") }.each {
|
||||
it.enabled = false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package me.dm7.barcodescanner.zxing;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.LuminanceSource;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.dm7.barcodescanner.core.BarcodeScannerView;
|
||||
import me.dm7.barcodescanner.core.DisplayUtils;
|
||||
|
||||
public class ZXingScannerView extends BarcodeScannerView {
|
||||
private static final String TAG = "ZXingScannerView";
|
||||
|
||||
public interface ResultHandler {
|
||||
void handleResult(Result rawResult);
|
||||
}
|
||||
|
||||
private MultiFormatReader mMultiFormatReader;
|
||||
public static final List<BarcodeFormat> ALL_FORMATS = new ArrayList<>();
|
||||
private List<BarcodeFormat> mFormats;
|
||||
private ResultHandler mResultHandler;
|
||||
|
||||
static {
|
||||
ALL_FORMATS.add(BarcodeFormat.AZTEC);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODABAR);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODE_39);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODE_93);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODE_128);
|
||||
ALL_FORMATS.add(BarcodeFormat.DATA_MATRIX);
|
||||
ALL_FORMATS.add(BarcodeFormat.EAN_8);
|
||||
ALL_FORMATS.add(BarcodeFormat.EAN_13);
|
||||
ALL_FORMATS.add(BarcodeFormat.ITF);
|
||||
ALL_FORMATS.add(BarcodeFormat.MAXICODE);
|
||||
ALL_FORMATS.add(BarcodeFormat.PDF_417);
|
||||
ALL_FORMATS.add(BarcodeFormat.QR_CODE);
|
||||
ALL_FORMATS.add(BarcodeFormat.RSS_14);
|
||||
ALL_FORMATS.add(BarcodeFormat.RSS_EXPANDED);
|
||||
ALL_FORMATS.add(BarcodeFormat.UPC_A);
|
||||
ALL_FORMATS.add(BarcodeFormat.UPC_E);
|
||||
ALL_FORMATS.add(BarcodeFormat.UPC_EAN_EXTENSION);
|
||||
}
|
||||
|
||||
public ZXingScannerView(Context context) {
|
||||
super(context);
|
||||
initMultiFormatReader();
|
||||
}
|
||||
|
||||
public ZXingScannerView(Context context, AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
initMultiFormatReader();
|
||||
}
|
||||
|
||||
public void setFormats(List<BarcodeFormat> formats) {
|
||||
mFormats = formats;
|
||||
initMultiFormatReader();
|
||||
}
|
||||
|
||||
public void setResultHandler(ResultHandler resultHandler) {
|
||||
mResultHandler = resultHandler;
|
||||
}
|
||||
|
||||
public Collection<BarcodeFormat> getFormats() {
|
||||
if(mFormats == null) {
|
||||
return ALL_FORMATS;
|
||||
}
|
||||
return mFormats;
|
||||
}
|
||||
|
||||
private void initMultiFormatReader() {
|
||||
Map<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, getFormats());
|
||||
mMultiFormatReader = new MultiFormatReader();
|
||||
mMultiFormatReader.setHints(hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||
if(mResultHandler == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
Camera.Size size = parameters.getPreviewSize();
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
if (DisplayUtils.getScreenOrientation(getContext()) == Configuration.ORIENTATION_PORTRAIT) {
|
||||
int rotationCount = getRotationCount();
|
||||
if (rotationCount == 1 || rotationCount == 3) {
|
||||
int tmp = width;
|
||||
width = height;
|
||||
height = tmp;
|
||||
}
|
||||
data = getRotatedData(data, camera);
|
||||
}
|
||||
|
||||
Result rawResult = null;
|
||||
PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height);
|
||||
|
||||
if (source != null) {
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||
try {
|
||||
rawResult = mMultiFormatReader.decodeWithState(bitmap);
|
||||
} catch (ReaderException re) {
|
||||
// continue
|
||||
} catch (NullPointerException npe) {
|
||||
// This is terrible
|
||||
} catch (ArrayIndexOutOfBoundsException aoe) {
|
||||
|
||||
} finally {
|
||||
mMultiFormatReader.reset();
|
||||
}
|
||||
|
||||
if (rawResult == null) {
|
||||
LuminanceSource invertedSource = source.invert();
|
||||
bitmap = new BinaryBitmap(new HybridBinarizer(invertedSource));
|
||||
try {
|
||||
rawResult = mMultiFormatReader.decodeWithState(bitmap);
|
||||
} catch (NotFoundException e) {
|
||||
// continue
|
||||
} finally {
|
||||
mMultiFormatReader.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Result finalRawResult = rawResult;
|
||||
|
||||
if (finalRawResult != null) {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Stopping the preview can take a little long.
|
||||
// So we want to set result handler to null to discard subsequent calls to
|
||||
// onPreviewFrame.
|
||||
ResultHandler tmpResultHandler = mResultHandler;
|
||||
mResultHandler = null;
|
||||
|
||||
stopCameraPreview();
|
||||
if (tmpResultHandler != null) {
|
||||
tmpResultHandler.handleResult(finalRawResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
camera.setOneShotPreviewCallback(this);
|
||||
}
|
||||
} catch(RuntimeException e) {
|
||||
// TODO: Terrible hack. It is possible that this method is invoked after camera is released.
|
||||
Log.e(TAG, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void resumeCameraPreview(ResultHandler resultHandler) {
|
||||
mResultHandler = resultHandler;
|
||||
super.resumeCameraPreview();
|
||||
}
|
||||
|
||||
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
|
||||
Rect rect = getFramingRectInPreview(width, height);
|
||||
if (rect == null) {
|
||||
return null;
|
||||
}
|
||||
// Go ahead and assume it's YUV rather than die.
|
||||
PlanarYUVLuminanceSource source = null;
|
||||
|
||||
try {
|
||||
source = new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
|
||||
rect.width(), rect.height(), false);
|
||||
} catch(Exception e) {
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
4
library/external/jsonviewer/build.gradle
vendored
4
library/external/jsonviewer/build.gradle
vendored
|
@ -58,9 +58,7 @@ dependencies {
|
|||
|
||||
implementation libs.airbnb.mavericks
|
||||
// Span utils
|
||||
implementation('me.gujun.android:span:1.7') {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
implementation project(":library:external:span")
|
||||
|
||||
implementation libs.jetbrains.coroutinesCore
|
||||
implementation libs.jetbrains.coroutinesAndroid
|
||||
|
|
23
library/external/realmfieldnameshelper/build.gradle
vendored
Normal file
23
library/external/realmfieldnameshelper/build.gradle
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
apply plugin: 'kotlin'
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = versions.sourceCompat
|
||||
targetCompatibility = versions.sourceCompat
|
||||
|
||||
dependencies {
|
||||
implementation 'com.squareup:javapoet:1.13.0'
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: 'javadoc') {
|
||||
from javadoc.destinationDir
|
||||
classifier = 'javadoc'
|
||||
}
|
||||
task sourcesJar(type: Jar, dependsOn: 'classes') {
|
||||
from sourceSets.main.allSource
|
||||
classifier = 'sources'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
24
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/ClassData.kt
vendored
Normal file
24
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/ClassData.kt
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
package dk.ilios.realmfieldnames
|
||||
|
||||
import java.util.TreeMap
|
||||
|
||||
/**
|
||||
* Class responsible for keeping track of the metadata for each Realm model class.
|
||||
*/
|
||||
class ClassData(val packageName: String?, val simpleClassName: String, val libraryClass: Boolean = false) {
|
||||
|
||||
val fields = TreeMap<String, String?>() // <fieldName, linkedType or null>
|
||||
|
||||
fun addField(field: String, linkedType: String?) {
|
||||
fields.put(field, linkedType)
|
||||
}
|
||||
|
||||
val qualifiedClassName: String
|
||||
get() {
|
||||
if (packageName != null && !packageName.isEmpty()) {
|
||||
return packageName + "." + simpleClassName
|
||||
} else {
|
||||
return simpleClassName
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package dk.ilios.realmfieldnames
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Class for encapsulating the rules for converting between the field name in the Realm model class
|
||||
* and the matching name in the "<class>Fields" class.
|
||||
*/
|
||||
class FieldNameFormatter {
|
||||
|
||||
@JvmOverloads
|
||||
fun format(fieldName: String?, locale: Locale = Locale.US): String {
|
||||
if (fieldName == null || fieldName == "") {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Normalize word separator chars
|
||||
val normalizedFieldName: String = fieldName.replace('-', '_')
|
||||
|
||||
// Iterate field name using the following rules
|
||||
// lowerCase m followed by upperCase anything is considered hungarian notation
|
||||
// lowercase char followed by uppercase char is considered camel case
|
||||
// Two uppercase chars following each other is considered non-standard camelcase
|
||||
// _ and - are treated as word separators
|
||||
val result = StringBuilder(normalizedFieldName.length)
|
||||
|
||||
if (normalizedFieldName.codePointCount(0, normalizedFieldName.length) == 1) {
|
||||
result.append(normalizedFieldName)
|
||||
} else {
|
||||
var previousCodepoint: Int?
|
||||
var currentCodepoint: Int? = null
|
||||
val length = normalizedFieldName.length
|
||||
var offset = 0
|
||||
while (offset < length) {
|
||||
previousCodepoint = currentCodepoint
|
||||
currentCodepoint = normalizedFieldName.codePointAt(offset)
|
||||
|
||||
if (previousCodepoint != null) {
|
||||
if (Character.isUpperCase(currentCodepoint) &&
|
||||
!Character.isUpperCase(previousCodepoint) &&
|
||||
previousCodepoint === 'm'.code as Int? &&
|
||||
result.length == 1
|
||||
) {
|
||||
// Hungarian notation starting with: mX
|
||||
result.delete(0, 1)
|
||||
result.appendCodePoint(currentCodepoint)
|
||||
} else if (Character.isUpperCase(currentCodepoint) && Character.isUpperCase(previousCodepoint)) {
|
||||
// InvalidCamelCase: XXYx (should have been xxYx)
|
||||
if (offset + Character.charCount(currentCodepoint) < normalizedFieldName.length) {
|
||||
val nextCodePoint = normalizedFieldName.codePointAt(offset + Character.charCount(currentCodepoint))
|
||||
if (Character.isLowerCase(nextCodePoint)) {
|
||||
result.append("_")
|
||||
}
|
||||
}
|
||||
result.appendCodePoint(currentCodepoint)
|
||||
} else if (currentCodepoint === '-'.code as Int? || currentCodepoint === '_'.code as Int?) {
|
||||
// Word-separator: x-x or x_x
|
||||
result.append("_")
|
||||
} else if (Character.isUpperCase(currentCodepoint) && !Character.isUpperCase(previousCodepoint) && Character.isLetterOrDigit(
|
||||
previousCodepoint
|
||||
)) {
|
||||
// camelCase: xX
|
||||
result.append("_")
|
||||
result.appendCodePoint(currentCodepoint)
|
||||
} else {
|
||||
// Unknown type
|
||||
result.appendCodePoint(currentCodepoint)
|
||||
}
|
||||
} else {
|
||||
// Only triggered for first code point
|
||||
result.appendCodePoint(currentCodepoint)
|
||||
}
|
||||
offset += Character.charCount(currentCodepoint)
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString().uppercase(locale)
|
||||
}
|
||||
}
|
77
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/FileGenerator.kt
vendored
Normal file
77
library/external/realmfieldnameshelper/src/main/kotlin/dk/ilios/realmfieldnames/FileGenerator.kt
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
package dk.ilios.realmfieldnames
|
||||
|
||||
import com.squareup.javapoet.FieldSpec
|
||||
import com.squareup.javapoet.JavaFile
|
||||
import com.squareup.javapoet.TypeSpec
|
||||
import java.io.IOException
|
||||
import javax.annotation.processing.Filer
|
||||
import javax.lang.model.element.Modifier
|
||||
|
||||
/**
|
||||
* Class responsible for creating the final output files.
|
||||
*/
|
||||
class FileGenerator(private val filer: Filer) {
|
||||
private val formatter: FieldNameFormatter
|
||||
|
||||
init {
|
||||
this.formatter = FieldNameFormatter()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates all the "<class>Fields" fields with field name references.
|
||||
* @param fileData Files to create.
|
||||
* *
|
||||
* @return `true` if the files where generated, `false` if not.
|
||||
*/
|
||||
fun generate(fileData: Set<ClassData>): Boolean {
|
||||
return fileData
|
||||
.filter { !it.libraryClass }
|
||||
.all { generateFile(it, fileData) }
|
||||
}
|
||||
|
||||
private fun generateFile(classData: ClassData, classPool: Set<ClassData>): Boolean {
|
||||
val fileBuilder = TypeSpec.classBuilder(classData.simpleClassName + "Fields")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
|
||||
.addJavadoc("This class enumerate all queryable fields in {@link \$L.\$L}\n",
|
||||
classData.packageName, classData.simpleClassName)
|
||||
|
||||
// Add a static field reference to each queryable field in the Realm model class
|
||||
classData.fields.forEach { fieldName, value ->
|
||||
if (value != null) {
|
||||
// Add linked field names (only up to depth 1)
|
||||
for (data in classPool) {
|
||||
if (data.qualifiedClassName == value) {
|
||||
val linkedTypeSpec = TypeSpec.classBuilder(formatter.format(fieldName))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
|
||||
val linkedClassFields = data.fields
|
||||
addField(linkedTypeSpec, "$", fieldName)
|
||||
for (linkedFieldName in linkedClassFields.keys) {
|
||||
addField(linkedTypeSpec, linkedFieldName, fieldName + "." + linkedFieldName)
|
||||
}
|
||||
fileBuilder.addType(linkedTypeSpec.build())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add normal field name
|
||||
addField(fileBuilder, fieldName, fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
val javaFile = JavaFile.builder(classData.packageName, fileBuilder.build()).build()
|
||||
try {
|
||||
javaFile.writeTo(filer)
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
// e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun addField(fileBuilder: TypeSpec.Builder, fieldName: String, fieldNameValue: String) {
|
||||
val field = FieldSpec.builder(String::class.java, formatter.format(fieldName))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("\$S", fieldNameValue)
|
||||
.build()
|
||||
fileBuilder.addField(field)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package dk.ilios.realmfieldnames
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor
|
||||
import javax.annotation.processing.Messager
|
||||
import javax.annotation.processing.ProcessingEnvironment
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.annotation.processing.SupportedAnnotationTypes
|
||||
import javax.lang.model.SourceVersion
|
||||
import javax.lang.model.element.Element
|
||||
import javax.lang.model.element.ElementKind
|
||||
import javax.lang.model.element.Modifier
|
||||
import javax.lang.model.element.PackageElement
|
||||
import javax.lang.model.element.TypeElement
|
||||
import javax.lang.model.element.VariableElement
|
||||
import javax.lang.model.type.DeclaredType
|
||||
import javax.lang.model.type.TypeMirror
|
||||
import javax.lang.model.util.Elements
|
||||
import javax.lang.model.util.Types
|
||||
import javax.tools.Diagnostic
|
||||
|
||||
/**
|
||||
* The Realm Field Names Generator is a processor that looks at all available Realm model classes
|
||||
* and create an companion class with easy, type-safe access to all field names.
|
||||
*/
|
||||
|
||||
@SupportedAnnotationTypes("io.realm.annotations.RealmClass")
|
||||
class RealmFieldNamesProcessor : AbstractProcessor() {
|
||||
|
||||
private val classes = HashSet<ClassData>()
|
||||
private lateinit var typeUtils: Types
|
||||
private lateinit var messager: Messager
|
||||
private lateinit var elementUtils: Elements
|
||||
private var ignoreAnnotation: TypeMirror? = null
|
||||
private var realmClassAnnotation: TypeElement? = null
|
||||
private var realmModelInterface: TypeMirror? = null
|
||||
private var realmListClass: DeclaredType? = null
|
||||
private var realmResultsClass: DeclaredType? = null
|
||||
private var fileGenerator: FileGenerator? = null
|
||||
private var done = false
|
||||
|
||||
@Synchronized
|
||||
override fun init(processingEnv: ProcessingEnvironment) {
|
||||
super.init(processingEnv)
|
||||
typeUtils = processingEnv.typeUtils!!
|
||||
messager = processingEnv.messager!!
|
||||
elementUtils = processingEnv.elementUtils!!
|
||||
|
||||
// If the Realm class isn't found something is wrong the project setup.
|
||||
// Most likely Realm isn't on the class path, so just disable the
|
||||
// annotation processor
|
||||
val isRealmAvailable = elementUtils.getTypeElement("io.realm.Realm") != null
|
||||
if (!isRealmAvailable) {
|
||||
done = true
|
||||
} else {
|
||||
ignoreAnnotation = elementUtils.getTypeElement("io.realm.annotations.Ignore")?.asType()
|
||||
realmClassAnnotation = elementUtils.getTypeElement("io.realm.annotations.RealmClass")
|
||||
realmModelInterface = elementUtils.getTypeElement("io.realm.RealmModel")?.asType()
|
||||
realmListClass = typeUtils.getDeclaredType(
|
||||
elementUtils.getTypeElement("io.realm.RealmList"),
|
||||
typeUtils.getWildcardType(null, null)
|
||||
)
|
||||
realmResultsClass = typeUtils.getDeclaredType(
|
||||
elementUtils.getTypeElement("io.realm.RealmResults"),
|
||||
typeUtils.getWildcardType(null, null)
|
||||
)
|
||||
fileGenerator = FileGenerator(processingEnv.filer)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSupportedSourceVersion(): SourceVersion {
|
||||
return SourceVersion.latestSupported()
|
||||
}
|
||||
|
||||
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
|
||||
if (done) {
|
||||
return CONSUME_ANNOTATIONS
|
||||
}
|
||||
|
||||
// Create all proxy classes
|
||||
roundEnv.getElementsAnnotatedWith(realmClassAnnotation).forEach { classElement ->
|
||||
if (typeUtils.isAssignable(classElement.asType(), realmModelInterface)) {
|
||||
val classData = processClass(classElement as TypeElement)
|
||||
classes.add(classData)
|
||||
}
|
||||
}
|
||||
|
||||
// If a model class references a library class, the library class will not be part of this
|
||||
// annotation processor round. For all those references we need to pull field information
|
||||
// from the classpath instead.
|
||||
val libraryClasses = HashMap<String, ClassData>()
|
||||
classes.forEach {
|
||||
it.fields.forEach { _, value ->
|
||||
// Analyze the library class file the first time it is encountered.
|
||||
if (value != null) {
|
||||
if (classes.all { it.qualifiedClassName != value } && !libraryClasses.containsKey(value)) {
|
||||
libraryClasses.put(value, processLibraryClass(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
classes.addAll(libraryClasses.values)
|
||||
|
||||
done = fileGenerator!!.generate(classes)
|
||||
return CONSUME_ANNOTATIONS
|
||||
}
|
||||
|
||||
private fun processClass(classElement: TypeElement): ClassData {
|
||||
val packageName = getPackageName(classElement)
|
||||
val className = classElement.simpleName.toString()
|
||||
val data = ClassData(packageName, className)
|
||||
|
||||
// Find all appropriate fields
|
||||
classElement.enclosedElements.forEach {
|
||||
val elementKind = it.kind
|
||||
if (elementKind == ElementKind.FIELD) {
|
||||
val variableElement = it as VariableElement
|
||||
|
||||
val modifiers = variableElement.modifiers
|
||||
if (modifiers.contains(Modifier.STATIC)) {
|
||||
return@forEach // completely ignore any static fields
|
||||
}
|
||||
|
||||
// Don't add any fields marked with @Ignore
|
||||
val ignoreField = variableElement.annotationMirrors
|
||||
.map { it.annotationType.toString() }
|
||||
.contains("io.realm.annotations.Ignore")
|
||||
|
||||
if (!ignoreField) {
|
||||
data.addField(it.getSimpleName().toString(), getLinkedFieldType(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
private fun processLibraryClass(qualifiedClassName: String): ClassData {
|
||||
val libraryClass = Class.forName(qualifiedClassName) // Library classes should be on the classpath
|
||||
val packageName = libraryClass.`package`.name
|
||||
val className = libraryClass.simpleName
|
||||
val data = ClassData(packageName, className, libraryClass = true)
|
||||
|
||||
libraryClass.declaredFields.forEach { field ->
|
||||
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
|
||||
return@forEach // completely ignore any static fields
|
||||
}
|
||||
|
||||
// Add field if it is not being ignored.
|
||||
if (field.annotations.all { it.toString() != "io.realm.annotations.Ignore" }) {
|
||||
data.addField(field.name, field.type.name)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the qualified name of the linked Realm class field or `null` if it is not a linked
|
||||
* class.
|
||||
*/
|
||||
private fun getLinkedFieldType(field: Element): String? {
|
||||
if (typeUtils.isAssignable(field.asType(), realmModelInterface)) {
|
||||
// Object link
|
||||
val typeElement = elementUtils.getTypeElement(field.asType().toString())
|
||||
return typeElement.qualifiedName.toString()
|
||||
} else if (typeUtils.isAssignable(field.asType(), realmListClass) || typeUtils.isAssignable(field.asType(), realmResultsClass)) {
|
||||
// List link or LinkingObjects
|
||||
val fieldType = field.asType()
|
||||
val typeArguments = (fieldType as DeclaredType).typeArguments
|
||||
if (typeArguments.size == 0) {
|
||||
return null
|
||||
}
|
||||
return typeArguments[0].toString()
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageName(classElement: TypeElement): String? {
|
||||
val enclosingElement = classElement.enclosingElement
|
||||
|
||||
if (enclosingElement.kind != ElementKind.PACKAGE) {
|
||||
messager.printMessage(
|
||||
Diagnostic.Kind.ERROR,
|
||||
"Could not determine the package name. Enclosing element was: " + enclosingElement.kind
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
val packageElement = enclosingElement as PackageElement
|
||||
return packageElement.qualifiedName.toString()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONSUME_ANNOTATIONS = false
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
dk.ilios.realmfieldnames.RealmFieldNamesProcessor,aggregating
|
|
@ -0,0 +1 @@
|
|||
dk.ilios.realmfieldnames.RealmFieldNamesProcessor
|
20
library/external/span/build.gradle
vendored
Normal file
20
library/external/span/build.gradle
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
namespace "me.gujun.android.span"
|
||||
compileSdk versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.support:support-annotations:28.0.0'
|
||||
}
|
316
library/external/span/src/main/kotlin/me/gujun/android/span/Span.kt
vendored
Normal file
316
library/external/span/src/main/kotlin/me/gujun/android/span/Span.kt
vendored
Normal file
|
@ -0,0 +1,316 @@
|
|||
package me.gujun.android.span
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Layout
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.TextUtils
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.text.style.AlignmentSpan
|
||||
import android.text.style.BackgroundColorSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.ImageSpan
|
||||
import android.text.style.QuoteSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.SubscriptSpan
|
||||
import android.text.style.SuperscriptSpan
|
||||
import android.text.style.TypefaceSpan
|
||||
import android.text.style.URLSpan
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Dimension
|
||||
import me.gujun.android.span.style.CustomTypefaceSpan
|
||||
import me.gujun.android.span.style.LineSpacingSpan
|
||||
import me.gujun.android.span.style.SimpleClickableSpan
|
||||
import me.gujun.android.span.style.TextDecorationLineSpan
|
||||
import me.gujun.android.span.style.VerticalPaddingSpan
|
||||
|
||||
class Span(val parent: Span? = null) : SpannableStringBuilder() {
|
||||
|
||||
companion object {
|
||||
val EMPTY_STYLE = Span()
|
||||
|
||||
var globalStyle: Span = EMPTY_STYLE
|
||||
}
|
||||
|
||||
var text: CharSequence = ""
|
||||
|
||||
@ColorInt var textColor: Int? = parent?.textColor
|
||||
|
||||
@ColorInt var backgroundColor: Int? = parent?.backgroundColor
|
||||
|
||||
@Dimension(unit = Dimension.PX) var textSize: Int? = parent?.textSize
|
||||
|
||||
var fontFamily: String? = parent?.fontFamily
|
||||
|
||||
var typeface: Typeface? = parent?.typeface
|
||||
|
||||
var textStyle: String? = parent?.textStyle
|
||||
|
||||
var alignment: String? = parent?.alignment
|
||||
|
||||
var textDecorationLine: String? = parent?.textDecorationLine
|
||||
|
||||
@Dimension(unit = Dimension.PX) var lineSpacing: Int? = null
|
||||
|
||||
@Dimension(unit = Dimension.PX) var paddingTop: Int? = null
|
||||
|
||||
@Dimension(unit = Dimension.PX) var paddingBottom: Int? = null
|
||||
|
||||
@Dimension(unit = Dimension.PX) var verticalPadding: Int? = null
|
||||
|
||||
var onClick: (() -> Unit)? = null
|
||||
|
||||
var spans: ArrayList<Any> = ArrayList()
|
||||
|
||||
var style: Span = EMPTY_STYLE
|
||||
|
||||
private fun buildCharacterStyle(builder: ArrayList<Any>) {
|
||||
if (textColor != null) {
|
||||
builder.add(ForegroundColorSpan(textColor!!))
|
||||
}
|
||||
|
||||
if (backgroundColor != null) {
|
||||
builder.add(BackgroundColorSpan(backgroundColor!!))
|
||||
}
|
||||
|
||||
if (textSize != null) {
|
||||
builder.add(AbsoluteSizeSpan(textSize!!))
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(fontFamily)) {
|
||||
builder.add(TypefaceSpan(fontFamily))
|
||||
}
|
||||
|
||||
if (typeface != null) {
|
||||
builder.add(CustomTypefaceSpan(typeface!!))
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(textStyle)) {
|
||||
builder.add(StyleSpan(when (textStyle) {
|
||||
"normal" -> Typeface.NORMAL
|
||||
"bold" -> Typeface.BOLD
|
||||
"italic" -> Typeface.ITALIC
|
||||
"bold_italic" -> Typeface.BOLD_ITALIC
|
||||
else -> throw RuntimeException("Unknown text style")
|
||||
}))
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(textDecorationLine)) {
|
||||
builder.add(TextDecorationLineSpan(textDecorationLine!!))
|
||||
}
|
||||
|
||||
if (onClick != null) {
|
||||
builder.add(object : SimpleClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
onClick?.invoke()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildParagraphStyle(builder: ArrayList<Any>) {
|
||||
if (!TextUtils.isEmpty(alignment)) {
|
||||
builder.add(AlignmentSpan.Standard(when (alignment) {
|
||||
"normal" -> Layout.Alignment.ALIGN_NORMAL
|
||||
"opposite" -> Layout.Alignment.ALIGN_OPPOSITE
|
||||
"center" -> Layout.Alignment.ALIGN_CENTER
|
||||
else -> throw RuntimeException("Unknown text alignment")
|
||||
}))
|
||||
}
|
||||
|
||||
if (lineSpacing != null) {
|
||||
builder.add(LineSpacingSpan(lineSpacing!!))
|
||||
}
|
||||
|
||||
paddingTop = when {
|
||||
paddingTop != null -> paddingTop
|
||||
verticalPadding != null -> verticalPadding
|
||||
else -> 0
|
||||
}
|
||||
paddingBottom = when {
|
||||
paddingBottom != null -> paddingBottom
|
||||
verticalPadding != null -> verticalPadding
|
||||
else -> 0
|
||||
}
|
||||
if (paddingTop != 0 || paddingBottom != 0) {
|
||||
builder.add(VerticalPaddingSpan(paddingTop!!, paddingBottom!!))
|
||||
}
|
||||
}
|
||||
|
||||
private fun prebuild() {
|
||||
override(style)
|
||||
}
|
||||
|
||||
fun build(): Span {
|
||||
prebuild()
|
||||
val builder = ArrayList<Any>()
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
var p = this.parent
|
||||
while (p != null) {
|
||||
if (!TextUtils.isEmpty(p.text)) {
|
||||
throw RuntimeException("Can't nest \"$text\" in spans")
|
||||
}
|
||||
p = p.parent
|
||||
}
|
||||
append(text)
|
||||
buildCharacterStyle(builder)
|
||||
buildParagraphStyle(builder)
|
||||
} else {
|
||||
buildParagraphStyle(builder)
|
||||
}
|
||||
|
||||
builder.addAll(spans)
|
||||
builder.forEach {
|
||||
setSpan(it, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun override(style: Span) {
|
||||
if (textColor == null) {
|
||||
textColor = style.textColor
|
||||
}
|
||||
if (backgroundColor == null) {
|
||||
backgroundColor = style.backgroundColor
|
||||
}
|
||||
if (textSize == null) {
|
||||
textSize = style.textSize
|
||||
}
|
||||
if (fontFamily == null) {
|
||||
fontFamily = style.fontFamily
|
||||
}
|
||||
if (typeface == null) {
|
||||
typeface = style.typeface
|
||||
}
|
||||
if (textStyle == null) {
|
||||
textStyle = style.textStyle
|
||||
}
|
||||
if (alignment == null) {
|
||||
alignment = style.alignment
|
||||
}
|
||||
if (textDecorationLine == null) {
|
||||
textDecorationLine = style.textDecorationLine
|
||||
}
|
||||
if (lineSpacing == null) {
|
||||
lineSpacing = style.lineSpacing
|
||||
}
|
||||
if (paddingTop == null) {
|
||||
paddingTop = style.paddingTop
|
||||
}
|
||||
if (paddingBottom == null) {
|
||||
paddingBottom = style.paddingBottom
|
||||
}
|
||||
if (verticalPadding == null) {
|
||||
verticalPadding = style.verticalPadding
|
||||
}
|
||||
if (onClick == null) {
|
||||
onClick = style.onClick
|
||||
}
|
||||
spans.addAll(style.spans)
|
||||
}
|
||||
|
||||
operator fun CharSequence.unaryPlus(): CharSequence {
|
||||
return append(Span(parent = this@Span).apply {
|
||||
text = this@unaryPlus
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
operator fun Span.plus(other: CharSequence): CharSequence {
|
||||
return append(Span(parent = this).apply {
|
||||
text = other
|
||||
build()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun span(init: Span.() -> Unit): Span = Span().apply {
|
||||
override(Span.globalStyle)
|
||||
init()
|
||||
build()
|
||||
}
|
||||
|
||||
fun span(text: CharSequence, init: Span.() -> Unit): Span = Span().apply {
|
||||
override(Span.globalStyle)
|
||||
this.text = text
|
||||
init()
|
||||
build()
|
||||
}
|
||||
|
||||
fun style(init: Span.() -> Unit): Span = Span().apply {
|
||||
init()
|
||||
}
|
||||
|
||||
fun Span.span(init: Span.() -> Unit = {}): Span = apply {
|
||||
append(Span(parent = this).apply {
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.span(text: CharSequence, init: Span.() -> Unit = {}): Span = apply {
|
||||
append(Span(parent = this).apply {
|
||||
this.text = text
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.link(url: String, text: CharSequence = "",
|
||||
init: Span.() -> Unit = {}): Span = apply {
|
||||
append(Span(parent = this).apply {
|
||||
this.text = text
|
||||
this.spans.add(URLSpan(url))
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.quote(@ColorInt color: Int, text: CharSequence = "",
|
||||
init: Span.() -> Unit = {}): Span = apply {
|
||||
append(Span(parent = this).apply {
|
||||
this.text = text
|
||||
this.spans.add(QuoteSpan(color))
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.superscript(text: CharSequence = "", init: Span.() -> Unit = {}): Span = apply {
|
||||
append(Span(parent = this).apply {
|
||||
this.text = text
|
||||
this.spans.add(SuperscriptSpan())
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.subscript(text: CharSequence = "", init: Span.() -> Unit = {}): Span = apply {
|
||||
append(Span(parent = this).apply {
|
||||
this.text = text
|
||||
this.spans.add(SubscriptSpan())
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.image(drawable: Drawable, alignment: String = "bottom",
|
||||
init: Span.() -> Unit = {}): Span = apply {
|
||||
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
append(Span(parent = this).apply {
|
||||
this.text = " "
|
||||
this.spans.add(ImageSpan(drawable, when (alignment) {
|
||||
"bottom" -> ImageSpan.ALIGN_BOTTOM
|
||||
"baseline" -> ImageSpan.ALIGN_BASELINE
|
||||
else -> throw RuntimeException("Unknown image alignment")
|
||||
}))
|
||||
init()
|
||||
build()
|
||||
})
|
||||
}
|
||||
|
||||
fun Span.addSpan(what: Any) = apply {
|
||||
this.spans.add(what)
|
||||
}
|
36
library/external/span/src/main/kotlin/me/gujun/android/span/style/CustomTypefaceSpan.kt
vendored
Normal file
36
library/external/span/src/main/kotlin/me/gujun/android/span/style/CustomTypefaceSpan.kt
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package me.gujun.android.span.style
|
||||
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextPaint
|
||||
import android.text.style.MetricAffectingSpan
|
||||
|
||||
class CustomTypefaceSpan(private val tf: Typeface) : MetricAffectingSpan() {
|
||||
|
||||
override fun updateMeasureState(paint: TextPaint) {
|
||||
apply(paint, tf)
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
apply(ds, tf)
|
||||
}
|
||||
|
||||
private fun apply(paint: Paint, tf: Typeface) {
|
||||
val oldStyle: Int
|
||||
|
||||
val old = paint.typeface
|
||||
oldStyle = old?.style ?: 0
|
||||
|
||||
val fake = oldStyle and tf.style.inv()
|
||||
|
||||
if (fake and Typeface.BOLD != 0) {
|
||||
paint.isFakeBoldText = true
|
||||
}
|
||||
|
||||
if (fake and Typeface.ITALIC != 0) {
|
||||
paint.textSkewX = -0.25f
|
||||
}
|
||||
|
||||
paint.typeface = tf
|
||||
}
|
||||
}
|
31
library/external/span/src/main/kotlin/me/gujun/android/span/style/LineSpacingSpan.kt
vendored
Normal file
31
library/external/span/src/main/kotlin/me/gujun/android/span/style/LineSpacingSpan.kt
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
package me.gujun.android.span.style
|
||||
|
||||
import android.graphics.Paint.FontMetricsInt
|
||||
import android.text.Spanned
|
||||
import android.text.style.LineHeightSpan
|
||||
|
||||
class LineSpacingSpan(private val add: Int) : LineHeightSpan {
|
||||
|
||||
override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int,
|
||||
fm: FontMetricsInt) {
|
||||
text as Spanned
|
||||
/*val spanStart =*/ text.getSpanStart(this)
|
||||
val spanEnd = text.getSpanEnd(this)
|
||||
|
||||
// Log.d("DEBUG", "Text: start=$start end=$end v=$v") // end may include the \n character
|
||||
// Log.d("DEBUG", "${text.slice(start until end)}".replace("\n", "#"))
|
||||
// Log.d("DEBUG", "LineSpacingSpan: spanStart=$spanStart spanEnd=$spanEnd spanstartv=$spanstartv")
|
||||
// Log.d("DEBUG", "$fm")
|
||||
// Log.d("DEBUG", "-----------------------")
|
||||
|
||||
if (spanstartv == v) {
|
||||
fm.descent += add
|
||||
} else if (text[start - 1] == '\n') {
|
||||
fm.descent += add
|
||||
}
|
||||
|
||||
if (end == spanEnd || end - 1 == spanEnd) {
|
||||
fm.descent -= add
|
||||
}
|
||||
}
|
||||
}
|
10
library/external/span/src/main/kotlin/me/gujun/android/span/style/SimpleClickableSpan.kt
vendored
Normal file
10
library/external/span/src/main/kotlin/me/gujun/android/span/style/SimpleClickableSpan.kt
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
package me.gujun.android.span.style
|
||||
|
||||
import android.text.TextPaint
|
||||
import android.text.style.ClickableSpan
|
||||
|
||||
abstract class SimpleClickableSpan : ClickableSpan() {
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
// no-op
|
||||
}
|
||||
}
|
29
library/external/span/src/main/kotlin/me/gujun/android/span/style/TextDecorationLineSpan.kt
vendored
Normal file
29
library/external/span/src/main/kotlin/me/gujun/android/span/style/TextDecorationLineSpan.kt
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package me.gujun.android.span.style
|
||||
|
||||
import android.text.TextPaint
|
||||
import android.text.style.CharacterStyle
|
||||
|
||||
class TextDecorationLineSpan(private val textDecorationLine: String) : CharacterStyle() {
|
||||
|
||||
override fun updateDrawState(tp: TextPaint) {
|
||||
when (textDecorationLine) {
|
||||
"none" -> {
|
||||
tp.isUnderlineText = false
|
||||
tp.isStrikeThruText = false
|
||||
}
|
||||
"underline" -> {
|
||||
tp.isUnderlineText = true
|
||||
tp.isStrikeThruText = false
|
||||
}
|
||||
"line-through" -> {
|
||||
tp.isUnderlineText = false
|
||||
tp.isStrikeThruText = true
|
||||
}
|
||||
"underline line-through" -> {
|
||||
tp.isUnderlineText = true
|
||||
tp.isStrikeThruText = true
|
||||
}
|
||||
else -> throw RuntimeException("Unknown text decoration line")
|
||||
}
|
||||
}
|
||||
}
|
41
library/external/span/src/main/kotlin/me/gujun/android/span/style/VerticalPaddingSpan.kt
vendored
Normal file
41
library/external/span/src/main/kotlin/me/gujun/android/span/style/VerticalPaddingSpan.kt
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
package me.gujun.android.span.style
|
||||
|
||||
import android.graphics.Paint.FontMetricsInt
|
||||
import android.text.Spanned
|
||||
import android.text.style.LineHeightSpan
|
||||
|
||||
class VerticalPaddingSpan(private val paddingTop: Int,
|
||||
private val paddingBottom: Int) : LineHeightSpan {
|
||||
|
||||
private var flag: Boolean = true
|
||||
|
||||
override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int,
|
||||
fm: FontMetricsInt) {
|
||||
text as Spanned
|
||||
/*val spanStart =*/ text.getSpanStart(this)
|
||||
val spanEnd = text.getSpanEnd(this)
|
||||
|
||||
// Log.d("DEBUG", "Text: start=$start end=$end v=$v") // end may include the \n character
|
||||
// Log.d("DEBUG", "${text.slice(start until end)}".replace("\n", "#"))
|
||||
// Log.d("DEBUG", "VerticalPadding: spanStart=$spanStart spanEnd=$spanEnd spanstartv=$spanstartv")
|
||||
// Log.d("DEBUG", "$fm")
|
||||
// Log.d("DEBUG", "-----------------------")
|
||||
|
||||
if (spanstartv == v) {
|
||||
fm.top -= paddingTop
|
||||
fm.ascent -= paddingTop
|
||||
flag = true
|
||||
} else if (flag && text[start - 1] != '\n') {
|
||||
fm.top += paddingTop
|
||||
fm.ascent += paddingTop
|
||||
flag = false
|
||||
} else {
|
||||
flag = false
|
||||
}
|
||||
|
||||
if (end == spanEnd || end - 1 == spanEnd) {
|
||||
fm.descent += paddingBottom
|
||||
fm.bottom += paddingBottom
|
||||
}
|
||||
}
|
||||
}
|
25
library/external/textdrawable/build.gradle
vendored
Normal file
25
library/external/textdrawable/build.gradle
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
namespace "com.amulyakhare.textdrawable"
|
||||
|
||||
compileSdk versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk versions.minSdk
|
||||
targetSdk versions.targetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.sourceCompat
|
||||
targetCompatibility versions.targetCompat
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.findAll { it.name.startsWith("lint") }.each {
|
||||
it.enabled = false
|
||||
}
|
||||
}
|
316
library/external/textdrawable/src/main/java/com/amulyakhare/textdrawable/TextDrawable.java
vendored
Normal file
316
library/external/textdrawable/src/main/java/com/amulyakhare/textdrawable/TextDrawable.java
vendored
Normal file
|
@ -0,0 +1,316 @@
|
|||
package com.amulyakhare.textdrawable;
|
||||
|
||||
import android.graphics.*;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.OvalShape;
|
||||
import android.graphics.drawable.shapes.RectShape;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
|
||||
/**
|
||||
* @author amulya
|
||||
* @datetime 14 Oct 2014, 3:53 PM
|
||||
*/
|
||||
public class TextDrawable extends ShapeDrawable {
|
||||
|
||||
private final Paint textPaint;
|
||||
private final Paint borderPaint;
|
||||
private static final float SHADE_FACTOR = 0.9f;
|
||||
private final String text;
|
||||
private final int color;
|
||||
private final RectShape shape;
|
||||
private final int height;
|
||||
private final int width;
|
||||
private final int fontSize;
|
||||
private final float radius;
|
||||
private final int borderThickness;
|
||||
|
||||
private TextDrawable(Builder builder) {
|
||||
super(builder.shape);
|
||||
|
||||
// shape properties
|
||||
shape = builder.shape;
|
||||
height = builder.height;
|
||||
width = builder.width;
|
||||
radius = builder.radius;
|
||||
|
||||
// text and color
|
||||
text = builder.toUpperCase ? builder.text.toUpperCase() : builder.text;
|
||||
color = builder.color;
|
||||
|
||||
// text paint settings
|
||||
fontSize = builder.fontSize;
|
||||
textPaint = new Paint();
|
||||
textPaint.setColor(builder.textColor);
|
||||
textPaint.setAntiAlias(true);
|
||||
textPaint.setFakeBoldText(builder.isBold);
|
||||
textPaint.setStyle(Paint.Style.FILL);
|
||||
textPaint.setTypeface(builder.font);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
textPaint.setStrokeWidth(builder.borderThickness);
|
||||
|
||||
// border paint settings
|
||||
borderThickness = builder.borderThickness;
|
||||
borderPaint = new Paint();
|
||||
borderPaint.setColor(getDarkerShade(color));
|
||||
borderPaint.setStyle(Paint.Style.STROKE);
|
||||
borderPaint.setStrokeWidth(borderThickness);
|
||||
|
||||
// drawable paint color
|
||||
Paint paint = getPaint();
|
||||
paint.setColor(color);
|
||||
|
||||
}
|
||||
|
||||
private int getDarkerShade(int color) {
|
||||
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
|
||||
(int)(SHADE_FACTOR * Color.green(color)),
|
||||
(int)(SHADE_FACTOR * Color.blue(color)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
Rect r = getBounds();
|
||||
|
||||
|
||||
// draw border
|
||||
if (borderThickness > 0) {
|
||||
drawBorder(canvas);
|
||||
}
|
||||
|
||||
int count = canvas.save();
|
||||
canvas.translate(r.left, r.top);
|
||||
|
||||
// draw text
|
||||
int width = this.width < 0 ? r.width() : this.width;
|
||||
int height = this.height < 0 ? r.height() : this.height;
|
||||
int fontSize = this.fontSize < 0 ? (Math.min(width, height) / 2) : this.fontSize;
|
||||
textPaint.setTextSize(fontSize);
|
||||
canvas.drawText(text, width / 2, height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2), textPaint);
|
||||
|
||||
canvas.restoreToCount(count);
|
||||
|
||||
}
|
||||
|
||||
private void drawBorder(Canvas canvas) {
|
||||
RectF rect = new RectF(getBounds());
|
||||
rect.inset(borderThickness/2, borderThickness/2);
|
||||
|
||||
if (shape instanceof OvalShape) {
|
||||
canvas.drawOval(rect, borderPaint);
|
||||
}
|
||||
else if (shape instanceof RoundRectShape) {
|
||||
canvas.drawRoundRect(rect, radius, radius, borderPaint);
|
||||
}
|
||||
else {
|
||||
canvas.drawRect(rect, borderPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
textPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
textPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public static IShapeBuilder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder implements IConfigBuilder, IShapeBuilder, IBuilder {
|
||||
|
||||
private String text;
|
||||
|
||||
private int color;
|
||||
|
||||
private int borderThickness;
|
||||
|
||||
private int width;
|
||||
|
||||
private int height;
|
||||
|
||||
private Typeface font;
|
||||
|
||||
private RectShape shape;
|
||||
|
||||
public int textColor;
|
||||
|
||||
private int fontSize;
|
||||
|
||||
private boolean isBold;
|
||||
|
||||
private boolean toUpperCase;
|
||||
|
||||
public float radius;
|
||||
|
||||
private Builder() {
|
||||
text = "";
|
||||
color = Color.GRAY;
|
||||
textColor = Color.WHITE;
|
||||
borderThickness = 0;
|
||||
width = -1;
|
||||
height = -1;
|
||||
shape = new RectShape();
|
||||
font = Typeface.create("sans-serif-light", Typeface.NORMAL);
|
||||
fontSize = -1;
|
||||
isBold = false;
|
||||
toUpperCase = false;
|
||||
}
|
||||
|
||||
public IConfigBuilder width(int width) {
|
||||
this.width = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder height(int height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder textColor(int color) {
|
||||
this.textColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder withBorder(int thickness) {
|
||||
this.borderThickness = thickness;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder useFont(Typeface font) {
|
||||
this.font = font;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder fontSize(int size) {
|
||||
this.fontSize = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder bold() {
|
||||
this.isBold = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IConfigBuilder toUpperCase() {
|
||||
this.toUpperCase = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IConfigBuilder beginConfig() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShapeBuilder endConfig() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBuilder rect() {
|
||||
this.shape = new RectShape();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBuilder round() {
|
||||
this.shape = new OvalShape();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBuilder roundRect(int radius) {
|
||||
this.radius = radius;
|
||||
float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};
|
||||
this.shape = new RoundRectShape(radii, null, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextDrawable buildRect(String text, int color) {
|
||||
rect();
|
||||
return build(text, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextDrawable buildRoundRect(String text, int color, int radius) {
|
||||
roundRect(radius);
|
||||
return build(text, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextDrawable buildRound(String text, int color) {
|
||||
round();
|
||||
return build(text, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextDrawable build(String text, int color) {
|
||||
this.color = color;
|
||||
this.text = text;
|
||||
return new TextDrawable(this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IConfigBuilder {
|
||||
public IConfigBuilder width(int width);
|
||||
|
||||
public IConfigBuilder height(int height);
|
||||
|
||||
public IConfigBuilder textColor(int color);
|
||||
|
||||
public IConfigBuilder withBorder(int thickness);
|
||||
|
||||
public IConfigBuilder useFont(Typeface font);
|
||||
|
||||
public IConfigBuilder fontSize(int size);
|
||||
|
||||
public IConfigBuilder bold();
|
||||
|
||||
public IConfigBuilder toUpperCase();
|
||||
|
||||
public IShapeBuilder endConfig();
|
||||
}
|
||||
|
||||
public static interface IBuilder {
|
||||
|
||||
public TextDrawable build(String text, int color);
|
||||
}
|
||||
|
||||
public static interface IShapeBuilder {
|
||||
|
||||
public IConfigBuilder beginConfig();
|
||||
|
||||
public IBuilder rect();
|
||||
|
||||
public IBuilder round();
|
||||
|
||||
public IBuilder roundRect(int radius);
|
||||
|
||||
public TextDrawable buildRect(String text, int color);
|
||||
|
||||
public TextDrawable buildRoundRect(String text, int color, int radius);
|
||||
|
||||
public TextDrawable buildRound(String text, int color);
|
||||
}
|
||||
}
|
|
@ -189,7 +189,7 @@ dependencies {
|
|||
// Database
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
|
||||
|
||||
kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
|
||||
kapt project(":library:external:realmfieldnameshelper")
|
||||
|
||||
// Shared Preferences
|
||||
implementation libs.androidx.preferenceKtx
|
||||
|
|
|
@ -11,6 +11,12 @@ include ':library:multipicker'
|
|||
include ':library:external:jsonviewer'
|
||||
include ':library:external:diff-match-patch'
|
||||
include ':library:external:dialpad'
|
||||
include ':library:external:textdrawable'
|
||||
include ':library:external:autocomplete'
|
||||
include ':library:external:realmfieldnameshelper'
|
||||
include ':library:external:span'
|
||||
include ':library:external:barcodescanner:core'
|
||||
include ':library:external:barcodescanner:zxing'
|
||||
|
||||
include ':library:rustCrypto'
|
||||
include ':matrix-sdk-android'
|
||||
|
|
|
@ -396,6 +396,7 @@ dependencies {
|
|||
implementation project(':vector')
|
||||
implementation project(':vector-config')
|
||||
implementation project(':library:core-utils')
|
||||
debugImplementation project(':library:external:span')
|
||||
debugImplementation project(':library:ui-styles')
|
||||
implementation libs.dagger.hilt
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
|
|
|
@ -116,6 +116,8 @@ dependencies {
|
|||
implementation project(":matrix-sdk-android-flow")
|
||||
implementation project(":library:external:jsonviewer")
|
||||
implementation project(":library:external:diff-match-patch")
|
||||
implementation project(":library:external:textdrawable")
|
||||
implementation project(":library:external:autocomplete")
|
||||
implementation project(":library:ui-strings")
|
||||
implementation project(":library:ui-styles")
|
||||
implementation project(":library:core-utils")
|
||||
|
@ -184,11 +186,8 @@ dependencies {
|
|||
api libs.androidx.preferenceKtx
|
||||
|
||||
// UI
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
implementation libs.google.material
|
||||
api('me.gujun.android:span:1.7') {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
}
|
||||
implementation project(":library:external:span")
|
||||
implementation libs.markwon.core
|
||||
implementation libs.markwon.extLatex
|
||||
implementation libs.markwon.imageGlide
|
||||
|
@ -210,8 +209,6 @@ dependencies {
|
|||
// Alerter
|
||||
implementation 'com.github.tapadoo:alerter:7.2.4'
|
||||
|
||||
implementation 'com.otaliastudios:autocomplete:1.1.0'
|
||||
|
||||
// Shake detection
|
||||
implementation 'com.squareup:seismic:1.0.3'
|
||||
|
||||
|
@ -266,11 +263,7 @@ dependencies {
|
|||
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
|
||||
implementation 'com.google.zxing:core:3.3.3'
|
||||
|
||||
// Excludes the legacy support library annotation usages
|
||||
// https://github.com/dm77/barcodescanner/blob/d036996c8a6f36a68843ffe539c834c28944b2d5/core/src/main/java/me/dm7/barcodescanner/core/CameraWrapper.java#L4
|
||||
implementation ('me.dm7.barcodescanner:zxing:1.9.13') {
|
||||
exclude group: 'com.android.support', module: 'support-v4'
|
||||
}
|
||||
implementation project(":library:external:barcodescanner:zxing")
|
||||
|
||||
// Emoji Keyboard
|
||||
api libs.vanniktech.emojiMaterial
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.otaliastudios.autocomplete.AutocompletePresenter
|
||||
|
||||
abstract class RecyclerViewPresenter<T>(context: Context?) : AutocompletePresenter<T>(context) {
|
||||
abstract class RecyclerViewPresenter<T : Any>(context: Context) : AutocompletePresenter<T>(context) {
|
||||
|
||||
private var recyclerView: RecyclerView? = null
|
||||
private var clicks: ClickProvider<T>? = null
|
||||
|
|
|
@ -32,16 +32,15 @@ class CommandAutocompletePolicy @Inject constructor() : AutocompletePolicy {
|
|||
return ""
|
||||
}
|
||||
|
||||
override fun onDismiss(text: Spannable?) {
|
||||
override fun onDismiss(text: Spannable) {
|
||||
}
|
||||
|
||||
// Only if text which starts with '/' and without space
|
||||
override fun shouldShowPopup(text: Spannable?, cursorPos: Int): Boolean {
|
||||
return enabled && text?.startsWith("/") == true &&
|
||||
!text.contains(" ")
|
||||
override fun shouldShowPopup(text: Spannable, cursorPos: Int): Boolean {
|
||||
return enabled && text.startsWith("/") && !text.contains(" ")
|
||||
}
|
||||
|
||||
override fun shouldDismissPopup(text: Spannable?, cursorPos: Int): Boolean {
|
||||
override fun shouldDismissPopup(text: Spannable, cursorPos: Int): Boolean {
|
||||
return !shouldShowPopup(text, cursorPos)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue