├── .gitignore ├── README.md ├── app ├── build.gradle ├── libs │ └── androrm.jar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── fonts │ │ ├── Roboto-Black.ttf │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Light.ttf │ │ ├── Roboto-LightItalic.ttf │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-MediumItalic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-Thin.ttf │ │ ├── Roboto-ThinItalic.ttf │ │ ├── RobotoCondensed-Bold.ttf │ │ ├── RobotoCondensed-BoldItalic.ttf │ │ ├── RobotoCondensed-Italic.ttf │ │ ├── RobotoCondensed-Light.ttf │ │ ├── RobotoCondensed-LightItalic.ttf │ │ ├── RobotoCondensed-Regular.ttf │ │ └── roboto-condensed.TTF │ ├── java │ └── com │ │ └── eugene │ │ └── fithealth │ │ ├── AppActivity.java │ │ ├── api │ │ └── FatSecretRequest.java │ │ ├── db │ │ ├── SearchDao.java │ │ ├── SearchDatabase.java │ │ ├── SearchEntity.java │ │ └── SearchRepository.java │ │ ├── model │ │ ├── Food.java │ │ ├── FoodSearch.java │ │ └── Foods.java │ │ ├── search │ │ ├── FatSecretRecyclerAdapter.java │ │ ├── SearchActivity.java │ │ ├── SearchRecyclerAdapter.java │ │ └── SearchViewModel.java │ │ └── util │ │ ├── AppExecutors.java │ │ └── Globals.java │ └── res │ ├── drawable │ ├── ic_delete.xml │ └── ic_search.xml │ ├── layout │ ├── activity_search.xml │ ├── dialog_search.xml │ ├── recycler_fat_secret_item.xml │ └── recycler_search_item.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── device-2017-12-29-144222.png └── device-2017-12-29-144241.png ├── materialsearchview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── android │ │ └── materialsearchview │ │ └── MaterialSearchView.java │ └── res │ ├── drawable-v21 │ └── image_background.xml │ ├── drawable │ ├── ic_arrow_back.xml │ ├── ic_close.xml │ ├── ic_search.xml │ └── image_background.xml │ ├── layout │ ├── search_view.xml │ └── view_search.xml │ ├── values-land │ └── dimens.xml │ └── values │ ├── attrs.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── oldsearchview ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── eh │ │ └── workout │ │ └── journal │ │ └── com │ │ └── oldsearchview │ │ └── MaterialSearchView.java │ └── res │ ├── drawable-v21 │ └── image_background.xml │ ├── drawable │ ├── ic_arrow_back.xml │ ├── ic_close.xml │ ├── ic_search.xml │ └── image_background.xml │ ├── layout │ └── view_search.xml │ ├── values-land │ └── dimens.xml │ └── values │ ├── attrs.xml │ ├── dimens.xml │ └── strings.xml ├── repo └── example.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Gradle template 3 | .gradle 4 | build/ 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | ### JetBrains template 12 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 13 | 14 | *.iml 15 | 16 | ## Directory-based project format: 17 | .idea/ 18 | # if you remove the above rule, at least ignore the following: 19 | 20 | # User-specific stuff: 21 | # .idea/workspace.xml 22 | # .idea/tasks.xml 23 | # .idea/dictionaries 24 | 25 | # Sensitive or high-churn files: 26 | # .idea/dataSources.ids 27 | # .idea/dataSources.xml 28 | # .idea/sqlDataSources.xml 29 | # .idea/dynamic.xml 30 | # .idea/uiDesigner.xml 31 | 32 | # Gradle: 33 | # .idea/gradle.xml 34 | # .idea/libraries 35 | 36 | # Mongo Explorer plugin: 37 | # .idea/mongoSettings.xml 38 | 39 | ## File-based project format: 40 | *.ipr 41 | *.iws 42 | 43 | ## Plugin-specific files: 44 | 45 | # IntelliJ 46 | /out/ 47 | 48 | # mpeltonen/sbt-idea plugin 49 | .idea_modules/ 50 | 51 | # JIRA plugin 52 | atlassian-ide-plugin.xml 53 | 54 | # Crashlytics plugin (for Android Studio and IntelliJ) 55 | com_crashlytics_export_strings.xml 56 | crashlytics.properties 57 | crashlytics-build.properties 58 | ### Android template 59 | # Built application files 60 | *.apk 61 | *.ap_ 62 | 63 | # Files for the Dalvik VM 64 | *.dex 65 | 66 | # Java class files 67 | *.class 68 | 69 | # Generated files 70 | bin/ 71 | gen/ 72 | 73 | # Gradle files 74 | .gradle/ 75 | build/ 76 | 77 | # Local configuration file (sdk path, etc) 78 | local.properties 79 | 80 | # Proguard folder generated by Eclipse 81 | proguard/ 82 | 83 | # Log Files 84 | *.log 85 | 86 | # Android Studio Navigation editor temp files 87 | .navigation/ 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Material-SearchView 2 | 3 | **Update** 4 | - It has been a while since I worked on this project. I figured I would come back to it and convert it to a library. 5 | 6 | *TODO* 7 | - everything 8 | 9 | 10 | In the process of converting to a library. It is a mess at the moment but will be updating it over time. 11 | 12 | 13 | 14 | 15 |

16 | 17 | 18 |

