From 789fbbb89c093eca7b42c032f43c2dc7377d2112 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Fri, 14 Oct 2022 20:52:25 +0100 Subject: [PATCH 01/30] Migrated project to Gradle 7.5.1 --- build.gradle | 22 ++++--------------- gradle.properties | 24 ++++++++++++++------ gradle/wrapper/gradle-wrapper.properties | 6 ++--- library/build.gradle | 10 +++++---- sample/build.gradle | 28 +++++++++++++----------- settings.gradle | 15 +++++++++++++ 6 files changed, 60 insertions(+), 45 deletions(-) diff --git a/build.gradle b/build.gradle index 8e02c67..97cf3c4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - jcenter() - maven { url "https://jitpack.io" } - } +plugins { + id 'com.android.application' version '7.2.0' apply false + id 'com.android.library' version '7.2.0' apply false + id 'org.jetbrains.kotlin.android' version '1.6.21' apply false } task clean(type: Delete) { diff --git a/gradle.properties b/gradle.properties index 1d3591c..7638968 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,28 @@ # Project-wide Gradle settings. - # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - +org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.parallel=true +org.gradle.daemon=true +org.gradle.configureondemand=true +org.gradle.caching=true +org.gradle.unsafe.configuration-cache=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f23df6e..4793f80 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Oct 21 11:34:03 PDT 2015 +#Fri Oct 14 20:24:38 GMT+01:00 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/library/build.gradle b/library/build.gradle index 1b6c251..8614c38 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,4 +1,6 @@ -apply plugin: 'com.android.library' +plugins { + id 'com.android.library' +} android { compileSdkVersion 23 @@ -19,7 +21,7 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.android.support:recyclerview-v7:23.1.1' + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:23.1.1' + implementation 'com.android.support:recyclerview-v7:23.1.1' } diff --git a/sample/build.gradle b/sample/build.gradle index daa0ad7..cbf440a 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,5 +1,7 @@ -apply plugin: 'com.android.application' -apply plugin: 'com.neenbedankt.android-apt' +plugins { + id 'com.android.application' + id 'com.neenbedankt.android-apt' +} android { compileSdkVersion 23 @@ -25,22 +27,22 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':library') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':library') // Support - compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.android.support:design:23.1.1' + implementation 'com.android.support:appcompat-v7:23.1.1' + implementation 'com.android.support:design:23.1.1' // ToggleDrawable - compile 'com.github.renaudcerrato:ToggleDrawable:1.0.1' + implementation 'com.github.renaudcerrato:ToggleDrawable:1.0.1' // Retrofit - compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' - compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' - compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' - compile 'com.squareup.okhttp:logging-interceptor:2.7.0' + implementation 'com.squareup.retrofit:retrofit:2.0.0-beta2' + implementation 'com.squareup.retrofit:converter-gson:2.0.0-beta2' + implementation 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' + implementation 'com.squareup.okhttp:logging-interceptor:2.7.0' // RxJava - compile 'io.reactivex:rxandroid:1.1.0' + implementation 'io.reactivex:rxandroid:1.1.0' // Dagger - compile 'com.google.dagger:dagger:2.0.2' + implementation 'com.google.dagger:dagger:2.0.2' apt 'com.google.dagger:dagger-compiler:2.0.2' provided 'org.glassfish:javax.annotation:10.0-b28' } diff --git a/settings.gradle b/settings.gradle index 52baf7e..c85b323 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,16 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "FloatingSearchView" include ':sample', ':library' From 7e5beff6cce87ca1a7d9566529cf2627d8975892 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Fri, 14 Oct 2022 20:56:38 +0100 Subject: [PATCH 02/30] Compile SDK upgraded to Android 32 --- library/build.gradle | 8 ++++---- sample/build.gradle | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/build.gradle b/library/build.gradle index 8614c38..6113a8a 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -3,12 +3,12 @@ plugins { } android { - compileSdkVersion 23 - buildToolsVersion "23.0.2" + compileSdk 32 + //buildToolsVersion "23.0.2" defaultConfig { - minSdkVersion 15 - targetSdkVersion 23 + minSdk 21 + targetSdk 32 versionCode 1 versionName "1.0" } diff --git a/sample/build.gradle b/sample/build.gradle index cbf440a..0cb2cd5 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -4,13 +4,13 @@ plugins { } android { - compileSdkVersion 23 - buildToolsVersion "23.0.2" + compileSdk 32 + //buildToolsVersion "23.0.2" defaultConfig { applicationId "com.mypopsy.floatingsearchview.demo" - minSdkVersion 15 - targetSdkVersion 23 + minSdkVersion 21 + targetSdk 32 versionCode 1 versionName "1.0" buildConfigField "String", "PROJECT_URL", '"https://github.com/renaudcerrato/FloatingSearchView"' From 31f5d353d2174c6bf238f023c6afb891587d6111 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 05:50:37 +0100 Subject: [PATCH 03/30] Migrate project to Androidx --- library/build.gradle | 4 ++-- sample/build.gradle | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/build.gradle b/library/build.gradle index 6113a8a..5c5e475 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -22,6 +22,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:23.1.1' - implementation 'com.android.support:recyclerview-v7:23.1.1' + implementation 'androidx.appcompat:appcompat:1.5.1' + //implementation 'androidx:recyclerview-v7:23.1.1' } diff --git a/sample/build.gradle b/sample/build.gradle index 0cb2cd5..428c491 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -29,9 +29,9 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':library') - // Support - implementation 'com.android.support:appcompat-v7:23.1.1' - implementation 'com.android.support:design:23.1.1' + // AndroidX + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' // ToggleDrawable implementation 'com.github.renaudcerrato:ToggleDrawable:1.0.1' // Retrofit From 778b8d155f6688e9041034993b6b5a5facc5bede Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 06:04:01 +0100 Subject: [PATCH 04/30] Updated dependencies --- sample/build.gradle | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sample/build.gradle b/sample/build.gradle index 428c491..eb278b1 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -33,18 +33,18 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.6.1' // ToggleDrawable - implementation 'com.github.renaudcerrato:ToggleDrawable:1.0.1' + implementation 'com.github.renaudcerrato:ToggleDrawable:1.0.2' // Retrofit - implementation 'com.squareup.retrofit:retrofit:2.0.0-beta2' - implementation 'com.squareup.retrofit:converter-gson:2.0.0-beta2' - implementation 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' - implementation 'com.squareup.okhttp:logging-interceptor:2.7.0' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.retrofit2:adapter-rxjava:2.9.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0' // RxJava - implementation 'io.reactivex:rxandroid:1.1.0' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' // Dagger - implementation 'com.google.dagger:dagger:2.0.2' - apt 'com.google.dagger:dagger-compiler:2.0.2' - provided 'org.glassfish:javax.annotation:10.0-b28' + implementation 'com.google.dagger:dagger:2.44' + annotationProcessor 'com.google.dagger:dagger-compiler:2.44' + annotationProcessor 'org.glassfish:javax.annotation:10.0-b28' } afterEvaluate { project -> From 64aebd791eef6666d3bcf1cdedfcff3753263814 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 08:25:33 +0100 Subject: [PATCH 05/30] Build error fixes --- gradle.properties | 1 + sample/build.gradle | 27 ++++++++++++++------------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7638968..f205aa6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,6 +12,7 @@ org.gradle.daemon=true org.gradle.configureondemand=true org.gradle.caching=true org.gradle.unsafe.configuration-cache=true +org.gragle.warning.mode=all # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/sample/build.gradle b/sample/build.gradle index eb278b1..abea974 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,6 +1,6 @@ plugins { id 'com.android.application' - id 'com.neenbedankt.android-apt' +// id 'com.neenbedankt.android-apt' } android { @@ -47,15 +47,16 @@ dependencies { annotationProcessor 'org.glassfish:javax.annotation:10.0-b28' } -afterEvaluate { project -> - android.applicationVariants.each { variant -> - - Task copyApk = task(("release" + variant.variantData.name.capitalize()), type:Copy) { - from variant.outputs[0].outputFile - destinationDir project.projectDir - dependsOn variant.assemble - } - - variant.assemble.finalizedBy(copyApk) - } -} +//afterEvaluate { project -> +// android.applicationVariants.each { variant -> +// +// task copyApk(type:Copy) { +//// "release" + variant.buildType.name.capitalize() +// from variant.outputs[0].outputFile +// destinationDir project.projectDir +// dependsOn variant.assemble +// } +// +// variant.assemble.finalizedBy(copyApk) +// } +//} From 1bf968fee145101a9e95093369ba6b75650faf87 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 20:33:01 +0100 Subject: [PATCH 06/30] Enabled Jetifier --- gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle.properties b/gradle.properties index f205aa6..3a36e42 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,6 +21,7 @@ org.gragle.warning.mode=all # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the From 6c579ea7c02cb8553188ccbd83b9364389c5aa0c Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 20:36:17 +0100 Subject: [PATCH 07/30] Dependency fixes --- library/build.gradle | 2 +- settings.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/library/build.gradle b/library/build.gradle index 5c5e475..d085d96 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -23,5 +23,5 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.5.1' - //implementation 'androidx:recyclerview-v7:23.1.1' + implementation 'androidx.recyclerview:recyclerview:1.2.1' } diff --git a/settings.gradle b/settings.gradle index c85b323..ade249f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url "https://jitpack.io" } } } rootProject.name = "FloatingSearchView" From 6fe07104eb881b0197f506803c3c744ea6808197 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 20:37:47 +0100 Subject: [PATCH 08/30] Updated FloatingSearchView class --- .../mypopsy/widget/FloatingSearchView.java | 184 ++++++++---------- 1 file changed, 77 insertions(+), 107 deletions(-) diff --git a/library/src/main/java/com/mypopsy/widget/FloatingSearchView.java b/library/src/main/java/com/mypopsy/widget/FloatingSearchView.java index a21f992..c0fdce1 100644 --- a/library/src/main/java/com/mypopsy/widget/FloatingSearchView.java +++ b/library/src/main/java/com/mypopsy/widget/FloatingSearchView.java @@ -1,8 +1,14 @@ package com.mypopsy.widget; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.BOTTOM; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.LEFT; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.RIGHT; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.TOP; + import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; @@ -12,20 +18,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.MenuRes; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.StyleRes; -import android.support.v4.content.res.ResourcesCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.view.MarginLayoutParamsCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorCompat; -import android.support.v7.widget.ActionMenuView; -import android.support.v7.widget.AppCompatEditText; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Xml; @@ -42,6 +34,22 @@ import android.widget.ImageView; import android.widget.RelativeLayout; +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.MenuRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StyleRes; +import androidx.appcompat.widget.ActionMenuView; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.core.content.res.ResourcesCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.view.MarginLayoutParamsCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewPropertyAnimatorCompat; + import com.mypopsy.floatingsearchview.R; import com.mypopsy.widget.internal.RoundRectDrawableWithShadow; import com.mypopsy.widget.internal.SuggestionItemDecorator; @@ -54,12 +62,6 @@ import java.util.ArrayList; import java.util.List; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.BOTTOM; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.LEFT; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.RIGHT; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.TOP; - - public class FloatingSearchView extends RelativeLayout { private static final int DEFAULT_BACKGROUND_COLOR = 0x90000000; @@ -75,7 +77,7 @@ public class FloatingSearchView extends RelativeLayout { private static final Interpolator DECELERATE = new DecelerateInterpolator(3f); private static final Interpolator ACCELERATE = new AccelerateInterpolator(2f); - private RecyclerView.AdapterDataObserver mAdapterObserver = new android.support.v7.widget.RecyclerView.AdapterDataObserver() { + private final RecyclerView.AdapterDataObserver mAdapterObserver = new androidx.recyclerview.widget.RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { @@ -146,12 +148,12 @@ public FloatingSearchView(Context context, AttributeSet attrs, @AttrRes int defS inflate(getContext(), R.layout.fsv_floating_search_layout, this); - mSearchInput = (LogoEditText)findViewById(R.id.fsv_search_text); - mNavButtonView = (ImageView) findViewById(R.id.fsv_search_action_navigation); - mRecyclerView = (RecyclerView) findViewById(R.id.fsv_suggestions_list); + mSearchInput = findViewById(R.id.fsv_search_text); + mNavButtonView = findViewById(R.id.fsv_search_action_navigation); + mRecyclerView = findViewById(R.id.fsv_suggestions_list); mDivider = findViewById(R.id.fsv_suggestions_divider); - mSearchContainer = (ViewGroup) findViewById(R.id.fsv_search_container); - mActionMenu = (ActionMenuView) findViewById(R.id.fsv_search_action_menu); + mSearchContainer = findViewById(R.id.fsv_search_container); + mActionMenu = findViewById(R.id.fsv_search_action_menu); //TODO: move elevation parameters to XML attributes mSearchBackground = new RoundRectDrawableWithShadow( @@ -178,7 +180,7 @@ private void applyXmlAttributes(AttributeSet attrs, @AttrRes int defStyleAttr, @ suggestionsContainer.getLayoutParams().width = searchBarWidth; // Divider - mDivider.setBackgroundDrawable(a.getDrawable(R.styleable.FloatingSearchView_android_divider)); + mDivider.setBackground(a.getDrawable(R.styleable.FloatingSearchView_android_divider)); int dividerHeight = a.getDimensionPixelSize(R.styleable.FloatingSearchView_android_dividerHeight, -1); MarginLayoutParams dividerLP = (MarginLayoutParams) mDivider.getLayoutParams(); @@ -217,14 +219,15 @@ private void applyXmlAttributes(AttributeSet attrs, @AttrRes int defStyleAttr, @ a.recycle(); } + @SuppressLint("ClickableViewAccessibility") private void setupViews() { mSearchContainer.setLayoutTransition(getDefaultLayoutTransition()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) - mSearchContainer.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + mSearchContainer.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); - mSearchContainer.setBackgroundDrawable(mSearchBackground); + mSearchContainer.setBackground(mSearchBackground); mSearchContainer.setMinimumHeight((int) mSearchBackground.getMinHeight()); mSearchContainer.setMinimumWidth((int) mSearchBackground.getMinWidth()); @@ -239,39 +242,28 @@ private void setupViews() { else mBackgroundDrawable = new ColorDrawable(DEFAULT_BACKGROUND_COLOR); - setBackgroundDrawable(mBackgroundDrawable); + setBackground(mBackgroundDrawable); mBackgroundDrawable.setAlpha(0); - mNavButtonView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if(mNavigationClickListener != null) - mNavigationClickListener.onNavigationClick(); - } + mNavButtonView.setOnClickListener(v -> { + if(mNavigationClickListener != null) + mNavigationClickListener.onNavigationClick(); }); - setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (!isActivated()) return false; - setActivated(false); - return true; - } + setOnTouchListener((v, event) -> { + if (!isActivated()) return false; + setActivated(false); + return true; }); - mSearchInput.setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus != isActivated()) setActivated(hasFocus); - } + mSearchInput.setOnFocusChangeListener((v, hasFocus) -> { + if (hasFocus != isActivated()) setActivated(hasFocus); }); - mSearchInput.setOnKeyListener(new OnKeyListener() { - public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { - if (keyCode != KeyEvent.KEYCODE_ENTER) return false; - setActivated(false); - return true; - } + mSearchInput.setOnKeyListener((view, keyCode, keyEvent) -> { + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + setActivated(false); + return true; }); } @@ -298,29 +290,23 @@ public void inflateMenu(@MenuRes int menuRes) { if(menuRes == 0) return; if (isInEditMode()) return; getActivity().getMenuInflater().inflate(menuRes, mActionMenu.getMenu()); - - XmlResourceParser parser = null; - try { + @SuppressLint("ResourceType") @LayoutRes int layoutRes = menuRes; + try (XmlResourceParser parser = getResources().getLayout(layoutRes)) { //noinspection ResourceType - parser = getResources().getLayout(menuRes); AttributeSet attrs = Xml.asAttributeSet(parser); parseMenu(parser, attrs); - } catch (XmlPullParserException | IOException e) { + } + catch (XmlPullParserException | IOException e) { // should not happens throw new InflateException("Error parsing menu XML", e); - } finally { - if (parser != null) parser.close(); } } public void setOnSearchListener(final OnSearchListener listener) { - mSearchInput.setOnKeyListener(new OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode != KeyEvent.KEYCODE_ENTER) return false; - listener.onSearchAction(mSearchInput.getText()); - return true; - } + mSearchInput.setOnKeyListener((v, keyCode, event) -> { + if (keyCode != KeyEvent.KEYCODE_ENTER) return false; + listener.onSearchAction(mSearchInput.getText()); + return true; }); } @@ -424,32 +410,20 @@ public Drawable getIcon() { return mNavButtonView.getDrawable(); } - @SuppressWarnings("unchecked") @Nullable public RecyclerView.Adapter getAdapter() { - return mRecyclerView.getAdapter(); + return (RecyclerView.Adapter) mRecyclerView.getAdapter(); } protected LayoutTransition getDefaultLayoutTransition() { return new LayoutTransition(); } + @SuppressLint("ObjectAnimatorBinding") private void fadeIn(boolean enter) { ValueAnimator backgroundAnim; - if(Build.VERSION.SDK_INT >= 19) - backgroundAnim = ObjectAnimator.ofInt(mBackgroundDrawable, "alpha", enter ? 255 : 0); - else { - backgroundAnim = ValueAnimator.ofInt(enter ? 0 : 255, enter ? 255 : 0); - backgroundAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mBackgroundDrawable.setAlpha(value); - } - }); - } - + backgroundAnim = ObjectAnimator.ofInt(mBackgroundDrawable, "alpha", enter ? 255 : 0); backgroundAnim.setDuration(enter ? DEFAULT_DURATION_ENTER : DEFAULT_DURATION_EXIT); backgroundAnim.setInterpolator(enter ? DECELERATE : ACCELERATE); backgroundAnim.start(); @@ -486,16 +460,13 @@ private void showSuggestions(final boolean show) { int childCount = mRecyclerView.getChildCount(); int translation = 0; - final Runnable endAction = new Runnable() { - @Override - public void run() { - if(show) - updateDivider(); - else { - showDivider(false); - mRecyclerView.setVisibility(View.INVISIBLE); - mRecyclerView.setTranslationY(-mRecyclerView.getHeight()); - } + final Runnable endAction = () -> { + if(show) + updateDivider(); + else { + showDivider(false); + mRecyclerView.setVisibility(View.INVISIBLE); + mRecyclerView.setTranslationY(-mRecyclerView.getHeight()); } }; @@ -510,11 +481,11 @@ public void run() { showDivider(false); ViewPropertyAnimatorCompat listAnim = ViewCompat.animate(mRecyclerView) - .translationY(translation) - .setDuration(show ? DEFAULT_DURATION_ENTER : DEFAULT_DURATION_EXIT) - .setInterpolator(show ? DECELERATE : ACCELERATE) - .withLayer() - .withEndAction(endAction); + .translationY(translation) + .setDuration(show ? DEFAULT_DURATION_ENTER : DEFAULT_DURATION_EXIT) + .setInterpolator(show ? DECELERATE : ACCELERATE) + .withLayer() + .withEndAction(endAction); if(show || childCount > 0) listAnim.start(); @@ -554,6 +525,7 @@ private void showMenu(final boolean visible) { } } + @SuppressLint({"CustomViewStyleable", "PrivateResource"}) private void parseMenu(XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { @@ -588,11 +560,11 @@ private void parseMenu(XmlPullParser parser, AttributeSet attrs) tagName = parser.getName(); if (tagName.equals("item")) { - TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MenuItem); - int itemShowAsAction = a.getInt(R.styleable.MenuItem_showAsAction, -1); + TypedArray a = getContext().obtainStyledAttributes(attrs, androidx.appcompat.R.styleable.MenuItem); + int itemShowAsAction = a.getInt(androidx.appcompat.R.styleable.MenuItem_showAsAction, -1); if((itemShowAsAction & MenuItem.SHOW_AS_ACTION_ALWAYS) != 0) { - int itemId = a.getResourceId(R.styleable.MenuItem_android_id, NO_ID); + int itemId = a.getResourceId(androidx.appcompat.R.styleable.MenuItem_android_id, NO_ID); if(itemId != NO_ID) mAlwaysShowingMenu.add(itemId); } a.recycle(); @@ -620,17 +592,14 @@ private void parseMenu(XmlPullParser parser, AttributeSet attrs) } } + @SuppressLint("RestrictedApi") static private Drawable unwrap(Drawable icon) { - if(icon instanceof android.support.v7.graphics.drawable.DrawableWrapper) - return ((android.support.v7.graphics.drawable.DrawableWrapper)icon).getWrappedDrawable(); - if(icon instanceof android.support.v4.graphics.drawable.DrawableWrapper) - return ((android.support.v4.graphics.drawable.DrawableWrapper)icon).getWrappedDrawable(); if(Build.VERSION.SDK_INT >= 23 && icon instanceof android.graphics.drawable.DrawableWrapper) return ((android.graphics.drawable.DrawableWrapper)icon).getDrawable(); return DrawableCompat.unwrap(icon); } - private static class RecyclerView extends android.support.v7.widget.RecyclerView { + private static class RecyclerView extends androidx.recyclerview.widget.RecyclerView { public RecyclerView(Context context) { super(context); @@ -644,6 +613,7 @@ public RecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent e) { View child = findChildViewUnder(e.getX(), e.getY()); From 771a01c43df2248c133125a727730a1a0b2bccd6 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 20:39:12 +0100 Subject: [PATCH 09/30] Updated SuggestionItemDecorator class --- .../internal/SuggestionItemDecorator.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/library/src/main/java/com/mypopsy/widget/internal/SuggestionItemDecorator.java b/library/src/main/java/com/mypopsy/widget/internal/SuggestionItemDecorator.java index a00fa5f..316db9c 100644 --- a/library/src/main/java/com/mypopsy/widget/internal/SuggestionItemDecorator.java +++ b/library/src/main/java/com/mypopsy/widget/internal/SuggestionItemDecorator.java @@ -1,15 +1,16 @@ package com.mypopsy.widget.internal; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.BOTTOM; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.LEFT; +import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.RIGHT; + import android.graphics.Canvas; import android.graphics.Rect; -import android.support.annotation.ColorInt; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView; import android.view.View; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.BOTTOM; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.LEFT; -import static com.mypopsy.widget.internal.RoundRectDrawableWithShadow.RIGHT; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; public class SuggestionItemDecorator extends RecyclerView.ItemDecoration { @@ -20,7 +21,7 @@ public SuggestionItemDecorator(RoundRectDrawableWithShadow drawable) { } @Override - public void getItemOffsets(Rect rect, View view, RecyclerView parent, RecyclerView.State state) { + public void getItemOffsets(@NonNull Rect rect, @NonNull View view, RecyclerView parent, RecyclerView.State state) { int position = parent.getChildAdapterPosition(view); int count = state.getItemCount(); int shadows = LEFT|RIGHT; @@ -30,18 +31,18 @@ public void getItemOffsets(Rect rect, View view, RecyclerView parent, RecyclerVi } @Override - public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + public void onDraw(@NonNull Canvas canvas, RecyclerView parent, RecyclerView.State state) { int visibleCount = parent.getChildCount(); int count = state.getItemCount(); - RecyclerView.Adapter adapter = parent.getAdapter(); + RecyclerView.Adapter adapter = (RecyclerView.Adapter) parent.getAdapter(); int adapterCount = adapter != null ? adapter.getItemCount() : 0; for (int i = 0; i < visibleCount; i++) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); - float translationX = ViewCompat.getTranslationX(view); - float translationY = ViewCompat.getTranslationY(view); - float alpha = ViewCompat.getAlpha(view); + float translationX = view.getTranslationX(); + float translationY = view.getTranslationY(); + float alpha = view.getAlpha(); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); int shadows = LEFT|RIGHT; From d4a45fc617c594c1c420846a617d2ea0b1f46614 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 20:42:17 +0100 Subject: [PATCH 10/30] Updated RoundRectDrawableWithShadow class --- .../widget/internal/RoundRectDrawableWithShadow.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/main/java/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.java b/library/src/main/java/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.java index ee7f523..99df9e9 100644 --- a/library/src/main/java/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.java +++ b/library/src/main/java/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.java @@ -27,8 +27,8 @@ import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import android.util.Log; import java.lang.annotation.Retention; @@ -50,7 +50,7 @@ public class RoundRectDrawableWithShadow extends Drawable { @IntDef(value = {LEFT, RIGHT, TOP, BOTTOM}, flag = true) public @interface Gravity {} - public static final int LEFT = 1 << 0; + public static final int LEFT = 1; public static final int TOP = 1 << 1; public static final int RIGHT = 1 << 2; public static final int BOTTOM = 1 << 3; From 6e4e5743d2fb7ba396d996f5cc02c2332c277e3d Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sat, 15 Oct 2022 20:43:44 +0100 Subject: [PATCH 11/30] Updated ViewUtils class --- .../com/mypopsy/widget/internal/ViewUtils.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/library/src/main/java/com/mypopsy/widget/internal/ViewUtils.java b/library/src/main/java/com/mypopsy/widget/internal/ViewUtils.java index 64a5976..6cb98ca 100644 --- a/library/src/main/java/com/mypopsy/widget/internal/ViewUtils.java +++ b/library/src/main/java/com/mypopsy/widget/internal/ViewUtils.java @@ -21,9 +21,10 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.support.annotation.AttrRes; -import android.support.annotation.ColorInt; -import android.support.v4.graphics.drawable.DrawableCompat; +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; +import androidx.core.graphics.drawable.DrawableCompat; + import android.util.DisplayMetrics; import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -33,12 +34,9 @@ public class ViewUtils { private static final int[] TEMP_ARRAY = new int[1]; public static void showSoftKeyboardDelayed(final EditText editText, long delay){ - editText.postDelayed(new Runnable() { - @Override - public void run() { - InputMethodManager inputMethodManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); - } + editText.postDelayed(() -> { + InputMethodManager inputMethodManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); }, delay); } From ae6e86585780efef22a12c5e1688487fe57473e1 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 00:23:41 +0100 Subject: [PATCH 12/30] Library layout fixes --- .../res/layout/fsv_floating_search_layout.xml | 18 ++++++++++-------- .../res/layout/fsv_search_query_layout.xml | 6 +++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/library/src/main/res/layout/fsv_floating_search_layout.xml b/library/src/main/res/layout/fsv_floating_search_layout.xml index 3aacd7e..f9627da 100644 --- a/library/src/main/res/layout/fsv_floating_search_layout.xml +++ b/library/src/main/res/layout/fsv_floating_search_layout.xml @@ -1,6 +1,9 @@ - + - + tools:ignore="UnusedAttribute" + /> + android:layout_alignStart="@+id/fsv_search_container"> - + diff --git a/library/src/main/res/layout/fsv_search_query_layout.xml b/library/src/main/res/layout/fsv_search_query_layout.xml index 40bd4ca..b874a85 100644 --- a/library/src/main/res/layout/fsv_search_query_layout.xml +++ b/library/src/main/res/layout/fsv_search_query_layout.xml @@ -4,7 +4,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - @@ -20,7 +20,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" - android:layout_marginLeft="12dp" + android:layout_marginStart="12dp" android:singleLine="true" android:maxLines="1" android:selectAllOnFocus="true" @@ -28,7 +28,7 @@ android:inputType="textNoSuggestions" android:background="@null"/> - Date: Sun, 16 Oct 2022 15:58:38 +0100 Subject: [PATCH 13/30] Layout XML fixes in sample implementation --- sample/src/main/res/layout/activity_main.xml | 6 +-- sample/src/main/res/menu/search.xml | 47 +++++++++++++++----- sample/src/main/res/values/strings.xml | 9 ++-- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 5a5a04a..46432a2 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - - + diff --git a/sample/src/main/res/menu/search.xml b/sample/src/main/res/menu/search.xml index 53f885d..e8e1b1a 100644 --- a/sample/src/main/res/menu/search.xml +++ b/sample/src/main/res/menu/search.xml @@ -1,17 +1,44 @@ - + - - - - - + + + + + - - - + + + diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index f7d225b..f7609da 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -2,9 +2,12 @@ FloatingSearchView Search Say Something - speech to text doesn\'t seems supported - clear - text to speech + Speech to text is not supported + Clear + Text to speech Icon Visible Icon Type + Search + Drawer + Custom From 33f8fa29d31565e45b8b7699a58c4613a6b254ce Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 16:07:15 +0100 Subject: [PATCH 14/30] Migrate ArrayRecylerAdapter to Androidx --- .../demo/adapter/ArrayRecyclerAdapter.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/adapter/ArrayRecyclerAdapter.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/adapter/ArrayRecyclerAdapter.java index 5fa80dd..5eb8d84 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/adapter/ArrayRecyclerAdapter.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/adapter/ArrayRecyclerAdapter.java @@ -1,6 +1,8 @@ package com.mypopsy.floatingsearchview.demo.adapter; -import android.support.v7.widget.RecyclerView; +import android.annotation.SuppressLint; + +import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.Arrays; @@ -16,7 +18,7 @@ public abstract class ArrayRecyclerAdapter mObjects; + private final List mObjects; /** * Lock used to modify the content of {@link #mObjects}. Any write operation * performed on the array should be synchronized on this lock. @@ -71,7 +73,8 @@ public void addAll(Collection collection) { * * @param items The items to add at the end of the array. */ - public void addAll(T ... items) { + @SafeVarargs + public final void addAll (T... items) { int start; synchronized (mLock) { start = getItemCount(); @@ -108,6 +111,7 @@ public void remove(T object) { /** * Remove all elements from the list. */ + @SuppressLint("NotifyDataSetChanged") public void clear() { synchronized (mLock) { mObjects.clear(); @@ -120,6 +124,7 @@ public void clear() { * @param comparator The comparator used to sort the objects contained * in this adapter. */ + @SuppressLint("NotifyDataSetChanged") public void sort(Comparator comparator) { synchronized (mLock) { Collections.sort(mObjects, comparator); From 5cc766cfd6c21ffde75276429f24b0bdc3e5489e Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 16:12:25 +0100 Subject: [PATCH 15/30] Fixed Retrofit dependencies in RetrofitModule --- .../demo/dagger/RetrofitModule.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java index f9ff2d5..3142b0f 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java @@ -1,16 +1,15 @@ package com.mypopsy.floatingsearchview.demo.dagger; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.logging.HttpLoggingInterceptor; - import javax.inject.Singleton; import dagger.Module; import dagger.Provides; -import retrofit.Converter; -import retrofit.GsonConverterFactory; -import retrofit.Retrofit; -import retrofit.RxJavaCallAdapterFactory; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Converter; +import retrofit2.Retrofit; +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +import retrofit2.converter.gson.GsonConverterFactory; @Module public class RetrofitModule { From 2db459b949786790fc3b06bdba20b19b7fcd1439 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 16:13:51 +0100 Subject: [PATCH 16/30] Fixed Retrofit dependencies in SearchModule --- .../mypopsy/floatingsearchview/demo/dagger/SearchModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java index 6a5be17..eeba33c 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java @@ -8,7 +8,7 @@ import dagger.Module; import dagger.Provides; -import retrofit.Retrofit; +import retrofit2.Retrofit; @Module public class SearchModule { From 2c9d16e491c7bf3a71d773df4ce017bf99fc342c Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 20:32:20 +0100 Subject: [PATCH 17/30] Updated search mechanism for sample --- .../demo/search/GoogleSearch.java | 5 +- .../demo/search/GoogleSearchController.java | 79 ++++++------------- .../demo/search/Response.java | 2 +- .../demo/search/SearchController.java | 5 +- .../demo/search/SearchCursor.java | 2 +- 5 files changed, 32 insertions(+), 61 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearch.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearch.java index 7f6ad41..128c130 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearch.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearch.java @@ -1,8 +1,7 @@ package com.mypopsy.floatingsearchview.demo.search; - -import retrofit.http.GET; -import retrofit.http.Query; +import retrofit2.http.GET; +import retrofit2.http.Query; import rx.Observable; // https://developers.google.com/web-search/docs/?csw=1#API_Overview diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java index 1fc606c..3415162 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java @@ -8,14 +8,13 @@ import javax.inject.Inject; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Scheduler; import rx.Observable; -import rx.Scheduler; import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action0; -import rx.functions.Action1; import rx.functions.Func1; -import rx.functions.Func2; import rx.subjects.PublishSubject; public class GoogleSearchController implements SearchController { @@ -23,8 +22,8 @@ public class GoogleSearchController implements SearchController { private static final int DEFAULT_DEBOUNCE = 700; // milliseconds private final GoogleSearch mSearch; - private final Scheduler.Worker mWorker; - private PublishSubject mQuerySubject = PublishSubject.create(); + private final Scheduler.@NonNull Worker mWorker; + private final PublishSubject mQuerySubject = PublishSubject.create(); private Subscription mSubscription; private Listener mListener; @@ -53,20 +52,12 @@ public void cancel() { private Observable getQueryObservable(String query) { return mSearch.search(query) - .flatMap(new Func1>() { - @Override - public Observable call(Response response) { - if (response.responseData == null) - return Observable.error(new SearchException(response.responseDetails)); - return Observable.just(response.responseData.results); - } + .flatMap((Func1>) response -> { + if (response.responseData == null) + return Observable.error(new SearchException(response.responseDetails)); + return Observable.just(response.responseData.results); }) - .retry(new Func2() { - @Override - public Boolean call(Integer integer, Throwable throwable) { - return throwable instanceof InterruptedIOException; - } - }); + .retry((integer, throwable) -> throwable instanceof InterruptedIOException); } private void ensureSubscribed() { @@ -74,52 +65,32 @@ private void ensureSubscribed() { mSubscription = mQuerySubject.asObservable() .debounce(DEFAULT_DEBOUNCE, TimeUnit.MILLISECONDS) .distinctUntilChanged() - .flatMap(new Func1>() { - @Override - public Observable call(String query) { - if(TextUtils.isEmpty(query)) return Observable.just(null); - notifyStarted(query); - return getQueryObservable(query) - .onErrorResumeNext(new Func1>() { - @Override - public Observable call(Throwable throwable) { - notifyError(throwable); - return Observable.empty(); - } - }); - } - } - ) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Action1() { - @Override - public void call(SearchResult[] searchResults) { - if(mListener != null) mListener.onSearchResults(searchResults); - } + .flatMap((Func1>) query -> { + if(TextUtils.isEmpty(query)) return Observable.just(null); + notifyStarted(query); + return getQueryObservable(query) + .onErrorResumeNext((Func1>) throwable -> { + notifyError(throwable); + return Observable.empty(); + }); + }) +// .observeOn(AndroidSchedulers.mainThread()) + .subscribe(searchResults -> { + if(mListener != null) mListener.onSearchResults(searchResults); }); } private void notifyStarted(final String query) { if(mListener == null) return; - dispatchOnMainThread(new Action0() { - @Override - public void call() { - mListener.onSearchStarted(query); - } - }); + dispatchOnMainThread(() -> mListener.onSearchStarted(query)); } private void notifyError(final Throwable throwable) { if(mListener == null) return; - dispatchOnMainThread(new Action0() { - @Override - public void call() { - mListener.onSearchError(throwable); - } - }); + dispatchOnMainThread(() -> mListener.onSearchError(throwable)); } private void dispatchOnMainThread(Action0 action) { - mWorker.schedule(action); + mWorker.schedule((Runnable) action); } } diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/Response.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/Response.java index b08e852..72307c0 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/Response.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/Response.java @@ -7,7 +7,7 @@ public class Response { public Data responseData; public static class Data { - public SearchResult results[]; + public SearchResult[] results; public SearchCursor cursor; } } diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchController.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchController.java index ae1db66..519991f 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchController.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchController.java @@ -1,11 +1,12 @@ package com.mypopsy.floatingsearchview.demo.search; -import android.support.annotation.MainThread; +import androidx.annotation.MainThread; public interface SearchController { interface Listener { - @MainThread void onSearchStarted(String query); + @MainThread + void onSearchStarted(String query); @MainThread void onSearchResults(SearchResult ...results); @MainThread void onSearchError(Throwable throwable); } diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchCursor.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchCursor.java index 268792f..fbb0216 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchCursor.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/SearchCursor.java @@ -5,7 +5,7 @@ public class SearchCursor { public long estimatedResultCount; public String moreResultsUrl; public long currentPageIndex; - public Page pages[]; + public Page[] pages; public static class Page { public String start; From 906668b8954c4d134fbc4f53f86e2f336e617dcf Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 20:33:58 +0100 Subject: [PATCH 18/30] Updated utils package --- .../demo/utils/PackageUtils.java | 15 ++++++--------- .../floatingsearchview/demo/utils/ViewUtils.java | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/PackageUtils.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/PackageUtils.java index 9d1a2c2..aa83ee7 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/PackageUtils.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/PackageUtils.java @@ -5,10 +5,9 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.speech.RecognizerIntent; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.widget.Toast; import com.mypopsy.floatingsearchview.demo.R; @@ -22,13 +21,11 @@ public class PackageUtils { static public void start(Context context, @NonNull Uri uri) { Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - Bundle extras = new Bundle(); - extras.putBinder("android.support.customtabs.extra.SESSION", null); - intent.putExtras(extras); - intent.putExtra("android.support.customtabs.extra.TOOLBAR_COLOR", - ViewUtils.getThemeAttrColor(context, R.attr.colorPrimary)); - } + Bundle extras = new Bundle(); + extras.putBinder("android.support.customtabs.extra.SESSION", null); + intent.putExtras(extras); + intent.putExtra("android.support.customtabs.extra.TOOLBAR_COLOR", + ViewUtils.getThemeAttrColor(context, android.R.attr.colorPrimary)); try { context.startActivity(intent); }catch(ActivityNotFoundException e) { diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/ViewUtils.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/ViewUtils.java index 38f1687..5b7dc57 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/ViewUtils.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/utils/ViewUtils.java @@ -3,7 +3,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; -import android.support.annotation.AttrRes; +import androidx.annotation.AttrRes; import android.util.DisplayMetrics; /** From 35d8e931cfd54a9884ad016da5222d7bc907b2bf Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Sun, 16 Oct 2022 21:12:25 +0100 Subject: [PATCH 19/30] Updated BaseItemAnimator --- .../demo/BaseItemAnimator.java | 208 +++++++++--------- 1 file changed, 100 insertions(+), 108 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/BaseItemAnimator.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/BaseItemAnimator.java index 76475bf..d95d327 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/BaseItemAnimator.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/BaseItemAnimator.java @@ -15,15 +15,16 @@ */ package com.mypopsy.floatingsearchview.demo; -import android.support.v4.animation.AnimatorCompatHelper; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorCompat; -import android.support.v4.view.ViewPropertyAnimatorListener; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.RecyclerView.ViewHolder; -import android.support.v7.widget.SimpleItemAnimator; import android.view.View; +import androidx.annotation.NonNull; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewPropertyAnimatorCompat; +import androidx.core.view.ViewPropertyAnimatorListener; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import androidx.recyclerview.widget.SimpleItemAnimator; + import java.util.ArrayList; import java.util.List; @@ -31,19 +32,19 @@ abstract public class BaseItemAnimator extends SimpleItemAnimator { private static final boolean DEBUG = false; - private ArrayList mPendingRemovals = new ArrayList<>(); - private ArrayList mPendingAdditions = new ArrayList<>(); - private ArrayList mPendingMoves = new ArrayList<>(); - private ArrayList mPendingChanges = new ArrayList<>(); + private final ArrayList mPendingRemovals = new ArrayList<>(); + private final ArrayList mPendingAdditions = new ArrayList<>(); + private final ArrayList mPendingMoves = new ArrayList<>(); + private final ArrayList mPendingChanges = new ArrayList<>(); - private ArrayList> mAdditionsList = new ArrayList<>(); - private ArrayList> mMovesList = new ArrayList<>(); - private ArrayList> mChangesList = new ArrayList<>(); + private final ArrayList> mAdditionsList = new ArrayList<>(); + private final ArrayList> mMovesList = new ArrayList<>(); + private final ArrayList> mChangesList = new ArrayList<>(); - private ArrayList mAddAnimations = new ArrayList<>(); - private ArrayList mMoveAnimations = new ArrayList<>(); - private ArrayList mRemoveAnimations = new ArrayList<>(); - private ArrayList mChangeAnimations = new ArrayList<>(); + private final ArrayList mAddAnimations = new ArrayList<>(); + private final ArrayList mMoveAnimations = new ArrayList<>(); + private final ArrayList mRemoveAnimations = new ArrayList<>(); + private final ArrayList mChangeAnimations = new ArrayList<>(); private static class MoveInfo { public ViewHolder holder; @@ -75,6 +76,7 @@ private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, this.toY = toY; } + @NonNull @Override public String toString() { return "ChangeInfo{" + @@ -105,20 +107,16 @@ public void runPendingAnimations() { mPendingRemovals.clear(); // Next, move stuff if (movesPending) { - final ArrayList moves = new ArrayList<>(); - moves.addAll(mPendingMoves); + final ArrayList moves = new ArrayList<>(mPendingMoves); mMovesList.add(moves); mPendingMoves.clear(); - Runnable mover = new Runnable() { - @Override - public void run() { - for (MoveInfo moveInfo : moves) { - animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, - moveInfo.toX, moveInfo.toY); - } - moves.clear(); - mMovesList.remove(moves); + Runnable mover = () -> { + for (MoveInfo moveInfo : moves) { + animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY, + moveInfo.toX, moveInfo.toY); } + moves.clear(); + mMovesList.remove(moves); }; if (removalsPending) { View view = moves.get(0).holder.itemView; @@ -129,19 +127,15 @@ public void run() { } // Next, change stuff, to run in parallel with move animations if (changesPending) { - final ArrayList changes = new ArrayList<>(); - changes.addAll(mPendingChanges); + final ArrayList changes = new ArrayList<>(mPendingChanges); mChangesList.add(changes); mPendingChanges.clear(); - Runnable changer = new Runnable() { - @Override - public void run() { - for (ChangeInfo change : changes) { - animateChangeImpl(change); - } - changes.clear(); - mChangesList.remove(changes); + Runnable changer = () -> { + for (ChangeInfo change : changes) { + animateChangeImpl(change); } + changes.clear(); + mChangesList.remove(changes); }; if (removalsPending) { ViewHolder holder = changes.get(0).oldHolder; @@ -152,18 +146,15 @@ public void run() { } // Next, add stuff if (additionsPending) { - final ArrayList additions = new ArrayList<>(); - additions.addAll(mPendingAdditions); + final ArrayList additions = new ArrayList<>(mPendingAdditions); mAdditionsList.add(additions); mPendingAdditions.clear(); - Runnable adder = new Runnable() { - public void run() { - for (ViewHolder holder : additions) { - animateAddImpl(holder); - } - additions.clear(); - mAdditionsList.remove(additions); + Runnable adder = () -> { + for (ViewHolder holder : additions) { + animateAddImpl(holder); } + additions.clear(); + mAdditionsList.remove(additions); }; if (removalsPending || movesPending || changesPending) { long removeDuration = removalsPending ? getRemoveDuration() : 0; @@ -199,14 +190,14 @@ private void animateRemoveImpl(final ViewHolder holder) { animation.setDuration(getRemoveDuration()) .setListener(new VpaListenerAdapter() { @Override - public void onAnimationStart(View view) { + public void onAnimationStart(@NonNull View view) { dispatchRemoveStarting(holder); } @Override - public void onAnimationEnd(View view) { + public void onAnimationEnd(@NonNull View view) { animation.setListener(null); - ViewCompat.setAlpha(view, 1); + view.setAlpha(1); dispatchRemoveFinished(holder); mRemoveAnimations.remove(holder); dispatchFinishedWhenDone(); @@ -215,7 +206,7 @@ public void onAnimationEnd(View view) { } protected void preAnimateAdd(final ViewHolder holder) { - ViewCompat.setAlpha(holder.itemView, 0); + holder.itemView.setAlpha(0F); } @Override @@ -240,16 +231,16 @@ private void animateAddImpl(final ViewHolder holder) { animation.setDuration(getAddDuration()). setListener(new VpaListenerAdapter() { @Override - public void onAnimationStart(View view) { + public void onAnimationStart(@NonNull View view) { dispatchAddStarting(holder); } @Override - public void onAnimationCancel(View view) { - ViewCompat.setAlpha(view, 1); + public void onAnimationCancel(@NonNull View view) { + view.setAlpha(1); } @Override - public void onAnimationEnd(View view) { + public void onAnimationEnd(@NonNull View view) { animation.setListener(null); dispatchAddFinished(holder); mAddAnimations.remove(holder); @@ -262,8 +253,8 @@ public void onAnimationEnd(View view) { public boolean animateMove(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; - fromX += ViewCompat.getTranslationX(holder.itemView); - fromY += ViewCompat.getTranslationY(holder.itemView); + fromX += holder.itemView.getTranslationX(); + fromY += holder.itemView.getTranslationY(); resetAnimation(holder); int deltaX = toX - fromX; int deltaY = toY - fromY; @@ -272,10 +263,10 @@ public boolean animateMove(final ViewHolder holder, int fromX, int fromY, return false; } if (deltaX != 0) { - ViewCompat.setTranslationX(view, -deltaX); + view.setTranslationX(-deltaX); } if (deltaY != 0) { - ViewCompat.setTranslationY(view, -deltaY); + view.setTranslationY(-deltaY); } mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY)); return true; @@ -298,20 +289,20 @@ private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int mMoveAnimations.add(holder); animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() { @Override - public void onAnimationStart(View view) { + public void onAnimationStart(@NonNull View view) { dispatchMoveStarting(holder); } @Override - public void onAnimationCancel(View view) { + public void onAnimationCancel(@NonNull View view) { if (deltaX != 0) { - ViewCompat.setTranslationX(view, 0); + view.setTranslationX(0); } if (deltaY != 0) { - ViewCompat.setTranslationY(view, 0); + view.setTranslationY(0); } } @Override - public void onAnimationEnd(View view) { + public void onAnimationEnd(@NonNull View view) { animation.setListener(null); dispatchMoveFinished(holder); mMoveAnimations.remove(holder); @@ -328,22 +319,22 @@ public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, // run a move animation to handle position changes. return animateMove(oldHolder, fromX, fromY, toX, toY); } - final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView); - final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView); - final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView); + final float prevTranslationX = oldHolder.itemView.getTranslationX(); + final float prevTranslationY = oldHolder.itemView.getTranslationY(); + final float prevAlpha = oldHolder.itemView.getAlpha(); resetAnimation(oldHolder); int deltaX = (int) (toX - fromX - prevTranslationX); int deltaY = (int) (toY - fromY - prevTranslationY); // recover prev translation state after ending animation - ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX); - ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY); - ViewCompat.setAlpha(oldHolder.itemView, prevAlpha); + oldHolder.itemView.setTranslationX(prevTranslationX); + oldHolder.itemView.setTranslationY(prevTranslationY); + oldHolder.itemView.setAlpha(prevAlpha); if (newHolder != null) { // carry over translation values resetAnimation(newHolder); - ViewCompat.setTranslationX(newHolder.itemView, -deltaX); - ViewCompat.setTranslationY(newHolder.itemView, -deltaY); - ViewCompat.setAlpha(newHolder.itemView, 0); + newHolder.itemView.setTranslationX(-deltaX); + newHolder.itemView.setTranslationY(-deltaY); + newHolder.itemView.setAlpha(0); } mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY)); return true; @@ -362,16 +353,16 @@ private void animateChangeImpl(final ChangeInfo changeInfo) { oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY); oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { @Override - public void onAnimationStart(View view) { + public void onAnimationStart(@NonNull View view) { dispatchChangeStarting(changeInfo.oldHolder, true); } @Override - public void onAnimationEnd(View view) { + public void onAnimationEnd(@NonNull View view) { oldViewAnim.setListener(null); - ViewCompat.setAlpha(view, 1); - ViewCompat.setTranslationX(view, 0); - ViewCompat.setTranslationY(view, 0); + view.setAlpha(1); + view.setTranslationX(0); + view.setTranslationY(0); dispatchChangeFinished(changeInfo.oldHolder, true); mChangeAnimations.remove(changeInfo.oldHolder); dispatchFinishedWhenDone(); @@ -384,15 +375,15 @@ public void onAnimationEnd(View view) { newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()). alpha(1).setListener(new VpaListenerAdapter() { @Override - public void onAnimationStart(View view) { + public void onAnimationStart(@NonNull View view) { dispatchChangeStarting(changeInfo.newHolder, false); } @Override - public void onAnimationEnd(View view) { + public void onAnimationEnd(@NonNull View view) { newViewAnimation.setListener(null); - ViewCompat.setAlpha(newView, 1); - ViewCompat.setTranslationX(newView, 0); - ViewCompat.setTranslationY(newView, 0); + newView.setAlpha(1); + newView.setTranslationX(0); + newView.setTranslationY(0); dispatchChangeFinished(changeInfo.newHolder, false); mChangeAnimations.remove(changeInfo.newHolder); dispatchFinishedWhenDone(); @@ -430,9 +421,9 @@ private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder } else { return false; } - ViewCompat.setAlpha(item.itemView, 1); - ViewCompat.setTranslationX(item.itemView, 0); - ViewCompat.setTranslationY(item.itemView, 0); + item.itemView.setAlpha(1); + item.itemView.setTranslationX(0); + item.itemView.setTranslationY(0); dispatchChangeFinished(item, oldItem); return true; } @@ -446,19 +437,19 @@ public void endAnimation(ViewHolder item) { for (int i = mPendingMoves.size() - 1; i >= 0; i--) { MoveInfo moveInfo = mPendingMoves.get(i); if (moveInfo.holder == item) { - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); + view.setTranslationY(0); + view.setTranslationX(0); dispatchMoveFinished(item); mPendingMoves.remove(i); } } endChangeAnimation(mPendingChanges, item); if (mPendingRemovals.remove(item)) { - ViewCompat.setAlpha(view, 1); + view.setAlpha(1); dispatchRemoveFinished(item); } if (mPendingAdditions.remove(item)) { - ViewCompat.setAlpha(view, 1); + view.setAlpha(1); dispatchAddFinished(item); } @@ -474,8 +465,8 @@ public void endAnimation(ViewHolder item) { for (int j = moves.size() - 1; j >= 0; j--) { MoveInfo moveInfo = moves.get(j); if (moveInfo.holder == item) { - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); + view.setTranslationY(0); + view.setTranslationX(0); dispatchMoveFinished(item); moves.remove(j); if (moves.isEmpty()) { @@ -488,7 +479,7 @@ public void endAnimation(ViewHolder item) { for (int i = mAdditionsList.size() - 1; i >= 0; i--) { ArrayList additions = mAdditionsList.get(i); if (additions.remove(item)) { - ViewCompat.setAlpha(view, 1); + view.setAlpha(1); dispatchAddFinished(item); if (additions.isEmpty()) { mAdditionsList.remove(i); @@ -497,25 +488,25 @@ public void endAnimation(ViewHolder item) { } // animations should be ended by the cancel above. - //noinspection PointlessBooleanExpression,ConstantConditions +// noinspection pointlessCooleanExpression,constantconditions if (mRemoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mRemoveAnimations list"); } - //noinspection PointlessBooleanExpression,ConstantConditions + // noinspection pointlessCooleanExpression,constantconditions if (mAddAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mAddAnimations list"); } - //noinspection PointlessBooleanExpression,ConstantConditions + // noinspection pointlessCooleanExpression,constantconditions if (mChangeAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mChangeAnimations list"); } - //noinspection PointlessBooleanExpression,ConstantConditions + // noinspection pointlessCooleanExpression,constantconditions if (mMoveAnimations.remove(item) && DEBUG) { throw new IllegalStateException("after animation is cancelled, item should not be in " + "mMoveAnimations list"); @@ -524,7 +515,8 @@ public void endAnimation(ViewHolder item) { } private void resetAnimation(ViewHolder holder) { - AnimatorCompatHelper.clearInterpolator(holder.itemView); + holder.itemView.clearAnimation(); +// AnimatorCompatHelper.clearInterpolator(holder.itemView); endAnimation(holder); } @@ -560,8 +552,8 @@ public void endAnimations() { for (int i = count - 1; i >= 0; i--) { MoveInfo item = mPendingMoves.get(i); View view = item.holder.itemView; - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); + view.setTranslationY(0); + view.setTranslationX(0); dispatchMoveFinished(item.holder); mPendingMoves.remove(i); } @@ -575,7 +567,7 @@ public void endAnimations() { for (int i = count - 1; i >= 0; i--) { ViewHolder item = mPendingAdditions.get(i); View view = item.itemView; - ViewCompat.setAlpha(view, 1); + view.setAlpha(1); dispatchAddFinished(item); mPendingAdditions.remove(i); } @@ -596,8 +588,8 @@ public void endAnimations() { MoveInfo moveInfo = moves.get(j); ViewHolder item = moveInfo.holder; View view = item.itemView; - ViewCompat.setTranslationY(view, 0); - ViewCompat.setTranslationX(view, 0); + view.setTranslationY(0); + view.setTranslationX(0); dispatchMoveFinished(moveInfo.holder); moves.remove(j); if (moves.isEmpty()) { @@ -612,7 +604,7 @@ public void endAnimations() { for (int j = count - 1; j >= 0; j--) { ViewHolder item = additions.get(j); View view = item.itemView; - ViewCompat.setAlpha(view, 1); + view.setAlpha(1); dispatchAddFinished(item); additions.remove(j); if (additions.isEmpty()) { @@ -648,12 +640,12 @@ void cancelAll(List viewHolders) { private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { @Override - public void onAnimationStart(View view) {} + public void onAnimationStart(@NonNull View view) {} @Override - public void onAnimationEnd(View view) {} + public void onAnimationEnd(@NonNull View view) {} @Override - public void onAnimationCancel(View view) {} + public void onAnimationCancel(@NonNull View view) {} } } \ No newline at end of file From 0c2b64cbbeb0c93128d88a4192af346b5c361314 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 15:39:40 +0100 Subject: [PATCH 20/30] Updated MainActivity syntax --- sample/build.gradle | 9 +- sample/src/main/AndroidManifest.xml | 6 +- .../floatingsearchview/demo/MainActivity.java | 149 ++++++++---------- 3 files changed, 75 insertions(+), 89 deletions(-) diff --git a/sample/build.gradle b/sample/build.gradle index abea974..d295f81 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -13,12 +13,9 @@ android { targetSdk 32 versionCode 1 versionName "1.0" - buildConfigField "String", "PROJECT_URL", '"https://github.com/renaudcerrato/FloatingSearchView"' } buildTypes { - debug { - debuggable true - } + debug {debuggable true} release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -43,8 +40,8 @@ dependencies { implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' // Dagger implementation 'com.google.dagger:dagger:2.44' - annotationProcessor 'com.google.dagger:dagger-compiler:2.44' - annotationProcessor 'org.glassfish:javax.annotation:10.0-b28' +// annotationProcessor 'com.google.dagger:dagger-compiler:2.44' +// annotationProcessor 'org.glassfish:javax.annotation:10.0-b28' } //afterEvaluate { project -> diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 669d6f0..1ac402e 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -10,7 +10,11 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java index 6dccd99..329e8a0 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java @@ -1,17 +1,12 @@ package com.mypopsy.floatingsearchview.demo; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.speech.RecognizerIntent; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewPropertyAnimatorCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.ActionMenuView; -import android.support.v7.widget.RecyclerView; import android.text.Editable; import android.text.Html; import android.text.TextUtils; @@ -28,12 +23,20 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.graphics.drawable.DrawerArrowDrawable; +import androidx.appcompat.widget.ActionMenuView; +import androidx.core.graphics.drawable.DrawableCompat; +import androidx.core.view.ViewCompat; +import androidx.core.view.ViewPropertyAnimatorCompat; +import androidx.recyclerview.widget.RecyclerView; + import com.mypopsy.drawable.SearchArrowDrawable; import com.mypopsy.drawable.ToggleDrawable; import com.mypopsy.drawable.model.CrossModel; import com.mypopsy.drawable.util.Bezier; import com.mypopsy.floatingsearchview.demo.adapter.ArrayRecyclerAdapter; -import com.mypopsy.floatingsearchview.demo.dagger.DaggerAppComponent; import com.mypopsy.floatingsearchview.demo.search.SearchController; import com.mypopsy.floatingsearchview.demo.search.SearchResult; import com.mypopsy.floatingsearchview.demo.utils.PackageUtils; @@ -53,40 +56,32 @@ public class MainActivity extends AppCompatActivity implements private FloatingSearchView mSearchView; private SearchAdapter mAdapter; - @Inject - SearchController mSearch; + @Inject SearchController mSearch; @Override protected void onCreate(Bundle savedInstanceState) { - DaggerAppComponent.builder().build().inject(this); + //DaggerAppComponent.builder().build().inject(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSearch.setListener(this); mSearchView = (FloatingSearchView) findViewById(R.id.search); - mSearchView.setAdapter(mAdapter = new SearchAdapter()); + mAdapter = new SearchAdapter(); + mSearchView.setAdapter(mAdapter); mSearchView.showLogo(true); mSearchView.setItemAnimator(new CustomSuggestionItemAnimator(mSearchView)); - updateNavigationIcon(R.id.menu_icon_search); + updateNavigationIcon("Search"); mSearchView.showIcon(shouldShowNavigationIcon()); - mSearchView.setOnIconClickListener(new FloatingSearchView.OnIconClickListener() { - @Override - public void onNavigationClick() { - // toggle - mSearchView.setActivated(!mSearchView.isActivated()); - } + mSearchView.setOnIconClickListener(() -> { + // toggle + mSearchView.setActivated(!mSearchView.isActivated()); }); - mSearchView.setOnSearchListener(new FloatingSearchView.OnSearchListener() { - @Override - public void onSearchAction(CharSequence text) { - mSearchView.setActivated(false); - } - }); + mSearchView.setOnSearchListener(text -> mSearchView.setActivated(false)); mSearchView.setOnMenuItemClickListener(this); @@ -107,20 +102,17 @@ public void afterTextChanged(Editable s) { } }); - mSearchView.setOnSearchFocusChangedListener(new FloatingSearchView.OnSearchFocusChangedListener() { - @Override - public void onFocusChanged(final boolean focused) { - boolean textEmpty = mSearchView.getText().length() == 0; + mSearchView.setOnSearchFocusChangedListener(focused -> { + boolean textEmpty = mSearchView.getText().length() == 0; - showClearButton(focused && !textEmpty); - if(!focused) showProgressBar(false); - mSearchView.showLogo(!focused && textEmpty); + showClearButton(focused && !textEmpty); + if(!focused) showProgressBar(false); + mSearchView.showLogo(!focused && textEmpty); - if (focused) - mSearchView.showIcon(true); - else - mSearchView.showIcon(shouldShowNavigationIcon()); - } + if (focused) + mSearchView.showIcon(true); + else + mSearchView.showIcon(shouldShowNavigationIcon()); }); mSearchView.setText(null); @@ -131,24 +123,27 @@ private void search(String query) { mSearch.search(query); } - private void updateNavigationIcon(int itemId) { + private void updateNavigationIcon(String title) { Context context = mSearchView.getContext(); Drawable drawable = null; - switch(itemId) { - case R.id.menu_icon_search: + switch(title) { + case "Search": drawable = new SearchArrowDrawable(context); break; - case R.id.menu_icon_drawer: - drawable = new android.support.v7.graphics.drawable.DrawerArrowDrawable(context); + case "Drawer": + drawable = new DrawerArrowDrawable(context); break; - case R.id.menu_icon_custom: + case "Custom": drawable = new CustomDrawable(context); break; } - drawable = DrawableCompat.wrap(drawable); - DrawableCompat.setTint(drawable, ViewUtils.getThemeAttrColor(context, R.attr.colorControlNormal)); - mSearchView.setIcon(drawable); + + if (drawable != null) { + drawable = DrawableCompat.wrap(drawable); + DrawableCompat.setTint(drawable, ViewUtils.getThemeAttrColor(context, android.R.attr.colorControlNormal)); + mSearchView.setIcon(drawable); + } } private boolean shouldShowNavigationIcon() { @@ -159,14 +154,11 @@ private boolean shouldShowNavigationIcon() { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case REQ_CODE_SPEECH_INPUT: { - if (resultCode == RESULT_OK && null != data) { - ArrayList result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); - mSearchView.setActivated(true); - mSearchView.setText(result.get(0)); - } - break; + if (requestCode == REQ_CODE_SPEECH_INPUT) { + if (resultCode == RESULT_OK && null != data) { + ArrayList result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); + mSearchView.setActivated(true); + mSearchView.setText(result.get(0)); } } } @@ -179,22 +171,23 @@ protected void onDestroy() { @Override public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_clear: + + switch ((String) item.getTitle()) { + case "Clear": mSearchView.setText(null); mSearchView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); break; - case R.id.menu_toggle_icon: + case "Icon Visible": item.setChecked(!item.isChecked()); mSearchView.showIcon(item.isChecked()); break; - case R.id.menu_tts: + case "Text to speech": PackageUtils.startTextToSpeech(this, getString(R.string.speech_prompt), REQ_CODE_SPEECH_INPUT); break; - case R.id.menu_icon_search: - case R.id.menu_icon_drawer: - case R.id.menu_icon_custom: - updateNavigationIcon(item.getItemId()); + case"Search": + case "Drawer": + case "Custom": + updateNavigationIcon((String) item.getTitle()); Toast.makeText(MainActivity.this, item.getTitle(), Toast.LENGTH_SHORT).show(); break; } @@ -206,6 +199,7 @@ public void onSearchStarted(String query) { //nothing to do } + @SuppressLint("NotifyDataSetChanged") @Override public void onSearchResults(SearchResult ...searchResults) { mAdapter.setNotifyOnChange(false); @@ -236,7 +230,7 @@ private void showClearButton(boolean show) { } public void onGithubClick(View view) { - PackageUtils.start(this, Uri.parse(BuildConfig.PROJECT_URL)); + PackageUtils.start(this, Uri.parse("https://github.com/renaudcerrato/FloatingSearchView")); } private static SearchResult getErrorResult(Throwable throwable) { @@ -254,8 +248,9 @@ private class SearchAdapter extends ArrayRecyclerAdapter onItemClick(mAdapter.getItem(getBindingAdapterPosition()))); + right.setOnClickListener(v -> mSearchView.setText(text.getText())); } void bind(SearchResult result) { @@ -321,19 +306,19 @@ public CustomSuggestionItemAnimator(FloatingSearchView searchView) { @Override protected void preAnimateAdd(RecyclerView.ViewHolder holder) { if(!mSearchView.isActivated()) return; - ViewCompat.setTranslationX(holder.itemView, 0); - ViewCompat.setTranslationY(holder.itemView, -holder.itemView.getHeight()); - ViewCompat.setAlpha(holder.itemView, 0); + holder.itemView.setTranslationX(0); + holder.itemView.setTranslationY(-holder.itemView.getHeight()); + holder.itemView.setAlpha(0); } @Override protected ViewPropertyAnimatorCompat onAnimateAdd(RecyclerView.ViewHolder holder) { if(!mSearchView.isActivated()) return null; return ViewCompat.animate(holder.itemView) - .translationY(0) - .alpha(1) - .setStartDelay((getAddDuration() / 2) * holder.getLayoutPosition()) - .setInterpolator(INTERPOLATOR_ADD); + .translationY(0) + .alpha(1) + .setStartDelay((getAddDuration() / 2) * holder.getLayoutPosition()) + .setInterpolator(INTERPOLATOR_ADD); } @Override From a5ef63cab59c28a972a3f1a33802a825e80af97d Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 16:15:44 +0100 Subject: [PATCH 21/30] Migrate project to Hilt, configure Kotlin --- build.gradle | 7 +++++++ sample/build.gradle | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 97cf3c4..026dc4b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,11 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.hilt_version = "2.40" + dependencies { + classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" + classpath 'com.android.tools.build:gradle:7.2.0' + } +} plugins { id 'com.android.application' version '7.2.0' apply false id 'com.android.library' version '7.2.0' apply false diff --git a/sample/build.gradle b/sample/build.gradle index d295f81..45026bc 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,8 +1,11 @@ plugins { id 'com.android.application' + id 'dagger.hilt.android.plugin' + id 'kotlin-android' + id 'kotlin-kapt' // id 'com.neenbedankt.android-apt' } - +apply plugin: 'kotlin-android' android { compileSdk 32 //buildToolsVersion "23.0.2" @@ -21,6 +24,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { @@ -40,6 +50,10 @@ dependencies { implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' // Dagger implementation 'com.google.dagger:dagger:2.44' + // Hilt + implementation "com.google.dagger:hilt-android:$hilt_version" + + kapt "com.google.dagger:hilt-android-compiler:$hilt_version" // annotationProcessor 'com.google.dagger:dagger-compiler:2.44' // annotationProcessor 'org.glassfish:javax.annotation:10.0-b28' } From fbe97f7b4e586c5b78bf2b7e386b507f8e344916 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 16:46:04 +0100 Subject: [PATCH 22/30] Install Hilt dependency injection --- sample/src/main/AndroidManifest.xml | 1 + .../floatingsearchview/demo/MainActivity.java | 3 +++ .../floatingsearchview/demo/MainApplication.java | 12 ++++++++++++ .../demo/dagger/AppComponent.java | 15 ++++----------- .../GoogleSearchControllerEntryPoint.java | 15 +++++++++++++++ .../{dagger => hilt/module}/RetrofitModule.java | 5 ++++- .../{dagger => hilt/module}/SearchModule.java | 5 ++++- .../demo/search/GoogleSearchController.java | 10 ++++++++-- 8 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainApplication.java create mode 100644 sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java rename sample/src/main/java/com/mypopsy/floatingsearchview/demo/{dagger => hilt/module}/RetrofitModule.java (86%) rename sample/src/main/java/com/mypopsy/floatingsearchview/demo/{dagger => hilt/module}/SearchModule.java (79%) diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 1ac402e..68c68ea 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java index 329e8a0..c0564b4 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java @@ -47,6 +47,9 @@ import javax.inject.Inject; +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public class MainActivity extends AppCompatActivity implements ActionMenuView.OnMenuItemClickListener, SearchController.Listener { diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainApplication.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainApplication.java new file mode 100644 index 0000000..3e582b6 --- /dev/null +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainApplication.java @@ -0,0 +1,12 @@ +package com.mypopsy.floatingsearchview.demo; + +import android.app.Application; + +import dagger.hilt.android.HiltAndroidApp; + +@HiltAndroidApp +public class MainApplication extends Application { + + + +} diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/AppComponent.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/AppComponent.java index 9f0f1f9..ac7b570 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/AppComponent.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/AppComponent.java @@ -1,15 +1,8 @@ package com.mypopsy.floatingsearchview.demo.dagger; -import com.mypopsy.floatingsearchview.demo.MainActivity; -import com.mypopsy.floatingsearchview.demo.search.SearchController; - -import javax.inject.Singleton; - -import dagger.Component; - -@Singleton -@Component(modules = {RetrofitModule.class, SearchModule.class}) +//@Singleton +//@Component(modules = {RetrofitModule.class, SearchModule.class}) public interface AppComponent { - void inject(MainActivity mainActivity); - SearchController getSearch(); +// void inject(MainActivity mainActivity); +// SearchController getSearch(); } diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java new file mode 100644 index 0000000..01f9a96 --- /dev/null +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java @@ -0,0 +1,15 @@ +package com.mypopsy.floatingsearchview.demo.hilt.entrypoint; + +import com.mypopsy.floatingsearchview.demo.search.GoogleSearch; + +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; + +@EntryPoint +@InstallIn(SingletonComponent.class) + +public interface GoogleSearchControllerEntryPoint { + + public GoogleSearch getGoogleSearch(); +} diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java similarity index 86% rename from sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java rename to sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java index 3142b0f..e7bf98f 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/RetrofitModule.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java @@ -1,9 +1,11 @@ -package com.mypopsy.floatingsearchview.demo.dagger; +package com.mypopsy.floatingsearchview.demo.hilt.module; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Converter; @@ -11,6 +13,7 @@ import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; +@InstallIn(SingletonComponent.class) @Module public class RetrofitModule { diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/SearchModule.java similarity index 79% rename from sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java rename to sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/SearchModule.java index eeba33c..cff775a 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/dagger/SearchModule.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/SearchModule.java @@ -1,4 +1,4 @@ -package com.mypopsy.floatingsearchview.demo.dagger; +package com.mypopsy.floatingsearchview.demo.hilt.module; import com.mypopsy.floatingsearchview.demo.search.GoogleSearch; import com.mypopsy.floatingsearchview.demo.search.GoogleSearchController; @@ -8,8 +8,11 @@ import dagger.Module; import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; import retrofit2.Retrofit; +@InstallIn(SingletonComponent.class) @Module public class SearchModule { diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java index 3415162..cefd0fe 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java @@ -1,13 +1,18 @@ package com.mypopsy.floatingsearchview.demo.search; +import android.content.Context; import android.text.TextUtils; +import com.mypopsy.floatingsearchview.demo.hilt.entrypoint.GoogleSearchControllerEntryPoint; + import java.io.InterruptedIOException; import java.util.concurrent.TimeUnit; import javax.inject.Inject; +import dagger.hilt.android.EntryPointAccessors; +import dagger.hilt.android.qualifiers.ApplicationContext; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.annotations.NonNull; import io.reactivex.rxjava3.core.Scheduler; @@ -28,8 +33,9 @@ public class GoogleSearchController implements SearchController { private Listener mListener; @Inject - public GoogleSearchController(GoogleSearch search) { - mSearch = search; + public GoogleSearchController(@ApplicationContext Context context) { + GoogleSearchControllerEntryPoint entryPoint = EntryPointAccessors.fromApplication(context, GoogleSearchControllerEntryPoint.class); + mSearch = entryPoint.getGoogleSearch(); mWorker = AndroidSchedulers.mainThread().createWorker(); } From 849db88546f3fe9e8daed19cc229e2a9e571452e Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 18:29:54 +0100 Subject: [PATCH 23/30] RetrofitModule.java: Fixed interceptor error --- .../hilt/entrypoint/GoogleSearchControllerEntryPoint.java | 2 +- .../floatingsearchview/demo/hilt/module/RetrofitModule.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java index 01f9a96..7649755 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/entrypoint/GoogleSearchControllerEntryPoint.java @@ -11,5 +11,5 @@ public interface GoogleSearchControllerEntryPoint { - public GoogleSearch getGoogleSearch(); + GoogleSearch getGoogleSearch(); } diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java index e7bf98f..f239e43 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/hilt/module/RetrofitModule.java @@ -22,9 +22,8 @@ public class RetrofitModule { OkHttpClient provideHttpClient() { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.BODY); - OkHttpClient httpClient = new OkHttpClient(); - httpClient.interceptors().add(logging); - return httpClient; + return new OkHttpClient.Builder() + .addInterceptor(logging).build(); } @Provides From 1feeca7458b5e54c0c5fb8ad2607480a6bdc0d9b Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 19:59:30 +0100 Subject: [PATCH 24/30] GoogleSearchController: added onError() in subscribing function. --- .../com/mypopsy/floatingsearchview/demo/MainActivity.java | 2 +- .../demo/search/GoogleSearchController.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java index c0564b4..eb1c331 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/MainActivity.java @@ -204,7 +204,7 @@ public void onSearchStarted(String query) { @SuppressLint("NotifyDataSetChanged") @Override - public void onSearchResults(SearchResult ...searchResults) { + public void onSearchResults(SearchResult... searchResults) { mAdapter.setNotifyOnChange(false); mAdapter.clear(); if (searchResults != null) mAdapter.addAll(searchResults); diff --git a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java index cefd0fe..b30d45e 100644 --- a/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java +++ b/sample/src/main/java/com/mypopsy/floatingsearchview/demo/search/GoogleSearchController.java @@ -20,6 +20,7 @@ import rx.Subscription; import rx.functions.Action0; import rx.functions.Func1; +import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; public class GoogleSearchController implements SearchController { @@ -27,7 +28,7 @@ public class GoogleSearchController implements SearchController { private static final int DEFAULT_DEBOUNCE = 700; // milliseconds private final GoogleSearch mSearch; - private final Scheduler.@NonNull Worker mWorker; + private final @NonNull Scheduler.Worker mWorker; private final PublishSubject mQuerySubject = PublishSubject.create(); private Subscription mSubscription; private Listener mListener; @@ -80,10 +81,11 @@ private void ensureSubscribed() { return Observable.empty(); }); }) + .observeOn(Schedulers.immediate()) // .observeOn(AndroidSchedulers.mainThread()) .subscribe(searchResults -> { if(mListener != null) mListener.onSearchResults(searchResults); - }); + }, this::notifyError); } private void notifyStarted(final String query) { @@ -97,6 +99,6 @@ private void notifyError(final Throwable throwable) { } private void dispatchOnMainThread(Action0 action) { - mWorker.schedule((Runnable) action); + mWorker.schedule(action::call); } } From f3c8d527d41d0fb1f4717b79ae0a2741769c7e90 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 22:01:56 +0100 Subject: [PATCH 25/30] Initial commit for Kotlin library version --- library-ktx/.gitignore | 1 + library-ktx/build.gradle | 40 +++++++++++++++++++ library-ktx/consumer-rules.pro | 0 library-ktx/proguard-rules.pro | 21 ++++++++++ .../internal/ExampleInstrumentedTest.kt | 24 +++++++++++ library-ktx/src/main/AndroidManifest.xml | 5 +++ .../widget/internal/ExampleUnitTest.kt | 17 ++++++++ settings.gradle | 1 + 8 files changed, 109 insertions(+) create mode 100644 library-ktx/.gitignore create mode 100644 library-ktx/build.gradle create mode 100644 library-ktx/consumer-rules.pro create mode 100644 library-ktx/proguard-rules.pro create mode 100644 library-ktx/src/androidTest/java/com/mypopsy/widget/internal/ExampleInstrumentedTest.kt create mode 100644 library-ktx/src/main/AndroidManifest.xml create mode 100644 library-ktx/src/test/java/com/mypopsy/widget/internal/ExampleUnitTest.kt diff --git a/library-ktx/.gitignore b/library-ktx/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/library-ktx/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/library-ktx/build.gradle b/library-ktx/build.gradle new file mode 100644 index 0000000..5cf629a --- /dev/null +++ b/library-ktx/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 32 + + defaultConfig { + minSdk 21 + targetSdk 32 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.8.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/library-ktx/consumer-rules.pro b/library-ktx/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/library-ktx/proguard-rules.pro b/library-ktx/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/library-ktx/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/library-ktx/src/androidTest/java/com/mypopsy/widget/internal/ExampleInstrumentedTest.kt b/library-ktx/src/androidTest/java/com/mypopsy/widget/internal/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..9dd22ea --- /dev/null +++ b/library-ktx/src/androidTest/java/com/mypopsy/widget/internal/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.mypopsy.widget.internal + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.mypopsy.widget.internal.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/library-ktx/src/main/AndroidManifest.xml b/library-ktx/src/main/AndroidManifest.xml new file mode 100644 index 0000000..adb3ecc --- /dev/null +++ b/library-ktx/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/library-ktx/src/test/java/com/mypopsy/widget/internal/ExampleUnitTest.kt b/library-ktx/src/test/java/com/mypopsy/widget/internal/ExampleUnitTest.kt new file mode 100644 index 0000000..993d5d7 --- /dev/null +++ b/library-ktx/src/test/java/com/mypopsy/widget/internal/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.mypopsy.widget.internal + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index ade249f..a37d48f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,4 @@ dependencyResolutionManagement { } rootProject.name = "FloatingSearchView" include ':sample', ':library' +include ':library-ktx' From 8bff85f4bfce34a8d084c99005195ddda418d8f4 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Tue, 18 Oct 2022 22:26:16 +0100 Subject: [PATCH 26/30] ViewUtils.kt: Kotlin variant --- .../internal/RoundRectDrawableWithShadow.kt | 4 + .../com/mypopsy/widget/internal/ViewUtils.kt | 76 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt create mode 100644 library-ktx/src/main/kotlin/com/mypopsy/widget/internal/ViewUtils.kt diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt new file mode 100644 index 0000000..d6cae25 --- /dev/null +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt @@ -0,0 +1,4 @@ +package com.mypopsy.widget.internal + +class RoundRectDrawableWithShadow { +} \ No newline at end of file diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/ViewUtils.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/ViewUtils.kt new file mode 100644 index 0000000..d2cc775 --- /dev/null +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/ViewUtils.kt @@ -0,0 +1,76 @@ +package com.mypopsy.widget.internal + +/* + * Copyright (C) 2015 Arlib + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.app.Activity +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.core.graphics.drawable.DrawableCompat + +object ViewUtils { + private val TEMP_ARRAY = IntArray(1) + + fun showSoftKeyboardDelayed(editText: EditText, delay: Long) { + editText.postDelayed({ + val inputMethodManager = editText.context + .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showSoftInput( + editText, + InputMethodManager.SHOW_IMPLICIT + ) + }, delay) + } + + fun closeSoftKeyboard(activity: Activity) { + val currentFocusView = activity.currentFocus + currentFocusView?.let { + val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(it.windowToken, 0) + } + } + + fun dpToPx(dp: Int): Int { + val metrics = Resources.getSystem().displayMetrics + return (dp * metrics.density).toInt() + } + + fun pxToDp(px: Int): Int { + val metrics = Resources.getSystem().displayMetrics + return (px / metrics.density).toInt() + } + + fun getThemeAttrColor(context: Context, @AttrRes attr: Int): Int { + TEMP_ARRAY[0] = attr + val a = context.obtainStyledAttributes(null, TEMP_ARRAY) + return try { + a.getColor(0, 0) + } finally { + a.recycle() + } + } + + fun getTinted(icon: Drawable, @ColorInt color: Int): Drawable { + val icon1 = DrawableCompat.wrap(icon) + DrawableCompat.setTint(icon1, color) + return icon1 + } +} \ No newline at end of file From 55d334c70e49e7f181787fbe1312c5f00d0aa560 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Wed, 19 Oct 2022 11:03:45 +0100 Subject: [PATCH 27/30] RoundRectDrawableWithShadow.kt: Kotilin variant --- .../internal/RoundRectDrawableWithShadow.kt | 505 +++++++++++++++++- 1 file changed, 504 insertions(+), 1 deletion(-) diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt index d6cae25..fbfc013 100644 --- a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt @@ -1,4 +1,507 @@ package com.mypopsy.widget.internal -class RoundRectDrawableWithShadow { +import android.graphics.* +import android.graphics.drawable.Drawable +import android.util.Log +import androidx.annotation.IntDef +import kotlin.math.ceil +import kotlin.math.cos + +open class RoundRectDrawableWithShadow( + backgroundColor: Int, radius: Float, + shadowSize: Float, maxShadowSize: Float + ): Drawable() { + + companion object { + private const val DEBUG = false + private const val SHADOW_COLOR_START = 0x37000000 + private const val SHADOW_COLOR_END = 0x03000000 + private const val SHADOW_INSET_DP = 1 + + const val LEFT = 1 + const val TOP = 1 shl 1 + const val RIGHT = 1 shl 2 + const val BOTTOM = 1 shl 3 + + // used to calculate content padding + val COS_45 = cos(Math.toRadians(45.0)) + + const val SHADOW_MULTIPLIER = 1.5f + + fun calculateVerticalPadding( + maxShadowSize: Float, cornerRadius: Float, + addPaddingForCorners: Boolean + ): Float { + return if (addPaddingForCorners) { + (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius).toFloat() + } else { + maxShadowSize * SHADOW_MULTIPLIER + } + } + + fun calculateHorizontalPadding( + maxShadowSize: Float, cornerRadius: Float, + addPaddingForCorners: Boolean + ): Float { + return if (addPaddingForCorners) { + (maxShadowSize + (1 - COS_45) * cornerRadius).toFloat() + } else { + maxShadowSize + } + } + + } + + private var mInsetShadow = 0 // extra shadow to avoid gaps between card and shadow + + private val mCornerRect = RectF() + + private val mPaint: Paint + + private val mCornerShadowPaint: Paint + + private val mEdgeShadowPaint: Paint + + private var mBoundsPaint: Paint? = null + + private val mCardBounds: RectF + + var cornerRadius = 0f + set(value) { + val radius = (value + .5f).toInt().toFloat() + if (field == radius) { + return + } + field = radius + mDirty = true + invalidateSelf() + } + + private var mCornerShadowPath: Path? = null + + val color: Int + get() = mPaint.color + + // updated value with inset + var mMaxShadowSize = 0f + + // actual value set by developer + var maxShadowSize = 0f + private set + + // multiplied value to account for shadow offset + var shadowSize = 0f + private set + + // actual value set by developer + var mRawShadowSize = 0f + + var topLeftCorner = false + var bottomLeftCorner = false + var topRightCorner = false + var bottomRightCorner = false + + var leftShadow = false + var topShadow = false + var rightShadow = false + var bottomShadow = false + + private var mDirty = true + + var addPaddingForCorners = true + set(value) { + field = value + invalidateSelf() + } + + /** + * If shadow size is set to a value above max shadow, we print a warning + */ + private var mPrintedShadowClipWarning = false + + @Retention(AnnotationRetention.SOURCE) + @IntDef( + value = [LEFT, RIGHT, TOP, BOTTOM], + flag = true + ) + annotation class Gravity + + init { + if (DEBUG) Log.d( + javaClass.simpleName, + "RoundRectDrawableWithShadow($radius,$shadowSize,$maxShadowSize)" + ) + mInsetShadow = ViewUtils.dpToPx(SHADOW_INSET_DP) + mPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).also { it.color = backgroundColor } + mCornerShadowPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG).also { it.style = Paint.Style.FILL } + cornerRadius = (radius + .5f).toInt().toFloat() + mCardBounds = RectF() + mEdgeShadowPaint = Paint(mCornerShadowPaint).also { it.isAntiAlias = false } + if (DEBUG) { + mBoundsPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG) + mBoundsPaint?.apply { + color = Color.BLACK + style = Paint.Style.STROKE + strokeWidth = 1f + } + mPaint.apply { + style = Paint.Style.STROKE + strokeWidth = 1f + } + mCornerShadowPaint.strokeWidth = 1f + } + setShadow(TOP or LEFT or RIGHT or BOTTOM) + setShadowSize(shadowSize, maxShadowSize) + } + + fun setShadow(@Gravity flags: Int) { + topLeftCorner = flags and (TOP or LEFT) == LEFT or TOP + bottomLeftCorner = flags and (BOTTOM or LEFT) == LEFT or BOTTOM + topRightCorner = flags and (TOP or RIGHT) == TOP or RIGHT + bottomRightCorner = flags and (BOTTOM or RIGHT) == BOTTOM or RIGHT + leftShadow = flags and LEFT != 0 + topShadow = flags and TOP != 0 + rightShadow = flags and RIGHT != 0 + bottomShadow = flags and BOTTOM != 0 + mDirty = true + invalidateSelf() + } + + override fun mutate(): RoundRectDrawableWithShadow { + return RoundRectDrawableWithShadow(mPaint.color, cornerRadius, shadowSize, maxShadowSize) + } + + /** + * Casts the value to an even integer. + */ + private fun toEven(value: Float): Int { + val i = (value + .5f).toInt() + return if (i % 2 == 1) { + i - 1 + } else i + } + + override fun setAlpha(alpha: Int) { + mPaint.alpha = alpha + mCornerShadowPaint.alpha = alpha + mEdgeShadowPaint.alpha = alpha + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + if (DEBUG) Log.d( + javaClass.simpleName, + "onBoundsChange@" + hashCode() + ": " + + bounds + " (" + bounds.width() + "," + bounds.height() + ")" + ) + mDirty = true + } + + fun setShadowSize(shadowSize: Float, maxShadowSize: Float) { + require(!(shadowSize < 0 || maxShadowSize < 0)) { "invalid shadow size" } + val shadowSize1 = toEven(shadowSize).toFloat() + val maxShadowSize1 = toEven(maxShadowSize).toFloat() + if (shadowSize1 > maxShadowSize1) { + if (!mPrintedShadowClipWarning) { + mPrintedShadowClipWarning = true + } + } + if (mRawShadowSize == shadowSize && maxShadowSize == maxShadowSize) { + return + } + mRawShadowSize = shadowSize + this.maxShadowSize = maxShadowSize + this.shadowSize = (shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f).toInt().toFloat() + mMaxShadowSize = maxShadowSize + mInsetShadow + mDirty = true + invalidateSelf() + } + + override fun getPadding(padding: Rect): Boolean { + val vOffset = ceil( + calculateVerticalPadding( + maxShadowSize, cornerRadius, + addPaddingForCorners + ).toDouble() + ).toInt() + val hOffset = ceil( + calculateHorizontalPadding( + maxShadowSize, cornerRadius, + addPaddingForCorners + ).toDouble() + ).toInt() + padding[if (leftShadow) hOffset else 0, if (topShadow) vOffset else 0, if (rightShadow) hOffset else 0] = + if (bottomShadow) vOffset else 0 + return true + } + + fun calculateVerticalPadding( + maxShadowSize: Float, cornerRadius: Float, + addPaddingForCorners: Boolean + ): Float { + return if (addPaddingForCorners) { + (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius).toFloat() + } else { + maxShadowSize * SHADOW_MULTIPLIER + } + } + + fun calculateHorizontalPadding( + maxShadowSize: Float, cornerRadius: Float, + addPaddingForCorners: Boolean + ): Float { + return if (addPaddingForCorners) { + (maxShadowSize + (1 - COS_45) * cornerRadius).toFloat() + } else { + maxShadowSize + } + } + + override fun setColorFilter(cf: ColorFilter?) { + mPaint.colorFilter = cf + mCornerShadowPaint.colorFilter = cf + mEdgeShadowPaint.colorFilter = cf + } + + override fun getOpacity(): Int { + return PixelFormat.TRANSLUCENT + } + + override fun draw(canvas: Canvas) { + if (mDirty) { + buildComponents(bounds) + mDirty = false + } + canvas.translate(0f, mRawShadowSize / 2) + drawShadow(canvas) + canvas.translate(0f, -mRawShadowSize / 2) + drawBody(canvas, mCardBounds, cornerRadius, mPaint) + if (DEBUG) { + val bounds = bounds + canvas.drawRect(bounds, mBoundsPaint!!) + } + } + + private fun drawShadow(canvas: Canvas) { + val edgeShadowTop = -cornerRadius - shadowSize + val inset: Float = cornerRadius + mInsetShadow + mRawShadowSize / 2 + val drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0 + val drawVerticalEdges = mCardBounds.height() - 2 * inset > 0 + var saved: Int + + // LT + if (topLeftCorner || topShadow) { + saved = canvas.save() + canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset) + if (topLeftCorner) canvas.drawPath(mCornerShadowPath!!, mCornerShadowPaint) + if (drawHorizontalEdges && topShadow) { + canvas.drawRect( + if (topLeftCorner) 0F else -inset, + edgeShadowTop, + mCardBounds.width() - if (topRightCorner) 2 * inset else 0F, + -cornerRadius, + mEdgeShadowPaint + ) + } + canvas.restoreToCount(saved) + } + + // RB + if (bottomRightCorner || bottomShadow) { + saved = canvas.save() + canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset) + canvas.rotate(180f) + if (bottomRightCorner) canvas.drawPath(mCornerShadowPath!!, mCornerShadowPaint) + if (drawHorizontalEdges && bottomShadow) { + canvas.drawRect( + if (bottomLeftCorner) 0F else -inset, edgeShadowTop, + mCardBounds.width() - if (bottomRightCorner) 2F * inset else 0F, + -cornerRadius + shadowSize, + mEdgeShadowPaint + ) + } + canvas.restoreToCount(saved) + } + + // LB + if (bottomLeftCorner || leftShadow) { + saved = canvas.save() + canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset) + canvas.rotate(270f) + if (bottomLeftCorner) canvas.drawPath(mCornerShadowPath!!, mCornerShadowPaint) + if (drawVerticalEdges && leftShadow) { + canvas.drawRect( + if (bottomLeftCorner) 0F else -(inset - mInsetShadow), + edgeShadowTop, + mCardBounds.height() - if (topLeftCorner) 2 * inset else inset - mInsetShadow, + -cornerRadius, + mEdgeShadowPaint + ) + } + canvas.restoreToCount(saved) + } + + // RT + if (topRightCorner || rightShadow) { + saved = canvas.save() + canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset) + canvas.rotate(90f) + if (topRightCorner) canvas.drawPath(mCornerShadowPath!!, mCornerShadowPaint) + if (drawVerticalEdges && rightShadow) { + canvas.drawRect( + if (topRightCorner) 0F else -(inset + mInsetShadow), + edgeShadowTop, + mCardBounds.height() - if (bottomRightCorner) 2 * inset else inset + mInsetShadow, + -cornerRadius, + mEdgeShadowPaint + ) + } + canvas.restoreToCount(saved) + } + } + + fun drawBody( + canvas: Canvas, bounds: RectF, cornerRadius: Float, + paint: Paint + ) { + var cornerRadius1 = cornerRadius + val twoRadius = cornerRadius1 * 2 + val innerWidth = bounds.width() - twoRadius - 1 + val innerHeight = bounds.height() - twoRadius - 1 + + // increment it to account for half pixels. + if (cornerRadius1 >= 1f) { + cornerRadius1 += .5f + if (topLeftCorner || topRightCorner || bottomRightCorner || bottomLeftCorner) { + mCornerRect[-cornerRadius1, -cornerRadius1, cornerRadius1] = cornerRadius1 + val saved = canvas.save() + canvas.translate(bounds.left + cornerRadius1, bounds.top + cornerRadius1) + if (topLeftCorner) canvas.drawArc(mCornerRect, 180f, 90f, true, paint) + canvas.translate(innerWidth, 0f) + canvas.rotate(90f) + if (topRightCorner) canvas.drawArc(mCornerRect, 180f, 90f, true, paint) + canvas.translate(innerHeight, 0f) + canvas.rotate(90f) + if (bottomRightCorner) canvas.drawArc(mCornerRect, 180f, 90f, true, paint) + canvas.translate(innerWidth, 0f) + canvas.rotate(90f) + if (bottomLeftCorner) canvas.drawArc(mCornerRect, 180f, 90f, true, paint) + canvas.restoreToCount(saved) + } + //draw top and bottom pieces + if (topShadow) canvas.drawRect( + bounds.left + if (topLeftCorner) cornerRadius1 - 1F else 0F, + bounds.top, + bounds.right - if (topRightCorner) cornerRadius1 - 1F else 0F, + bounds.top + cornerRadius1, + paint + ) + if (bottomShadow) canvas.drawRect( + bounds.left + if (bottomLeftCorner) cornerRadius - 1F else 0F, + bounds.bottom - cornerRadius + 1f, + bounds.right - if (bottomRightCorner) cornerRadius - 1F else 0F, + bounds.bottom, paint + ) + } + //// center + if (DEBUG) Log.d( + javaClass.simpleName, "drawBody:" + RectF( + bounds.left, + bounds.top + if (topShadow) 0f.coerceAtLeast(cornerRadius - 1) else 0F, + bounds.right, + bounds.bottom - if (bottomShadow) cornerRadius - 1F else 0F + ) + ) + canvas.drawRect( + bounds.left, + bounds.top + if (topShadow) 0f.coerceAtLeast(cornerRadius - 1) else 0F, + bounds.right, + bounds.bottom - if (bottomShadow) cornerRadius - 1F else 0F, paint + ) + } + + private fun buildShadowCorners() { + val innerBounds = RectF(-cornerRadius, -cornerRadius, cornerRadius, cornerRadius) + val outerBounds = RectF(innerBounds) + outerBounds.inset(-shadowSize, -shadowSize) + if (mCornerShadowPath == null) { + mCornerShadowPath = Path() + } else { + mCornerShadowPath!!.reset() + } + mCornerShadowPath!!.apply { + fillType = Path.FillType.EVEN_ODD + moveTo(-cornerRadius, 0f) + rLineTo(-shadowSize, 0f) + // outer arc + arcTo(outerBounds, 180f, 90f, false) + // inner arc + arcTo(innerBounds, 270f, -90f, false) + close() + val startRatio: Float = cornerRadius / (cornerRadius + shadowSize) + mCornerShadowPaint.shader = + RadialGradient(0F, 0F, + cornerRadius + shadowSize, + intArrayOf(SHADOW_COLOR_START, SHADOW_COLOR_START, SHADOW_COLOR_END), + floatArrayOf(0f, startRatio, 1f), + Shader.TileMode.CLAMP + ) + + // we offset the content shadowSize/2 pixels up to make it more realistic. + // this is why edge shadow shader has some extra space + // When drawing bottom edge shadow, we use that extra space. + mEdgeShadowPaint.shader = LinearGradient(0F, + -cornerRadius + shadowSize, + 0F, + -cornerRadius - shadowSize, + intArrayOf(SHADOW_COLOR_START, SHADOW_COLOR_START, SHADOW_COLOR_END), + floatArrayOf(0f, .5f, 1f), + Shader.TileMode.CLAMP + ) + + } + mEdgeShadowPaint.isAntiAlias = false + } + + private fun buildComponents(bounds: Rect) { + // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift. + // We could have different top-bottom offsets to avoid extra gap above but in that case + // center aligning Views inside the CardView would be problematic. + val verticalOffset = maxShadowSize * SHADOW_MULTIPLIER + mCardBounds[bounds.left + (if (leftShadow) maxShadowSize else 0F), bounds.top + (if (topShadow) verticalOffset else 0F) + , bounds.right - (if (rightShadow) maxShadowSize else 0F)] = + bounds.bottom - if (bottomShadow) verticalOffset else 0F + buildShadowCorners() + } + + fun getMaxShadowAndCornerPadding(into: Rect) { + getPadding(into) + } + + fun setShadowSize(size: Float) { + setShadowSize(size, maxShadowSize) + } + + fun setMaxShadowSize(size: Float) { + setShadowSize(mRawShadowSize, size) + } + + fun getMinWidth(): Float { + val content: Float = 2 * + maxShadowSize.coerceAtLeast(cornerRadius + mInsetShadow + maxShadowSize / 2) + return content + (maxShadowSize + mInsetShadow) * 2 + } + + fun getMinHeight(): Float { + val content: Float = 2 * maxShadowSize.coerceAtLeast( + cornerRadius + mInsetShadow + + maxShadowSize * SHADOW_MULTIPLIER / 2 + ) + return content + (maxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2 + } + + fun setColor(color: Int) { + mPaint.color = color + invalidateSelf() + } } \ No newline at end of file From 41a7780c904e4c8b7432bb9c7606adabab83f7e7 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Wed, 19 Oct 2022 11:14:42 +0100 Subject: [PATCH 28/30] SuggestionItemDecorator.kt: Kotlin variant --- .../internal/SuggestionItemDecorator.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt new file mode 100644 index 0000000..8bb0a20 --- /dev/null +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt @@ -0,0 +1,63 @@ +package com.mypopsy.widget.internal + +import android.graphics.Canvas +import android.graphics.Rect +import android.view.View +import androidx.annotation.ColorInt +import androidx.recyclerview.widget.RecyclerView + +class SuggestionItemDecorator(private val drawable: RoundRectDrawableWithShadow): RecyclerView.ItemDecoration() { + + override fun getItemOffsets( + rect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val position = parent.getChildAdapterPosition(view) + val count = state.itemCount + var shadows = RoundRectDrawableWithShadow.LEFT or RoundRectDrawableWithShadow.RIGHT + if (position == count - 1) shadows = shadows or RoundRectDrawableWithShadow.BOTTOM + drawable.apply { + setShadow(shadows) + getPadding(rect) + } + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + val visibleCount = parent.childCount + val count = state.itemCount + val adapter = parent.adapter as RecyclerView.Adapter? + val adapterCount = adapter?.itemCount ?: 0 + for (i in 0 until visibleCount) { + with(parent.getChildAt(i)) { + val position = parent.getChildAdapterPosition(this) + val params = layoutParams as RecyclerView.LayoutParams + var shadows = RoundRectDrawableWithShadow.LEFT or RoundRectDrawableWithShadow.RIGHT + if (position == count - 1 && adapterCount != 0) shadows = + shadows or RoundRectDrawableWithShadow.BOTTOM + drawable.apply { + alpha = (255 * alpha) + setShadow(shadows) + setBounds(0, 0, parent.width, height) + } + val saved = canvas.save() + canvas.translate( + parent.paddingLeft + translationX, + top + params.topMargin + translationY + ) + drawable.draw(canvas) + canvas.restoreToCount(saved) + } + } + } + + fun setBackgroundColor(@ColorInt color: Int) { + drawable.setColor(color) + } + + fun setCornerRadius(radius: Float) { + drawable.setCornerRadius(radius) + } + +} \ No newline at end of file From f03491f7c8b86dc4ca7b845b80baa91a02e83e52 Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Wed, 19 Oct 2022 19:36:25 +0100 Subject: [PATCH 29/30] FloatingSearchView.kt: Kotlin variant --- .../com/mypopsy/widget/FloatingSearchView.kt | 645 ++++++++++++++++++ .../internal/SuggestionItemDecorator.kt | 2 +- .../res/layout/fsv_floating_search_layout.xml | 37 + .../res/layout/fsv_search_query_layout.xml | 37 + library-ktx/src/main/res/values/attrs.xml | 21 + library-ktx/src/main/res/values/strings.xml | 4 + 6 files changed, 745 insertions(+), 1 deletion(-) create mode 100644 library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt create mode 100644 library-ktx/src/main/res/layout/fsv_floating_search_layout.xml create mode 100644 library-ktx/src/main/res/layout/fsv_search_query_layout.xml create mode 100644 library-ktx/src/main/res/values/attrs.xml create mode 100644 library-ktx/src/main/res/values/strings.xml diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt new file mode 100644 index 0000000..b5e3869 --- /dev/null +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt @@ -0,0 +1,645 @@ +package com.mypopsy.widget + +import android.animation.LayoutTransition +import android.animation.ObjectAnimator +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.graphics.Canvas +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import android.os.Build +import android.text.TextWatcher +import android.util.AttributeSet +import android.util.Xml +import android.view.* +import android.view.View.OnFocusChangeListener +import android.view.animation.AccelerateInterpolator +import android.view.animation.DecelerateInterpolator +import android.view.animation.Interpolator +import android.widget.ImageView +import android.widget.RelativeLayout +import androidx.annotation.* +import androidx.appcompat.widget.ActionMenuView +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.MarginLayoutParamsCompat +import androidx.core.view.ViewCompat +import com.mypopsy.widget.internal.R +import com.mypopsy.widget.internal.RoundRectDrawableWithShadow +import com.mypopsy.widget.internal.SuggestionItemDecorator +import com.mypopsy.widget.internal.ViewUtils +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import java.io.IOException + +class FloatingSearchView @JvmOverloads constructor( //TODO: Open this class + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = R.attr.floatingSearchViewStyle) + : RelativeLayout(context, attrs, defStyle) { + + companion object { + private const val DEFAULT_BACKGROUND_COLOR = -0x70000000 + private const val DEFAULT_CONTENT_COLOR = -0xf0f10 + + private const val DEFAULT_RADIUS = 2 + private const val DEFAULT_ELEVATION = 2 + private const val DEFAULT_MAX_ELEVATION = 2 + + private const val DEFAULT_DURATION_ENTER: Long = 300 + private const val DEFAULT_DURATION_EXIT: Long = 400 + + private val DECELERATE: Interpolator = DecelerateInterpolator(3f) + private val ACCELERATE: Interpolator = AccelerateInterpolator(2f) + + @JvmStatic private fun unwrap(icon: Drawable): Drawable? { + return if (Build.VERSION.SDK_INT >= 23 && icon is DrawableWrapper) + icon.drawable else DrawableCompat.unwrap(icon) + } + + class RecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : + androidx.recyclerview.widget.RecyclerView(context, attrs, defStyle) { + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(e: MotionEvent): Boolean { + val child = findChildViewUnder(e.x, e.y) + return child != null && super.onTouchEvent(e) + } + } + + class LogoEditText@JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, defStyle: Int = 0) + : AppCompatEditText(context, attrs, defStyle) { + private var logo: Drawable? = null + private var logoShown = false + private var dirty = false + + fun showLogo(shown: Boolean) { + logoShown = shown + } + + fun setLogo(@DrawableRes res: Int) { + if (res == 0) setLogo(null) else ContextCompat.getDrawable(context, res)?.let { + setLogo(it) + } + } + + fun setLogo(logo: Drawable?) { + this.logo = logo + dirty = true + } + + override fun onDraw(canvas: Canvas) { + if (logoShown && logo != null) { + if (dirty) { + updateLogoBounds() + dirty = false + } + logo!!.draw(canvas) + } else super.onDraw(canvas) + } + + // fit center + private fun updateLogoBounds() { + logo?.apply { + val logoHeight = height.coerceAtMost(intrinsicHeight) + val top = (height - logoHeight) / 2 + val logoWidth = intrinsicWidth * logoHeight / intrinsicHeight + setBounds(0, top, logoWidth, top + logoHeight) + } + } + } + } + + private val mAdapterObserver: androidx.recyclerview.widget.RecyclerView.AdapterDataObserver = + object : androidx.recyclerview.widget.RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + onChanged() + } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + onChanged() + } + + override fun onChanged() { + updateSuggestionsVisibility() + } + } + + interface OnSearchListener { + fun onSearchAction(text: CharSequence) + } + + interface OnIconClickListener { + fun onNavigationClick() + } + + interface OnSearchFocusChangedListener { + fun onFocusChanged(focused: Boolean) + } + + private var mSearchInput: LogoEditText? = null + private var mNavButtonView: ImageView? = null + private var mRecyclerView: RecyclerView? = null + private var mSearchContainer: ViewGroup? = null + private var mDivider: View? = null + private var mActionMenu: ActionMenuView? = null + + private var mActivity: Activity? = null + + private var mSearchBackground: RoundRectDrawableWithShadow? = null + private var mCardDecorator: SuggestionItemDecorator? = null + + private val mAlwaysShowingMenu = arrayListOf() + + private var mFocusListener: OnSearchFocusChangedListener? = null + private var mNavigationClickListener: OnIconClickListener? = null + private var mBackgroundDrawable: Drawable? = null + private var mSuggestionsShown = false + + var text: CharSequence + get() = mSearchInput?.text?:"" + set(value) { mSearchInput?.setText(value) } + + var hint: CharSequence + get() = mSearchInput?.hint?:"" + set(value) { mSearchInput?.hint = value } + + init { + mActivity = if (isInEditMode) { + null + } else { + getActivity() + } + + isFocusable = true + isFocusableInTouchMode = true + + inflate(getContext(), R.layout.fsv_floating_search_layout, this) + + mSearchInput = findViewById(R.id.fsv_search_text) + mNavButtonView = findViewById(R.id.fsv_search_action_navigation) + mRecyclerView = findViewById(R.id.fsv_suggestions_list) + mDivider = findViewById(R.id.fsv_suggestions_divider) + mSearchContainer = findViewById(R.id.fsv_search_container) + mActionMenu = findViewById(R.id.fsv_search_action_menu) + + //TODO: move elevation parameters to XML attributes + + //TODO: move elevation parameters to XML attributes + mSearchBackground = RoundRectDrawableWithShadow( + DEFAULT_CONTENT_COLOR, + ViewUtils.dpToPx(DEFAULT_RADIUS).toFloat(), + ViewUtils.dpToPx(DEFAULT_ELEVATION).toFloat(), + ViewUtils.dpToPx(DEFAULT_MAX_ELEVATION).toFloat() + ) + mSearchBackground!!.apply{ + addPaddingForCorners = true + mCardDecorator = SuggestionItemDecorator(mutate()) + } + applyXmlAttributes(attrs, defStyle, 0) + setupViews() + } + + @SuppressLint("CustomViewStyleable") + private fun applyXmlAttributes(attrs: AttributeSet?, @AttrRes defStyleAttr: Int, @StyleRes defStyleRes: Int) { + val a = context.obtainStyledAttributes( + attrs, + R.styleable.FloatingSearchView, defStyleAttr, defStyleRes + ) + + // Search bar width + val suggestionsContainer = findViewById(R.id.fsv_suggestions_container) + val searchBarWidth = a.getDimensionPixelSize( + R.styleable.FloatingSearchView_fsv_searchBarWidth, + mSearchContainer!!.layoutParams.width + ) + mSearchContainer!!.layoutParams.width = searchBarWidth + suggestionsContainer.layoutParams.width = searchBarWidth + + // Divider + mDivider!!.background = a.getDrawable(R.styleable.FloatingSearchView_android_divider) + val dividerHeight = + a.getDimensionPixelSize(R.styleable.FloatingSearchView_android_dividerHeight, -1) + val dividerLP = mDivider!!.layoutParams as MarginLayoutParams + if (mDivider!!.background != null && dividerHeight != -1) dividerLP.height = dividerHeight + val maxShadowSize: Float = mSearchBackground!!.maxShadowSize + val cornerRadius: Float = mSearchBackground!!.cornerRadius + val horizontalPadding = (RoundRectDrawableWithShadow.calculateHorizontalPadding( + maxShadowSize, cornerRadius, false + ) + .5f).toInt() + dividerLP.setMargins( + horizontalPadding, + dividerLP.topMargin, + horizontalPadding, + dividerLP.bottomMargin + ) + mDivider!!.layoutParams = dividerLP + + // Content inset + val searchParams = mSearchInput!!.layoutParams as MarginLayoutParams + val contentInsetStart = a.getDimensionPixelSize( + R.styleable.FloatingSearchView_contentInsetStart, + MarginLayoutParamsCompat.getMarginStart(searchParams) + ) + val contentInsetEnd = a.getDimensionPixelSize( + R.styleable.FloatingSearchView_contentInsetEnd, + MarginLayoutParamsCompat.getMarginEnd(searchParams) + ) + MarginLayoutParamsCompat.setMarginStart(searchParams, contentInsetStart) + MarginLayoutParamsCompat.setMarginEnd(searchParams, contentInsetEnd) + + // anything else + setLogo(a.getDrawable(R.styleable.FloatingSearchView_logo)) + setContentBackgroundColor( + a.getColor( + R.styleable.FloatingSearchView_fsv_contentBackgroundColor, + DEFAULT_CONTENT_COLOR + ) + ) + setRadius( + a.getDimensionPixelSize( + R.styleable.FloatingSearchView_fsv_cornerRadius, + ViewUtils.dpToPx(DEFAULT_RADIUS) + ).toFloat() + ) + inflateMenu(a.getResourceId(R.styleable.FloatingSearchView_fsv_menu, 0)) + setPopupTheme(a.getResourceId(R.styleable.FloatingSearchView_popupTheme, 0)) + hint = a.getString(R.styleable.FloatingSearchView_android_hint).toString() + setIcon(a.getDrawable(R.styleable.FloatingSearchView_fsv_icon)) + a.recycle() + } + + @SuppressLint("ClickableViewAccessibility") + private fun setupViews() { + mSearchContainer?.apply { + layoutTransition = getDefaultLayoutTransition() + + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + layoutTransition.enableTransitionType(LayoutTransition.CHANGING) + mSearchBackground?.let { + background = it + minimumHeight = it.getMinHeight().toInt() + minimumWidth = it.getMinWidth().toInt() + } + } + mRecyclerView?.apply { + addItemDecoration(mCardDecorator!!) + setHasFixedSize(true) + visibility = INVISIBLE + } + mBackgroundDrawable = background + mBackgroundDrawable = mBackgroundDrawable?.mutate() ?: ColorDrawable(DEFAULT_BACKGROUND_COLOR) + background = mBackgroundDrawable + mBackgroundDrawable!!.alpha = 0 + mNavButtonView!!.setOnClickListener { mNavigationClickListener?.onNavigationClick() } + setOnTouchListener { _: View?, _: MotionEvent? -> + if (!isActivated) return@setOnTouchListener false + isActivated = false + true + } + mSearchInput!!.onFocusChangeListener = + OnFocusChangeListener { _: View?, hasFocus: Boolean -> + if (hasFocus != isActivated) isActivated = hasFocus + } + mSearchInput!!.setOnKeyListener { _: View?, keyCode: Int, _: KeyEvent? -> + if (keyCode != KeyEvent.KEYCODE_ENTER) return@setOnKeyListener false + isActivated = false + true + } + } + + fun setRadius(radius: Float) { + radius.let { + mSearchBackground!!.cornerRadius = it + mCardDecorator!!.setCornerRadius(it) + } + } + + fun setContentBackgroundColor(@ColorInt color: Int) { + color.let { + mSearchBackground!!.setColor(it) + mCardDecorator!!.setBackgroundColor(it) + mActionMenu!!.setBackgroundColor(it) + } + } + + fun getMenu(): Menu { + return mActionMenu!!.menu + } + + fun setPopupTheme(@StyleRes resId: Int) { + mActionMenu!!.popupTheme = resId + } + + @SuppressLint("ResourceType") + fun inflateMenu(@MenuRes menuRes: Int) { + if (menuRes == 0) return + if (isInEditMode) return + getActivity().getMenuInflater().inflate(menuRes, mActionMenu!!.menu) + try { + resources.getLayout(menuRes).use { parser -> + val attrs = Xml.asAttributeSet(parser) + parseMenu(parser, attrs) + } + } catch (e: XmlPullParserException) { + // should not happens + throw InflateException("Error parsing menu XML", e) + } catch (e: IOException) { + throw InflateException("Error parsing menu XML", e) + } + } + + fun setOnSearchListener(listener: OnSearchListener) { + mSearchInput!!.setOnKeyListener { _: View?, keyCode: Int, _: KeyEvent? -> + if (keyCode != KeyEvent.KEYCODE_ENTER) return@setOnKeyListener false + listener.onSearchAction(mSearchInput!!.text.toString()) + true + } + } + + fun setOnSearchListener(init: (CharSequence)-> Unit) { + val listener = object: OnSearchListener { + override fun onSearchAction(text: CharSequence) { + init(text) + } + } + mSearchInput!!.setOnKeyListener { _: View?, keyCode: Int, _: KeyEvent? -> + if (keyCode != KeyEvent.KEYCODE_ENTER) return@setOnKeyListener false + listener.onSearchAction(mSearchInput!!.text.toString()) + true + } + } + + fun setOnMenuItemClickListener(listener: ActionMenuView.OnMenuItemClickListener?) { + mActionMenu!!.setOnMenuItemClickListener(listener) + } + + fun setOnMenuItemClickListener(init: (MenuItem)->Unit) { + val listener = ActionMenuView.OnMenuItemClickListener { + init(it) + true + } + mActionMenu!!.setOnMenuItemClickListener(listener) + } + + override fun setActivated(activated: Boolean) { + if (activated == isActivated) return + super.setActivated(activated) + if (activated) { + mSearchInput!!.requestFocus() + ViewUtils.showSoftKeyboardDelayed(mSearchInput!!, 100) + } else { + requestFocus() + ViewUtils.closeSoftKeyboard(mActivity!!) + } + mFocusListener?.onFocusChanged(activated) + showMenu(!activated) + fadeIn(activated) + updateSuggestionsVisibility() + } + + fun setOnIconClickListener(navigationClickListener: OnIconClickListener) { + mNavigationClickListener = navigationClickListener + } + + fun setOnIconClickListener(init: (ImageView)-> Unit) { + val navigationClickListener = object: OnIconClickListener { + override fun onNavigationClick() { + init(mNavButtonView!!) + } + } + mNavigationClickListener = navigationClickListener + } + + fun setOnSearchFocusChangedListener(focusListener: OnSearchFocusChangedListener) { + mFocusListener = focusListener + } + + fun setOnSearchFocusChangedListener(init: (Boolean)-> Unit) { + val focusListener = object: OnSearchFocusChangedListener { + override fun onFocusChanged(focused: Boolean) { + init(focused) + } + } + mFocusListener = focusListener + } + + fun addTextChangedListener(textWatcher: TextWatcher) { + mSearchInput!!.addTextChangedListener(textWatcher) + } + + fun removeTextChangedListener(textWatcher: TextWatcher?) { + mSearchInput!!.removeTextChangedListener(textWatcher) + } + + fun setAdapter(adapter: androidx.recyclerview.widget.RecyclerView.Adapter) { + getAdapter()?.unregisterAdapterDataObserver(mAdapterObserver) + adapter.registerAdapterDataObserver(mAdapterObserver) + mRecyclerView!!.adapter = adapter + } + + fun setItemAnimator(itemAnimator: androidx.recyclerview.widget.RecyclerView.ItemAnimator?) { + mRecyclerView!!.itemAnimator = itemAnimator + } + + fun addItemDecoration(decoration: androidx.recyclerview.widget.RecyclerView.ItemDecoration?) { + mRecyclerView!!.addItemDecoration(decoration!!) + } + + fun setLogo(drawable: Drawable?) { + mSearchInput!!.setLogo(drawable) + } + + fun setLogo(@DrawableRes resId: Int) { + mSearchInput!!.setLogo(resId) + } + + fun setIcon(@DrawableRes resId: Int) { + showIcon(resId != 0) + mNavButtonView!!.setImageResource(resId) + } + + fun setIcon(drawable: Drawable?) { + showIcon(drawable != null) + mNavButtonView!!.setImageDrawable(drawable) + } + + fun showLogo(show: Boolean) { + mSearchInput!!.showLogo(show) + } + + fun showIcon(show: Boolean) { + mNavButtonView!!.visibility = if (show) VISIBLE else GONE + } + + fun getIcon(): Drawable? { + return if (mNavButtonView == null) null else mNavButtonView!!.drawable + } + + fun getAdapter(): androidx.recyclerview.widget.RecyclerView.Adapter? { + return mRecyclerView!!.adapter + } + + fun getDefaultLayoutTransition(): LayoutTransition = LayoutTransition() + + @SuppressLint("ObjectAnimatorBinding") + private fun fadeIn(enter: Boolean) { + val backgroundAnim = ObjectAnimator.ofInt(mBackgroundDrawable, "alpha", if (enter) 255 else 0) + backgroundAnim.apply { + duration = if (enter) DEFAULT_DURATION_ENTER else DEFAULT_DURATION_EXIT + interpolator = if (enter) DECELERATE else ACCELERATE + start() + val icon = unwrap(getIcon()!!) + icon?.let { + val iconAnim: ObjectAnimator = ObjectAnimator.ofFloat(icon, "progress", if (enter) 1F else 0F) + iconAnim.let { + duration = this.duration + interpolator = this.interpolator + } + iconAnim.start() + } + } + } + + private fun getSuggestionsCount(): Int { + val adapter = getAdapter() ?: return 0 + return adapter.itemCount + } + + private fun updateSuggestionsVisibility() { + showSuggestions(isActivated && getSuggestionsCount() > 0) + } + + private fun suggestionsShown() = mSuggestionsShown + + private fun showSuggestions(show: Boolean) { + if (show == suggestionsShown()) return + mSuggestionsShown = show + val childCount = mRecyclerView!!.childCount + var translation = 0 + val endAction = Runnable { + if (show) updateDivider() else { + showDivider(false) + mRecyclerView!!.visibility = INVISIBLE + mRecyclerView!!.translationY = -mRecyclerView!!.height.toFloat() + } + } + if (show) { + updateDivider() + mRecyclerView!!.visibility = VISIBLE + if (mRecyclerView!!.translationY == 0f) mRecyclerView!!.translationY = + -mRecyclerView!!.height.toFloat() + } else if (childCount > 0) translation = + -mRecyclerView!!.getChildAt(childCount - 1).bottom else showDivider(false) + val listAnim = ViewCompat.animate(mRecyclerView!!) + .translationY(translation.toFloat()) + .setDuration(if (show) DEFAULT_DURATION_ENTER else DEFAULT_DURATION_EXIT) + .setInterpolator(if (show) DECELERATE else ACCELERATE) + .withLayer() + .withEndAction(endAction) + if (show || childCount > 0) listAnim.start() else endAction.run() + } + + private fun showDivider(visible: Boolean) { + mDivider!!.visibility = if (visible) VISIBLE else GONE + var shadows = + RoundRectDrawableWithShadow.TOP or RoundRectDrawableWithShadow.LEFT or RoundRectDrawableWithShadow.RIGHT + if (!visible) shadows = shadows or RoundRectDrawableWithShadow.BOTTOM + mSearchBackground!!.setShadow(shadows) + } + + private fun updateDivider() { + showDivider(isActivated && getSuggestionsCount() > 0) + } + + private fun getActivity(): Activity { + var context = context + while (context is ContextWrapper) { + if (context is Activity) { + return context + } + context = context.baseContext + } + throw IllegalStateException() + } + + private fun showMenu(visible: Boolean) { + val menu = getMenu() + for (i in 0 until menu.size()) { + val item = menu.getItem(i) + if (mAlwaysShowingMenu.contains(item.itemId)) continue + item.isVisible = visible + } + } + + @Throws(XmlPullParserException::class, IOException::class) + @SuppressLint("CustomViewStyleable", "PrivateResource") + private fun parseMenu(parser: XmlPullParser, attrs: AttributeSet) { + var eventType = parser.eventType + var tagName: String + var lookingForEndOfUnknownTag = false + var unknownTagName: String? = null + + // This loop will skip to the menu start tag + do { + if (eventType == XmlPullParser.START_TAG) { + tagName = parser.name + if (tagName == "menu") { + // Go to next tag + eventType = parser.next() + break + } + throw RuntimeException("Expecting menu, got $tagName") + } + eventType = parser.next() + } while (eventType != XmlPullParser.END_DOCUMENT) + var reachedEndOfMenu = false + while (!reachedEndOfMenu) { + when (eventType) { + XmlPullParser.START_TAG -> { + if (lookingForEndOfUnknownTag) { + break + } + tagName = parser.name + if (tagName == "item") { + val a = context.obtainStyledAttributes( + attrs, + androidx.appcompat.R.styleable.MenuItem + ) + val itemShowAsAction = + a.getInt(androidx.appcompat.R.styleable.MenuItem_showAsAction, -1) + if (itemShowAsAction and MenuItem.SHOW_AS_ACTION_ALWAYS != 0) { + val itemId = a.getResourceId( + androidx.appcompat.R.styleable.MenuItem_android_id, + NO_ID + ) + if (itemId != NO_ID) mAlwaysShowingMenu.add(itemId) + } + a.recycle() + } else { + lookingForEndOfUnknownTag = true + unknownTagName = tagName + } + } + XmlPullParser.END_TAG -> { + tagName = parser.name + if (lookingForEndOfUnknownTag && tagName == unknownTagName) { + lookingForEndOfUnknownTag = false + unknownTagName = null + } else if (tagName == "menu") { + reachedEndOfMenu = true + } + } + XmlPullParser.END_DOCUMENT -> throw RuntimeException("Unexpected end of document") + } + eventType = parser.next() + } + } + + +} \ No newline at end of file diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt index 8bb0a20..a48e49d 100644 --- a/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/SuggestionItemDecorator.kt @@ -57,7 +57,7 @@ class SuggestionItemDecorator(private val drawable: RoundRectDrawableWithShadow) } fun setCornerRadius(radius: Float) { - drawable.setCornerRadius(radius) + drawable.cornerRadius = radius } } \ No newline at end of file diff --git a/library-ktx/src/main/res/layout/fsv_floating_search_layout.xml b/library-ktx/src/main/res/layout/fsv_floating_search_layout.xml new file mode 100644 index 0000000..6c43e85 --- /dev/null +++ b/library-ktx/src/main/res/layout/fsv_floating_search_layout.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/library-ktx/src/main/res/layout/fsv_search_query_layout.xml b/library-ktx/src/main/res/layout/fsv_search_query_layout.xml new file mode 100644 index 0000000..5eede36 --- /dev/null +++ b/library-ktx/src/main/res/layout/fsv_search_query_layout.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/library-ktx/src/main/res/values/attrs.xml b/library-ktx/src/main/res/values/attrs.xml new file mode 100644 index 0000000..efc0a69 --- /dev/null +++ b/library-ktx/src/main/res/values/attrs.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library-ktx/src/main/res/values/strings.xml b/library-ktx/src/main/res/values/strings.xml new file mode 100644 index 0000000..07ef81d --- /dev/null +++ b/library-ktx/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + fsv_transition_search_layout + \ No newline at end of file From 6a4f0688a24f4fe328311e9415a62ea0a90c91eb Mon Sep 17 00:00:00 2001 From: IODevBlue Date: Wed, 19 Oct 2022 19:39:42 +0100 Subject: [PATCH 30/30] FloatingSearchView.kt: Finalized class --- .../src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt index b5e3869..ba62d7a 100644 --- a/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt +++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt @@ -36,7 +36,7 @@ import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException import java.io.IOException -class FloatingSearchView @JvmOverloads constructor( //TODO: Open this class +class FloatingSearchView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.floatingSearchViewStyle) @@ -72,7 +72,7 @@ class FloatingSearchView @JvmOverloads constructor( //TODO: Open this class } class LogoEditText@JvmOverloads constructor(context: Context, - attrs: AttributeSet? = null, defStyle: Int = 0) + attrs: AttributeSet? = null, defStyle: Int = 0) : AppCompatEditText(context, attrs, defStyle) { private var logo: Drawable? = null private var logoShown = false