diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..fb7f4a8
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..a9854ef
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..164b6b0
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..51100c4
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,42 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "com.searchablespinner.searchablespinner"
+ minSdk 21
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation 'com.google.android.material:material:1.6.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'com.facebook.stetho:stetho:1.5.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+ implementation project(path: ':searchspinnerlibrary')
+ implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/searchablespinner/searchablespinner/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/searchablespinner/searchablespinner/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..395f0ef
--- /dev/null
+++ b/app/src/androidTest/java/com/searchablespinner/searchablespinner/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.searchablespinner.searchablespinner;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.searchablespinner.searchablespinner", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2d09c2c
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/searchablespinner/searchablespinner/MainActivity.java b/app/src/main/java/com/searchablespinner/searchablespinner/MainActivity.java
new file mode 100644
index 0000000..04a5881
--- /dev/null
+++ b/app/src/main/java/com/searchablespinner/searchablespinner/MainActivity.java
@@ -0,0 +1,208 @@
+package com.searchablespinner.searchablespinner;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+
+import com.searchablespinner.searchspinnerlibrary.SearchableSpinner;
+import com.searchablespinner.searchspinnerlibrary.interfaces.IStatusListener;
+import com.searchablespinner.searchspinnerlibrary.interfaces.OnItemSelectedListener;
+
+import java.util.ArrayList;
+
+public class MainActivity extends AppCompatActivity {
+
+ private final ArrayList mStrings = new ArrayList<>();
+ private SearchableSpinner mSearchableSpinner;
+ private SearchableSpinner mSearchableSpinner1;
+ private SearchableSpinner mSearchableSpinner2;
+ private SearchableSpinner mSearchableSpinner3;
+ private SimpleListAdapter mSimpleListAdapter;
+ private SimpleArrayListAdapter mSimpleArrayListAdapter;
+ private OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(View view, int position, long id) {
+ Toast.makeText(MainActivity.this, "Item on position " + position + " : " + mSimpleListAdapter.getItem(position) + " Selected", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onNothingSelected() {
+ Toast.makeText(MainActivity.this, "Nothing Selected", Toast.LENGTH_SHORT).show();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ initListValues();
+ mSimpleListAdapter = new SimpleListAdapter(this, mStrings);
+ mSimpleArrayListAdapter = new SimpleArrayListAdapter(this, mStrings);
+
+ mSearchableSpinner = findViewById(R.id.SearchableSpinner);
+ mSearchableSpinner.setAdapter(mSimpleArrayListAdapter);
+ mSearchableSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ mSearchableSpinner.setStatusListener(new IStatusListener() {
+ @Override
+ public void spinnerIsOpening() {
+ mSearchableSpinner1.hideEdit();
+ mSearchableSpinner2.hideEdit();
+ }
+
+ @Override
+ public void spinnerIsClosing() {
+
+ }
+ });
+
+ mSearchableSpinner1 = findViewById(R.id.SearchableSpinner1);
+ mSearchableSpinner1.setAdapter(mSimpleListAdapter);
+ mSearchableSpinner1.setOnItemSelectedListener(mOnItemSelectedListener);
+ mSearchableSpinner1.setStatusListener(new IStatusListener() {
+ @Override
+ public void spinnerIsOpening() {
+ mSearchableSpinner.hideEdit();
+ mSearchableSpinner2.hideEdit();
+ }
+
+ @Override
+ public void spinnerIsClosing() {
+
+ }
+ });
+
+ mSearchableSpinner2 = findViewById(R.id.SearchableSpinner2);
+ mSearchableSpinner2.setAdapter(mSimpleListAdapter);
+ mSearchableSpinner2.setOnItemSelectedListener(mOnItemSelectedListener);
+ mSearchableSpinner2.setStatusListener(new IStatusListener() {
+ @Override
+ public void spinnerIsOpening() {
+ mSearchableSpinner.hideEdit();
+ mSearchableSpinner1.hideEdit();
+ }
+
+ @Override
+ public void spinnerIsClosing() {
+
+ }
+ });
+
+ mSearchableSpinner3 = findViewById(R.id.SearchableSpinner3);
+ mSearchableSpinner3.setAdapter(mSimpleListAdapter);
+ mSearchableSpinner3.setOnItemSelectedListener(mOnItemSelectedListener);
+ mSearchableSpinner3.setStatusListener(new IStatusListener() {
+ @Override
+ public void spinnerIsOpening() {
+ mSearchableSpinner.hideEdit();
+ mSearchableSpinner3.hideEdit();
+ }
+
+ @Override
+ public void spinnerIsClosing() {
+
+ }
+ });
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mSearchableSpinner.isInsideSearchEditText(event)) {
+ mSearchableSpinner.hideEdit();
+ }
+ if (!mSearchableSpinner1.isInsideSearchEditText(event)) {
+ mSearchableSpinner1.hideEdit();
+ }
+ if (!mSearchableSpinner2.isInsideSearchEditText(event)) {
+ mSearchableSpinner2.hideEdit();
+ }
+ return super.onTouchEvent(event);
+ }
+
+ private void initListValues() {
+ mStrings.add("Samwel Nyandoro");
+ mStrings.add("Samwel Nyandoro1");
+ mStrings.add("Samwel Nyandoro2");
+ mStrings.add("Samwel Nyandoro3");
+ mStrings.add("Samwel Nyandoro4");
+ mStrings.add("Samwel Nyandoro5");
+ mStrings.add("Samwel Nyandoro6");
+ mStrings.add("Samwel Nyandoro7");
+ mStrings.add("Samwel Nyandoro8");
+ mStrings.add("Samwel Nyandoro9");
+ mStrings.add("Samwel Nyandoro10");
+ mStrings.add("Samwel Nyandoro11");
+ mStrings.add("Samwel Nyandoro12");
+ mStrings.add("Samwel Nyandoro13");
+ mStrings.add("Samwel Nyandoro14");
+ mStrings.add("Samwel Nyandoro15");
+ mStrings.add("Samwel Nyandoro16");
+ mStrings.add("Samwel Nyandoro17");
+ mStrings.add("Samwel Nyandoro18");
+ mStrings.add("Samwel Nyandoro19");
+ mStrings.add("Samwel Nyandoro20");
+ mStrings.add("Samwel Nyandoro21");
+ mStrings.add("Samwel Nyandoro22");
+ mStrings.add("Samwel Nyandoro23");
+ mStrings.add("Samwel Nyandoro24");
+ mStrings.add("Samwel Nyandoro25");
+ mStrings.add("Samwel Nyandoro26");
+ mStrings.add("Samwel Nyandoro27");
+ mStrings.add("Samwel Nyandoro28");
+ mStrings.add("Samwel Nyandoro29");
+ mStrings.add("Samwel Nyandoro30");
+ mStrings.add("Samwel Nyandoro31");
+ mStrings.add("Samwel Nyandoro32");
+ mStrings.add("Samwel Nyandoro33");
+ mStrings.add("Samwel Nyandoro34");
+ mStrings.add("Samwel Nyandoro35");
+ mStrings.add("Samwel Nyandoro36");
+ mStrings.add("Samwel Nyandoro37");
+ mStrings.add("Samwel Nyandoro38");
+ mStrings.add("Samwel Nyandoro39");
+ mStrings.add("Samwel Nyandoro40");
+ mStrings.add("Samwel Nyandoro41");
+ mStrings.add("Samwel Nyandoro42");
+ mStrings.add("Samwel Nyandoro43");
+ mStrings.add("Samwel Nyandoro44");
+ mStrings.add("Samwel Nyandoro45");
+ mStrings.add("Samwel Nyandoro46");
+ mStrings.add("Samwel Nyandoro48");
+ mStrings.add("Samwel Nyandoro49");
+ mStrings.add("Samwel Nyandoro50");
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_reset) {
+ mSearchableSpinner.setSelectedItem(0);
+ mSearchableSpinner1.setSelectedItem(0);
+ mSearchableSpinner2.setSelectedItem(0);
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/com/searchablespinner/searchablespinner/SearchableSpinnerApp.java b/app/src/main/java/com/searchablespinner/searchablespinner/SearchableSpinnerApp.java
new file mode 100644
index 0000000..caf2851
--- /dev/null
+++ b/app/src/main/java/com/searchablespinner/searchablespinner/SearchableSpinnerApp.java
@@ -0,0 +1,17 @@
+package com.searchablespinner.searchablespinner;
+
+import android.app.Application;
+
+import com.facebook.stetho.Stetho;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class SearchableSpinnerApp extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Stetho.initializeWithDefaults(this);
+ }
+}
diff --git a/app/src/main/java/com/searchablespinner/searchablespinner/SimpleArrayListAdapter.java b/app/src/main/java/com/searchablespinner/searchablespinner/SimpleArrayListAdapter.java
new file mode 100644
index 0000000..014dcc3
--- /dev/null
+++ b/app/src/main/java/com/searchablespinner/searchablespinner/SimpleArrayListAdapter.java
@@ -0,0 +1,162 @@
+package com.searchablespinner.searchablespinner;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.amulyakhare.textdrawable.TextDrawable;
+import com.amulyakhare.textdrawable.util.ColorGenerator;
+import com.searchablespinner.searchspinnerlibrary.interfaces.ISpinnerSelectedView;
+
+import java.util.ArrayList;
+
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class SimpleArrayListAdapter extends ArrayAdapter implements Filterable, ISpinnerSelectedView {
+
+ private Context mContext;
+ private ArrayList mBackupStrings;
+ private ArrayList mStrings;
+ private StringFilter mStringFilter = new StringFilter();
+
+ public SimpleArrayListAdapter(Context context, ArrayList strings) {
+ super(context, R.layout.view_list_item);
+ mContext = context;
+ mStrings = strings;
+ mBackupStrings = strings;
+ }
+
+ @Override
+ public int getCount() {
+ return mStrings == null ? 0 : mStrings.size() + 1;
+ }
+
+ @Override
+ public String getItem(int position) {
+ if (mStrings != null && position > 0)
+ return mStrings.get(position - 1);
+ else
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (mStrings == null && position > 0)
+ return mStrings.get(position).hashCode();
+ else
+ return -1;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = null;
+ if (position == 0) {
+ view = getNoSelectionView();
+ } else {
+ view = View.inflate(mContext, R.layout.view_list_item, null);
+ ImageView letters = view.findViewById(R.id.ImgVw_Letters);
+ TextView dispalyName = view.findViewById(R.id.TxtVw_DisplayName);
+ letters.setImageDrawable(getTextDrawable(mStrings.get(position - 1)));
+ dispalyName.setText(mStrings.get(position - 1));
+ }
+ return view;
+ }
+
+ @Override
+ public View getSelectedView(int position) {
+ View view = null;
+ if (position == 0) {
+ view = getNoSelectionView();
+ } else {
+ view = View.inflate(mContext, R.layout.view_list_item, null);
+ ImageView letters = view.findViewById(R.id.ImgVw_Letters);
+ TextView dispalyName = view.findViewById(R.id.TxtVw_DisplayName);
+ letters.setImageDrawable(getTextDrawable(mStrings.get(position - 1)));
+ dispalyName.setText(mStrings.get(position - 1));
+ }
+ return view;
+ }
+
+ @Override
+ public View getNoSelectionView() {
+ View view = View.inflate(mContext, R.layout.view_list_no_selection_item, null);
+ return view;
+ }
+
+ private TextDrawable getTextDrawable(String displayName) {
+ TextDrawable drawable = null;
+ if (!TextUtils.isEmpty(displayName)) {
+ int color2 = ColorGenerator.MATERIAL.getColor(displayName);
+ drawable = TextDrawable.builder()
+ .beginConfig()
+ .width(UiTools.dpToPx(mContext, 32))
+ .height(UiTools.dpToPx(mContext, 32))
+ .textColor(Color.WHITE)
+ .toUpperCase()
+ .endConfig()
+ .round()
+ .build(displayName.substring(0, 1), color2);
+ } else {
+ drawable = TextDrawable.builder()
+ .beginConfig()
+ .width(UiTools.dpToPx(mContext, 32))
+ .height(UiTools.dpToPx(mContext, 32))
+ .endConfig()
+ .round()
+ .build("?", Color.GRAY);
+ }
+ return drawable;
+ }
+
+ @Override
+ public Filter getFilter() {
+ return mStringFilter;
+ }
+
+ public enum ItemViewType {
+ ITEM, NO_SELECTION_ITEM
+ }
+
+ public class StringFilter extends Filter {
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ final FilterResults filterResults = new FilterResults();
+ if (TextUtils.isEmpty(constraint)) {
+ filterResults.count = mBackupStrings.size();
+ filterResults.values = mBackupStrings;
+ return filterResults;
+ }
+ final ArrayList filterStrings = new ArrayList<>();
+ for (String text : mBackupStrings) {
+ if (text.toLowerCase().contains(constraint)) {
+ filterStrings.add(text);
+ }
+ }
+ filterResults.count = filterStrings.size();
+ filterResults.values = filterStrings;
+ return filterResults;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ mStrings = (ArrayList) results.values;
+ notifyDataSetChanged();
+ }
+ }
+
+ private class ItemView {
+ public ImageView mImageView;
+ public TextView mTextView;
+ }
+}
diff --git a/app/src/main/java/com/searchablespinner/searchablespinner/SimpleListAdapter.java b/app/src/main/java/com/searchablespinner/searchablespinner/SimpleListAdapter.java
new file mode 100644
index 0000000..df98668
--- /dev/null
+++ b/app/src/main/java/com/searchablespinner/searchablespinner/SimpleListAdapter.java
@@ -0,0 +1,160 @@
+package com.searchablespinner.searchablespinner;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.amulyakhare.textdrawable.TextDrawable;
+import com.amulyakhare.textdrawable.util.ColorGenerator;
+import com.searchablespinner.searchspinnerlibrary.interfaces.ISpinnerSelectedView;
+
+import java.util.ArrayList;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class SimpleListAdapter extends BaseAdapter implements Filterable, ISpinnerSelectedView {
+
+ private Context mContext;
+ private ArrayList mBackupStrings;
+ private ArrayList mStrings;
+ private StringFilter mStringFilter = new StringFilter();
+
+ public SimpleListAdapter(Context context, ArrayList strings) {
+ mContext = context;
+ mStrings = strings;
+ mBackupStrings = strings;
+ }
+
+ @Override
+ public int getCount() {
+ return mStrings == null ? 0 : mStrings.size() + 1;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ if (mStrings != null && position > 0)
+ return mStrings.get(position - 1);
+ else
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (mStrings == null && position > 0)
+ return mStrings.get(position).hashCode();
+ else
+ return -1;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = null;
+ if (position == 0) {
+ view = getNoSelectionView();
+ } else {
+ view = View.inflate(mContext, R.layout.view_list_item, null);
+ ImageView letters = view.findViewById(R.id.ImgVw_Letters);
+ TextView dispalyName = view.findViewById(R.id.TxtVw_DisplayName);
+ letters.setImageDrawable(getTextDrawable(mStrings.get(position - 1)));
+ dispalyName.setText(mStrings.get(position - 1));
+ }
+ return view;
+ }
+
+ @Override
+ public View getSelectedView(int position) {
+ View view = null;
+ if (position == 0) {
+ view = getNoSelectionView();
+ } else {
+ view = View.inflate(mContext, R.layout.view_list_item, null);
+ ImageView letters = view.findViewById(R.id.ImgVw_Letters);
+ TextView dispalyName = view.findViewById(R.id.TxtVw_DisplayName);
+ letters.setImageDrawable(getTextDrawable(mStrings.get(position - 1)));
+ dispalyName.setText(mStrings.get(position - 1));
+ }
+ return view;
+ }
+
+ @Override
+ public View getNoSelectionView() {
+ View view = View.inflate(mContext, R.layout.view_list_no_selection_item, null);
+ return view;
+ }
+
+ private TextDrawable getTextDrawable(String displayName) {
+ TextDrawable drawable = null;
+ if (!TextUtils.isEmpty(displayName)) {
+ int color2 = ColorGenerator.MATERIAL.getColor(displayName);
+ drawable = TextDrawable.builder()
+ .beginConfig()
+ .width(UiTools.dpToPx(mContext, 32))
+ .height(UiTools.dpToPx(mContext, 32))
+ .textColor(Color.WHITE)
+ .toUpperCase()
+ .endConfig()
+ .round()
+ .build(displayName.substring(0, 1), color2);
+ } else {
+ drawable = TextDrawable.builder()
+ .beginConfig()
+ .width(UiTools.dpToPx(mContext, 32))
+ .height(UiTools.dpToPx(mContext, 32))
+ .endConfig()
+ .round()
+ .build("?", Color.GRAY);
+ }
+ return drawable;
+ }
+
+ @Override
+ public Filter getFilter() {
+ return mStringFilter;
+ }
+
+ public enum ItemViewType {
+ ITEM, NO_SELECTION_ITEM
+ }
+
+ public class StringFilter extends Filter {
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ final FilterResults filterResults = new FilterResults();
+ if (TextUtils.isEmpty(constraint)) {
+ filterResults.count = mBackupStrings.size();
+ filterResults.values = mBackupStrings;
+ return filterResults;
+ }
+ final ArrayList filterStrings = new ArrayList<>();
+ for (String text : mBackupStrings) {
+ if (text.toLowerCase().contains(constraint)) {
+ filterStrings.add(text);
+ }
+ }
+ filterResults.count = filterStrings.size();
+ filterResults.values = filterStrings;
+ return filterResults;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ mStrings = (ArrayList) results.values;
+ notifyDataSetChanged();
+ }
+ }
+
+ private class ItemView {
+ public ImageView mImageView;
+ public TextView mTextView;
+ }
+}
diff --git a/app/src/main/java/com/searchablespinner/searchablespinner/UiTools.java b/app/src/main/java/com/searchablespinner/searchablespinner/UiTools.java
new file mode 100644
index 0000000..776bbfb
--- /dev/null
+++ b/app/src/main/java/com/searchablespinner/searchablespinner/UiTools.java
@@ -0,0 +1,11 @@
+package com.searchablespinner.searchablespinner;
+
+import android.content.Context;
+
+public class UiTools {
+
+ public static int dpToPx(Context context, float dp) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return Math.round(dp * scale);
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..2c4e943
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..af49b44
--- /dev/null
+++ b/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_list_item.xml b/app/src/main/res/layout/view_list_item.xml
new file mode 100644
index 0000000..47936c7
--- /dev/null
+++ b/app/src/main/res/layout/view_list_item.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_list_no_selection_item.xml b/app/src/main/res/layout/view_list_no_selection_item.xml
new file mode 100644
index 0000000..b569cf4
--- /dev/null
+++ b/app/src/main/res/layout/view_list_no_selection_item.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..c1a49cb
--- /dev/null
+++ b/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,10 @@
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..a11ac56
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+ #BDBDBD
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..59a0b0c
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c45c27a
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ SearchableSpinner
+ Reset
+ No Selection
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..545b9c6
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/searchablespinner/searchablespinner/ExampleUnitTest.java b/app/src/test/java/com/searchablespinner/searchablespinner/ExampleUnitTest.java
new file mode 100644
index 0000000..f186544
--- /dev/null
+++ b/app/src/test/java/com/searchablespinner/searchablespinner/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.searchablespinner.searchablespinner;
+
+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() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..a550ce3
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,9 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.2.1' apply false
+ id 'com.android.library' version '7.2.1' apply false
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..1d77e86
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,22 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+android.enableJetifier=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b96692e
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Aug 01 10:47:27 EAT 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/searchspinnerlibrary/.gitignore b/searchspinnerlibrary/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/searchspinnerlibrary/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/searchspinnerlibrary/build.gradle b/searchspinnerlibrary/build.gradle
new file mode 100644
index 0000000..e48f699
--- /dev/null
+++ b/searchspinnerlibrary/build.gradle
@@ -0,0 +1,39 @@
+plugins {
+ id 'com.android.library'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ minSdk 21
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation 'com.google.android.material:material:1.6.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'com.joanzapata.iconify:android-iconify-material:2.2.2'
+ implementation 'com.facebook.stetho:stetho:1.5.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/searchspinnerlibrary/proguard-rules.pro b/searchspinnerlibrary/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/searchspinnerlibrary/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/androidTest/java/com/searchablespinner/searchspinnerlibrary/ExampleInstrumentedTest.java b/searchspinnerlibrary/src/androidTest/java/com/searchablespinner/searchspinnerlibrary/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..c805a8a
--- /dev/null
+++ b/searchspinnerlibrary/src/androidTest/java/com/searchablespinner/searchspinnerlibrary/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.searchablespinner.searchspinnerlibrary;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.searchablespinner.searchspinnerlibrary", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/main/AndroidManifest.xml b/searchspinnerlibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1216937
--- /dev/null
+++ b/searchspinnerlibrary/src/main/AndroidManifest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/SearchableSpinner.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/SearchableSpinner.java
new file mode 100644
index 0000000..b521b94
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/SearchableSpinner.java
@@ -0,0 +1,669 @@
+package com.searchablespinner.searchspinnerlibrary;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Filterable;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.StyleRes;
+import androidx.appcompat.widget.AppCompatEditText;
+import androidx.cardview.widget.CardView;
+import androidx.core.content.ContextCompat;
+
+import com.joanzapata.iconify.Iconify;
+import com.joanzapata.iconify.fonts.MaterialModule;
+import com.joanzapata.iconify.widget.IconTextView;
+import com.searchablespinner.searchspinnerlibrary.interfaces.ISpinnerSelectedView;
+import com.searchablespinner.searchspinnerlibrary.interfaces.IStatusListener;
+import com.searchablespinner.searchspinnerlibrary.interfaces.OnItemSelectedListener;
+import com.searchablespinner.searchspinnerlibrary.tools.EditCursorColor;
+import com.searchablespinner.searchspinnerlibrary.tools.UITools;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class SearchableSpinner extends RelativeLayout implements View.OnClickListener {
+
+ private static final int DefaultElevation = 16;
+ private static final int DefaultAnimationDuration = 400;
+
+ static {
+ Iconify.with(new MaterialModule());
+ }
+
+ private ViewState mViewState = ViewState.ShowingRevealedLayout;
+ private IStatusListener mStatusListener;
+ private CardView mRevealContainerCardView;
+ private LinearLayout mRevealItem;
+ private IconTextView mStartSearchImageView;
+ private CardView mContainerCardView;
+ private AppCompatEditText mSearchEditText;
+ private IconTextView mDoneSearchImageView;
+ private LinearLayout mSpinnerListContainer;
+ private PopupWindow mPopupWindow;
+ private ListView mSpinnerListView;
+ private TextView mEmptyTextView;
+ private Context mContext;
+ private OnItemSelectedListener mOnItemSelected;
+ private SelectedView mCurrSelectedView;
+ private int mScreenHeightPixels;
+ private int mScreenWidthPixels;
+ /* Attributes */
+ private @ColorInt
+ int mRevealViewBackgroundColor;
+ private @ColorInt
+ int mStartEditTintColor;
+ private @ColorInt
+ int mEditViewBackgroundColor;
+ private @ColorInt
+ int mEditViewTextColor;
+ private @ColorInt
+ int mDoneEditTintColor;
+ private @ColorInt
+ int mBoarderColor;
+ private Drawable mListItemDivider;
+ private @Px
+ int mBordersSize;
+ private @Px
+ int mExpandSize;
+ private @Px
+ int mListDividerSize;
+ private boolean mShowBorders;
+ private boolean mKeepLastSearch;
+ private String mRevealEmptyText;
+ private String mSearchHintText;
+ private String mNoItemsFoundText;
+ private int mAnimDuration;
+ private AdapterView.OnItemClickListener mOnItemSelectedListener = new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (mCurrSelectedView == null) {
+ Adapter adapter = parent.getAdapter();
+ if (adapter instanceof ISpinnerSelectedView) {
+ View selectedView = ((ISpinnerSelectedView) adapter).getSelectedView(position);
+ mCurrSelectedView = new SelectedView(selectedView, position, selectedView.getId());
+ } else {
+ mCurrSelectedView = new SelectedView(view, position, id);
+ }
+ mSpinnerListView.setSelection(position);
+ } else {
+ Adapter adapter = parent.getAdapter();
+ if (adapter instanceof ISpinnerSelectedView) {
+ View selectedView = ((ISpinnerSelectedView) adapter).getSelectedView(position);
+ mCurrSelectedView = new SelectedView(selectedView, position, selectedView.getId());
+ } else {
+ mCurrSelectedView.setView(view);
+ mCurrSelectedView.setPosition(position);
+ mCurrSelectedView.setId(id);
+ }
+ mSpinnerListView.setSelection(position);
+ }
+ if (mCurrSelectedView == null) {
+ if (mOnItemSelected != null)
+ mOnItemSelected.onNothingSelected();
+ } else if (mCurrSelectedView != null) {
+ mRevealItem.removeAllViews();
+ mSpinnerListView.removeViewInLayout(mCurrSelectedView.getView());
+ mRevealItem.addView(mCurrSelectedView.getView());
+ ((BaseAdapter) mSpinnerListView.getAdapter()).notifyDataSetChanged();
+ if (mOnItemSelected != null)
+ mOnItemSelected.onItemSelected(mCurrSelectedView.getView(), mCurrSelectedView.getPosition(), mCurrSelectedView.getId());
+ }
+ hideEdit();
+ }
+ };
+ private OnClickListener mOnRevelViewClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mViewState == ViewState.ShowingRevealedLayout) {
+ revealEditView();
+ } else if (mViewState == ViewState.ShowingEditLayout) {
+ hideEditView();
+ }
+ }
+ };
+ private TextWatcher mTextWatcher = 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) {
+ Filterable filterable = (Filterable) mSpinnerListView.getAdapter();
+ if (filterable != null)
+ filterable.getFilter().filter(s);
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+ };
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public SearchableSpinner(@NonNull Context context) {
+ this(context, null);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public SearchableSpinner(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, -1);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public SearchableSpinner(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public SearchableSpinner(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mContext = context;
+ getAttributeSet(attrs, defStyleAttr, defStyleRes);
+
+ final LayoutInflater factory = LayoutInflater.from(context);
+ factory.inflate(R.layout.view_searchable_spinner, this, true);
+
+ mSpinnerListContainer = (LinearLayout) factory.inflate(R.layout.view_list, this, false);
+ mSpinnerListView = mSpinnerListContainer.findViewById(R.id.LstVw_SpinnerListView);
+ if (mListItemDivider != null) {
+ mSpinnerListView.setDivider(mListItemDivider);
+ mSpinnerListView.setDividerHeight(mListDividerSize);
+ }
+ mEmptyTextView = mSpinnerListContainer.findViewById(R.id.TxtVw_EmptyText);
+ mSpinnerListView.setEmptyView(mEmptyTextView);
+ }
+
+ private void getAttributeSet(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ if (attrs != null) {
+ try {
+ TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.SearchableSpinner, defStyleAttr, defStyleRes);
+ mRevealViewBackgroundColor = attributes.getColor(R.styleable.SearchableSpinner_RevealViewBackgroundColor, Color.WHITE);
+ mStartEditTintColor = attributes.getColor(R.styleable.SearchableSpinner_StartSearchTintColor, Color.GRAY);
+ mEditViewBackgroundColor = attributes.getColor(R.styleable.SearchableSpinner_SearchViewBackgroundColor, Color.WHITE);
+ mEditViewTextColor = attributes.getColor(R.styleable.SearchableSpinner_SearchViewTextColor, Color.BLACK);
+ mDoneEditTintColor = attributes.getColor(R.styleable.SearchableSpinner_DoneSearchTintColor, Color.GRAY);
+ mBordersSize = attributes.getDimensionPixelSize(R.styleable.SearchableSpinner_BordersSize, 4);
+ mExpandSize = attributes.getDimensionPixelSize(R.styleable.SearchableSpinner_SpinnerExpandHeight, 0);
+ mShowBorders = attributes.getBoolean(R.styleable.SearchableSpinner_ShowBorders, false);
+ mBoarderColor = attributes.getColor(R.styleable.SearchableSpinner_BoarderColor, Color.GRAY);
+ mAnimDuration = attributes.getColor(R.styleable.SearchableSpinner_AnimDuration, DefaultAnimationDuration);
+ mKeepLastSearch = attributes.getBoolean(R.styleable.SearchableSpinner_KeepLastSearch, false);
+ mRevealEmptyText = attributes.getString(R.styleable.SearchableSpinner_RevealEmptyText);
+ mSearchHintText = attributes.getString(R.styleable.SearchableSpinner_SearchHintText);
+ mNoItemsFoundText = attributes.getString(R.styleable.SearchableSpinner_NoItemsFoundText);
+ mListItemDivider = attributes.getDrawable(R.styleable.SearchableSpinner_ItemsDivider);
+ mListDividerSize = attributes.getDimensionPixelSize(R.styleable.SearchableSpinner_DividerHeight, 0);
+ } catch (UnsupportedOperationException e) {
+ Log.e("SearchableSpinner", "getAttributeSet --> " + e.getLocalizedMessage());
+ }
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mRevealContainerCardView = findViewById(R.id.CrdVw_RevealContainer);
+ mRevealContainerCardView.setOnClickListener(mOnRevelViewClickListener);
+ mRevealItem = findViewById(R.id.FrmLt_SelectedItem);
+ mStartSearchImageView = findViewById(R.id.ImgVw_StartSearch);
+
+ mContainerCardView = findViewById(R.id.CrdVw_Container);
+ mSearchEditText = findViewById(R.id.EdtTxt_SearchEditText);
+ mDoneSearchImageView = findViewById(R.id.ImgVw_DoneSearch);
+ init();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ getScreenSize();
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ if (mShowBorders) { // + 4 because of card layout_margin in the view_searchable_spinner.xml
+ width -= UITools.dpToPx(mContext, (mBordersSize + 4));
+ } else {
+ width -= UITools.dpToPx(mContext, 8);
+ }
+ mPopupWindow.setWidth(width);
+ if (mExpandSize <= 0) {
+ mPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
+ } else {
+ mPopupWindow.setHeight(heightMeasureSpec);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+
+ super.onLayout(changed, l, t, r, b);
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ requestLayout();
+ super.onScrollChanged(l, t, oldl, oldt);
+ }
+
+ private void init() {
+ setupColors();
+ setupList();
+ mSearchEditText.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+ mStartSearchImageView.setOnClickListener(this);
+ mDoneSearchImageView.setOnClickListener(this);
+ mSearchEditText.addTextChangedListener(mTextWatcher);
+
+ mPopupWindow = new PopupWindow(mContext);
+ mPopupWindow.setContentView(mSpinnerListContainer);
+ mPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+ mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+ mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ hideEdit();
+ }
+ });
+ mPopupWindow.setFocusable(false);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mPopupWindow.setElevation(DefaultElevation);
+ }
+ mPopupWindow.setBackgroundDrawable(ContextCompat.getDrawable(mContext, R.drawable.spinner_drawable));
+
+ mSpinnerListView.setOnItemClickListener(mOnItemSelectedListener);
+ if (mCurrSelectedView == null) {
+ if (!TextUtils.isEmpty(mSearchHintText)) {
+ mSearchEditText.setHint(mSearchHintText);
+ }
+ if (!TextUtils.isEmpty(mNoItemsFoundText)) {
+ mEmptyTextView.setText(mNoItemsFoundText);
+ }
+ if (mCurrSelectedView == null && !TextUtils.isEmpty(mRevealEmptyText)) {
+ TextView textView = new TextView(mContext);
+ textView.setText(mRevealEmptyText);
+ mCurrSelectedView = new SelectedView(textView, -1, 0);
+ mRevealItem.addView(textView);
+ }
+ } else {
+ mSpinnerListView.performItemClick(mCurrSelectedView.getView(), mCurrSelectedView.getPosition(), mCurrSelectedView.getId());
+ }
+ clearAnimation();
+ clearFocus();
+ }
+
+ public Object getSelectedItem() {
+ if (mCurrSelectedView != null) {
+ int position = mCurrSelectedView.getPosition();
+ Adapter adapter = mSpinnerListView.getAdapter();
+ if (adapter != null && adapter.getCount() > 0 && position >= 0) {
+ return adapter.getItem(position);
+ } else {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public void setSelectedItem(int position) {
+ Adapter adapter = mSpinnerListView.getAdapter();
+ if (adapter instanceof ISpinnerSelectedView) {
+ View selectedView = ((ISpinnerSelectedView) adapter).getSelectedView(position);
+ mCurrSelectedView = new SelectedView(selectedView, position, selectedView.getId());
+ mSpinnerListView.setSelection(position);
+ } else {
+ TextView textView = new TextView(mContext);
+ textView.setText(mRevealEmptyText);
+ mCurrSelectedView = new SelectedView(textView, -1, 0);
+ mRevealItem.addView(textView);
+ }
+ if (mCurrSelectedView == null) {
+ if (mOnItemSelected != null)
+ mOnItemSelected.onNothingSelected();
+ } else if (mCurrSelectedView != null) {
+ mRevealItem.removeAllViews();
+ mSpinnerListView.removeViewInLayout(mCurrSelectedView.getView());
+ mRevealItem.addView(mCurrSelectedView.getView());
+ ((BaseAdapter) mSpinnerListView.getAdapter()).notifyDataSetChanged();
+ if (mOnItemSelected != null)
+ mOnItemSelected.onItemSelected(mCurrSelectedView.getView(), mCurrSelectedView.getPosition(), mCurrSelectedView.getId());
+ }
+ hideEdit();
+ }
+
+ public void setSelectedItem(Object item) {
+ int itemPosition = getItemPosition(item);
+ if (itemPosition >= 0) {
+ setSelectedItem(itemPosition);
+ }
+ }
+
+ public int getItemPosition(Object item) {
+ if (item == null)
+ return -1;
+ Adapter adapter = mSpinnerListView.getAdapter();
+ if (adapter != null) {
+ for (int i = 0; i < adapter.getCount(); i++) {
+ Object adpItem = adapter.getItem(i);
+ if (adpItem != null && adpItem.equals(item)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public int getSelectedPosition() {
+ if (mCurrSelectedView != null) {
+ return mCurrSelectedView.getPosition();
+ }
+ return -1;
+ }
+
+ private void setupColors() {
+ mRevealContainerCardView.setBackgroundColor(mRevealViewBackgroundColor);
+ mRevealItem.setBackgroundColor(mRevealViewBackgroundColor);
+ mStartSearchImageView.setBackgroundColor(mRevealViewBackgroundColor);
+ mStartSearchImageView.setTextColor(mStartEditTintColor);
+
+ mContainerCardView.setBackgroundColor(mEditViewBackgroundColor);
+ mSearchEditText.setBackgroundColor(mEditViewBackgroundColor);
+ mSearchEditText.setTextColor(mEditViewTextColor);
+ mSearchEditText.setHintTextColor(mStartEditTintColor);
+ EditCursorColor.setCursorColor(mSearchEditText, mEditViewTextColor);
+ mDoneSearchImageView.setBackgroundColor(mEditViewBackgroundColor);
+ mDoneSearchImageView.setTextColor(mDoneEditTintColor);
+ }
+
+ private void setupList() {
+ MarginLayoutParams spinnerListViewLayoutParams = (MarginLayoutParams) mSpinnerListView.getLayoutParams();
+ ViewGroup.LayoutParams spinnerListContainerLayoutParams = mSpinnerListContainer.getLayoutParams();
+ LinearLayout.LayoutParams listLayoutParams = (LinearLayout.LayoutParams) mSpinnerListView.getLayoutParams();
+
+ spinnerListContainerLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+ if (mExpandSize <= 0) {
+ listLayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ listLayoutParams.height = mExpandSize;
+ }
+ mSpinnerListContainer.setBackgroundColor(mBoarderColor);
+ if (mShowBorders && mBordersSize > 0) {
+ spinnerListViewLayoutParams.setMargins(mBordersSize, mBordersSize, mBordersSize, mBordersSize);
+ } else {
+ spinnerListViewLayoutParams.setMargins(0, 0, 0, 0);
+ }
+ }
+
+ public void setAdapter(ListAdapter adapter) {
+ if (!(adapter instanceof Filterable))
+ throw new IllegalArgumentException("Adapter should implement the Filterable interface");
+ mSpinnerListView.setAdapter(adapter);
+ }
+
+ public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
+ mOnItemSelected = onItemSelectedListener;
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ hideEdit();
+ getScreenSize();
+ }
+
+ private void getScreenSize() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metrics);
+ mScreenHeightPixels = metrics.heightPixels;
+ mScreenWidthPixels = metrics.widthPixels;
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.ImgVw_StartSearch) {
+ revealEdit();
+ } else if (id == R.id.ImgVw_DoneSearch) {
+ hideEdit();
+ }
+ }
+
+ public void revealEdit() {
+ if (mViewState == ViewState.ShowingRevealedLayout) {
+ if (!mKeepLastSearch)
+ mSearchEditText.setText(null);
+ revealEditView();
+ }
+ }
+
+ public void hideEdit() {
+ if (mViewState == ViewState.ShowingEditLayout) {
+ hideEditView();
+ }
+ }
+
+ private void revealEditView() {
+
+ mViewState = ViewState.ShowingAnimation;
+ mViewState = ViewState.ShowingEditLayout;
+ mRevealContainerCardView.setVisibility(View.INVISIBLE);
+
+ if (!mPopupWindow.isShowing())
+ mPopupWindow.showAsDropDown(this, 0, 0);
+// mPopupWindow.showAsDropDown(this, cx, 0);
+
+ mContainerCardView.setVisibility(View.VISIBLE);
+ mViewState = ViewState.ShowingEditLayout;
+
+ mSpinnerListContainer.setVisibility(View.VISIBLE);
+ mContainerCardView.setVisibility(View.VISIBLE);
+
+ mSpinnerListContainer.setVisibility(View.VISIBLE);
+
+
+ }
+
+ private void hideEditView() {
+ mViewState = ViewState.ShowingAnimation;
+ if (mStatusListener != null)
+ mStatusListener.spinnerIsClosing();
+
+ mViewState = ViewState.ShowingRevealedLayout;
+ mRevealContainerCardView.setVisibility(View.VISIBLE);
+
+ mContainerCardView.setVisibility(View.INVISIBLE);
+ mViewState = ViewState.ShowingRevealedLayout;
+ ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(mSearchEditText.getWindowToken(), 0);
+
+ mRevealContainerCardView.setVisibility(View.VISIBLE);
+
+ if (mPopupWindow.isShowing()) {
+
+ mSpinnerListContainer.setVisibility(View.GONE);
+ mPopupWindow.dismiss();
+ }
+ }
+
+ public boolean isInsideSearchEditText(MotionEvent event) {
+ Rect editTextRect = new Rect();
+ mSearchEditText.getHitRect(editTextRect);
+ return editTextRect.contains((int) event.getX(), (int) event.getY());
+ }
+
+ public void setStatusListener(IStatusListener statusListener) {
+ mStatusListener = statusListener;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.mViewState = ViewState.ShowingRevealedLayout;
+ ss.mAnimDuration = mAnimDuration;
+ ss.mBordersSize = mBordersSize;
+ ss.mExpandSize = mExpandSize;
+ ss.mBoarderColor = mBoarderColor;
+ ss.mRevealViewBackgroundColor = mRevealViewBackgroundColor;
+ ss.mStartEditTintColor = mStartEditTintColor;
+ ss.mEditViewBackgroundColor = mEditViewBackgroundColor;
+ ss.mEditViewTextColor = mEditViewTextColor;
+ ss.mDoneEditTintColor = mDoneEditTintColor;
+ ss.mShowBorders = mShowBorders;
+ ss.mKeepLastSearch = mKeepLastSearch;
+ ss.mRevealEmptyText = mRevealEmptyText;
+ ss.mSearchHintText = mSearchHintText;
+ ss.mNoItemsFoundText = mNoItemsFoundText;
+ ss.mSelectedViewPosition = mCurrSelectedView != null ? mCurrSelectedView.getPosition() : -1;
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(state);
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ mViewState = ss.mViewState;
+ mAnimDuration = ss.mAnimDuration;
+ mBordersSize = ss.mBordersSize;
+ mExpandSize = ss.mExpandSize;
+ mBoarderColor = ss.mBoarderColor;
+ mRevealViewBackgroundColor = ss.mRevealViewBackgroundColor;
+ mStartEditTintColor = ss.mStartEditTintColor;
+ mEditViewBackgroundColor = ss.mEditViewBackgroundColor;
+ mEditViewTextColor = ss.mEditViewTextColor;
+ mDoneEditTintColor = ss.mDoneEditTintColor;
+ mShowBorders = ss.mShowBorders;
+ mKeepLastSearch = ss.mKeepLastSearch;
+ mRevealEmptyText = ss.mRevealEmptyText;
+ mSearchHintText = ss.mSearchHintText;
+ mNoItemsFoundText = ss.mNoItemsFoundText;
+ int mSelectedViewPosition = ss.mSelectedViewPosition;
+
+ if (mSelectedViewPosition >= 0) {
+ View v = mSpinnerListView.getAdapter().getView(mSelectedViewPosition, null, null);
+ mSpinnerListView.performItemClick(v, mSelectedViewPosition, v.getId());
+ }
+ }
+
+ public enum ViewState {
+ ShowingRevealedLayout,
+ ShowingEditLayout,
+ ShowingAnimation
+ }
+
+ static class SavedState extends BaseSavedState {
+ public static final Creator CREATOR = new Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ ViewState mViewState;
+ int mAnimDuration;
+ @Px
+ int mBordersSize;
+ @Px
+ int mExpandSize;
+ @ColorInt
+ int mBoarderColor;
+ @ColorInt
+ int mRevealViewBackgroundColor;
+ @ColorInt
+ int mStartEditTintColor;
+ @ColorInt
+ int mEditViewBackgroundColor;
+ @ColorInt
+ int mEditViewTextColor;
+ @ColorInt
+ int mDoneEditTintColor;
+ boolean mShowBorders;
+ boolean mKeepLastSearch;
+ String mRevealEmptyText;
+ String mSearchHintText;
+ String mNoItemsFoundText;
+ int mSelectedViewPosition;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ mViewState = ViewState.values()[in.readInt()];
+ mAnimDuration = in.readInt();
+ mBordersSize = in.readInt();
+ mExpandSize = in.readInt();
+ mBoarderColor = in.readInt();
+ mRevealViewBackgroundColor = in.readInt();
+ mStartEditTintColor = in.readInt();
+ mEditViewBackgroundColor = in.readInt();
+ mEditViewTextColor = in.readInt();
+ mDoneEditTintColor = in.readInt();
+ mShowBorders = in.readInt() > 0;
+ mKeepLastSearch = in.readInt() > 0;
+ mRevealEmptyText = in.readString();
+ mSearchHintText = in.readString();
+ mNoItemsFoundText = in.readString();
+ mSelectedViewPosition = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(mViewState.ordinal());
+ out.writeInt(mAnimDuration);
+ out.writeInt(mBordersSize);
+ out.writeInt(mExpandSize);
+ out.writeInt(mBoarderColor);
+ out.writeInt(mRevealViewBackgroundColor);
+ out.writeInt(mStartEditTintColor);
+ out.writeInt(mEditViewBackgroundColor);
+ out.writeInt(mEditViewTextColor);
+ out.writeInt(mDoneEditTintColor);
+ out.writeInt(mShowBorders ? 1 : 0);
+ out.writeInt(mKeepLastSearch ? 1 : 0);
+ out.writeString(mRevealEmptyText);
+ out.writeString(mSearchHintText);
+ out.writeString(mNoItemsFoundText);
+ out.writeInt(mSelectedViewPosition);
+ }
+ }
+}
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/SelectedView.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/SelectedView.java
new file mode 100644
index 0000000..a2bac30
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/SelectedView.java
@@ -0,0 +1,96 @@
+package com.searchablespinner.searchspinnerlibrary;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+import androidx.annotation.IdRes;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class SelectedView implements Parcelable {
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public SelectedView createFromParcel(Parcel in) {
+ return new SelectedView(in);
+ }
+
+ @Override
+ public SelectedView[] newArray(int size) {
+ return new SelectedView[size];
+ }
+ };
+ private View mView;
+ private int mPosition;
+ private @IdRes
+ long mId;
+
+ public SelectedView(View view, int position, @IdRes long id) {
+ mView = view;
+ mPosition = position;
+ mId = id;
+ }
+
+ protected SelectedView(Parcel in) {
+ mPosition = in.readInt();
+ mId = in.readLong();
+ }
+
+ public View getView() {
+ return mView;
+ }
+
+ public void setView(View view) {
+ mView = view;
+ }
+
+ public int getPosition() {
+ return mPosition;
+ }
+
+ public void setPosition(int position) {
+ mPosition = position;
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public void setId(@IdRes long id) {
+ mId = id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ SelectedView that = (SelectedView) o;
+
+ if (mPosition != that.mPosition) return false;
+ if (mId != that.mId) return false;
+ return mView != null ? mView.equals(that.mView) : that.mView == null;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mView != null ? mView.hashCode() : 0;
+ result = 31 * result + mPosition;
+ result = 31 * result + (int) (mId ^ (mId >>> 32));
+ return result;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mPosition);
+ dest.writeLong(mId);
+ }
+}
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/ISpinnerSelectedView.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/ISpinnerSelectedView.java
new file mode 100644
index 0000000..dc32324
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/ISpinnerSelectedView.java
@@ -0,0 +1,13 @@
+package com.searchablespinner.searchspinnerlibrary.interfaces;
+
+import android.view.View;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public interface ISpinnerSelectedView {
+ View getNoSelectionView();
+
+ View getSelectedView(int position);
+}
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/IStatusListener.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/IStatusListener.java
new file mode 100644
index 0000000..7f379aa
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/IStatusListener.java
@@ -0,0 +1,11 @@
+package com.searchablespinner.searchspinnerlibrary.interfaces;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public interface IStatusListener {
+ void spinnerIsOpening();
+
+ void spinnerIsClosing();
+}
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/OnItemSelectedListener.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/OnItemSelectedListener.java
new file mode 100644
index 0000000..6956fe3
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/interfaces/OnItemSelectedListener.java
@@ -0,0 +1,13 @@
+package com.searchablespinner.searchspinnerlibrary.interfaces;
+
+import android.view.View;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public interface OnItemSelectedListener {
+ void onItemSelected(View view, int position, long id);
+
+ void onNothingSelected();
+}
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/tools/EditCursorColor.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/tools/EditCursorColor.java
new file mode 100644
index 0000000..9619308
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/tools/EditCursorColor.java
@@ -0,0 +1,55 @@
+package com.searchablespinner.searchspinnerlibrary.tools;
+
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.lang.reflect.Field;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class EditCursorColor {
+
+ private EditCursorColor() {
+ }
+
+ public static void setCursorColor(EditText editText, int color) {
+ try {
+ final Field drawableResField = TextView.class.getDeclaredField("mCursorDrawableRes");
+ drawableResField.setAccessible(true);
+ final Drawable drawable = getDrawable(editText.getContext(), drawableResField.getInt(editText));
+ if (drawable == null) {
+ return;
+ }
+ drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ final Object drawableFieldOwner;
+ final Class> drawableFieldClass;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ drawableFieldOwner = editText;
+ drawableFieldClass = TextView.class;
+ } else {
+ final Field editorField = TextView.class.getDeclaredField("mEditor");
+ editorField.setAccessible(true);
+ drawableFieldOwner = editorField.get(editText);
+ drawableFieldClass = drawableFieldOwner.getClass();
+ }
+ final Field drawableField = drawableFieldClass.getDeclaredField("mCursorDrawable");
+ drawableField.setAccessible(true);
+ drawableField.set(drawableFieldOwner, new Drawable[]{drawable, drawable});
+ } catch (Exception ignored) {
+ }
+ }
+
+ private static Drawable getDrawable(Context context, int id) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return context.getResources().getDrawable(id);
+ } else {
+ return context.getDrawable(id);
+ }
+ }
+}
diff --git a/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/tools/UITools.java b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/tools/UITools.java
new file mode 100644
index 0000000..c1e2e86
--- /dev/null
+++ b/searchspinnerlibrary/src/main/java/com/searchablespinner/searchspinnerlibrary/tools/UITools.java
@@ -0,0 +1,18 @@
+package com.searchablespinner.searchspinnerlibrary.tools;
+
+import android.content.Context;
+
+/**
+ * Created by samwel nyandoro on 01/08/2022.
+ */
+
+public class UITools {
+
+ private UITools() {
+ }
+
+ public static int dpToPx(Context context, float dp) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return Math.round(dp * scale);
+ }
+}
diff --git a/searchspinnerlibrary/src/main/res/drawable-v24/ic_launcher_foreground.xml b/searchspinnerlibrary/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/main/res/drawable/ic_launcher_background.xml b/searchspinnerlibrary/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/searchspinnerlibrary/src/main/res/drawable/spinner_drawable.xml b/searchspinnerlibrary/src/main/res/drawable/spinner_drawable.xml
new file mode 100644
index 0000000..c0b1dd6
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/drawable/spinner_drawable.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/main/res/layout/view_list.xml b/searchspinnerlibrary/src/main/res/layout/view_list.xml
new file mode 100644
index 0000000..b104663
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/layout/view_list.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/main/res/layout/view_searchable_spinner.xml b/searchspinnerlibrary/src/main/res/layout/view_searchable_spinner.xml
new file mode 100644
index 0000000..4f81b89
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/layout/view_searchable_spinner.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/searchspinnerlibrary/src/main/res/values/attrs.xml b/searchspinnerlibrary/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..a18be98
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/values/attrs.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/searchspinnerlibrary/src/main/res/values/strings.xml b/searchspinnerlibrary/src/main/res/values/strings.xml
new file mode 100644
index 0000000..81c4341
--- /dev/null
+++ b/searchspinnerlibrary/src/main/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+ SearchSpinnerlibrary
+ Done Search
+ Start search
+ Search…
+ No Items Found
+
diff --git a/searchspinnerlibrary/src/test/java/com/searchablespinner/searchspinnerlibrary/ExampleUnitTest.java b/searchspinnerlibrary/src/test/java/com/searchablespinner/searchspinnerlibrary/ExampleUnitTest.java
new file mode 100644
index 0000000..b0005cd
--- /dev/null
+++ b/searchspinnerlibrary/src/test/java/com/searchablespinner/searchspinnerlibrary/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.searchablespinner.searchspinnerlibrary;
+
+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() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..9dd442f
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ jcenter()
+ }
+}
+rootProject.name = "SearchableSpinner"
+include ':app'
+include ':searchspinnerlibrary'