diff --git a/build.gradle b/build.gradle
index 8e02c67..026dc4b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,22 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
buildscript {
- repositories {
- jcenter()
- }
+ ext.hilt_version = "2.40"
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
+ classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
+ classpath 'com.android.tools.build:gradle:7.2.0'
}
}
-
-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..3a36e42 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,18 +1,30 @@
# 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
+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
-# 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
+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
+# 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-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/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/FloatingSearchView.kt
new file mode 100644
index 0000000..ba62d7a
--- /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(
+ 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/RoundRectDrawableWithShadow.kt b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt
new file mode 100644
index 0000000..fbfc013
--- /dev/null
+++ b/library-ktx/src/main/kotlin/com/mypopsy/widget/internal/RoundRectDrawableWithShadow.kt
@@ -0,0 +1,507 @@
+package com.mypopsy.widget.internal
+
+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
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..a48e49d
--- /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.cornerRadius = radius
+ }
+
+}
\ 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
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
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/library/build.gradle b/library/build.gradle
index 1b6c251..d085d96 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -1,12 +1,14 @@
-apply plugin: 'com.android.library'
+plugins {
+ id 'com.android.library'
+}
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"
}
@@ -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 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
}
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 extends RecyclerView.ViewHolder> getAdapter() {
- return mRecyclerView.getAdapter();
+ return (RecyclerView.Adapter extends RecyclerView.ViewHolder>) 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());
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;
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 extends RecyclerView.ViewHolder> adapter = (RecyclerView.Adapter extends RecyclerView.ViewHolder>) 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;
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);
}
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"/>
-
- 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)
- }
+ 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'
}
+
+//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)
+// }
+//}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 669d6f0..68c68ea 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -7,10 +7,15 @@
-
+
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
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..eb1c331 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;
@@ -44,6 +47,9 @@
import javax.inject.Inject;
+import dagger.hilt.android.AndroidEntryPoint;
+
+@AndroidEntryPoint
public class MainActivity extends AppCompatActivity implements
ActionMenuView.OnMenuItemClickListener,
SearchController.Listener {
@@ -53,40 +59,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 +105,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 +126,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 +157,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 +174,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,8 +202,9 @@ public void onSearchStarted(String query) {
//nothing to do
}
+ @SuppressLint("NotifyDataSetChanged")
@Override
- public void onSearchResults(SearchResult ...searchResults) {
+ public void onSearchResults(SearchResult... searchResults) {
mAdapter.setNotifyOnChange(false);
mAdapter.clear();
if (searchResults != null) mAdapter.addAll(searchResults);
@@ -236,7 +233,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 +251,9 @@ private class SearchAdapter extends ArrayRecyclerAdapter onItemClick(mAdapter.getItem(getBindingAdapterPosition())));
+ right.setOnClickListener(v -> mSearchView.setText(text.getText()));
}
void bind(SearchResult result) {
@@ -321,19 +309,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
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/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 extends T> 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 super T> comparator) {
synchronized (mLock) {
Collections.sort(mObjects, comparator);
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..7649755
--- /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 {
+
+ 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 60%
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 f9ff2d5..f239e43 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,17 +1,19 @@
-package com.mypopsy.floatingsearchview.demo.dagger;
-
-import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.logging.HttpLoggingInterceptor;
+package com.mypopsy.floatingsearchview.demo.hilt.module;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
-import retrofit.Converter;
-import retrofit.GsonConverterFactory;
-import retrofit.Retrofit;
-import retrofit.RxJavaCallAdapterFactory;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Converter;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+@InstallIn(SingletonComponent.class)
@Module
public class RetrofitModule {
@@ -20,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
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 76%
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 6a5be17..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 retrofit.Retrofit;
+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/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..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
@@ -1,21 +1,26 @@
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;
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.schedulers.Schedulers;
import rx.subjects.PublishSubject;
public class GoogleSearchController implements SearchController {
@@ -23,14 +28,15 @@ 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 @NonNull Scheduler.Worker mWorker;
+ private final PublishSubject mQuerySubject = PublishSubject.create();
private Subscription mSubscription;
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();
}
@@ -53,20 +59,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 +72,33 @@ 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(Schedulers.immediate())
+// .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(searchResults -> {
+ if(mListener != null) mListener.onSearchResults(searchResults);
+ }, this::notifyError);
}
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(action::call);
}
}
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;
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;
/**
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 @@
-