19 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | 6 | defaultConfig { 7 | applicationId "com.eugene.fithealth" 8 | minSdkVersion 16 9 | targetSdkVersion 27 10 | versionCode 1 11 | versionName "1.0" 12 | vectorDrawables.useSupportLibrary = true 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | dataBinding { 21 | enabled = true 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(include: ['*.jar'], dir: 'libs') 27 | implementation 'com.android.support:appcompat-v7:27.0.2' 28 | implementation 'com.android.support:design:27.0.2' 29 | implementation 'com.android.support:cardview-v7:27.0.2' 30 | implementation 'com.android.support:recyclerview-v7:27.0.2' 31 | implementation 'com.android.support:support-vector-drawable:27.0.2' 32 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 33 | implementation 'com.google.code.gson:gson:2.8.2' 34 | // Arch 35 | implementation 'android.arch.lifecycle:extensions:1.0.0' 36 | implementation 'android.arch.persistence.room:runtime:1.0.0' 37 | annotationProcessor 'android.arch.lifecycle:compiler:1.0.0' 38 | annotationProcessor 'android.arch.persistence.room:compiler:1.0.0' 39 | implementation project(':materialsearchview') 40 | } 41 | -------------------------------------------------------------------------------- /app/libs/androrm.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/libs/androrm.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -keepclassmembers class ** { 22 | @com.squareup.otto.Subscribe public *; 23 | @com.squareup.otto.Produce public *; 24 | } 25 | 26 | -keep class butterknife.** { *; } 27 | -dontwarn butterknife.internal.** 28 | -keep class **$$ViewBinder { *; } 29 | 30 | -keepclasseswithmembernames class * { 31 | @butterknife.* ; 32 | } 33 | 34 | -keepclasseswithmembernames class * { 35 | @butterknife.* ; 36 | } 37 | 38 | -dontwarn com.viewpagerindicator.** -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/RobotoCondensed-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/RobotoCondensed-Bold.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/RobotoCondensed-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/RobotoCondensed-BoldItalic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/RobotoCondensed-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/RobotoCondensed-Italic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/RobotoCondensed-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/RobotoCondensed-Light.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/RobotoCondensed-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/RobotoCondensed-LightItalic.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/RobotoCondensed-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/RobotoCondensed-Regular.ttf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/roboto-condensed.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/assets/fonts/roboto-condensed.TTF -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/AppActivity.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth; 2 | 3 | import android.app.Application; 4 | 5 | import com.eugene.fithealth.db.SearchDatabase; 6 | import com.eugene.fithealth.db.SearchRepository; 7 | import com.eugene.fithealth.util.AppExecutors; 8 | 9 | public class AppActivity extends Application { 10 | private AppExecutors appExecutors; 11 | 12 | public void onCreate() { 13 | super.onCreate(); 14 | appExecutors = new AppExecutors(); 15 | } 16 | 17 | public SearchDatabase getDatabase() { 18 | return SearchDatabase.getInstance(this); 19 | } 20 | 21 | public SearchRepository getRepository() { 22 | return SearchRepository.getInstance(appExecutors, getDatabase()); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/api/FatSecretRequest.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.api; 2 | 3 | import android.net.Uri; 4 | import android.util.Base64; 5 | import android.util.Log; 6 | 7 | import com.eugene.fithealth.util.Globals; 8 | import com.eugene.fithealth.model.FoodSearch; 9 | import com.eugene.fithealth.model.Foods; 10 | import com.google.gson.Gson; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.net.URL; 15 | import java.net.URLConnection; 16 | import java.util.ArrayList; 17 | import java.util.Arrays; 18 | import java.util.List; 19 | import java.util.Random; 20 | 21 | import javax.crypto.Mac; 22 | import javax.crypto.SecretKey; 23 | import javax.crypto.spec.SecretKeySpec; 24 | 25 | public class FatSecretRequest { 26 | 27 | public FatSecretRequest() { 28 | } 29 | 30 | public Foods getFoods(String query, String page) { 31 | List params = new ArrayList<>(Arrays.asList(generateOauthParams(page))); 32 | String[] template = new String[1]; 33 | params.add("method=foods.search"); 34 | params.add("search_expression=" + Uri.encode(query)); 35 | params.add("oauth_signature=" + sign(Globals.APP_METHOD, Globals.APP_URL, params.toArray(template))); 36 | try { 37 | URL url = new URL(Globals.APP_URL + "?" + paramify(params.toArray(template))); 38 | Log.e("Testing", url.toString()); 39 | URLConnection api = url.openConnection(); 40 | InputStreamReader inputStreamReader = new InputStreamReader(api.getInputStream()); 41 | FoodSearch response = new Gson().fromJson(inputStreamReader, FoodSearch.class); 42 | return response.getFoods(); 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | } 47 | 48 | private String[] generateOauthParams(String i) { 49 | return new String[]{ 50 | "oauth_consumer_key=" + Globals.APP_KEY, 51 | "oauth_signature_method=HMAC-SHA1", 52 | "oauth_timestamp=" + 53 | Long.valueOf(System.currentTimeMillis() * 1000).toString(), 54 | "oauth_nonce=" + nonce(), 55 | "oauth_version=1.0", 56 | "format=json", 57 | "page_number=" + i, 58 | "max_results=" + 20}; 59 | } 60 | 61 | private String sign(String method, String uri, String[] params) { 62 | String[] p = {method, Uri.encode(uri), Uri.encode(paramify(params))}; 63 | String s = join(p, "&"); 64 | SecretKey sk = new SecretKeySpec(Globals.APP_SECRET.getBytes(), Globals.HMAC_SHA1_ALGORITHM); 65 | try { 66 | Mac m = Mac.getInstance(Globals.HMAC_SHA1_ALGORITHM); 67 | m.init(sk); 68 | return Uri.encode(new String(Base64.encode(m.doFinal(s.getBytes()), Base64.DEFAULT)).trim()); 69 | } catch (java.security.NoSuchAlgorithmException e) { 70 | Log.w("FatSecret_TEST FAIL", e.getMessage()); 71 | return null; 72 | } catch (java.security.InvalidKeyException e) { 73 | Log.w("FatSecret_TEST FAIL", e.getMessage()); 74 | return null; 75 | } 76 | } 77 | 78 | private String paramify(String[] params) { 79 | String[] p = Arrays.copyOf(params, params.length); 80 | Arrays.sort(p); 81 | return join(p, "&"); 82 | } 83 | 84 | private String join(String[] array, String separator) { 85 | StringBuilder b = new StringBuilder(); 86 | for (int i = 0; i < array.length; i++) { 87 | if (i > 0) 88 | b.append(separator); 89 | b.append(array[i]); 90 | } 91 | return b.toString(); 92 | } 93 | 94 | private String nonce() { 95 | Random r = new Random(); 96 | StringBuilder n = new StringBuilder(); 97 | for (int i = 0; i < r.nextInt(8) + 2; i++) 98 | n.append(r.nextInt(26) + 'a'); 99 | return n.toString(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/db/SearchDao.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.db; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.persistence.room.Dao; 5 | import android.arch.persistence.room.Delete; 6 | import android.arch.persistence.room.Insert; 7 | import android.arch.persistence.room.OnConflictStrategy; 8 | import android.arch.persistence.room.Query; 9 | 10 | import java.util.List; 11 | 12 | @Dao 13 | public abstract class SearchDao { 14 | 15 | @Insert(onConflict = OnConflictStrategy.REPLACE) 16 | public abstract void insertSearches(SearchEntity... searchEntities); 17 | 18 | @Delete 19 | public abstract void deleteSearches(SearchEntity... searchEntities); 20 | 21 | @Query("SELECT * FROM search_table ORDER BY timestamp DESC LIMIT 5") 22 | public abstract List getSearchList(); 23 | 24 | @Query("SELECT * FROM search_table ORDER BY timestamp DESC LIMIT 5") 25 | public abstract LiveData> getSearchListLive(); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/db/SearchDatabase.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.db; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.Room; 5 | import android.arch.persistence.room.RoomDatabase; 6 | import android.content.Context; 7 | 8 | @Database(entities = SearchEntity.class, version = 1, exportSchema = false) 9 | public abstract class SearchDatabase extends RoomDatabase { 10 | private static final String DATABASE_NAME = "search-db"; 11 | private static SearchDatabase instance; 12 | 13 | public abstract SearchDao getSearchDao(); 14 | 15 | public static SearchDatabase getInstance(final Context context) { 16 | if (instance == null) { 17 | synchronized (SearchDatabase.class) { 18 | if (instance == null) { 19 | instance = buildDatabase(context.getApplicationContext()); 20 | } 21 | } 22 | } 23 | return instance; 24 | } 25 | 26 | private static SearchDatabase buildDatabase(final Context context) { 27 | return Room.databaseBuilder(context, SearchDatabase.class, DATABASE_NAME).build(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/db/SearchEntity.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.db; 2 | 3 | import android.arch.persistence.room.Entity; 4 | import android.arch.persistence.room.PrimaryKey; 5 | import android.support.annotation.NonNull; 6 | 7 | @Entity(tableName = "search_table") 8 | public class SearchEntity { 9 | @PrimaryKey 10 | @NonNull 11 | private String text; 12 | private long timestamp; 13 | 14 | public SearchEntity(@NonNull String text, long timestamp) { 15 | this.text = text; 16 | this.timestamp = timestamp; 17 | } 18 | 19 | @NonNull 20 | public String getText() { 21 | return text; 22 | } 23 | 24 | public void setText(@NonNull String text) { 25 | this.text = text; 26 | } 27 | 28 | public long getTimestamp() { 29 | return timestamp; 30 | } 31 | 32 | public void setTimestamp(long timestamp) { 33 | this.timestamp = timestamp; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/db/SearchRepository.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.db; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.arch.lifecycle.MutableLiveData; 5 | 6 | import com.eugene.fithealth.util.AppExecutors; 7 | 8 | import java.util.List; 9 | 10 | 11 | public class SearchRepository { 12 | private static SearchRepository instance; 13 | private final SearchDatabase database; 14 | private final AppExecutors appExecutors; 15 | 16 | private SearchRepository(AppExecutors appExecutors, final SearchDatabase database) { 17 | this.appExecutors = appExecutors; 18 | this.database = database; 19 | } 20 | 21 | public static SearchRepository getInstance(final AppExecutors appExecutors, final SearchDatabase database) { 22 | if (instance == null) { 23 | synchronized (SearchRepository.class) { 24 | if (instance == null) { 25 | instance = new SearchRepository(appExecutors, database); 26 | } 27 | } 28 | } 29 | return instance; 30 | } 31 | 32 | public void insertSearches(final SearchEntity... searchEntities) { 33 | appExecutors.diskIO().execute(new Runnable() { 34 | @Override 35 | public void run() { 36 | database.getSearchDao().insertSearches(searchEntities); 37 | } 38 | }); 39 | } 40 | 41 | public void deleteSearches(final SearchEntity... searchEntities) { 42 | appExecutors.diskIO().execute(new Runnable() { 43 | @Override 44 | public void run() { 45 | database.getSearchDao().deleteSearches(searchEntities); 46 | } 47 | }); 48 | } 49 | 50 | public LiveData> getSearchListLive() { 51 | return database.getSearchDao().getSearchListLive(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/model/Food.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eugene.fithealth.model; 3 | 4 | import com.google.gson.annotations.Expose; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | public class Food { 8 | 9 | @SerializedName("food_description") 10 | @Expose 11 | private String foodDescription; 12 | @SerializedName("food_id") 13 | @Expose 14 | private String foodId; 15 | @SerializedName("food_name") 16 | @Expose 17 | private String foodName; 18 | @SerializedName("food_type") 19 | @Expose 20 | private String foodType; 21 | @SerializedName("food_url") 22 | @Expose 23 | private String foodUrl; 24 | 25 | public String getFoodDescription() { 26 | return foodDescription; 27 | } 28 | 29 | public void setFoodDescription(String foodDescription) { 30 | this.foodDescription = foodDescription; 31 | } 32 | 33 | public String getFoodId() { 34 | return foodId; 35 | } 36 | 37 | public void setFoodId(String foodId) { 38 | this.foodId = foodId; 39 | } 40 | 41 | public String getFoodName() { 42 | return foodName; 43 | } 44 | 45 | public void setFoodName(String foodName) { 46 | this.foodName = foodName; 47 | } 48 | 49 | public String getFoodType() { 50 | return foodType; 51 | } 52 | 53 | public void setFoodType(String foodType) { 54 | this.foodType = foodType; 55 | } 56 | 57 | public String getFoodUrl() { 58 | return foodUrl; 59 | } 60 | 61 | public void setFoodUrl(String foodUrl) { 62 | this.foodUrl = foodUrl; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/model/FoodSearch.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eugene.fithealth.model; 3 | 4 | import com.google.gson.annotations.Expose; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | public class FoodSearch { 8 | 9 | @SerializedName("foods") 10 | @Expose 11 | private Foods foods; 12 | 13 | public Foods getFoods() { 14 | return foods; 15 | } 16 | 17 | public void setFoods(Foods foods) { 18 | this.foods = foods; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/model/Foods.java: -------------------------------------------------------------------------------- 1 | 2 | package com.eugene.fithealth.model; 3 | 4 | import com.google.gson.annotations.Expose; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | import java.util.List; 8 | 9 | public class Foods { 10 | 11 | @SerializedName("food") 12 | @Expose 13 | private List food; 14 | @SerializedName("max_results") 15 | @Expose 16 | private String maxResults; 17 | @SerializedName("page_number") 18 | @Expose 19 | private String pageNumber; 20 | @SerializedName("total_results") 21 | @Expose 22 | private String totalResults; 23 | 24 | public List getFood() { 25 | return food; 26 | } 27 | 28 | public void setFood(List food) { 29 | this.food = food; 30 | } 31 | 32 | public String getMaxResults() { 33 | return maxResults; 34 | } 35 | 36 | public void setMaxResults(String maxResults) { 37 | this.maxResults = maxResults; 38 | } 39 | 40 | public String getPageNumber() { 41 | return pageNumber; 42 | } 43 | 44 | public void setPageNumber(String pageNumber) { 45 | this.pageNumber = pageNumber; 46 | } 47 | 48 | public String getTotalResults() { 49 | return totalResults; 50 | } 51 | 52 | public void setTotalResults(String totalResults) { 53 | this.totalResults = totalResults; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/search/FatSecretRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.search; 2 | 3 | 4 | import android.support.v7.util.DiffUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.ViewGroup; 8 | 9 | import com.eugene.fithealth.databinding.RecyclerFatSecretItemBinding; 10 | import com.eugene.fithealth.model.Food; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class FatSecretRecyclerAdapter extends RecyclerView.Adapter { 16 | private List itemList = new ArrayList<>(); 17 | 18 | void setItems(final List items) { 19 | DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { 20 | @Override 21 | public int getOldListSize() { 22 | return itemList.size(); 23 | } 24 | 25 | @Override 26 | public int getNewListSize() { 27 | return items.size(); 28 | } 29 | 30 | @Override 31 | public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 32 | Food newItem = items.get(newItemPosition); 33 | Food oldItem = itemList.get(oldItemPosition); 34 | return oldItem.getFoodId().equals(newItem.getFoodId()); 35 | } 36 | 37 | @Override 38 | public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 39 | Food oldItem = itemList.get(oldItemPosition); 40 | Food newItem = items.get(newItemPosition); 41 | return oldItem.getFoodId().equals(newItem.getFoodId()) && newItem.getFoodDescription().equals(oldItem.getFoodDescription()); 42 | } 43 | }); 44 | this.itemList.clear(); 45 | this.itemList.addAll(items); 46 | diffResult.dispatchUpdatesTo(this); 47 | } 48 | 49 | @Override 50 | public FatSecretViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 51 | return new FatSecretViewHolder(RecyclerFatSecretItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); 52 | } 53 | 54 | @Override 55 | public void onBindViewHolder(FatSecretViewHolder holder, int position) { 56 | holder.bindItem(itemList.get(position)); 57 | } 58 | 59 | @Override 60 | public int getItemCount() { 61 | return itemList.size(); 62 | } 63 | 64 | class FatSecretViewHolder extends RecyclerView.ViewHolder { 65 | RecyclerFatSecretItemBinding binding; 66 | 67 | FatSecretViewHolder(RecyclerFatSecretItemBinding binding) { 68 | super(binding.getRoot()); 69 | this.binding = binding; 70 | } 71 | 72 | void bindItem(Food food) { 73 | binding.setObject(food); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/search/SearchActivity.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.search; 2 | 3 | import android.arch.lifecycle.Observer; 4 | import android.arch.lifecycle.ViewModelProviders; 5 | import android.databinding.DataBindingUtil; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.DividerItemDecoration; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | 13 | import com.android.materialsearchview.MaterialSearchView; 14 | import com.eugene.fithealth.R; 15 | import com.eugene.fithealth.databinding.ActivitySearchBinding; 16 | import com.eugene.fithealth.db.SearchEntity; 17 | import com.eugene.fithealth.model.Food; 18 | 19 | import java.util.List; 20 | 21 | public class SearchActivity extends AppCompatActivity implements 22 | MaterialSearchView.OnQueryTextListener, 23 | SearchRecyclerAdapter.SearchRecyclerInterface { 24 | 25 | private ActivitySearchBinding binding; 26 | private SearchViewModel model; 27 | private SearchRecyclerAdapter adapterSearch; 28 | private FatSecretRecyclerAdapter adapterFatSecret; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | binding = DataBindingUtil.setContentView(this, R.layout.activity_search); 34 | setSupportActionBar(binding.toolbar); 35 | adapterSearch = new SearchRecyclerAdapter(this); 36 | adapterFatSecret = new FatSecretRecyclerAdapter(); 37 | binding.recycler.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 38 | binding.recycler.setAdapter(adapterFatSecret); 39 | binding.searchHolder.setSearchRecyclerAdapter(adapterSearch); 40 | binding.searchHolder.addQueryTextListener(this); 41 | model = ViewModelProviders.of(this).get(SearchViewModel.class); 42 | observerFoodSearchList(model); 43 | observeSearchList(model); 44 | } 45 | 46 | @Override 47 | public void onBackPressed() { 48 | if (binding.searchHolder.isVisible()) { 49 | binding.searchHolder.hideSearch(); 50 | } else { 51 | super.onBackPressed(); 52 | } 53 | } 54 | 55 | // MaterialSearchView listeners 56 | @Override 57 | public boolean onQueryTextSubmit(String query) { 58 | binding.searchHolder.hideRecycler(); 59 | model.searchFood(query, 0); 60 | return true; 61 | } 62 | 63 | @Override 64 | public boolean onQueryTextChange(String newText) { 65 | binding.searchHolder.showRecycler(); 66 | return true; 67 | } 68 | 69 | // adapter item clicked 70 | @Override 71 | public void onSearchItemClicked(String query) { 72 | binding.searchHolder.setSearchText(query); 73 | binding.searchHolder.hideRecycler(); 74 | onQueryTextSubmit(query); 75 | } 76 | 77 | @Override 78 | public void onSearchDeleteClicked(SearchEntity searchEntity) { 79 | model.deleteSearchEntity(searchEntity); 80 | } 81 | 82 | private void observeSearchList(SearchViewModel model) { 83 | model.getSearchListLive().observe(this, new Observer>() { 84 | @Override 85 | public void onChanged(@Nullable List searchEntities) { 86 | adapterSearch.setItems(searchEntities); 87 | } 88 | }); 89 | } 90 | 91 | private void observerFoodSearchList(SearchViewModel model) { 92 | model.getFoodList().observe(this, new Observer>() { 93 | @Override 94 | public void onChanged(@Nullable List foods) { 95 | adapterFatSecret.setItems(foods); 96 | } 97 | }); 98 | } 99 | 100 | @Override 101 | public boolean onCreateOptionsMenu(Menu menu) { 102 | getMenuInflater().inflate(R.menu.menu_main, menu); 103 | return true; 104 | } 105 | 106 | @Override 107 | public boolean onOptionsItemSelected(MenuItem item) { 108 | int id = item.getItemId(); 109 | if (id == R.id.action_search) { 110 | binding.searchHolder.showSearch(); 111 | return true; 112 | } 113 | return super.onOptionsItemSelected(item); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/search/SearchRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.search; 2 | 3 | 4 | import android.support.v7.util.DiffUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import com.eugene.fithealth.databinding.RecyclerSearchItemBinding; 11 | import com.eugene.fithealth.db.SearchEntity; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class SearchRecyclerAdapter extends RecyclerView.Adapter { 17 | private List itemList = new ArrayList<>(); 18 | private SearchRecyclerInterface listener; 19 | 20 | SearchRecyclerAdapter(SearchRecyclerInterface listener) { 21 | this.listener = listener; 22 | } 23 | 24 | public interface SearchRecyclerInterface { 25 | void onSearchItemClicked(String query); 26 | 27 | void onSearchDeleteClicked(SearchEntity searchEntity); 28 | } 29 | 30 | void setItems(final List items) { 31 | DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { 32 | @Override 33 | public int getOldListSize() { 34 | return itemList.size(); 35 | } 36 | 37 | @Override 38 | public int getNewListSize() { 39 | return items.size(); 40 | } 41 | 42 | @Override 43 | public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 44 | SearchEntity newItem = items.get(newItemPosition); 45 | SearchEntity oldItem = itemList.get(oldItemPosition); 46 | return oldItem.getText().equals(newItem.getText()); 47 | } 48 | 49 | @Override 50 | public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 51 | SearchEntity oldItem = itemList.get(oldItemPosition); 52 | SearchEntity newItem = items.get(newItemPosition); 53 | return oldItem.getText().equals(newItem.getText()) && newItem.getTimestamp() == oldItem.getTimestamp(); 54 | } 55 | }); 56 | this.itemList.clear(); 57 | this.itemList.addAll(items); 58 | diffResult.dispatchUpdatesTo(this); 59 | } 60 | 61 | @Override 62 | public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 63 | return new SearchViewHolder(RecyclerSearchItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); 64 | } 65 | 66 | @Override 67 | public void onBindViewHolder(SearchViewHolder holder, int position) { 68 | holder.bindView(itemList.get(position)); 69 | } 70 | 71 | @Override 72 | public int getItemCount() { 73 | return itemList.size(); 74 | } 75 | 76 | class SearchViewHolder extends RecyclerView.ViewHolder { 77 | private RecyclerSearchItemBinding binding; 78 | 79 | SearchViewHolder(RecyclerSearchItemBinding binding) { 80 | super(binding.getRoot()); 81 | this.binding = binding; 82 | } 83 | 84 | void bindView(final SearchEntity entity) { 85 | binding.setText(entity.getText()); 86 | binding.itemHolder.setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View view) { 89 | listener.onSearchItemClicked(entity.getText()); 90 | } 91 | }); 92 | binding.imgDelete.setOnClickListener(new View.OnClickListener() { 93 | @Override 94 | public void onClick(View view) { 95 | listener.onSearchDeleteClicked(entity); 96 | } 97 | }); 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/search/SearchViewModel.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.search; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Application; 5 | import android.arch.lifecycle.AndroidViewModel; 6 | import android.arch.lifecycle.LiveData; 7 | import android.arch.lifecycle.MutableLiveData; 8 | import android.os.AsyncTask; 9 | import android.support.annotation.NonNull; 10 | import android.text.TextUtils; 11 | 12 | import com.eugene.fithealth.AppActivity; 13 | import com.eugene.fithealth.api.FatSecretRequest; 14 | import com.eugene.fithealth.db.SearchEntity; 15 | import com.eugene.fithealth.db.SearchRepository; 16 | import com.eugene.fithealth.model.Food; 17 | import com.eugene.fithealth.model.Foods; 18 | 19 | import org.w3c.dom.Text; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Date; 23 | import java.util.List; 24 | import java.util.UUID; 25 | 26 | public class SearchViewModel extends AndroidViewModel { 27 | private SearchRepository repository; 28 | private MutableLiveData> getFoodList; 29 | private LiveData> getSearchListLive; 30 | 31 | public SearchViewModel(@NonNull Application application) { 32 | super(application); 33 | repository = ((AppActivity) application).getRepository(); 34 | initSearch(); 35 | } 36 | 37 | private void initSearch() { 38 | getSearchListLive = repository.getSearchListLive(); 39 | } 40 | 41 | private void insertSearchEntity(String query) { 42 | repository.insertSearches(new SearchEntity(query, new Date().getTime())); 43 | } 44 | 45 | void deleteSearchEntity(SearchEntity searchEntity) { 46 | repository.deleteSearches(searchEntity); 47 | } 48 | 49 | void searchFood(String query, int page) { 50 | insertSearchEntity(query); 51 | String[] searchQuery = new String[2]; 52 | searchQuery[0] = query; 53 | searchQuery[1] = String.valueOf(page); 54 | new FoodTask().execute(searchQuery); 55 | } 56 | 57 | LiveData> getSearchListLive() { 58 | return getSearchListLive; 59 | } 60 | 61 | MutableLiveData> getFoodList() { 62 | if (getFoodList == null) { 63 | getFoodList = new MutableLiveData<>(); 64 | } 65 | return getFoodList; 66 | } 67 | 68 | @Override 69 | protected void onCleared() { 70 | super.onCleared(); 71 | } 72 | 73 | @SuppressLint("StaticFieldLeak") 74 | class FoodTask extends AsyncTask { 75 | @Override 76 | protected Foods doInBackground(String... query) { 77 | return new FatSecretRequest().getFoods(query[0], query[1]); 78 | } 79 | 80 | @Override 81 | protected void onPostExecute(Foods foods) { 82 | super.onPostExecute(foods); 83 | getFoodList().setValue(foods.getFood()); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/util/AppExecutors.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.util; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.support.annotation.NonNull; 6 | 7 | import java.util.concurrent.Executor; 8 | import java.util.concurrent.Executors; 9 | 10 | public class AppExecutors { 11 | 12 | private final Executor mDiskIO; 13 | 14 | private final Executor mNetworkIO; 15 | 16 | private final Executor mMainThread; 17 | 18 | private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) { 19 | this.mDiskIO = diskIO; 20 | this.mNetworkIO = networkIO; 21 | this.mMainThread = mainThread; 22 | } 23 | 24 | public AppExecutors() { 25 | this(Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(3), 26 | new MainThreadExecutor()); 27 | } 28 | 29 | public Executor diskIO() { 30 | return mDiskIO; 31 | } 32 | 33 | public Executor networkIO() { 34 | return mNetworkIO; 35 | } 36 | 37 | public Executor mainThread() { 38 | return mMainThread; 39 | } 40 | 41 | private static class MainThreadExecutor implements Executor { 42 | private Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 43 | 44 | @Override 45 | public void execute(@NonNull Runnable command) { 46 | mainThreadHandler.post(command); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/eugene/fithealth/util/Globals.java: -------------------------------------------------------------------------------- 1 | package com.eugene.fithealth.util; 2 | 3 | public class Globals { 4 | final static public String APP_METHOD = "GET"; 5 | final static public String APP_KEY = "5ae8743c128b4edb9778933725f14c76"; 6 | final static public String APP_SECRET = "0001f83d88984ac5818fcf4fcfb0bd8a&"; 7 | final static public String APP_URL = "http://platform.fatsecret.com/rest/server.api/"; 8 | final public static String HMAC_SHA1_ALGORITHM = "HmacSHA1"; 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 26 | 27 | 28 | 37 | 38 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_search.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recycler_fat_secret_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 23 | 24 | 33 | 34 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recycler_search_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 22 | 23 | 32 | 33 | 46 | 47 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00BCD4 4 | #00838F 5 | #BDBDBD 6 | #00BCD4 7 | #00838F 8 | #BDBDBD 9 | #6D6D6D 10 | #FEFEFE 11 | #BDBDBD 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 8dp 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Fit Health 3 | Hello world! 4 | Settings 5 | Search 6 | Back 7 | SEARCH 8 | Voice Cancel 9 | 10 | 11 | database_log 12 | Filler 13 | SearchActivity 14 | Hello World from section: %1$d 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | } 10 | allprojects { 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Dec 28 12:52:41 EST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/device-2017-12-29-144222.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/images/device-2017-12-29-144222.png -------------------------------------------------------------------------------- /images/device-2017-12-29-144241.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/images/device-2017-12-29-144241.png -------------------------------------------------------------------------------- /materialsearchview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /materialsearchview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | minSdkVersion 16 7 | targetSdkVersion 27 8 | versionCode 1 9 | versionName "1.0" 10 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 11 | vectorDrawables.useSupportLibrary = true 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | dataBinding { 21 | enabled = true 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'com.android.support:appcompat-v7:27.0.2' 28 | implementation 'com.android.support:design:27.0.2' 29 | implementation 'com.android.support:cardview-v7:27.0.2' 30 | implementation 'com.android.support:recyclerview-v7:27.0.2' 31 | implementation 'com.android.support:support-vector-drawable:27.0.2' 32 | } 33 | -------------------------------------------------------------------------------- /materialsearchview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /materialsearchview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /materialsearchview/src/main/java/com/android/materialsearchview/MaterialSearchView.java: -------------------------------------------------------------------------------- 1 | package com.android.materialsearchview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.databinding.DataBindingUtil; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffColorFilter; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Build; 12 | import android.support.annotation.NonNull; 13 | import android.support.annotation.Nullable; 14 | import android.support.v7.widget.CardView; 15 | import android.support.v7.widget.RecyclerView; 16 | import android.text.Editable; 17 | import android.text.TextUtils; 18 | import android.text.TextWatcher; 19 | import android.util.AttributeSet; 20 | import android.view.KeyEvent; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewAnimationUtils; 24 | import android.widget.EditText; 25 | import android.widget.ImageView; 26 | import android.widget.TextView; 27 | 28 | import com.android.materialsearchview.databinding.ViewSearchBinding; 29 | 30 | public class MaterialSearchView extends CardView { 31 | static final String LOG_TAG = "MaterialSearchView"; 32 | private static final int ANIMATION_DURATION = 250; 33 | 34 | private boolean animateSearchView; 35 | private int searchMenuPosition; 36 | private String searchHint; 37 | private int searchTextColor; 38 | private Integer searchIconColor; 39 | private CharSequence mOldQueryText; 40 | private CharSequence mUserQuery; 41 | private boolean hasAdapter = false; 42 | private boolean hideSearch = false; 43 | 44 | private ViewSearchBinding b; 45 | private OnQueryTextListener listenerQuery; 46 | private OnVisibilityListener visibilityListener; 47 | 48 | public MaterialSearchView(@NonNull Context context) { 49 | super(context); 50 | init(context, null); 51 | } 52 | 53 | 54 | public MaterialSearchView(@NonNull Context context, @Nullable AttributeSet attrs) { 55 | super(context, attrs); 56 | init(context, attrs); 57 | } 58 | 59 | public MaterialSearchView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | init(context, attrs); 62 | } 63 | 64 | private void init(Context context, AttributeSet attrs) { 65 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaterialSearchView, 0, 0); 66 | 67 | final LayoutInflater inflater = LayoutInflater.from(context); 68 | b = DataBindingUtil.inflate(inflater, R.layout.view_search, this, true); 69 | animateSearchView = a.getBoolean(R.styleable.MaterialSearchView_search_animate, true); 70 | searchMenuPosition = a.getInteger(R.styleable.MaterialSearchView_search_menu_position, 0); 71 | searchHint = a.getString(R.styleable.MaterialSearchView_search_hint); 72 | searchTextColor = a.getColor(R.styleable.MaterialSearchView_search_text_color, getResources().getColor(android.R.color.black)); 73 | searchIconColor = a.getColor(R.styleable.MaterialSearchView_search_icon_color, getResources().getColor(android.R.color.black)); 74 | 75 | b.imgBack.setOnClickListener(mOnClickListener); 76 | b.imgClear.setOnClickListener(mOnClickListener); 77 | b.editText.addTextChangedListener(mTextWatcher); 78 | b.editText.setOnEditorActionListener(mOnEditorActionListener); 79 | 80 | final int imeOptions = a.getInt(R.styleable.MaterialSearchView_search_ime_options, -1); 81 | if (imeOptions != -1) { 82 | setImeOptions(imeOptions); 83 | } 84 | 85 | final int inputType = a.getInt(R.styleable.MaterialSearchView_search_input_type, -1); 86 | if (inputType != -1) { 87 | setInputType(inputType); 88 | } 89 | 90 | boolean focusable; 91 | focusable = a.getBoolean(R.styleable.MaterialSearchView_search_focusable, true); 92 | b.editText.setFocusable(focusable); 93 | 94 | a.recycle(); 95 | 96 | b.editText.setHint(getSearchHint()); 97 | b.editText.setTextColor(getTextColor()); 98 | setDrawableTint(b.imgBack.getDrawable(), searchIconColor); 99 | setDrawableTint(b.imgClear.getDrawable(), searchIconColor); 100 | checkForAdapter(); 101 | } 102 | 103 | 104 | private OnClickListener mOnClickListener = new OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | if (v == b.imgClear) { 108 | setSearchText(null); 109 | } else if (v == b.imgBack) { 110 | hideSearch(); 111 | } 112 | } 113 | }; 114 | /** 115 | * Callback to watch the text field for empty/non-empty 116 | */ 117 | private TextWatcher mTextWatcher = new TextWatcher() { 118 | @Override 119 | public void beforeTextChanged(CharSequence s, int start, int before, int after) { 120 | } 121 | 122 | @Override 123 | public void onTextChanged(CharSequence s, int start, int before, int after) { 124 | submitText(s); 125 | } 126 | 127 | @Override 128 | public void afterTextChanged(Editable s) { 129 | } 130 | }; 131 | 132 | private final TextView.OnEditorActionListener mOnEditorActionListener = new TextView.OnEditorActionListener() { 133 | @Override 134 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 135 | onSubmitQuery(); 136 | return true; 137 | } 138 | }; 139 | 140 | 141 | private void submitText(CharSequence s) { 142 | mUserQuery = b.editText.getText(); 143 | updateClearButton(); 144 | if (listenerQuery != null && !TextUtils.equals(s, mOldQueryText)) { 145 | listenerQuery.onQueryTextChange(String.valueOf(s)); 146 | } 147 | mOldQueryText = s.toString(); 148 | } 149 | 150 | void onSubmitQuery() { 151 | if (listenerQuery != null) { 152 | b.linearItemsHolder.setVisibility(GONE); 153 | listenerQuery.onQueryTextSubmit(b.editText.getText().toString()); 154 | } 155 | } 156 | 157 | private void updateClearButton() { 158 | final boolean hasText = !TextUtils.isEmpty(b.editText.getText()); 159 | b.imgClear.setVisibility(hasText ? VISIBLE : GONE); 160 | } 161 | 162 | 163 | public void setQuery(CharSequence query, boolean submit) { 164 | b.editText.setText(query); 165 | if (query != null) { 166 | mUserQuery = query; 167 | } 168 | // If the query is not empty and submit is requested, submit the query 169 | if (submit && !TextUtils.isEmpty(query)) { 170 | onSubmitQuery(); 171 | } 172 | } 173 | 174 | /** 175 | * Sets the IME options on the query text field. 176 | */ 177 | public void setImeOptions(int imeOptions) { 178 | b.editText.setImeOptions(imeOptions); 179 | } 180 | 181 | /** 182 | * Returns the IME options set on the query text field. 183 | */ 184 | public int getImeOptions() { 185 | return b.editText.getImeOptions(); 186 | } 187 | 188 | /** 189 | * Sets the input type on the query text field. 190 | */ 191 | public void setInputType(int inputType) { 192 | b.editText.setInputType(inputType); 193 | } 194 | 195 | /** 196 | * Returns the input type set on the query text field. 197 | */ 198 | public int getInputType() { 199 | return b.editText.getInputType(); 200 | } 201 | 202 | 203 | public boolean isVisible() { 204 | return getVisibility() == VISIBLE; 205 | } 206 | 207 | public void setSearchText(String queryText) { 208 | b.editText.setText(queryText); 209 | } 210 | 211 | public void setSearchRecyclerAdapter(RecyclerView.Adapter adapter) { 212 | b.recycler.setAdapter(adapter); 213 | checkForAdapter(); 214 | } 215 | 216 | public void hideRecycler() { 217 | hideSearch = true; 218 | b.linearItemsHolder.setVisibility(GONE); 219 | } 220 | 221 | public void showRecycler() { 222 | hideSearch = false; 223 | b.linearItemsHolder.setVisibility(VISIBLE); 224 | } 225 | 226 | 227 | public void showSearch() { 228 | hideSearch = false; 229 | checkForAdapter(); 230 | setVisibility(View.VISIBLE); 231 | if (animateSearchView) 232 | if (Build.VERSION.SDK_INT >= 21) { 233 | Animator animatorShow = ViewAnimationUtils.createCircularReveal( 234 | this, // view 235 | getCenterX(), // center x 236 | (int) convertDpToPixel(23), // center y 237 | 0, // start radius 238 | (float) Math.hypot(getWidth(), getHeight()) // end radius 239 | ); 240 | animatorShow.addListener(new AnimatorListenerAdapter() { 241 | @Override 242 | public void onAnimationEnd(Animator animation) { 243 | super.onAnimationEnd(animation); 244 | if (visibilityListener != null) { 245 | visibilityListener.onOpen(); 246 | } 247 | if (hasAdapter) { 248 | b.linearItemsHolder.setVisibility(View.VISIBLE); 249 | } 250 | } 251 | }); 252 | animatorShow.start(); 253 | } else { 254 | if (hasAdapter) { 255 | b.linearItemsHolder.setVisibility(View.VISIBLE); 256 | } 257 | } 258 | } 259 | 260 | public void hideSearch() { 261 | checkForAdapter(); 262 | if (hasAdapter) { 263 | b.linearItemsHolder.setVisibility(View.GONE); 264 | } 265 | if (animateSearchView) { 266 | if (Build.VERSION.SDK_INT >= 21) { 267 | Animator animatorHide = ViewAnimationUtils.createCircularReveal( 268 | this, // View 269 | getCenterX(), // center x 270 | (int) convertDpToPixel(23), // center y 271 | (float) Math.hypot(getWidth(), getHeight()), // start radius 272 | 0// end radius 273 | ); 274 | animatorHide.setStartDelay(hasAdapter ? ANIMATION_DURATION : 0); 275 | animatorHide.addListener(new AnimatorListenerAdapter() { 276 | @Override 277 | public void onAnimationEnd(Animator animation) { 278 | super.onAnimationEnd(animation); 279 | if (visibilityListener != null) { 280 | visibilityListener.onClose(); 281 | } 282 | setVisibility(GONE); 283 | } 284 | }); 285 | animatorHide.start(); 286 | } else { 287 | setVisibility(GONE); 288 | } 289 | } 290 | } 291 | 292 | public void setMenuPosition(int menuPosition) { 293 | this.searchMenuPosition = menuPosition; 294 | invalidate(); 295 | requestFocus(); 296 | } 297 | 298 | // search searchHint 299 | public String getSearchHint() { 300 | if (TextUtils.isEmpty(searchHint)) { 301 | return "Search"; 302 | } 303 | return searchHint; 304 | } 305 | 306 | public void setSearchHint(String searchHint) { 307 | this.searchHint = searchHint; 308 | invalidate(); 309 | requestFocus(); 310 | } 311 | 312 | // text color 313 | public void setTextColor(int textColor) { 314 | this.searchTextColor = textColor; 315 | invalidate(); 316 | requestFocus(); 317 | } 318 | 319 | public void setSearchIconColor(int searchIconColor) { 320 | this.searchIconColor = searchIconColor; 321 | invalidate(); 322 | requestFocus(); 323 | } 324 | 325 | public int getTextColor() { 326 | return searchTextColor; 327 | } 328 | 329 | /** 330 | * Get views 331 | */ 332 | public EditText getEditText() { 333 | return b.editText; 334 | } 335 | 336 | public ImageView getImageBack() { 337 | return b.imgBack; 338 | } 339 | 340 | public ImageView getImageClear() { 341 | return b.imgClear; 342 | } 343 | 344 | public RecyclerView getRecyclerView() { 345 | return b.recycler; 346 | } 347 | 348 | /** 349 | * Interface 350 | */ 351 | public void addQueryTextListener(OnQueryTextListener listenerQuery) { 352 | this.listenerQuery = listenerQuery; 353 | } 354 | 355 | public interface OnQueryTextListener { 356 | boolean onQueryTextSubmit(String query); 357 | 358 | boolean onQueryTextChange(String newText); 359 | } 360 | 361 | public void setOnVisibilityListener(OnVisibilityListener visibilityListener) { 362 | this.visibilityListener = visibilityListener; 363 | } 364 | 365 | 366 | public interface OnVisibilityListener { 367 | boolean onOpen(); 368 | 369 | boolean onClose(); 370 | } 371 | 372 | /** 373 | * Helpers 374 | */ 375 | public void setDrawableTint(Drawable resDrawable, int resColor) { 376 | resDrawable.setColorFilter(new PorterDuffColorFilter(resColor, PorterDuff.Mode.SRC_ATOP)); 377 | resDrawable.mutate(); 378 | } 379 | 380 | public float convertDpToPixel(float dp) { 381 | return dp * (getContext().getResources().getDisplayMetrics().densityDpi / 160f); 382 | } 383 | 384 | private void checkForAdapter() { 385 | hasAdapter = !hideSearch && b.recycler.getAdapter() != null && b.recycler.getAdapter().getItemCount() > 0; 386 | } 387 | 388 | /* 389 | TODO not correct but close 390 | Need to do correct measure 391 | */ 392 | private int getCenterX() { 393 | int icons = (int) (getWidth() - convertDpToPixel(21 * (1 + searchMenuPosition))); 394 | int padding = (int) convertDpToPixel(searchMenuPosition * 21); 395 | return icons - padding; 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/drawable-v21/image_background.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/drawable/image_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/layout/search_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/layout/view_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 24 | 25 | 41 | 42 | 57 | 58 | 65 | 66 | 71 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36dp 4 | 8dp 5 | 16sp 6 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48dp 4 | 12dp 5 | 18sp 6 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MaterialSearchView 3 | Search 4 | 5 | -------------------------------------------------------------------------------- /materialsearchview/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /oldsearchview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /oldsearchview/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | minSdkVersion 16 7 | targetSdkVersion 27 8 | versionCode 1 9 | versionName "1.0" 10 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 11 | vectorDrawables.useSupportLibrary = true 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | dataBinding { 21 | enabled = true 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'com.android.support:appcompat-v7:27.0.2' 28 | implementation 'com.android.support:design:27.0.2' 29 | implementation 'com.android.support:cardview-v7:27.0.2' 30 | implementation 'com.android.support:recyclerview-v7:27.0.2' 31 | implementation 'com.android.support:support-vector-drawable:27.0.2' 32 | } 33 | -------------------------------------------------------------------------------- /oldsearchview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /oldsearchview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /oldsearchview/src/main/java/eh/workout/journal/com/oldsearchview/MaterialSearchView.java: -------------------------------------------------------------------------------- 1 | package eh.workout.journal.com.oldsearchview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.databinding.DataBindingUtil; 8 | import android.graphics.PorterDuff; 9 | import android.graphics.PorterDuffColorFilter; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Build; 12 | import android.support.annotation.NonNull; 13 | import android.support.annotation.Nullable; 14 | import android.support.v7.widget.CardView; 15 | import android.support.v7.widget.RecyclerView; 16 | import android.text.Editable; 17 | import android.text.TextUtils; 18 | import android.text.TextWatcher; 19 | import android.util.AttributeSet; 20 | import android.view.KeyEvent; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewAnimationUtils; 24 | import android.view.inputmethod.EditorInfo; 25 | import android.widget.EditText; 26 | import android.widget.ImageView; 27 | import android.widget.TextView; 28 | 29 | import eh.workout.journal.com.oldsearchview.databinding.ViewSearchBinding; 30 | 31 | public class MaterialSearchView extends CardView implements View.OnClickListener, TextWatcher, TextView.OnEditorActionListener { 32 | private static final int ANIMATION_DURATION = 250; 33 | private int menuPosition; 34 | private String searchHint; 35 | private int textColor; 36 | private Integer iconColor; 37 | private boolean hasAdapter = false; 38 | private boolean hideSearch = false; 39 | 40 | private ViewSearchBinding binding; 41 | private OnQueryTextListener listenerQuery; 42 | private OnVisibilityListener visibilityListener; 43 | 44 | 45 | public MaterialSearchView(@NonNull Context context) { 46 | super(context); 47 | init(context, null); 48 | } 49 | 50 | public MaterialSearchView(@NonNull Context context, @Nullable AttributeSet attrs) { 51 | super(context, attrs); 52 | init(context, attrs); 53 | } 54 | 55 | public MaterialSearchView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 56 | super(context, attrs, defStyleAttr); 57 | init(context, attrs); 58 | } 59 | 60 | private void init(Context context, AttributeSet attrs) { 61 | TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MaterialSearchView, 0, 0); 62 | try { 63 | menuPosition = a.getInteger(R.styleable.MaterialSearchView_menu_position, 0); 64 | searchHint = a.getString(R.styleable.MaterialSearchView_search_hint); 65 | textColor = a.getColor(R.styleable.MaterialSearchView_text_color, getResources().getColor(android.R.color.black)); 66 | iconColor = a.getColor(R.styleable.MaterialSearchView_icon_color, getResources().getColor(android.R.color.black)); 67 | } finally { 68 | a.recycle(); 69 | } 70 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 71 | if (inflater != null) { 72 | binding = DataBindingUtil.inflate(inflater, R.layout.view_search, this, true); 73 | binding.imgBack.setOnClickListener(this); 74 | binding.imgClear.setOnClickListener(this); 75 | binding.editText.addTextChangedListener(this); 76 | binding.editText.setOnEditorActionListener(this); 77 | } 78 | binding.editText.setHint(getSearchHint()); 79 | binding.editText.setTextColor(getTextColor()); 80 | setDrawableTint(binding.imgBack.getDrawable(), iconColor); 81 | setDrawableTint(binding.imgClear.getDrawable(), iconColor); 82 | checkForAdapter(); 83 | } 84 | 85 | public boolean isVisible() { 86 | return getVisibility() == VISIBLE; 87 | } 88 | 89 | public void setSearchText(String queryText) { 90 | binding.editText.setText(queryText); 91 | } 92 | 93 | public void setSearchRecyclerAdapter(RecyclerView.Adapter adapter) { 94 | binding.recycler.setAdapter(adapter); 95 | checkForAdapter(); 96 | } 97 | 98 | public void hideRecycler() { 99 | hideSearch = true; 100 | binding.linearItemsHolder.setVisibility(GONE); 101 | } 102 | 103 | public void showRecycler() { 104 | hideSearch = false; 105 | binding.linearItemsHolder.setVisibility(VISIBLE); 106 | } 107 | 108 | 109 | public void showSearch() { 110 | hideSearch = false; 111 | checkForAdapter(); 112 | setVisibility(View.VISIBLE); 113 | if (Build.VERSION.SDK_INT >= 21) { 114 | Animator animatorShow = ViewAnimationUtils.createCircularReveal( 115 | this, // view 116 | getCenterX(), // center x 117 | (int) convertDpToPixel(23), // center y 118 | 0, // start radius 119 | (float) Math.hypot(getWidth(), getHeight()) // end radius 120 | ); 121 | animatorShow.addListener(new AnimatorListenerAdapter() { 122 | @Override 123 | public void onAnimationEnd(Animator animation) { 124 | super.onAnimationEnd(animation); 125 | if (hasAdapter) { 126 | binding.linearItemsHolder.setVisibility(View.VISIBLE); 127 | } 128 | } 129 | }); 130 | animatorShow.start(); 131 | } else { 132 | if (hasAdapter) { 133 | binding.linearItemsHolder.setVisibility(View.VISIBLE); 134 | } 135 | } 136 | } 137 | 138 | public void hideSearch() { 139 | checkForAdapter(); 140 | if (hasAdapter) { 141 | binding.linearItemsHolder.setVisibility(View.GONE); 142 | } 143 | if (Build.VERSION.SDK_INT >= 21) { 144 | Animator animatorHide = ViewAnimationUtils.createCircularReveal( 145 | this, // View 146 | getCenterX(), // center x 147 | (int) convertDpToPixel(23), // center y 148 | (float) Math.hypot(getWidth(), getHeight()), // start radius 149 | 0// end radius 150 | ); 151 | animatorHide.setStartDelay(hasAdapter ? ANIMATION_DURATION : 0); 152 | animatorHide.addListener(new AnimatorListenerAdapter() { 153 | @Override 154 | public void onAnimationEnd(Animator animation) { 155 | super.onAnimationEnd(animation); 156 | setVisibility(GONE); 157 | } 158 | }); 159 | animatorHide.start(); 160 | } else { 161 | setVisibility(GONE); 162 | } 163 | } 164 | 165 | @Override 166 | public void onClick(View view) { 167 | if (view == binding.imgClear) { 168 | setSearchText(null); 169 | } else if (view == binding.imgBack) { 170 | hideSearch(); 171 | } 172 | } 173 | 174 | @Override 175 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 176 | binding.imgClear.setVisibility(i2 == 0 ? GONE : VISIBLE); 177 | if (listenerQuery != null) { 178 | listenerQuery.onQueryTextChange(String.valueOf(charSequence)); 179 | } 180 | } 181 | 182 | @Override 183 | public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { 184 | if (actionId == EditorInfo.IME_ACTION_SEARCH) { 185 | if (listenerQuery != null) { 186 | binding.linearItemsHolder.setVisibility(GONE); 187 | listenerQuery.onQueryTextSubmit(textView.getText().toString().trim()); 188 | } 189 | return false; 190 | } 191 | return false; 192 | } 193 | 194 | public void setMenuPosition(int menuPosition) { 195 | this.menuPosition = menuPosition; 196 | invalidate(); 197 | requestFocus(); 198 | } 199 | 200 | // search searchHint 201 | public String getSearchHint() { 202 | if (TextUtils.isEmpty(searchHint)) { 203 | return "Search"; 204 | } 205 | return searchHint; 206 | } 207 | 208 | public void setSearchHint(String searchHint) { 209 | this.searchHint = searchHint; 210 | invalidate(); 211 | requestFocus(); 212 | } 213 | 214 | // text color 215 | public void setTextColor(int textColor) { 216 | this.textColor = textColor; 217 | invalidate(); 218 | requestFocus(); 219 | } 220 | 221 | public void setIconColor(int iconColor) { 222 | this.iconColor = iconColor; 223 | invalidate(); 224 | requestFocus(); 225 | } 226 | 227 | public int getTextColor() { 228 | return textColor; 229 | } 230 | 231 | /** 232 | * Get views 233 | */ 234 | public EditText getEditText() { 235 | return binding.editText; 236 | } 237 | 238 | public ImageView getImageBack() { 239 | return binding.imgBack; 240 | } 241 | 242 | public ImageView getImageClear() { 243 | return binding.imgClear; 244 | } 245 | 246 | public RecyclerView getRecyclerView() { 247 | return binding.recycler; 248 | } 249 | 250 | /** 251 | * Interface 252 | */ 253 | public void addQueryTextListener(OnQueryTextListener listenerQuery) { 254 | this.listenerQuery = listenerQuery; 255 | } 256 | 257 | public interface OnQueryTextListener { 258 | boolean onQueryTextSubmit(String query); 259 | 260 | boolean onQueryTextChange(String newText); 261 | } 262 | 263 | public void setOnVisibilityListener(OnVisibilityListener visibilityListener) { 264 | this.visibilityListener = visibilityListener; 265 | } 266 | 267 | public interface OnVisibilityListener { 268 | boolean onOpen(); 269 | 270 | boolean onClose(); 271 | } 272 | 273 | /** 274 | * Helpers 275 | */ 276 | public void setDrawableTint(Drawable resDrawable, int resColor) { 277 | resDrawable.setColorFilter(new PorterDuffColorFilter(resColor, PorterDuff.Mode.SRC_ATOP)); 278 | resDrawable.mutate(); 279 | } 280 | 281 | public float convertDpToPixel(float dp) { 282 | return dp * (getContext().getResources().getDisplayMetrics().densityDpi / 160f); 283 | } 284 | 285 | private void checkForAdapter() { 286 | hasAdapter = !hideSearch && binding.recycler.getAdapter() != null && binding.recycler.getAdapter().getItemCount() > 0; 287 | } 288 | 289 | /* 290 | TODO not correct but close 291 | Need to do correct measure 292 | */ 293 | private int getCenterX() { 294 | int icons = (int) (getWidth() - convertDpToPixel(21 * (1 + menuPosition))); 295 | int padding = (int) convertDpToPixel(menuPosition * 21); 296 | return icons - padding; 297 | } 298 | 299 | /** 300 | * Not used 301 | */ 302 | @Override 303 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 304 | } 305 | 306 | @Override 307 | public void afterTextChanged(Editable editable) { 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/drawable-v21/image_background.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/drawable/image_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/layout/view_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 24 | 25 | 41 | 42 | 57 | 58 | 65 | 66 | 71 | 72 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36dp 4 | 8dp 5 | 16sp 6 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48dp 4 | 12dp 5 | 18sp 6 | -------------------------------------------------------------------------------- /oldsearchview/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MaterialSearchView 3 | Search 4 | 5 | -------------------------------------------------------------------------------- /repo/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EugeneHoran/Android-Material-SearchView/c27deb7ca4b20e0d058773313801ba99ffcdf32c/repo/example.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':materialsearchview' --------------------------------------------------------------------------------