├── .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 |
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 |
15 |
16 |
17 |
18 |
19 |
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'
--------------------------------------------------------------------------------