From 053cbdf43e596f51eb4b093d70a19aeb0206abe5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 2 Jul 2018 20:06:47 +0200 Subject: [PATCH] include MaterialChipsInput as subdir lib --- OpenKeychain/build.gradle | 2 +- .../layout/encrypt_asymmetric_fragment.xml | 1 - extern/MaterialChipsInput/.gitignore | 8 + extern/MaterialChipsInput/build.gradle | 36 ++ extern/MaterialChipsInput/proguard-rules.pro | 25 + .../ExampleInstrumentedTest.java | 26 ++ .../src/main/AndroidManifest.xml | 4 + .../com/pchmn/materialchips/ChipView.java | 399 ++++++++++++++++ .../com/pchmn/materialchips/ChipsInput.java | 427 ++++++++++++++++++ .../materialchips/adapter/ChipsAdapter.java | 384 ++++++++++++++++ .../adapter/FilterableAdapter.java | 253 +++++++++++ .../com/pchmn/materialchips/model/Chip.java | 39 ++ .../materialchips/model/ChipInterface.java | 12 + .../materialchips/util/ActivityUtil.java | 20 + .../pchmn/materialchips/util/ColorUtil.java | 38 ++ .../util/LetterTileProvider.java | 221 +++++++++ .../materialchips/util/MyWindowCallback.java | 173 +++++++ .../pchmn/materialchips/util/ViewUtil.java | 81 ++++ .../views/ChipsInputEditText.java | 31 ++ .../materialchips/views/DetailedChipView.java | 233 ++++++++++ .../views/FilterableListView.java | 156 +++++++ .../views/ScrollViewMaxHeight.java | 49 ++ .../res/drawable-v21/ripple_chip_view.xml | 11 + .../src/main/res/drawable/bg_chip_view.xml | 10 + .../main/res/drawable/bg_chip_view_opened.xml | 10 + .../main/res/drawable/ic_cancel_grey_24dp.xml | 9 + .../res/drawable/ic_cancel_white_24dp.xml | 9 + .../drawable/ic_person_outline_white_24dp.xml | 9 + .../res/drawable/ic_person_white_24dp.xml | 9 + .../main/res/drawable/ripple_chip_view.xml | 10 + .../src/main/res/layout/chip_view.xml | 41 ++ .../src/main/res/layout/chips_input.xml | 17 + .../main/res/layout/detailed_chip_view.xml | 75 +++ .../main/res/layout/item_list_filterable.xml | 42 ++ .../main/res/layout/list_filterable_view.xml | 13 + .../src/main/res/values/attrs.xml | 34 ++ .../src/main/res/values/colors.xml | 10 + .../src/main/res/values/strings.xml | 20 + .../pchmn/materialchips/ExampleUnitTest.java | 17 + settings.gradle | 1 + 40 files changed, 2963 insertions(+), 2 deletions(-) create mode 100644 extern/MaterialChipsInput/.gitignore create mode 100644 extern/MaterialChipsInput/build.gradle create mode 100644 extern/MaterialChipsInput/proguard-rules.pro create mode 100644 extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java create mode 100644 extern/MaterialChipsInput/src/main/AndroidManifest.xml create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java create mode 100644 extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java create mode 100644 extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml create mode 100644 extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/chips_input.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml create mode 100644 extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml create mode 100644 extern/MaterialChipsInput/src/main/res/values/attrs.xml create mode 100644 extern/MaterialChipsInput/src/main/res/values/colors.xml create mode 100644 extern/MaterialChipsInput/src/main/res/values/strings.xml create mode 100644 extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 94d9cffbf..36d4afdb9 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -28,7 +28,6 @@ dependencies { // UI compile 'org.sufficientlysecure:html-textview:3.1' - compile 'com.github.sikeeoh:MaterialChipsInput:1.1.1' compile 'com.jpardogo.materialtabstrip:library:1.1.1' compile 'com.getbase:floatingactionbutton:1.10.1' compile 'com.nispok:snackbar:2.11.0' @@ -61,6 +60,7 @@ dependencies { implementation project(':extern:minidns') implementation project(':KeybaseLib') implementation project(':safeslinger-exchange') + implementation project(':extern:MaterialChipsInput') implementation "android.arch.work:work-runtime:1.0.0-alpha02" diff --git a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml index b191c1b80..1ff410f43 100644 --- a/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml +++ b/OpenKeychain/src/main/res/layout/encrypt_asymmetric_fragment.xml @@ -46,7 +46,6 @@ android:paddingLeft="8dp" android:paddingRight="8dp" app:hint="@string/label_to" - app:chip_hasAvatarIcon="false" app:maxRows="2" app:chip_detailed_backgroundColor="@color/colorChipViewBackground" /> diff --git a/extern/MaterialChipsInput/.gitignore b/extern/MaterialChipsInput/.gitignore new file mode 100644 index 000000000..09b993d06 --- /dev/null +++ b/extern/MaterialChipsInput/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/extern/MaterialChipsInput/build.gradle b/extern/MaterialChipsInput/build.gradle new file mode 100644 index 000000000..d6a67320a --- /dev/null +++ b/extern/MaterialChipsInput/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 27 + buildToolsVersion "27.0.3" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 27 + versionCode 114 + versionName "1.1.4" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:27.1.0' + testCompile 'junit:junit:4.12' + + // recycler + compile 'com.android.support:recyclerview-v7:27.1.0' + compile 'com.beloo.widget:ChipsLayoutManager:0.3.7@aar' +} + diff --git a/extern/MaterialChipsInput/proguard-rules.pro b/extern/MaterialChipsInput/proguard-rules.pro new file mode 100644 index 000000000..0ae68584d --- /dev/null +++ b/extern/MaterialChipsInput/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/couleurwhatever/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java b/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java new file mode 100644 index 000000000..a28c3f326 --- /dev/null +++ b/extern/MaterialChipsInput/src/androidTest/java/com/pchmn/materialchips/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.pchmn.materialchips; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.pchmn.library.test", appContext.getPackageName()); + } +} diff --git a/extern/MaterialChipsInput/src/main/AndroidManifest.xml b/extern/MaterialChipsInput/src/main/AndroidManifest.xml new file mode 100644 index 000000000..ce7e64f72 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java new file mode 100644 index 000000000..7b57353e2 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipView.java @@ -0,0 +1,399 @@ +package com.pchmn.materialchips; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.support.annotation.ColorInt; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.LetterTileProvider; +import com.pchmn.materialchips.util.ViewUtil; + +public class ChipView extends RelativeLayout { + + private static final String TAG = ChipView.class.toString(); + // context + private Context mContext; + // xml elements + private LinearLayout mContentLayout; + private TextView mLabelTextView; + private ImageButton mDeleteButton; + // attributes + private static final int NONE = -1; + private String mLabel; + private ColorStateList mLabelColor; + private boolean mDeletable = false; + private Drawable mDeleteIcon; + private ColorStateList mDeleteIconColor; + private ColorStateList mBackgroundColor; + // letter tile provider + private LetterTileProvider mLetterTileProvider; + // chip + private ChipInterface mChip; + + public ChipView(Context context) { + super(context); + mContext = context; + init(null); + } + + public ChipView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(attrs); + } + + /** + * Inflate the view according to attributes + * + * @param attrs the attributes + */ + private void init(AttributeSet attrs) { + // inflate layout + View rootView = inflate(getContext(), R.layout.chip_view, this); + + mContentLayout = (LinearLayout) rootView.findViewById(R.id.content); + mLabelTextView = (TextView) rootView.findViewById(R.id.label); + mDeleteButton = (ImageButton) rootView.findViewById(R.id.delete_button); + + // letter tile provider + mLetterTileProvider = new LetterTileProvider(mContext); + + // attributes + if (attrs != null) { + TypedArray a = mContext.getTheme().obtainStyledAttributes( + attrs, + R.styleable.ChipView, + 0, 0); + + try { + // label + mLabel = a.getString(R.styleable.ChipView_label); + mLabelColor = a.getColorStateList(R.styleable.ChipView_labelColor); + mDeletable = a.getBoolean(R.styleable.ChipView_deletable, false); + mDeleteIconColor = a.getColorStateList(R.styleable.ChipView_deleteIconColor); + int deleteIconId = a.getResourceId(R.styleable.ChipView_deleteIcon, NONE); + if (deleteIconId != NONE) + mDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId); + // background color + mBackgroundColor = a.getColorStateList(R.styleable.ChipView_backgroundColor); + } finally { + a.recycle(); + } + } + + // inflate + inflateWithAttributes(); + } + + /** + * Inflate the view + */ + private void inflateWithAttributes() { + // label + setLabel(mLabel); + if (mLabelColor != null) + setLabelColor(mLabelColor); + + // delete button + setDeletable(mDeletable); + + // background color + if (mBackgroundColor != null) + setChipBackgroundColor(mBackgroundColor); + } + + public void inflate(ChipInterface chip) { + mChip = chip; + // label + mLabel = mChip.getLabel(); + // inflate + inflateWithAttributes(); + } + + /** + * Get label + * + * @return the label + */ + public String getLabel() { + return mLabel; + } + + /** + * Set label + * + * @param label the label to set + */ + public void setLabel(String label) { + mLabel = label; + mLabelTextView.setText(label); + } + + /** + * Set label color + * + * @param color the color to set + */ + public void setLabelColor(ColorStateList color) { + mLabelColor = color; + mLabelTextView.setTextColor(color); + } + + /** + * Set label color + * + * @param color the color to set + */ + public void setLabelColor(@ColorInt int color) { + mLabelColor = ColorStateList.valueOf(color); + mLabelTextView.setTextColor(color); + } + +// /** +// * Show or hide avatar icon +// * +// * @param hasAvatarIcon true to show, false to hide +// */ +// public void setHasAvatarIcon(boolean hasAvatarIcon) { +// mHasAvatarIcon = hasAvatarIcon; +// +// if(!mHasAvatarIcon) { +// // hide icon +// mAvatarIconImageView.setVisibility(GONE); +// // adjust padding +// if(mDeleteButton.getVisibility() == VISIBLE) +// mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0); +// else +// mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0); +// +// } +// else { +// // show icon +// mAvatarIconImageView.setVisibility(VISIBLE); +// // adjust padding +// if(mDeleteButton.getVisibility() == VISIBLE) +// mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, 0, 0); +// else +// mLabelTextView.setPadding(ViewUtil.dpToPx(8), 0, ViewUtil.dpToPx(12), 0); +// +// // set icon +// if(mAvatarIconUri != null) +// mAvatarIconImageView.setImageURI(mAvatarIconUri); +// else if(mAvatarIconDrawable != null) +// mAvatarIconImageView.setImageDrawable(mAvatarIconDrawable); +// else +// mAvatarIconImageView.setImageBitmap(mLetterTileProvider.getLetterTile(getLabel())); +// } +// } + +// /** +// * Set avatar icon +// * +// * @param avatarIcon the icon to set +// */ +// public void setAvatarIcon(Drawable avatarIcon) { +// mAvatarIconDrawable = avatarIcon; +// mHasAvatarIcon = true; +// inflateWithAttributes(); +// } + +// /** +// * Set avatar icon +// * +// * @param avatarUri the uri of the icon to set +// */ +// public void setAvatarIcon(Uri avatarUri) { +// mAvatarIconUri = avatarUri; +// mHasAvatarIcon = true; +// inflateWithAttributes(); +// } + + /** + * Show or hide delte button + * + * @param deletable true to show, false to hide + */ + public void setDeletable(boolean deletable) { + mDeletable = deletable; + if (!mDeletable) { + // hide delete icon + mDeleteButton.setVisibility(GONE); + // adjust padding + mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, ViewUtil.dpToPx(12), 0); + } else { + // show icon + mDeleteButton.setVisibility(VISIBLE); + // adjust padding + mLabelTextView.setPadding(ViewUtil.dpToPx(12), 0, 0, 0); + + // set icon + if (mDeleteIcon != null) + mDeleteButton.setImageDrawable(mDeleteIcon); + if (mDeleteIconColor != null) + mDeleteButton.getDrawable().mutate().setColorFilter(mDeleteIconColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + } + } + + /** + * Set delete icon color + * + * @param color the color to set + */ + public void setDeleteIconColor(ColorStateList color) { + mDeleteIconColor = color; + mDeletable = true; + inflateWithAttributes(); + } + + /** + * Set delete icon color + * + * @param color the color to set + */ + public void setDeleteIconColor(@ColorInt int color) { + mDeleteIconColor = ColorStateList.valueOf(color); + mDeletable = true; + inflateWithAttributes(); + } + + /** + * Set delete icon + * + * @param deleteIcon the icon to set + */ + public void setDeleteIcon(Drawable deleteIcon) { + mDeleteIcon = deleteIcon; + mDeletable = true; + inflateWithAttributes(); + } + + /** + * Set background color + * + * @param color the color to set + */ + public void setChipBackgroundColor(ColorStateList color) { + mBackgroundColor = color; + setChipBackgroundColor(color.getDefaultColor()); + } + + /** + * Set background color + * + * @param color the color to set + */ + public void setChipBackgroundColor(@ColorInt int color) { + mBackgroundColor = ColorStateList.valueOf(color); + mContentLayout.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + + /** + * Set the chip object + * + * @param chip the chip + */ + public void setChip(ChipInterface chip) { + mChip = chip; + } + + /** + * Set OnClickListener on the delete button + * + * @param onClickListener the OnClickListener + */ + public void setOnDeleteClicked(OnClickListener onClickListener) { + mDeleteButton.setOnClickListener(onClickListener); + } + + /** + * Set OnclickListener on the entire chip + * + * @param onClickListener the OnClickListener + */ + public void setOnChipClicked(OnClickListener onClickListener) { + mContentLayout.setOnClickListener(onClickListener); + } + + /** + * Builder class + */ + public static class Builder { + private Context context; + private String label; + private ColorStateList labelColor; + private boolean deletable = false; + private Drawable deleteIcon; + private ColorStateList deleteIconColor; + private ColorStateList backgroundColor; + private ChipInterface chip; + + public Builder(Context context) { + this.context = context; + } + + public Builder label(String label) { + this.label = label; + return this; + } + + public Builder labelColor(ColorStateList labelColor) { + this.labelColor = labelColor; + return this; + } + + public Builder deletable(boolean deletable) { + this.deletable = deletable; + return this; + } + + public Builder deleteIcon(Drawable deleteIcon) { + this.deleteIcon = deleteIcon; + return this; + } + + public Builder deleteIconColor(ColorStateList deleteIconColor) { + this.deleteIconColor = deleteIconColor; + return this; + } + + public Builder backgroundColor(ColorStateList backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public Builder chip(ChipInterface chip) { + this.chip = chip; + this.label = chip.getLabel(); + return this; + } + + public ChipView build() { + return newInstance(this); + } + } + + private static ChipView newInstance(Builder builder) { + ChipView chipView = new ChipView(builder.context); + chipView.mLabel = builder.label; + chipView.mLabelColor = builder.labelColor; + chipView.mDeletable = builder.deletable; + chipView.mDeleteIcon = builder.deleteIcon; + chipView.mDeleteIconColor = builder.deleteIconColor; + chipView.mBackgroundColor = builder.backgroundColor; + chipView.mChip = builder.chip; + chipView.inflateWithAttributes(); + + return chipView; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java new file mode 100644 index 000000000..d1e7ad18d --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/ChipsInput.java @@ -0,0 +1,427 @@ +package com.pchmn.materialchips; + + +import android.app.Activity; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.beloo.widget.chipslayoutmanager.ChipsLayoutManager; +import com.pchmn.materialchips.adapter.ChipsAdapter; +import com.pchmn.materialchips.model.Chip; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ActivityUtil; +import com.pchmn.materialchips.util.MyWindowCallback; +import com.pchmn.materialchips.util.ViewUtil; +import com.pchmn.materialchips.views.ChipsInputEditText; +import com.pchmn.materialchips.views.DetailedChipView; +import com.pchmn.materialchips.views.FilterableListView; +import com.pchmn.materialchips.views.ScrollViewMaxHeight; + +import java.util.ArrayList; +import java.util.List; + +public class ChipsInput extends ScrollViewMaxHeight { + + private static final String TAG = ChipsInput.class.toString(); + // context + private Context mContext; + // xml element + private RecyclerView mRecyclerView; + // adapter + private ChipsAdapter mChipsAdapter; + // attributes + private static final int NONE = -1; + private String mHint; + private ColorStateList mHintColor; + private ColorStateList mTextColor; + private int mMaxRows = 2; + private ColorStateList mChipLabelColor; + private boolean mChipDeletable = false; + private Drawable mChipDeleteIcon; + private ColorStateList mChipDeleteIconColor; + private ColorStateList mChipBackgroundColor; + private boolean mShowChipDetailed = true; + private ColorStateList mChipDetailedTextColor; + private ColorStateList mChipDetailedDeleteIconColor; + private ColorStateList mChipDetailedBackgroundColor; + private ColorStateList mFilterableListBackgroundColor; + private ColorStateList mFilterableListTextColor; + // chips listener + private List mChipsListenerList = new ArrayList<>(); + private ChipsListener mChipsListener; + // chip list + private List mFilterableChipList; + private FilterableListView mFilterableListView; + // chip validator + private ChipValidator mChipValidator; + private ViewGroup filterableListLayout; + private ChipsInputEditText mEditText; + + public ChipsInput(Context context) { + super(context); + mContext = context; + init(null); + } + + public ChipsInput(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(attrs); + } + + /** + * Inflate the view according to attributes + * + * @param attrs the attributes + */ + private void init(AttributeSet attrs) { + // inflate filterableListLayout + View rootView = inflate(getContext(), R.layout.chips_input, this); + + mRecyclerView = (RecyclerView) rootView.findViewById(R.id.chips_recycler); + + initEditText(); + + // attributes + if (attrs != null) { + TypedArray a = mContext.getTheme().obtainStyledAttributes( + attrs, + R.styleable.ChipsInput, + 0, 0); + + try { + // hint + mHint = a.getString(R.styleable.ChipsInput_hint); + mHintColor = a.getColorStateList(R.styleable.ChipsInput_hintColor); + mTextColor = a.getColorStateList(R.styleable.ChipsInput_textColor); + mMaxRows = a.getInteger(R.styleable.ChipsInput_maxRows, 2); + setMaxHeight(ViewUtil.dpToPx((40 * mMaxRows) + 8)); + //setVerticalScrollBarEnabled(true); + // chip label color + mChipLabelColor = a.getColorStateList(R.styleable.ChipsInput_chip_labelColor); + // chip delete icon + mChipDeletable = a.getBoolean(R.styleable.ChipsInput_chip_deletable, false); + mChipDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_deleteIconColor); + int deleteIconId = a.getResourceId(R.styleable.ChipsInput_chip_deleteIcon, NONE); + if (deleteIconId != NONE) + mChipDeleteIcon = ContextCompat.getDrawable(mContext, deleteIconId); + // chip background color + mChipBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_backgroundColor); + // show chip detailed + mShowChipDetailed = a.getBoolean(R.styleable.ChipsInput_showChipDetailed, true); + // chip detailed text color + mChipDetailedTextColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_textColor); + mChipDetailedBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_backgroundColor); + mChipDetailedDeleteIconColor = a.getColorStateList(R.styleable.ChipsInput_chip_detailed_deleteIconColor); + // filterable list + mFilterableListBackgroundColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_backgroundColor); + mFilterableListTextColor = a.getColorStateList(R.styleable.ChipsInput_filterable_list_textColor); + } finally { + a.recycle(); + } + } + + // adapter + mChipsAdapter = new ChipsAdapter(mContext, this, mEditText, mRecyclerView); + ChipsLayoutManager chipsLayoutManager = ChipsLayoutManager.newBuilder(mContext) + .setOrientation(ChipsLayoutManager.HORIZONTAL) + .build(); + mRecyclerView.setLayoutManager(chipsLayoutManager); + mRecyclerView.setNestedScrollingEnabled(false); + mRecyclerView.setAdapter(mChipsAdapter); + + // set window callback + // will hide DetailedOpenView and hide keyboard on touch outside + Activity activity = ActivityUtil.scanForActivity(mContext); + if (activity == null) + throw new ClassCastException("android.view.Context cannot be cast to android.app.Activity"); + + android.view.Window.Callback mCallBack = (activity).getWindow().getCallback(); + activity.getWindow().setCallback(new MyWindowCallback(mCallBack, activity)); + } + + private void initEditText() { + mEditText = new ChipsInputEditText(mContext); + if (mHintColor != null) + mEditText.setHintTextColor(mHintColor); + if (mTextColor != null) + mEditText.setTextColor(mTextColor); + + mEditText.setLayoutParams(new RelativeLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + mEditText.setHint(mHint); + mEditText.setBackgroundResource(android.R.color.transparent); + // prevent fullscreen on landscape + mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); + mEditText.setPrivateImeOptions("nm"); + // no suggestion + mEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + + // handle back space + mEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + // backspace + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + // remove last chip + if (mEditText.getText().toString().length() == 0) + mChipsAdapter.removeLastChip(); + } + return false; + } + }); + + mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if ((actionId == EditorInfo.IME_ACTION_DONE) || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { + ChipsInput.this.onActionDone(mEditText.getText().toString()); + } + return false; + } + }); + + // text changed + mEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + ChipsInput.this.onTextChanged(s); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + public void addChips(List chipList) { + mChipsAdapter.addChipsProgrammatically(chipList); + } + + public void addChip(ChipInterface chip) { + mChipsAdapter.addChip(chip); + } + + public void addChip(Object id, String label, String info) { + Chip chip = new Chip(id, label, info); + mChipsAdapter.addChip(chip); + } + + public void addChip(String label, String info) { + Chip chip = new Chip(label, info); + mChipsAdapter.addChip(chip); + } + + public void removeChip(ChipInterface chip) { + mChipsAdapter.removeChip(chip); + } + + public void removeChipById(Object id) { + mChipsAdapter.removeChipById(id); + } + + public void removeChipByLabel(String label) { + mChipsAdapter.removeChipByLabel(label); + } + + public void removeChipByInfo(String info) { + mChipsAdapter.removeChipByInfo(info); + } + + public ChipView getChipView() { + int padding = ViewUtil.dpToPx(4); + ChipView chipView = new ChipView.Builder(mContext) + .labelColor(mChipLabelColor) + .deletable(mChipDeletable) + .deleteIcon(mChipDeleteIcon) + .deleteIconColor(mChipDeleteIconColor) + .backgroundColor(mChipBackgroundColor) + .build(); + + chipView.setPadding(padding, padding, padding, padding); + + return chipView; + } + + public ChipsInputEditText getEditText() { + return mChipsAdapter.getmEditText(); + } + + public DetailedChipView getDetailedChipView(ChipInterface chip) { + return new DetailedChipView.Builder(mContext) + .chip(chip) + .textColor(mChipDetailedTextColor) + .backgroundColor(mChipDetailedBackgroundColor) + .deleteIconColor(mChipDetailedDeleteIconColor) + .build(); + } + + public void addChipsListener(ChipsListener chipsListener) { + mChipsListenerList.add(chipsListener); + mChipsListener = chipsListener; + } + + public void onChipAdded(ChipInterface chip, int size) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onChipAdded(chip, size); + } + } + + public void onChipRemoved(ChipInterface chip, int size) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onChipRemoved(chip, size); + } + } + + public void onTextChanged(CharSequence text) { + if (mChipsListener != null) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onTextChanged(text); + } + // show filterable list + if (mFilterableListView != null) { + if (text.length() > 0) + mFilterableListView.filterList(text); + else + mFilterableListView.fadeOut(); + } + } + } + + public void onActionDone(CharSequence text) { + if (mChipsListener != null) { + for (ChipsListener chipsListener : mChipsListenerList) { + chipsListener.onActionDone(text); + } + } + } + + public List getSelectedChipList() { + return mChipsAdapter.getChipList(); + } + + public String getHint() { + return mHint; + } + + public void setHint(String mHint) { + this.mHint = mHint; + } + + public void setHintColor(ColorStateList mHintColor) { + this.mHintColor = mHintColor; + } + + public void setTextColor(ColorStateList mTextColor) { + this.mTextColor = mTextColor; + } + + public ChipsInput setMaxRows(int mMaxRows) { + this.mMaxRows = mMaxRows; + return this; + } + + public void setChipLabelColor(ColorStateList mLabelColor) { + this.mChipLabelColor = mLabelColor; + } + + public void setChipDeletable(boolean mDeletable) { + this.mChipDeletable = mDeletable; + } + + public void setChipDeleteIcon(Drawable mDeleteIcon) { + this.mChipDeleteIcon = mDeleteIcon; + } + + public void setChipDeleteIconColor(ColorStateList mDeleteIconColor) { + this.mChipDeleteIconColor = mDeleteIconColor; + } + + public void setChipBackgroundColor(ColorStateList mBackgroundColor) { + this.mChipBackgroundColor = mBackgroundColor; + } + + public ChipsInput setShowChipDetailed(boolean mShowChipDetailed) { + this.mShowChipDetailed = mShowChipDetailed; + return this; + } + + public boolean isShowChipDetailed() { + return mShowChipDetailed; + } + + public void setChipDetailedTextColor(ColorStateList mChipDetailedTextColor) { + this.mChipDetailedTextColor = mChipDetailedTextColor; + } + + public void setChipDetailedDeleteIconColor(ColorStateList mChipDetailedDeleteIconColor) { + this.mChipDetailedDeleteIconColor = mChipDetailedDeleteIconColor; + } + + public void setChipDetailedBackgroundColor(ColorStateList mChipDetailedBackgroundColor) { + this.mChipDetailedBackgroundColor = mChipDetailedBackgroundColor; + } + + public void setFilterableListLayout(ViewGroup layout) { + this.filterableListLayout = layout; + } + + public void setFilterableList(List list) { + mFilterableChipList = list; + if (filterableListLayout != null) { + mFilterableListView = new FilterableListView(mContext, filterableListLayout); + } else { + mFilterableListView = new FilterableListView(mContext); + } + mFilterableListView.build(mFilterableChipList, this, mFilterableListBackgroundColor, mFilterableListTextColor); + mChipsAdapter.setFilterableListView(mFilterableListView); + } + + public List getFilterableList() { + return mFilterableChipList; + } + + public ChipValidator getChipValidator() { + return mChipValidator; + } + + public void setChipValidator(ChipValidator mChipValidator) { + this.mChipValidator = mChipValidator; + } + + public interface ChipsListener { + void onChipAdded(ChipInterface chip, int newSize); + + void onChipRemoved(ChipInterface chip, int newSize); + + void onTextChanged(CharSequence text); + + void onActionDone(CharSequence text); + } + + public interface ChipValidator { + boolean areEquals(ChipInterface chip1, ChipInterface chip2); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java new file mode 100644 index 000000000..64d8216a1 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/ChipsAdapter.java @@ -0,0 +1,384 @@ +package com.pchmn.materialchips.adapter; + +import android.content.Context; +import android.os.Build; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.EditText; +import android.widget.RelativeLayout; + +import com.pchmn.materialchips.ChipView; +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ViewUtil; +import com.pchmn.materialchips.views.ChipsInputEditText; +import com.pchmn.materialchips.views.DetailedChipView; +import com.pchmn.materialchips.views.FilterableListView; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +public class ChipsAdapter extends RecyclerView.Adapter { + + private static final int TYPE_EDIT_TEXT = 0; + private static final int TYPE_ITEM = 1; + private Context mContext; + private ChipsInput mChipsInput; + private List mChipList = new ArrayList<>(); + private String mHintLabel; + + private ChipsInputEditText mEditText; + + private RecyclerView mRecycler; + + public ChipsAdapter(Context context, ChipsInput chipsInput, RecyclerView recycler) { + mContext = context; + mChipsInput = chipsInput; + mRecycler = recycler; + mHintLabel = mChipsInput.getHint(); + } + + public ChipsAdapter(Context mContext, ChipsInput chipsInput, ChipsInputEditText mEditText, RecyclerView mRecyclerView) { + this(mContext, chipsInput, mRecyclerView); + this.mEditText = mEditText; + } + + private class ItemViewHolder extends RecyclerView.ViewHolder { + + private final ChipView chipView; + + ItemViewHolder(View view) { + super(view); + chipView = (ChipView) view; + } + + } + private class EditTextViewHolder extends RecyclerView.ViewHolder { + + private final EditText editText; + + EditTextViewHolder(View view) { + super(view); + editText = (EditText) view; + } + + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == TYPE_EDIT_TEXT) { + return new EditTextViewHolder(mEditText); + } else { + return new ItemViewHolder(mChipsInput.getChipView()); + } + + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + // edit text + if (position == mChipList.size()) { + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + + // auto fit edit text + autofitEditText(); + } + // chip + else if (getItemCount() > 1) { + ItemViewHolder itemViewHolder = (ItemViewHolder) holder; + itemViewHolder.chipView.inflate(getItem(position)); + // handle click + handleClickOnEditText(itemViewHolder.chipView, position); + } + } + + @Override + public int getItemCount() { + return mChipList.size() + 1; + } + + private ChipInterface getItem(int position) { + return mChipList.get(position); + } + + @Override + public int getItemViewType(int position) { + if (position == mChipList.size()) { + return TYPE_EDIT_TEXT; + } + + return TYPE_ITEM; + } + + @Override + public long getItemId(int position) { + return mChipList.get(position).hashCode(); + } + + private void autofitEditText() { + // min width of edit text = 50 dp + ViewGroup.LayoutParams params = mEditText.getLayoutParams(); + params.width = ViewUtil.dpToPx(50); + mEditText.setLayoutParams(params); + + // listen to change in the tree + mEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + + @Override + public void onGlobalLayout() { + // get right of recycler and left of edit text + int right = mRecycler.getRight(); + int left = mEditText.getLeft(); + + // edit text will fill the space + ViewGroup.LayoutParams params = mEditText.getLayoutParams(); + params.width = right - left - ViewUtil.dpToPx(8); + mEditText.setLayoutParams(params); + + // request focus + mEditText.requestFocus(); + + // remove the listener: + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mEditText.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mEditText.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + } + + }); + } + + private void handleClickOnEditText(ChipView chipView, final int position) { + // delete chip + chipView.setOnDeleteClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeChip(position); + } + }); + + // show detailed chip + if (mChipsInput.isShowChipDetailed()) { + chipView.setOnChipClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + // get chip position + int[] coord = new int[2]; + v.getLocationInWindow(coord); + + final DetailedChipView detailedChipView = mChipsInput.getDetailedChipView(getItem(position)); + setDetailedChipViewPosition(detailedChipView, coord); + + // delete button + detailedChipView.setOnDeleteClicked(new View.OnClickListener() { + @Override + public void onClick(View v) { + removeChip(position); + detailedChipView.fadeOut(); + } + }); + } + }); + } + } + + private void setDetailedChipViewPosition(DetailedChipView detailedChipView, int[] coord) { + // window width + ViewGroup rootView = (ViewGroup) mRecycler.getRootView(); + int windowWidth = ViewUtil.getWindowWidth(mContext); + + // chip size + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + ViewUtil.dpToPx(300), + ViewUtil.dpToPx(100)); + + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + // align left window + if (coord[0] <= 0) { + layoutParams.leftMargin = 0; + layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13); + detailedChipView.alignLeft(); + } + // align right + else if (coord[0] + ViewUtil.dpToPx(300) > windowWidth + ViewUtil.dpToPx(13)) { + layoutParams.leftMargin = windowWidth - ViewUtil.dpToPx(300); + layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13); + detailedChipView.alignRight(); + } + // same position as chip + else { + layoutParams.leftMargin = coord[0] - ViewUtil.dpToPx(13); + layoutParams.topMargin = coord[1] - ViewUtil.dpToPx(13); + } + + // show view + rootView.addView(detailedChipView, layoutParams); + detailedChipView.fadeIn(); + } + + public void setFilterableListView(FilterableListView filterableListView) { + if (mEditText != null) { + mEditText.setFilterableListView(filterableListView); + } + } + + public void addChipsProgrammatically(List chipList) { + if (chipList != null) { + if (chipList.size() > 0) { + int chipsBeforeAdding = getItemCount(); + for (ChipInterface chip : chipList) { + mChipList.add(chip); + mChipsInput.onChipAdded(chip, getItemCount()); + } + + // hide hint + mEditText.setHint(null); + // reset text + mEditText.setText(null); + + notifyItemRangeChanged(chipsBeforeAdding, chipList.size()); + } + } + } + + public void addChip(ChipInterface chip) { + if (!listContains(mChipList, chip)) { + mChipList.add(chip); + // notify listener + mChipsInput.onChipAdded(chip, mChipList.size()); + // hide hint + mEditText.setHint(null); + // reset text + mEditText.setText(null); + // refresh data + notifyItemInserted(mChipList.size()); + } + } + + public void removeChip(ChipInterface chip) { + int position = mChipList.indexOf(chip); + mChipList.remove(position); + // notify listener + notifyItemRangeChanged(position, getItemCount()); + mChipsInput.onChipRemoved(chip, mChipList.size()); + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChip(int position) { + ChipInterface chip = mChipList.get(position); + // remove contact + mChipList.remove(position); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChipById(Object id) { + for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { + ChipInterface chip = iter.next(); + if (chip.getId() != null && chip.getId().equals(id)) { + // remove chip + iter.remove(); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + } + } + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChipByLabel(String label) { + for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { + ChipInterface chip = iter.next(); + if (chip.getLabel().equals(label)) { + // remove chip + iter.remove(); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + } + } + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeChipByInfo(String info) { + for (Iterator iter = mChipList.listIterator(); iter.hasNext(); ) { + ChipInterface chip = iter.next(); + if (chip.getInfo() != null && chip.getInfo().equals(info)) { + // remove chip + iter.remove(); + // notify listener + mChipsInput.onChipRemoved(chip, mChipList.size()); + } + } + // if 0 chip + if (mChipList.size() == 0) { + mEditText.setHint(mHintLabel); + } + // refresh data + notifyDataSetChanged(); + } + + public void removeLastChip() { + if (mChipList.size() > 0) { + removeChip(mChipList.get(mChipList.size() - 1)); + } + } + + public List getChipList() { + return mChipList; + } + + private boolean listContains(List contactList, ChipInterface chip) { + + if (mChipsInput.getChipValidator() != null) { + for (ChipInterface item : contactList) { + if (mChipsInput.getChipValidator().areEquals(item, chip)) { + return true; + } + } + } else { + for (ChipInterface item : contactList) { + if (chip.getId() != null && chip.getId().equals(item.getId())) { + return true; + } + if (chip.getLabel().equals(item.getLabel())) { + return true; + } + } + } + + return false; + } + + public ChipsInputEditText getmEditText() { + return mEditText; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java new file mode 100644 index 000000000..2895a4252 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/adapter/FilterableAdapter.java @@ -0,0 +1,253 @@ +package com.pchmn.materialchips.adapter; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.TextView; + +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ColorUtil; +import com.pchmn.materialchips.util.LetterTileProvider; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import static android.view.View.GONE; + +public class FilterableAdapter extends RecyclerView.Adapter implements Filterable { + + private static final String TAG = FilterableAdapter.class.toString(); + // context + private Context mContext; + // list + private List mOriginalList = new ArrayList<>(); + private List mChipList = new ArrayList<>(); + private List mFilteredList = new ArrayList<>(); + private ChipFilter mFilter; + private ChipsInput mChipsInput; + private LetterTileProvider mLetterTileProvider; + private ColorStateList mBackgroundColor; + private ColorStateList mTextColor; + // recycler + private RecyclerView mRecyclerView; + // sort + private Comparator mComparator; + private Collator mCollator; + + + public FilterableAdapter(Context context, + RecyclerView recyclerView, + List chipList, + ChipsInput chipsInput, + ColorStateList backgroundColor, + ColorStateList textColor) { + mContext = context; + mRecyclerView = recyclerView; + mCollator = Collator.getInstance(Locale.getDefault()); + mCollator.setStrength(Collator.PRIMARY); + mComparator = new Comparator() { + @Override + public int compare(ChipInterface o1, ChipInterface o2) { + return mCollator.compare(o1.getLabel(), o2.getLabel()); + } + }; + // remove chips that do not have label + Iterator iterator = chipList.iterator(); + while (iterator.hasNext()) { + if (iterator.next().getLabel() == null) + iterator.remove(); + } + sortList(chipList); + mOriginalList.addAll(chipList); + mChipList.addAll(chipList); + mFilteredList.addAll(chipList); + mLetterTileProvider = new LetterTileProvider(mContext); + mBackgroundColor = backgroundColor; + mTextColor = textColor; + mChipsInput = chipsInput; + + mChipsInput.addChipsListener(new ChipsInput.ChipsListener() { + @Override + public void onChipAdded(ChipInterface chip, int newSize) { + removeChip(chip); + } + + @Override + public void onChipRemoved(ChipInterface chip, int newSize) { + addChip(chip); + } + + @Override + public void onTextChanged(CharSequence text) { + mRecyclerView.scrollToPosition(0); + } + + @Override + public void onActionDone(CharSequence text) { + mRecyclerView.scrollToPosition(0); + } + }); + } + + private class ItemViewHolder extends RecyclerView.ViewHolder { + + private TextView mLabel; + private TextView mInfo; + + ItemViewHolder(View view) { + super(view); + mLabel = (TextView) view.findViewById(R.id.label); + mInfo = (TextView) view.findViewById(R.id.info); + } + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(mContext).inflate(R.layout.item_list_filterable, parent, false); + return new ItemViewHolder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ItemViewHolder itemViewHolder = (ItemViewHolder) holder; + final ChipInterface chip = getItem(position); + + // label + itemViewHolder.mLabel.setText(chip.getLabel()); + + // info + if (chip.getInfo() != null) { + itemViewHolder.mInfo.setVisibility(View.VISIBLE); + itemViewHolder.mInfo.setText(chip.getInfo()); + } else { + itemViewHolder.mInfo.setVisibility(GONE); + } + + // colors + if (mBackgroundColor != null) + itemViewHolder.itemView.getBackground().setColorFilter(mBackgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + if (mTextColor != null) { + itemViewHolder.mLabel.setTextColor(mTextColor); + itemViewHolder.mInfo.setTextColor(ColorUtil.alpha(mTextColor.getDefaultColor(), 150)); + } + + // onclick + itemViewHolder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mChipsInput != null) + mChipsInput.addChip(chip); + } + }); + } + + @Override + public int getItemCount() { + return mFilteredList.size(); + } + + private ChipInterface getItem(int position) { + return mFilteredList.get(position); + } + + @Override + public Filter getFilter() { + if (mFilter == null) + mFilter = new ChipFilter(this, mChipList); + return mFilter; + } + + private class ChipFilter extends Filter { + + private FilterableAdapter adapter; + private List originalList; + private List filteredList; + + public ChipFilter(FilterableAdapter adapter, List originalList) { + super(); + this.adapter = adapter; + this.originalList = originalList; + this.filteredList = new ArrayList<>(); + } + + @Override + protected FilterResults performFiltering(CharSequence constraint) { + filteredList.clear(); + FilterResults results = new FilterResults(); + if (constraint.length() == 0) { + filteredList.addAll(originalList); + } else { + final String filterPattern = constraint.toString().toLowerCase().trim(); + for (ChipInterface chip : originalList) { + if (chip.getLabel().toLowerCase().contains(filterPattern)) { + filteredList.add(chip); + } else if (chip.getInfo() != null && chip.getInfo().toLowerCase().replaceAll("\\s", "").contains(filterPattern)) { + filteredList.add(chip); + } + } + } + + results.values = filteredList; + results.count = filteredList.size(); + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mFilteredList.clear(); + mFilteredList.addAll((ArrayList) results.values); + notifyDataSetChanged(); + } + } + + private void removeChip(ChipInterface chip) { + int position = mFilteredList.indexOf(chip); + if (position >= 0) + mFilteredList.remove(position); + + position = mChipList.indexOf(chip); + if (position >= 0) + mChipList.remove(position); + + notifyDataSetChanged(); + } + + private void addChip(ChipInterface chip) { + if (contains(chip)) { + mChipList.add(chip); + mFilteredList.add(chip); + // sort original list + sortList(mChipList); + // sort filtered list + sortList(mFilteredList); + + notifyDataSetChanged(); + } + } + + private boolean contains(ChipInterface chip) { + for (ChipInterface item : mOriginalList) { + if (item.equals(chip)) + return true; + } + return false; + } + + private void sortList(List list) { + Collections.sort(list, mComparator); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java new file mode 100644 index 000000000..e79c79cd9 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/Chip.java @@ -0,0 +1,39 @@ +package com.pchmn.materialchips.model; + + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class Chip implements ChipInterface { + + private Object id; + private String label; + private String info; + + public Chip(@NonNull Object id, @NonNull String label, @Nullable String info) { + this.id = id; + this.label = label; + this.info = info; + } + + + public Chip(@NonNull String label, @Nullable String info) { + this.label = label; + this.info = info; + } + + @Override + public Object getId() { + return id; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public String getInfo() { + return info; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java new file mode 100644 index 000000000..804ef47f1 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/model/ChipInterface.java @@ -0,0 +1,12 @@ +package com.pchmn.materialchips.model; + + +import android.graphics.drawable.Drawable; +import android.net.Uri; + +public interface ChipInterface { + + Object getId(); + String getLabel(); + String getInfo(); +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java new file mode 100644 index 000000000..c119fad07 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ActivityUtil.java @@ -0,0 +1,20 @@ +package com.pchmn.materialchips.util; + + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; + +public class ActivityUtil { + + public static Activity scanForActivity(Context context) { + if (context == null) + return null; + else if (context instanceof Activity) + return (Activity)context; + else if (context instanceof ContextWrapper) + return scanForActivity(((ContextWrapper)context).getBaseContext()); + + return null; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java new file mode 100644 index 000000000..c1b118c1c --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ColorUtil.java @@ -0,0 +1,38 @@ +package com.pchmn.materialchips.util; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.util.TypedValue; + +import com.pchmn.materialchips.R; + +public class ColorUtil { + + public static int lighter(int color, float factor) { + int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255); + int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255); + int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255); + return Color.argb(Color.alpha(color), red, green, blue); + } + + public static int lighter(ColorStateList color, float factor) { + return lighter(color.getDefaultColor(), factor); + } + + public static int alpha(int color, int alpha) { + return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); + } + + public static boolean isColorDark(int color){ + double darkness = 1 - (0.2126*Color.red(color) + 0.7152*Color.green(color) + 0.0722*Color.blue(color))/255; + return darkness >= 0.5; + } + + public static int getThemeAccentColor (final Context context) { + final TypedValue value = new TypedValue (); + context.getTheme ().resolveAttribute (R.attr.colorAccent, value, true); + return value.data; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java new file mode 100644 index 000000000..9028a5286 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/LetterTileProvider.java @@ -0,0 +1,221 @@ +package com.pchmn.materialchips.util; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; +import android.text.TextPaint; +import android.util.Log; + +import com.pchmn.materialchips.R; + +/** + * Used to create a {@link Bitmap} that contains a letter used in the English + * alphabet or digit, if there is no letter or digit available, a default image + * is shown instead + */ +public class LetterTileProvider { + + /** The number of available tile colors (see R.array.letter_tile_colors) */ + private static final int NUM_OF_TILE_COLORS = 8; + + /** The {@link TextPaint} used to draw the letter onto the tile */ + private final TextPaint mPaint = new TextPaint(); + /** The bounds that enclose the letter */ + private final Rect mBounds = new Rect(); + /** The {@link Canvas} to draw on */ + private final Canvas mCanvas = new Canvas(); + /** The first char of the name being displayed */ + private final char[] mFirstChar = new char[1]; + + /** The background colors of the tile */ + private final TypedArray mColors; + /** The font size used to display the letter */ + private final int mTileLetterFontSize; + /** The default image to display */ + private final Bitmap mDefaultBitmap; + + /** Width */ + private final int mWidth; + /** Height */ + private final int mHeight; + + /** + * Constructor for LetterTileProvider + * + * @param context The {@link Context} to use + */ + public LetterTileProvider(Context context) { + final Resources res = context.getResources(); + + mPaint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + mPaint.setColor(Color.WHITE); + mPaint.setTextAlign(Paint.Align.CENTER); + mPaint.setAntiAlias(true); + + mColors = res.obtainTypedArray(R.array.letter_tile_colors); + mTileLetterFontSize = res.getDimensionPixelSize(R.dimen.tile_letter_font_size); + + //mDefaultBitmap = BitmapFactory.decodeResource(res, android.R.drawable.); + mDefaultBitmap = drawableToBitmap(ContextCompat.getDrawable(context, R.drawable.ic_person_white_24dp)); + mWidth = res.getDimensionPixelSize(R.dimen.letter_tile_size); + mHeight = res.getDimensionPixelSize(R.dimen.letter_tile_size); + } + + /** + * @param displayName The name used to create the letter for the tile + * @return A {@link Bitmap} that contains a letter used in the English + * alphabet or digit, if there is no letter or digit available, a + * default image is shown instead + */ + public Bitmap getLetterTile(String displayName) { + // workaround + if(displayName == null || displayName.length() == 0) + return null; + + final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); + + final char firstChar = displayName.charAt(0); + + final Canvas c = mCanvas; + c.setBitmap(bitmap); + c.drawColor(pickColor(displayName)); + + if (isLetterOrDigit(firstChar)) { + mFirstChar[0] = Character.toUpperCase(firstChar); + mPaint.setTextSize(mTileLetterFontSize); + mPaint.getTextBounds(mFirstChar, 0, 1, mBounds); + c.drawText(mFirstChar, 0, 1, mWidth / 2, mHeight / 2 + + (mBounds.bottom - mBounds.top) / 2, mPaint); + } + else { + // (32 - 24) / 2 = 4 + c.drawBitmap(mDefaultBitmap, ViewUtil.dpToPx(4), ViewUtil.dpToPx(4), null); + } + return bitmap; + } + + /** + * @param displayName The name used to create the letter for the tile + * @return A circular {@link Bitmap} that contains a letter used in the English + * alphabet or digit, if there is no letter or digit available, a + * default image is shown instead + */ + public Bitmap getCircularLetterTile(String displayName) { + // workaround + if(displayName == null || displayName.length() == 0) + displayName = "."; + + final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); + final char firstChar = displayName.charAt(0); + + final Canvas c = mCanvas; + c.setBitmap(bitmap); + c.drawColor(pickColor(displayName)); + + if (isLetterOrDigit(firstChar)) { + mFirstChar[0] = Character.toUpperCase(firstChar); + mPaint.setTextSize(mTileLetterFontSize); + mPaint.getTextBounds(mFirstChar, 0, 1, mBounds); + c.drawText(mFirstChar, 0, 1, mWidth / 2, mHeight / 2 + + (mBounds.bottom - mBounds.top) / 2, mPaint); + } else { + // (32 - 24) / 2 = 4 + c.drawBitmap(mDefaultBitmap, ViewUtil.dpToPx(4), ViewUtil.dpToPx(4), null); + } + return getCircularBitmap(bitmap); + } + + /** + * @param c The char to check + * @return True if c is in the English alphabet or is a digit, + * false otherwise + */ + private static boolean isLetterOrDigit(char c) { + //return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9'; + return Character.isLetterOrDigit(c); + } + + /** + * @param key The key used to generate the tile color + * @return A new or previously chosen color for key used as the + * tile background color + */ + private int pickColor(String key) { + // String.hashCode() is not supposed to change across java versions, so + // this should guarantee the same key always maps to the same color + final int color = Math.abs(key.hashCode()) % NUM_OF_TILE_COLORS; + try { + return mColors.getColor(color, Color.BLACK); + } finally { + // bug with recycler view + //mColors.recycle(); + } + } + + private Bitmap getCircularBitmap(Bitmap bitmap) { + Bitmap output; + + if (bitmap.getWidth() > bitmap.getHeight()) { + output = Bitmap.createBitmap(bitmap.getHeight(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); + } else { + output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getWidth(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + + float r = 0; + + if (bitmap.getWidth() > bitmap.getHeight()) { + r = bitmap.getHeight() / 2; + } else { + r = bitmap.getWidth() / 2; + } + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawCircle(r, r, r, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + return output; + } + + public static Bitmap drawableToBitmap (Drawable drawable) { + Bitmap bitmap = null; + + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if(bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } + + if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } + +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java new file mode 100644 index 000000000..924350801 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/MyWindowCallback.java @@ -0,0 +1,173 @@ +package com.pchmn.materialchips.util; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.view.ActionMode; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import com.pchmn.materialchips.views.ChipsInputEditText; +import com.pchmn.materialchips.views.DetailedChipView; + +public class MyWindowCallback implements Window.Callback { + + private Window.Callback mLocalCallback; + private Activity mActivity; + + public MyWindowCallback(Window.Callback localCallback, Activity activity) { + mLocalCallback = localCallback; + mActivity = activity; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent keyEvent) { + return mLocalCallback.dispatchKeyEvent(keyEvent); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent keyEvent) { + return mLocalCallback.dispatchKeyShortcutEvent(keyEvent); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent motionEvent) { + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + View v = mActivity.getCurrentFocus(); + if(v instanceof DetailedChipView) { + Rect outRect = new Rect(); + v.getGlobalVisibleRect(outRect); + if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { + ((DetailedChipView) v).fadeOut(); + } + } + if (v instanceof ChipsInputEditText) { + Rect outRect = new Rect(); + v.getGlobalVisibleRect(outRect); + if (!outRect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY()) + && !((ChipsInputEditText) v).isFilterableListVisible()) { + InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + } + } + return mLocalCallback.dispatchTouchEvent(motionEvent); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent motionEvent) { + return mLocalCallback.dispatchTrackballEvent(motionEvent); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) { + return mLocalCallback.dispatchGenericMotionEvent(motionEvent); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent accessibilityEvent) { + return mLocalCallback.dispatchPopulateAccessibilityEvent(accessibilityEvent); + } + + @Nullable + @Override + public View onCreatePanelView(int i) { + return mLocalCallback.onCreatePanelView(i); + } + + @Override + public boolean onCreatePanelMenu(int i, Menu menu) { + return mLocalCallback.onCreatePanelMenu(i, menu); + } + + @Override + public boolean onPreparePanel(int i, View view, Menu menu) { + return mLocalCallback.onPreparePanel(i, view, menu); + } + + @Override + public boolean onMenuOpened(int i, Menu menu) { + return mLocalCallback.onMenuOpened(i, menu); + } + + @Override + public boolean onMenuItemSelected(int i, MenuItem menuItem) { + return mLocalCallback.onMenuItemSelected(i, menuItem); + } + + @Override + public void onWindowAttributesChanged(WindowManager.LayoutParams layoutParams) { + mLocalCallback.onWindowAttributesChanged(layoutParams); + } + + @Override + public void onContentChanged() { + mLocalCallback.onContentChanged(); + } + + @Override + public void onWindowFocusChanged(boolean b) { + mLocalCallback.onWindowFocusChanged(b); + } + + @Override + public void onAttachedToWindow() { + mLocalCallback.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow() { + mLocalCallback.onDetachedFromWindow(); + } + + @Override + public void onPanelClosed(int i, Menu menu) { + mLocalCallback.onPanelClosed(i, menu); + } + + @Override + public boolean onSearchRequested() { + return mLocalCallback.onSearchRequested(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return mLocalCallback.onSearchRequested(searchEvent); + } + + @Nullable + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) { + return mLocalCallback.onWindowStartingActionMode(callback); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + @Nullable + @Override + public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int i) { + return mLocalCallback.onWindowStartingActionMode(callback, i); + } + + @Override + public void onActionModeStarted(ActionMode actionMode) { + mLocalCallback.onActionModeStarted(actionMode); + } + + @Override + public void onActionModeFinished(ActionMode actionMode) { + mLocalCallback.onActionModeFinished(actionMode); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java new file mode 100644 index 000000000..1cae87da7 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/util/ViewUtil.java @@ -0,0 +1,81 @@ +package com.pchmn.materialchips.util; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +public class ViewUtil { + + private static int windowWidthPortrait = 0; + private static int windowWidthLandscape = 0; + + public static int dpToPx(int dp) { + return (int) (dp * Resources.getSystem().getDisplayMetrics().density); + } + + public static int pxToDp(int px) { + return (int) (px / Resources.getSystem().getDisplayMetrics().density); + } + + public static int getWindowWidth(Context context) { + if(context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){ + return getWindowWidthPortrait(context); + } + else { + return getWindowWidthLandscape(context); + } + } + + private static int getWindowWidthPortrait(Context context) { + if(windowWidthPortrait == 0) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + windowWidthPortrait = metrics.widthPixels; + } + + return windowWidthPortrait; + } + + private static int getWindowWidthLandscape(Context context) { + if(windowWidthLandscape == 0) { + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + windowWidthLandscape = metrics.widthPixels; + } + + return windowWidthLandscape; + } + + public static int getNavBarHeight(Context context) { + int result = 0; + boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey(); + boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); + + if(!hasMenuKey && !hasBackKey) { + //The device has a navigation bar + Resources resources = context.getResources(); + + int orientation = context.getResources().getConfiguration().orientation; + int resourceId; + if (isTablet(context)){ + resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android"); + } else { + resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android"); + } + + if (resourceId > 0) { + return context.getResources().getDimensionPixelSize(resourceId); + } + } + return result; + } + + + private static boolean isTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java new file mode 100644 index 000000000..d3bb04f5d --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ChipsInputEditText.java @@ -0,0 +1,31 @@ +package com.pchmn.materialchips.views; + + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; + +public class ChipsInputEditText extends android.support.v7.widget.AppCompatEditText { + + private FilterableListView filterableListView; + + public ChipsInputEditText(Context context) { + super(context); + } + + public ChipsInputEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public boolean isFilterableListVisible() { + return filterableListView != null && filterableListView.getVisibility() == VISIBLE; + } + + public FilterableListView getFilterableListView() { + return filterableListView; + } + + public void setFilterableListView(FilterableListView filterableListView) { + this.filterableListView = filterableListView; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java new file mode 100644 index 000000000..b5a7f6eb3 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/DetailedChipView.java @@ -0,0 +1,233 @@ +package com.pchmn.materialchips.views; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ColorUtil; +import com.pchmn.materialchips.util.LetterTileProvider; + + +public class DetailedChipView extends LinearLayout { + + private static final String TAG = DetailedChipView.class.toString(); + // context + private Context mContext; + // xml elements + private LinearLayout mContentLayout; + private TextView mNameTextView; + private TextView mInfoTextView; + private ImageButton mDeleteButton; + // letter tile provider + private static LetterTileProvider mLetterTileProvider; + // attributes + private ColorStateList mBackgroundColor; + + public DetailedChipView(Context context) { + super(context); + mContext = context; + init(null); + } + + public DetailedChipView(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + init(attrs); + } + + /** + * Inflate the view according to attributes + * + * @param attrs the attributes + */ + private void init(AttributeSet attrs) { + // inflate layout + View rootView = inflate(getContext(), R.layout.detailed_chip_view, this); + + mContentLayout = (LinearLayout) rootView.findViewById(R.id.content); + mNameTextView = (TextView) rootView.findViewById(R.id.name); + mInfoTextView = (TextView) rootView.findViewById(R.id.info); + mDeleteButton = (ImageButton) rootView.findViewById(R.id.delete_button); + + // letter tile provider + mLetterTileProvider = new LetterTileProvider(mContext); + + // hide on first + setVisibility(GONE); + // hide on touch outside + hideOnTouchOutside(); + } + + /** + * Hide the view on touch outside of it + */ + private void hideOnTouchOutside() { + // set focusable + setFocusable(true); + setFocusableInTouchMode(true); + setClickable(true); + } + + /** + * Fade in + */ + public void fadeIn() { + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(VISIBLE); + // focus on the view + requestFocus(); + } + + /** + * Fade out + */ + public void fadeOut() { + AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(GONE); + // fix onclick issue + clearFocus(); + setClickable(false); + } + + public void setName(String name) { + mNameTextView.setText(name); + } + + public void setInfo(String info) { + if(info != null) { + mInfoTextView.setVisibility(VISIBLE); + mInfoTextView.setText(info); + } + else { + mInfoTextView.setVisibility(GONE); + } + } + + public void setTextColor(ColorStateList color) { + mNameTextView.setTextColor(color); + mInfoTextView.setTextColor(ColorUtil.alpha(color.getDefaultColor(), 150)); + } + + public void setBackGroundcolor(ColorStateList color) { + mBackgroundColor = color; + mContentLayout.getBackground().setColorFilter(color.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + } + + public int getBackgroundColor() { + return mBackgroundColor == null ? ContextCompat.getColor(mContext, R.color.colorAccent) : mBackgroundColor.getDefaultColor(); + } + + public void setDeleteIconColor(ColorStateList color) { + mDeleteButton.getDrawable().mutate().setColorFilter(color.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + } + + public void setOnDeleteClicked(OnClickListener onClickListener) { + mDeleteButton.setOnClickListener(onClickListener); + } + + public void alignLeft() { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentLayout.getLayoutParams(); + params.leftMargin = 0; + mContentLayout.setLayoutParams(params); + } + + public void alignRight() { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mContentLayout.getLayoutParams(); + params.rightMargin = 0; + mContentLayout.setLayoutParams(params); + } + + public static class Builder { + private Context context; + private String name; + private String info; + private ColorStateList textColor; + private ColorStateList backgroundColor; + private ColorStateList deleteIconColor; + + public Builder(Context context) { + this.context = context; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder info(String info) { + this.info = info; + return this; + } + + public Builder chip(ChipInterface chip) { + this.name = chip.getLabel(); + this.info = chip.getInfo(); + return this; + } + + public Builder textColor(ColorStateList textColor) { + this.textColor = textColor; + return this; + } + + public Builder backgroundColor(ColorStateList backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public Builder deleteIconColor(ColorStateList deleteIconColor) { + this.deleteIconColor = deleteIconColor; + return this; + } + + public DetailedChipView build() { + return DetailedChipView.newInstance(this); + } + } + + private static DetailedChipView newInstance(Builder builder) { + DetailedChipView detailedChipView = new DetailedChipView(builder.context); + // avatar + // background color + if(builder.backgroundColor != null) + detailedChipView.setBackGroundcolor(builder.backgroundColor); + + // text color + if(builder.textColor != null) + detailedChipView.setTextColor(builder.textColor); + else if(ColorUtil.isColorDark(detailedChipView.getBackgroundColor())) + detailedChipView.setTextColor(ColorStateList.valueOf(Color.WHITE)); + else + detailedChipView.setTextColor(ColorStateList.valueOf(Color.BLACK)); + + // delete icon color + if(builder.deleteIconColor != null) + detailedChipView.setDeleteIconColor(builder.deleteIconColor); + else if(ColorUtil.isColorDark(detailedChipView.getBackgroundColor())) + detailedChipView.setDeleteIconColor(ColorStateList.valueOf(Color.WHITE)); + else + detailedChipView.setDeleteIconColor(ColorStateList.valueOf(Color.BLACK)); + + detailedChipView.setName(builder.name); + detailedChipView.setInfo(builder.info); + return detailedChipView; + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java new file mode 100644 index 000000000..ba4034fb7 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/FilterableListView.java @@ -0,0 +1,156 @@ +package com.pchmn.materialchips.views; + + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.os.Build; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.AlphaAnimation; +import android.widget.Filter; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +import com.pchmn.materialchips.ChipsInput; +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.adapter.FilterableAdapter; +import com.pchmn.materialchips.model.ChipInterface; +import com.pchmn.materialchips.util.ViewUtil; + +import java.util.List; + +public class FilterableListView extends RelativeLayout { + + private static final String TAG = FilterableListView.class.toString(); + private FrameLayout frameLayout; + private Context mContext; + // list + private RecyclerView mRecyclerView; + private FilterableAdapter mAdapter; + private List mFilterableList; + // others + private ChipsInput mChipsInput; + private ViewGroup rootView; + + public FilterableListView(Context context) { + this(context, null); + } + + public FilterableListView(Context context, ViewGroup layout) { + super(context); + this.mContext = context; + this.rootView = layout; + init(); + } + + private void init() { + // inflate layout + View view = inflate(getContext(), R.layout.list_filterable_view, this); + + mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); + + // recycler + mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); + + // hide on first + setVisibility(GONE); + } + + public void build(List filterableList, ChipsInput chipsInput, ColorStateList backgroundColor, ColorStateList textColor) { + mFilterableList = filterableList; + mChipsInput = chipsInput; + + // adapter + mAdapter = new FilterableAdapter(mContext, mRecyclerView, filterableList, chipsInput, backgroundColor, textColor); + mRecyclerView.setAdapter(mAdapter); + if(backgroundColor != null) + mRecyclerView.getBackground().setColorFilter(backgroundColor.getDefaultColor(), PorterDuff.Mode.SRC_ATOP); + + // listen to change in the tree + mChipsInput.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + + @Override + public void onGlobalLayout() { + + // position + if(rootView == null){ + rootView = (ViewGroup) mChipsInput.getRootView(); + } + + // size + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + ViewUtil.getWindowWidth(mContext), + ViewGroup.LayoutParams.WRAP_CONTENT); + + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + layoutParams.bottomMargin = ViewUtil.getNavBarHeight(mContext); + } + + //If this child view is already added to the parent rootView, then remove it first + ViewGroup parent = (ViewGroup) FilterableListView.this.getParent(); + if (parent != null) { + parent.removeView(FilterableListView.this); + } + // add view + rootView.addView(FilterableListView.this, layoutParams); + + + // remove the listener: + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + mChipsInput.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } else { + mChipsInput.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } + } + + }); + } + + public void filterList(CharSequence text) { + mAdapter.getFilter().filter(text, new Filter.FilterListener() { + @Override + public void onFilterComplete(int count) { + // show if there are results + if(mAdapter.getItemCount() > 0) + fadeIn(); + else + fadeOut(); + } + }); + } + + /** + * Fade in + */ + public void fadeIn() { + if(getVisibility() == VISIBLE) + return; + + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(VISIBLE); + } + + /** + * Fade out + */ + public void fadeOut() { + if(getVisibility() == GONE) + return; + + AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f); + anim.setDuration(200); + startAnimation(anim); + setVisibility(GONE); + } +} diff --git a/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java new file mode 100644 index 000000000..db212ff65 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/java/com/pchmn/materialchips/views/ScrollViewMaxHeight.java @@ -0,0 +1,49 @@ +package com.pchmn.materialchips.views; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.widget.NestedScrollView; +import android.util.AttributeSet; + +import com.pchmn.materialchips.R; +import com.pchmn.materialchips.util.ViewUtil; + +public class ScrollViewMaxHeight extends NestedScrollView { + + private int mMaxHeight; + private int mWidthMeasureSpec; + + public ScrollViewMaxHeight(Context context) { + super(context); + } + + public ScrollViewMaxHeight(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.ScrollViewMaxHeight, + 0, 0); + + try { + mMaxHeight = a.getDimensionPixelSize(R.styleable.ScrollViewMaxHeight_maxHeight, ViewUtil.dpToPx(300)); + } + finally { + a.recycle(); + } + } + + public void setMaxHeight(int height) { + mMaxHeight = height; + int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST); + measure(mWidthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mWidthMeasureSpec = widthMeasureSpec; + heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml b/extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml new file mode 100644 index 000000000..aea2c3a4f --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable-v21/ripple_chip_view.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml new file mode 100644 index 000000000..58b375ecc --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml new file mode 100644 index 000000000..69dbc5bde --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/bg_chip_view_opened.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml new file mode 100644 index 000000000..f9b266324 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_grey_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml new file mode 100644 index 000000000..e6545bf8a --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_cancel_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml new file mode 100644 index 000000000..69453b4e1 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_outline_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml new file mode 100644 index 000000000..22ca15668 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ic_person_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml b/extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml new file mode 100644 index 000000000..58b375ecc --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/drawable/ripple_chip_view.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/chip_view.xml b/extern/MaterialChipsInput/src/main/res/layout/chip_view.xml new file mode 100644 index 000000000..34e73036c --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/chip_view.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml b/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml new file mode 100644 index 000000000..f39d6fc70 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/chips_input.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml b/extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml new file mode 100644 index 000000000..0533d3e5d --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/detailed_chip_view.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml b/extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml new file mode 100644 index 000000000..6298b696c --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/item_list_filterable.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml b/extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml new file mode 100644 index 000000000..3dc228971 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/layout/list_filterable_view.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/attrs.xml b/extern/MaterialChipsInput/src/main/res/values/attrs.xml new file mode 100644 index 000000000..ab9511869 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/values/attrs.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/colors.xml b/extern/MaterialChipsInput/src/main/res/values/colors.xml new file mode 100644 index 000000000..9f540beb8 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + + #E0E0E0 + #009688 + #ababab + #b9ffffff + ?attr/colorAccent + + \ No newline at end of file diff --git a/extern/MaterialChipsInput/src/main/res/values/strings.xml b/extern/MaterialChipsInput/src/main/res/values/strings.xml new file mode 100644 index 000000000..9b3be0701 --- /dev/null +++ b/extern/MaterialChipsInput/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + + #f16364 + #f58559 + #f9a43e + #e4c62e + #67bf74 + #59a2be + #2093cd + #ad62a7 + + + + 17sp + + 32dp + + diff --git a/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java b/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java new file mode 100644 index 000000000..b68d97902 --- /dev/null +++ b/extern/MaterialChipsInput/src/test/java/com/pchmn/materialchips/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.pchmn.materialchips; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7c3b44eab..01b3c106c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ include ':extern:bouncycastle:core' include ':extern:bouncycastle:pg' include ':extern:bouncycastle:prov' include ':extern:minidns' +include ':extern:MaterialChipsInput:library' // Workaround for Android Gradle Plugin 2.0, as described in http://stackoverflow.com/a/36544850 //include ':libkeychain'