Merge branch 'redesign' of https://github.com/ztimms73/Kotatsu into devel
This commit is contained in:
@@ -37,7 +37,7 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), OnApplyWindo
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (get<AppSettings>().isAmoledTheme) {
|
||||
setTheme(R.style.AppTheme_Amoled)
|
||||
setTheme(R.style.AppTheme_AMOLED)
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
@@ -59,12 +59,12 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), OnApplyWindo
|
||||
this.binding = binding
|
||||
super.setContentView(binding.root)
|
||||
(binding.root.findViewById<View>(R.id.toolbar) as? Toolbar)?.let(this::setSupportActionBar)
|
||||
val params = (binding.root.findViewById<View>(R.id.toolbar) as? Toolbar)?.layoutParams as AppBarLayout.LayoutParams
|
||||
val params = (binding.root.findViewById<View>(R.id.toolbar) as? Toolbar)?.layoutParams as? AppBarLayout.LayoutParams
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root, this)
|
||||
if (get<AppSettings>().isToolbarHideWhenScrolling) {
|
||||
params.scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS
|
||||
params?.scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS
|
||||
} else {
|
||||
params.scrollFlags = SCROLL_FLAG_NO_SCROLL
|
||||
params?.scrollFlags = SCROLL_FLAG_NO_SCROLL
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class SectionItemDecoration(
|
||||
|
||||
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
super.onDrawOver(c, parent, state)
|
||||
val textView = headerView ?: parent.inflate<TextView>(R.layout.item_header).also {
|
||||
val textView = headerView ?: parent.inflate<TextView>(R.layout.item_filter_header).also {
|
||||
headerView = it
|
||||
}
|
||||
fixLayoutSize(textView, parent)
|
||||
|
||||
@@ -4,16 +4,17 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View.OnClickListener
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.children
|
||||
import com.google.android.material.R
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipDrawable
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class ChipsView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = R.attr.chipGroupStyle
|
||||
defStyleAttr: Int = com.google.android.material.R.attr.chipGroupStyle
|
||||
) : ChipGroup(context, attrs, defStyleAttr) {
|
||||
|
||||
private var isLayoutSuppressedCompat = false
|
||||
@@ -64,7 +65,9 @@ class ChipsView @JvmOverloads constructor(
|
||||
|
||||
private fun addChip(): Chip {
|
||||
val chip = Chip(context)
|
||||
chip.setTextColor(context.getThemeColor(android.R.attr.textColorPrimary))
|
||||
val drawable = ChipDrawable.createFromAttributes(context, null, 0, R.style.Widget_Kotatsu_Chip)
|
||||
chip.setChipDrawable(drawable)
|
||||
chip.setTextColor(ContextCompat.getColor(context, R.color.blue_primary))
|
||||
chip.isCloseIconVisible = false
|
||||
chip.setEnsureMinTouchTargetSize(false)
|
||||
chip.setOnClickListener(chipOnClickListener)
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*https://github.com/lapism/search*/
|
||||
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.widgets.search.internal.SearchLayout
|
||||
|
||||
class MaterialSearchView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : SearchLayout(context, attrs, defStyleAttr, defStyleRes), CoordinatorLayout.AttachedBehavior {
|
||||
|
||||
// *********************************************************************************************
|
||||
private var mBehavior: CoordinatorLayout.Behavior<*> = SearchBehavior<MaterialSearchView>()
|
||||
private var mTransition: LayoutTransition? = null
|
||||
private var mStrokeWidth: Int = 0
|
||||
private var mRadius: Float = 0f
|
||||
private var mElevation: Float = 0f
|
||||
|
||||
// *********************************************************************************************
|
||||
init {
|
||||
inflate(context, R.layout.layout_search_view, this)
|
||||
init()
|
||||
setTransition()
|
||||
|
||||
val a = context.obtainStyledAttributes(
|
||||
attrs, R.styleable.MaterialSearchView, defStyleAttr, defStyleRes
|
||||
)
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_navigationIconSupport)) {
|
||||
navigationIconSupport = a.getInt(
|
||||
R.styleable.MaterialSearchView_search_navigationIconSupport,
|
||||
NavigationIconSupport.NONE
|
||||
)
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_navigationIcon)) {
|
||||
setNavigationIconImageDrawable(a.getDrawable(R.styleable.MaterialSearchView_search_navigationIcon))
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_clearIcon)) {
|
||||
setClearIconImageDrawable(a.getDrawable(R.styleable.MaterialSearchView_search_clearIcon))
|
||||
} else {
|
||||
setClearIconImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_clear
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_micIcon)) {
|
||||
setMicIconImageDrawable(a.getDrawable(R.styleable.MaterialSearchView_search_micIcon))
|
||||
} else {
|
||||
setMicIconImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_mic_none
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_menuIcon)) {
|
||||
setMicIconImageDrawable(a.getDrawable(R.styleable.MaterialSearchView_search_menuIcon))
|
||||
} else {
|
||||
setMicIconImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_more
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_dividerColor)) {
|
||||
setDividerColor(a.getInt(R.styleable.MaterialSearchView_search_dividerColor, 0))
|
||||
}
|
||||
|
||||
val defaultShadowColor = ContextCompat.getColor(context, R.color.shadow)
|
||||
setShadowColor(
|
||||
a.getInt(
|
||||
R.styleable.MaterialSearchView_search_shadowColor,
|
||||
defaultShadowColor
|
||||
)
|
||||
)
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_textHint)) {
|
||||
setTextHint(a.getText(R.styleable.MaterialSearchView_search_textHint))
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_strokeColor)) {
|
||||
setBackgroundStrokeColor(a.getInt(R.styleable.MaterialSearchView_search_strokeColor, 0))
|
||||
}
|
||||
|
||||
if (a.hasValue(R.styleable.MaterialSearchView_search_strokeWidth)) {
|
||||
setBackgroundStrokeWidth(a.getInt(R.styleable.MaterialSearchView_search_strokeWidth, 0))
|
||||
}
|
||||
|
||||
val defaultTransitionDuration =
|
||||
context.resources.getInteger(R.integer.search_animation_duration)
|
||||
setTransitionDuration(
|
||||
a.getInt(
|
||||
R.styleable.MaterialSearchView_search_transitionDuration,
|
||||
defaultTransitionDuration
|
||||
).toLong()
|
||||
)
|
||||
|
||||
val defaultRadius = context.resources.getDimensionPixelSize(R.dimen.search_radius)
|
||||
setBackgroundRadius(
|
||||
a.getInt(R.styleable.MaterialSearchView_search_radius, defaultRadius).toFloat()
|
||||
)
|
||||
|
||||
val defaultElevation = context.resources.getDimensionPixelSize(R.dimen.search_elevation)
|
||||
elevation =
|
||||
a.getInt(R.styleable.MaterialSearchView_android_elevation, defaultElevation).toFloat()
|
||||
|
||||
val imeOptions = a.getInt(R.styleable.MaterialSearchView_android_imeOptions, -1)
|
||||
if (imeOptions != -1) {
|
||||
setTextImeOptions(imeOptions)
|
||||
}
|
||||
|
||||
val inputType = a.getInt(R.styleable.MaterialSearchView_android_inputType, -1)
|
||||
if (inputType != -1) {
|
||||
setTextInputType(inputType)
|
||||
}
|
||||
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
override fun addFocus() {
|
||||
mOnFocusChangeListener?.onFocusChange(true)
|
||||
showKeyboard()
|
||||
|
||||
mStrokeWidth = getBackgroundStrokeWidth()
|
||||
mRadius = getBackgroundRadius()
|
||||
mElevation = elevation
|
||||
|
||||
setBackgroundStrokeWidth(context.resources.getDimensionPixelSize(R.dimen.search_stroke_width_focus))
|
||||
setBackgroundRadius(resources.getDimensionPixelSize(R.dimen.search_radius_focus).toFloat())
|
||||
elevation =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_elevation_focus).toFloat()
|
||||
|
||||
val left = context.resources.getDimensionPixelSize(R.dimen.search_dp_16)
|
||||
val params = mSearchEditText?.layoutParams as LinearLayout.LayoutParams
|
||||
params.setMargins(left, 0, 0, 0)
|
||||
mSearchEditText?.layoutParams = params
|
||||
|
||||
margins = Margins.FOCUS
|
||||
setLayoutHeight(context.resources.getDimensionPixelSize(R.dimen.search_layout_height_focus))
|
||||
|
||||
mViewShadow?.visibility = View.VISIBLE
|
||||
|
||||
mViewDivider?.visibility = View.VISIBLE
|
||||
mViewAnim?.visibility = View.VISIBLE
|
||||
mRecyclerView?.visibility = View.VISIBLE
|
||||
|
||||
// layoutTransition = null
|
||||
}
|
||||
|
||||
override fun removeFocus() {
|
||||
// layoutTransition = mTransition
|
||||
|
||||
mOnFocusChangeListener?.onFocusChange(false)
|
||||
hideKeyboard()
|
||||
|
||||
val params = mSearchEditText?.layoutParams as LinearLayout.LayoutParams
|
||||
params.setMargins(0, 0, 0, 0)
|
||||
mSearchEditText?.layoutParams = params
|
||||
|
||||
setBackgroundStrokeWidth(mStrokeWidth)
|
||||
setBackgroundRadius(mRadius)
|
||||
elevation = mElevation
|
||||
|
||||
setLayoutHeight(context.resources.getDimensionPixelSize(R.dimen.search_layout_height))
|
||||
margins = Margins.NO_FOCUS
|
||||
|
||||
mViewShadow?.visibility = View.GONE
|
||||
|
||||
mRecyclerView?.visibility = View.GONE
|
||||
mViewAnim?.visibility = View.GONE
|
||||
mViewDivider?.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun getBehavior(): CoordinatorLayout.Behavior<*> {
|
||||
return mBehavior
|
||||
}
|
||||
|
||||
fun setBehavior(behavior: CoordinatorLayout.Behavior<*>) {
|
||||
mBehavior = behavior
|
||||
}
|
||||
|
||||
fun setTransitionDuration(duration: Long) {
|
||||
mTransition?.setDuration(duration)
|
||||
layoutTransition = mTransition
|
||||
}
|
||||
|
||||
private fun setTransition() {
|
||||
mTransition = LayoutTransition()
|
||||
mTransition?.enableTransitionType(LayoutTransition.CHANGING)
|
||||
mTransition?.addTransitionListener(object : LayoutTransition.TransitionListener {
|
||||
override fun startTransition(
|
||||
transition: LayoutTransition?,
|
||||
container: ViewGroup?,
|
||||
view: View?,
|
||||
transitionType: Int
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
override fun endTransition(
|
||||
transition: LayoutTransition?,
|
||||
container: ViewGroup?,
|
||||
view: View?,
|
||||
transitionType: Int
|
||||
) {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*https://github.com/lapism/search*/
|
||||
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.util.Property
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
class SearchArrowDrawable constructor(context: Context) : DrawerArrowDrawable(context) {
|
||||
|
||||
var position: Float
|
||||
get() = progress
|
||||
set(position) {
|
||||
progress = position
|
||||
}
|
||||
|
||||
init {
|
||||
color = ContextCompat.getColor(context, android.R.color.white)
|
||||
}
|
||||
|
||||
fun animate(state: Float, duration: Long) {
|
||||
val anim: ObjectAnimator = if (state == ARROW) {
|
||||
ObjectAnimator.ofFloat(
|
||||
this,
|
||||
PROGRESS,
|
||||
MENU,
|
||||
state
|
||||
)
|
||||
} else {
|
||||
ObjectAnimator.ofFloat(
|
||||
this,
|
||||
PROGRESS,
|
||||
ARROW,
|
||||
state
|
||||
)
|
||||
}
|
||||
anim.interpolator = AccelerateDecelerateInterpolator()
|
||||
anim.duration = duration
|
||||
anim.start()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val MENU = 0.0f
|
||||
const val ARROW = 1.0f
|
||||
|
||||
private val PROGRESS =
|
||||
object : Property<SearchArrowDrawable, Float>(Float::class.java, "progress") {
|
||||
override fun set(obj: SearchArrowDrawable, value: Float?) {
|
||||
obj.progress = value!!
|
||||
}
|
||||
|
||||
override fun get(obj: SearchArrowDrawable): Float {
|
||||
return obj.progress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*https://github.com/lapism/search*/
|
||||
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search
|
||||
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import org.koitharu.kotatsu.base.ui.widgets.search.internal.SearchLayout
|
||||
|
||||
class SearchBehavior<S : SearchLayout> : CoordinatorLayout.Behavior<S>() {
|
||||
|
||||
override fun layoutDependsOn(
|
||||
parent: CoordinatorLayout,
|
||||
child: S,
|
||||
dependency: View
|
||||
): Boolean {
|
||||
return if (dependency is AppBarLayout) {
|
||||
true
|
||||
} else
|
||||
if (dependency is LinearLayout || dependency is BottomNavigationView) {
|
||||
dependency.z = child.z + 1
|
||||
true
|
||||
} else {
|
||||
super.layoutDependsOn(parent, child, dependency)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDependentViewChanged(
|
||||
parent: CoordinatorLayout,
|
||||
child: S,
|
||||
dependency: View
|
||||
): Boolean {
|
||||
if (dependency is AppBarLayout) {
|
||||
child.translationY = dependency.getY()
|
||||
return true
|
||||
}
|
||||
return super.onDependentViewChanged(parent, child, dependency)
|
||||
}
|
||||
|
||||
override fun onStartNestedScroll(
|
||||
coordinatorLayout: CoordinatorLayout,
|
||||
child: S,
|
||||
directTargetChild: View,
|
||||
target: View,
|
||||
axes: Int,
|
||||
type: Int
|
||||
): Boolean {
|
||||
return axes == ViewCompat.SCROLL_AXIS_VERTICAL
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*https://github.com/lapism/search*/
|
||||
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search.internal
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
|
||||
class SearchEditText : AppCompatEditText {
|
||||
|
||||
var clearFocusOnBackPressed: Boolean = false
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
override fun onKeyPreIme(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP && clearFocusOnBackPressed) {
|
||||
if (hasFocus()) {
|
||||
clearFocus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onKeyPreIme(keyCode, event)
|
||||
}
|
||||
|
||||
override fun clearFocus() {
|
||||
super.clearFocus()
|
||||
text?.clear()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
/*https://github.com/lapism/search*/
|
||||
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search.internal
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
abstract class SearchLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
defStyleRes: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), View.OnClickListener {
|
||||
|
||||
// *********************************************************************************************
|
||||
// Better way than enum class :-)
|
||||
@IntDef(
|
||||
NavigationIconSupport.NONE,
|
||||
NavigationIconSupport.MENU,
|
||||
NavigationIconSupport.ARROW,
|
||||
NavigationIconSupport.SEARCH
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class NavigationIconSupport {
|
||||
companion object {
|
||||
const val NONE = 0
|
||||
const val MENU = 1
|
||||
const val ARROW = 2
|
||||
const val SEARCH = 3
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef(
|
||||
Margins.NO_FOCUS,
|
||||
Margins.FOCUS
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
internal annotation class Margins {
|
||||
companion object {
|
||||
const val NO_FOCUS = 4
|
||||
const val FOCUS = 5
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
private var mImageViewMic: ImageButton? = null
|
||||
private var mImageViewMenu: ImageButton? = null
|
||||
protected var mRecyclerView: RecyclerView? = null
|
||||
private var mMaterialCardView: MaterialCardView? = null
|
||||
var mSearchEditText: SearchEditText? = null
|
||||
protected var mViewShadow: View? = null
|
||||
protected var mViewDivider: View? = null
|
||||
protected var mViewAnim: View? = null
|
||||
protected var mOnFocusChangeListener: OnFocusChangeListener? = null
|
||||
|
||||
private var mLinearLayout: LinearLayout? = null
|
||||
private var mImageViewNavigation: ImageButton? = null
|
||||
private var mImageViewClear: ImageButton? = null
|
||||
private var mOnQueryTextListener: OnQueryTextListener? = null
|
||||
private var mOnNavigationClickListener: OnNavigationClickListener? = null
|
||||
private var mOnMicClickListener: OnMicClickListener? = null
|
||||
private var mOnMenuClickListener: OnMenuClickListener? = null
|
||||
private var mOnClearClickListener: OnClearClickListener? = null
|
||||
|
||||
// *********************************************************************************************
|
||||
@NavigationIconSupport
|
||||
@get:NavigationIconSupport
|
||||
var navigationIconSupport: Int = 0
|
||||
set(@NavigationIconSupport navigationIconSupport) {
|
||||
field = navigationIconSupport
|
||||
|
||||
when (navigationIconSupport) {
|
||||
NavigationIconSupport.NONE
|
||||
-> {
|
||||
setNavigationIconImageDrawable(null)
|
||||
}
|
||||
NavigationIconSupport.MENU -> {
|
||||
setNavigationIconImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_menu
|
||||
)
|
||||
)
|
||||
}
|
||||
NavigationIconSupport.ARROW -> {
|
||||
setNavigationIconImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_arrow_back
|
||||
)
|
||||
)
|
||||
}
|
||||
NavigationIconSupport.SEARCH -> {
|
||||
setNavigationIconImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_search
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Margins
|
||||
@get:Margins
|
||||
protected var margins: Int = 0
|
||||
set(@Margins margins) {
|
||||
field = margins
|
||||
|
||||
val left: Int
|
||||
val top: Int
|
||||
val right: Int
|
||||
val bottom: Int
|
||||
val params = mMaterialCardView?.layoutParams as LayoutParams?
|
||||
|
||||
when (margins) {
|
||||
Margins.NO_FOCUS -> {
|
||||
left =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_left_right)
|
||||
top =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_top_bottom)
|
||||
right =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_left_right)
|
||||
bottom =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_top_bottom)
|
||||
|
||||
params?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
params?.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
params?.setMargins(left, top, right, bottom)
|
||||
mMaterialCardView?.layoutParams = params
|
||||
}
|
||||
Margins.FOCUS -> {
|
||||
left =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_focus)
|
||||
top =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_focus)
|
||||
right =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_focus)
|
||||
bottom =
|
||||
context.resources.getDimensionPixelSize(R.dimen.search_margins_focus)
|
||||
|
||||
params?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
params?.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
params?.setMargins(left, top, right, bottom)
|
||||
mMaterialCardView?.layoutParams = params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
protected abstract fun addFocus()
|
||||
|
||||
protected abstract fun removeFocus()
|
||||
|
||||
// *********************************************************************************************
|
||||
protected fun init() {
|
||||
mLinearLayout = findViewById(R.id.search_linear_layout)
|
||||
|
||||
mImageViewNavigation = findViewById(R.id.search_image_view_navigation)
|
||||
mImageViewNavigation?.setOnClickListener(this)
|
||||
|
||||
mImageViewMic = findViewById(R.id.search_image_view_mic)
|
||||
mImageViewMic?.setOnClickListener(this)
|
||||
|
||||
mImageViewMenu = findViewById(R.id.search_image_view_menu)
|
||||
mImageViewMenu?.setOnClickListener(this)
|
||||
|
||||
mImageViewClear = findViewById(R.id.search_image_view_clear)
|
||||
mImageViewClear?.visibility = View.GONE
|
||||
mImageViewClear?.setOnClickListener(this)
|
||||
|
||||
mSearchEditText = findViewById(R.id.search_search_edit_text)
|
||||
mSearchEditText?.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
this@SearchLayout.onTextChanged(s)
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
|
||||
}
|
||||
})
|
||||
mSearchEditText?.setOnEditorActionListener { _, _, _ ->
|
||||
onSubmitQuery()
|
||||
return@setOnEditorActionListener true // true
|
||||
}
|
||||
mSearchEditText?.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
addFocus()
|
||||
} else {
|
||||
removeFocus()
|
||||
}
|
||||
}
|
||||
|
||||
mRecyclerView = findViewById(R.id.search_recycler_view)
|
||||
mRecyclerView?.visibility = View.GONE
|
||||
mRecyclerView?.layoutManager = LinearLayoutManager(context)
|
||||
mRecyclerView?.isNestedScrollingEnabled = false
|
||||
mRecyclerView?.itemAnimator = DefaultItemAnimator()
|
||||
mRecyclerView?.overScrollMode = View.OVER_SCROLL_NEVER
|
||||
mRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mViewShadow = findViewById(R.id.search_view_shadow)
|
||||
mViewShadow?.visibility = View.GONE
|
||||
|
||||
mViewDivider = findViewById(R.id.search_view_divider)
|
||||
mViewDivider?.visibility = View.GONE
|
||||
|
||||
mViewAnim = findViewById(R.id.search_view_anim)
|
||||
mViewAnim?.visibility = View.GONE
|
||||
|
||||
mMaterialCardView = findViewById(R.id.search_material_card_view)
|
||||
margins = Margins.NO_FOCUS
|
||||
|
||||
isClickable = true
|
||||
isFocusable = true
|
||||
isFocusableInTouchMode = true
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setNavigationIconVisibility(visibility: Int) {
|
||||
mImageViewNavigation?.visibility = visibility
|
||||
}
|
||||
|
||||
fun setNavigationIconImageResource(@DrawableRes resId: Int) {
|
||||
mImageViewNavigation?.setImageResource(resId)
|
||||
}
|
||||
|
||||
fun setNavigationIconImageDrawable(@Nullable drawable: Drawable?) {
|
||||
mImageViewNavigation?.setImageDrawable(drawable)
|
||||
}
|
||||
|
||||
fun setNavigationIconColorFilter(color: Int) {
|
||||
mImageViewNavigation?.setColorFilter(color)
|
||||
}
|
||||
|
||||
fun setNavigationIconColorFilter(color: Int, mode: PorterDuff.Mode) {
|
||||
mImageViewNavigation?.setColorFilter(color, mode)
|
||||
}
|
||||
|
||||
fun setNavigationIconColorFilter(cf: ColorFilter?) {
|
||||
mImageViewNavigation?.colorFilter = cf
|
||||
}
|
||||
|
||||
fun clearNavigationIconColorFilter() {
|
||||
mImageViewNavigation?.clearColorFilter()
|
||||
}
|
||||
|
||||
fun setNavigationIconContentDescription(contentDescription: CharSequence) {
|
||||
mImageViewNavigation?.contentDescription = contentDescription
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setMicIconImageResource(@DrawableRes resId: Int) {
|
||||
mImageViewMic?.setImageResource(resId)
|
||||
}
|
||||
|
||||
fun setMicIconImageDrawable(@Nullable drawable: Drawable?) {
|
||||
mImageViewMic?.setImageDrawable(drawable)
|
||||
}
|
||||
|
||||
fun setMicIconColorFilter(color: Int) {
|
||||
mImageViewMic?.setColorFilter(color)
|
||||
}
|
||||
|
||||
fun setMicIconColorFilter(color: Int, mode: PorterDuff.Mode) {
|
||||
mImageViewMic?.setColorFilter(color, mode)
|
||||
}
|
||||
|
||||
fun setMicIconColorFilter(cf: ColorFilter?) {
|
||||
mImageViewMic?.colorFilter = cf
|
||||
}
|
||||
|
||||
fun clearMicIconColorFilter() {
|
||||
mImageViewMic?.clearColorFilter()
|
||||
}
|
||||
|
||||
fun setMicIconContentDescription(contentDescription: CharSequence) {
|
||||
mImageViewMic?.contentDescription = contentDescription
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setMenuIconImageResource(@DrawableRes resId: Int) {
|
||||
mImageViewMenu?.setImageResource(resId)
|
||||
}
|
||||
|
||||
fun setMenuIconImageDrawable(@Nullable drawable: Drawable?) {
|
||||
mImageViewMenu?.setImageDrawable(drawable)
|
||||
}
|
||||
|
||||
fun setMenuIconColorFilter(color: Int) {
|
||||
mImageViewMenu?.setColorFilter(color)
|
||||
}
|
||||
|
||||
fun setMenuIconColorFilter(color: Int, mode: PorterDuff.Mode) {
|
||||
mImageViewMenu?.setColorFilter(color, mode)
|
||||
}
|
||||
|
||||
fun setMenuIconColorFilter(cf: ColorFilter?) {
|
||||
mImageViewMenu?.colorFilter = cf
|
||||
}
|
||||
|
||||
fun clearMenuIconColorFilter() {
|
||||
mImageViewMenu?.clearColorFilter()
|
||||
}
|
||||
|
||||
fun setMenuIconContentDescription(contentDescription: CharSequence) {
|
||||
mImageViewMenu?.contentDescription = contentDescription
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setClearIconImageResource(@DrawableRes resId: Int) {
|
||||
mImageViewClear?.setImageResource(resId)
|
||||
}
|
||||
|
||||
fun setClearIconImageDrawable(@Nullable drawable: Drawable?) {
|
||||
mImageViewClear?.setImageDrawable(drawable)
|
||||
}
|
||||
|
||||
fun setClearIconColorFilter(color: Int) {
|
||||
mImageViewClear?.setColorFilter(color)
|
||||
}
|
||||
|
||||
fun setClearIconColorFilter(color: Int, mode: PorterDuff.Mode) {
|
||||
mImageViewClear?.setColorFilter(color, mode)
|
||||
}
|
||||
|
||||
fun setClearIconColorFilter(cf: ColorFilter?) {
|
||||
mImageViewClear?.colorFilter = cf
|
||||
}
|
||||
|
||||
fun clearClearIconColorFilter() {
|
||||
mImageViewClear?.clearColorFilter()
|
||||
}
|
||||
|
||||
fun setClearIconContentDescription(contentDescription: CharSequence) {
|
||||
mImageViewClear?.contentDescription = contentDescription
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setAdapterLayoutManager(@Nullable layout: RecyclerView.LayoutManager?) {
|
||||
mRecyclerView?.layoutManager = layout
|
||||
}
|
||||
|
||||
// only when height == match_parent
|
||||
fun setAdapterHasFixedSize(hasFixedSize: Boolean) {
|
||||
mRecyclerView?.setHasFixedSize(hasFixedSize)
|
||||
}
|
||||
|
||||
fun addAdapterItemDecoration(@NonNull decor: RecyclerView.ItemDecoration) {
|
||||
mRecyclerView?.addItemDecoration(decor)
|
||||
}
|
||||
|
||||
fun removeAdapterItemDecoration(@NonNull decor: RecyclerView.ItemDecoration) {
|
||||
mRecyclerView?.removeItemDecoration(decor)
|
||||
}
|
||||
|
||||
fun setAdapter(@Nullable adapter: RecyclerView.Adapter<*>?) {
|
||||
mRecyclerView?.adapter = adapter
|
||||
}
|
||||
|
||||
@Nullable
|
||||
fun getAdapter(): RecyclerView.Adapter<*>? {
|
||||
return mRecyclerView?.adapter
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
/**
|
||||
* Typeface.NORMAL
|
||||
* Typeface.BOLD
|
||||
* Typeface.ITALIC
|
||||
* Typeface.BOLD_ITALIC
|
||||
*
|
||||
* Typeface.DEFAULT
|
||||
* Typeface.DEFAULT_BOLD
|
||||
* Typeface.SANS_SERIF
|
||||
* Typeface.SERIF
|
||||
* Typeface.MONOSPACE
|
||||
*
|
||||
* Typeface.create(Typeface.NORMAL, Typeface.DEFAULT)
|
||||
*/
|
||||
fun setTextTypeface(@Nullable tf: Typeface?) {
|
||||
mSearchEditText?.typeface = tf
|
||||
}
|
||||
|
||||
fun getTextTypeface(): Typeface? {
|
||||
return mSearchEditText?.typeface
|
||||
}
|
||||
|
||||
fun setTextInputType(type: Int) {
|
||||
mSearchEditText?.inputType = type
|
||||
}
|
||||
|
||||
fun getTextInputType(): Int? {
|
||||
return mSearchEditText?.inputType
|
||||
}
|
||||
|
||||
fun setTextImeOptions(imeOptions: Int) {
|
||||
mSearchEditText?.imeOptions = imeOptions
|
||||
}
|
||||
|
||||
fun getTextImeOptions(): Int? {
|
||||
return mSearchEditText?.imeOptions
|
||||
}
|
||||
|
||||
fun setTextQuery(query: CharSequence?, submit: Boolean) {
|
||||
mSearchEditText?.setText(query)
|
||||
if (query != null) {
|
||||
mSearchEditText?.setSelection(mSearchEditText?.length()!!)
|
||||
}
|
||||
if (submit && !TextUtils.isEmpty(query)) {
|
||||
onSubmitQuery()
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
fun getTextQuery(): CharSequence? {
|
||||
return mSearchEditText?.text
|
||||
}
|
||||
|
||||
fun setTextHint(hint: CharSequence?) {
|
||||
mSearchEditText?.hint = hint
|
||||
}
|
||||
|
||||
fun getTextHint(): CharSequence? {
|
||||
return mSearchEditText?.hint
|
||||
}
|
||||
|
||||
fun setTextColor(@ColorInt color: Int) {
|
||||
mSearchEditText?.setTextColor(color)
|
||||
}
|
||||
|
||||
fun setTextSize(size: Float) {
|
||||
mSearchEditText?.textSize = size
|
||||
}
|
||||
|
||||
fun setTextGravity(gravity: Int) {
|
||||
mSearchEditText?.gravity = gravity
|
||||
}
|
||||
|
||||
fun setTextHint(@StringRes resid: Int) {
|
||||
mSearchEditText?.setHint(resid)
|
||||
}
|
||||
|
||||
fun setTextHintColor(@ColorInt color: Int) {
|
||||
mSearchEditText?.setHintTextColor(color)
|
||||
}
|
||||
|
||||
fun setClearFocusOnBackPressed(clearFocusOnBackPressed: Boolean) {
|
||||
mSearchEditText?.clearFocusOnBackPressed = clearFocusOnBackPressed
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
override fun setBackgroundColor(@ColorInt color: Int) {
|
||||
mMaterialCardView?.setCardBackgroundColor(color)
|
||||
}
|
||||
|
||||
fun setBackgroundColor(@Nullable color: ColorStateList?) {
|
||||
mMaterialCardView?.setCardBackgroundColor(color)
|
||||
}
|
||||
|
||||
override fun setElevation(elevation: Float) {
|
||||
mMaterialCardView?.cardElevation = elevation
|
||||
mMaterialCardView?.maxCardElevation = elevation
|
||||
}
|
||||
|
||||
override fun getElevation(): Float {
|
||||
return mMaterialCardView?.elevation!!
|
||||
}
|
||||
|
||||
fun setBackgroundRadius(radius: Float) {
|
||||
mMaterialCardView?.radius = radius
|
||||
}
|
||||
|
||||
fun getBackgroundRadius(): Float {
|
||||
return mMaterialCardView?.radius!!
|
||||
}
|
||||
|
||||
fun setBackgroundRippleColor(@ColorRes rippleColorResourceId: Int) {
|
||||
mMaterialCardView?.setRippleColorResource(rippleColorResourceId)
|
||||
}
|
||||
|
||||
fun setBackgroundRippleColorResource(@Nullable rippleColor: ColorStateList?) {
|
||||
mMaterialCardView?.rippleColor = rippleColor
|
||||
}
|
||||
|
||||
fun setBackgroundStrokeColor(@ColorInt strokeColor: Int) {
|
||||
mMaterialCardView?.strokeColor = strokeColor
|
||||
}
|
||||
|
||||
fun setBackgroundStrokeColor(strokeColor: ColorStateList) {
|
||||
mMaterialCardView?.setStrokeColor(strokeColor)
|
||||
}
|
||||
|
||||
fun setBackgroundStrokeWidth(@Dimension strokeWidth: Int) {
|
||||
mMaterialCardView?.strokeWidth = strokeWidth
|
||||
}
|
||||
|
||||
@Dimension
|
||||
fun getBackgroundStrokeWidth(): Int {
|
||||
return mMaterialCardView?.strokeWidth!!
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setDividerColor(@ColorInt color: Int) {
|
||||
mViewDivider?.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
fun setShadowColor(@ColorInt color: Int) {
|
||||
mViewShadow?.setBackgroundColor(color)
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun setOnFocusChangeListener(listener: OnFocusChangeListener) {
|
||||
mOnFocusChangeListener = listener
|
||||
}
|
||||
|
||||
fun setOnQueryTextListener(listener: OnQueryTextListener) {
|
||||
mOnQueryTextListener = listener
|
||||
}
|
||||
|
||||
fun setOnNavigationClickListener(listener: OnNavigationClickListener) {
|
||||
mOnNavigationClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnMicClickListener(listener: OnMicClickListener) {
|
||||
mOnMicClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnMenuClickListener(listener: OnMenuClickListener) {
|
||||
mOnMenuClickListener = listener
|
||||
}
|
||||
|
||||
fun setOnClearClickListener(listener: OnClearClickListener) {
|
||||
mOnClearClickListener = listener
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
fun showKeyboard() {
|
||||
if (!isInEditMode) {
|
||||
val inputMethodManager =
|
||||
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.showSoftInput(
|
||||
mSearchEditText,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun hideKeyboard() {
|
||||
if (!isInEditMode) {
|
||||
val inputMethodManager =
|
||||
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(
|
||||
windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
protected fun setLayoutHeight(height: Int) {
|
||||
val params = mLinearLayout?.layoutParams
|
||||
params?.height = height
|
||||
params?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
mLinearLayout?.layoutParams = params
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
private fun onTextChanged(newText: CharSequence) {
|
||||
if (!TextUtils.isEmpty(newText)) {
|
||||
mImageViewMic?.visibility = View.GONE
|
||||
mImageViewClear?.visibility = View.VISIBLE
|
||||
} else {
|
||||
mImageViewClear?.visibility = View.GONE
|
||||
if (mSearchEditText?.hasFocus()!!) {
|
||||
mImageViewMic?.visibility = View.VISIBLE
|
||||
} else {
|
||||
mImageViewMic?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
if (mOnQueryTextListener != null) {
|
||||
mOnQueryTextListener?.onQueryTextChange(newText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSubmitQuery() {
|
||||
val query = mSearchEditText?.text
|
||||
if (query != null && TextUtils.getTrimmedLength(query) > 0) {
|
||||
if (mOnQueryTextListener == null || !mOnQueryTextListener!!.onQueryTextSubmit(query.toString())) {
|
||||
mSearchEditText?.text = query
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
override fun onSaveInstanceState(): Parcelable? {
|
||||
val superState = super.onSaveInstanceState()
|
||||
val ss = SearchViewSavedState(superState!!)
|
||||
if (mSearchEditText?.text!!.isNotEmpty()) {
|
||||
ss.query = mSearchEditText?.text
|
||||
}
|
||||
ss.hasFocus = mSearchEditText?.hasFocus()!!
|
||||
return ss
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable?) {
|
||||
if (state !is SearchViewSavedState) {
|
||||
super.onRestoreInstanceState(state)
|
||||
return
|
||||
}
|
||||
super.onRestoreInstanceState(state.superState)
|
||||
if (state.hasFocus) {
|
||||
mSearchEditText?.requestFocus()
|
||||
}
|
||||
if (state.query != null) {
|
||||
setTextQuery(state.query, false)
|
||||
}
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
override fun requestFocus(direction: Int, previouslyFocusedRect: Rect?): Boolean {
|
||||
return if (!isFocusable) {
|
||||
false
|
||||
} else {
|
||||
mSearchEditText?.requestFocus(direction, previouslyFocusedRect)!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearFocus() {
|
||||
super.clearFocus()
|
||||
mSearchEditText?.clearFocus()
|
||||
}
|
||||
|
||||
override fun onClick(view: View?) {
|
||||
if (view === mImageViewNavigation) {
|
||||
if (mOnNavigationClickListener != null) {
|
||||
mOnNavigationClickListener?.onNavigationClick(mSearchEditText?.hasFocus()!!)
|
||||
}
|
||||
} else if (view === mImageViewMic) {
|
||||
if (mOnMicClickListener != null) {
|
||||
mOnMicClickListener?.onMicClick()
|
||||
}
|
||||
} else if (view === mImageViewMenu) {
|
||||
if (mOnMenuClickListener != null) {
|
||||
mOnMenuClickListener?.onMenuClick()
|
||||
}
|
||||
} else if (view === mImageViewClear) {
|
||||
if (mSearchEditText?.text!!.isNotEmpty()) {
|
||||
mSearchEditText?.text!!.clear()
|
||||
}
|
||||
if (mOnClearClickListener != null) {
|
||||
mOnClearClickListener?.onClearClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************************************************
|
||||
interface OnFocusChangeListener {
|
||||
|
||||
fun onFocusChange(hasFocus: Boolean)
|
||||
}
|
||||
|
||||
interface OnQueryTextListener {
|
||||
|
||||
fun onQueryTextChange(newText: CharSequence): Boolean
|
||||
|
||||
fun onQueryTextSubmit(query: CharSequence): Boolean
|
||||
}
|
||||
|
||||
interface OnNavigationClickListener {
|
||||
|
||||
fun onNavigationClick(hasFocus: Boolean)
|
||||
}
|
||||
|
||||
interface OnMicClickListener {
|
||||
|
||||
fun onMicClick()
|
||||
}
|
||||
|
||||
interface OnMenuClickListener {
|
||||
|
||||
fun onMenuClick()
|
||||
}
|
||||
|
||||
interface OnClearClickListener {
|
||||
|
||||
fun onClearClick()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*https://github.com/lapism/search*/
|
||||
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search.internal
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
|
||||
internal class SearchViewSavedState(superState: Parcelable) : View.BaseSavedState(superState) {
|
||||
|
||||
var query: CharSequence? = null
|
||||
var hasFocus: Boolean = false
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
super.writeToParcel(out, flags)
|
||||
TextUtils.writeToParcel(query, out, flags)
|
||||
out.writeInt(if (hasFocus) 1 else 0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.koitharu.kotatsu.base.ui.widgets.search.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.speech.RecognizerIntent
|
||||
|
||||
object SearchUtils {
|
||||
|
||||
const val SPEECH_REQUEST_CODE = 300
|
||||
|
||||
@JvmStatic
|
||||
fun setVoiceSearch(activity: Activity, text: String) {
|
||||
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(
|
||||
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
||||
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
||||
)
|
||||
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, text)
|
||||
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1)
|
||||
|
||||
activity.startActivityForResult(
|
||||
intent,
|
||||
SPEECH_REQUEST_CODE
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isVoiceSearchAvailable(context: Context): Boolean {
|
||||
val pm = context.packageManager
|
||||
val activities =
|
||||
pm.queryIntentActivities(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0)
|
||||
return activities.size != 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -61,16 +61,42 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
|
||||
.enqueueWith(coil)
|
||||
textViewTitle.text = manga.title
|
||||
textViewSubtitle.textAndVisible = manga.altTitle
|
||||
textViewAuthor.textAndVisible = manga.author
|
||||
textViewSource.text = manga.source.title
|
||||
textViewDescription.text =
|
||||
manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank)
|
||||
?: getString(R.string.no_description)
|
||||
if (manga.rating == Manga.NO_RATING) {
|
||||
ratingBar.isVisible = false
|
||||
if (manga.chapters?.isNotEmpty() == true) {
|
||||
chaptersContainer.isVisible = true
|
||||
textViewChapters.text = manga.chapters.let {
|
||||
resources.getQuantityString(
|
||||
R.plurals.chapters,
|
||||
it.size,
|
||||
manga.chapters.size
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ratingBar.progress = (ratingBar.max * manga.rating).roundToInt()
|
||||
ratingBar.isVisible = true
|
||||
chaptersContainer.isVisible = false
|
||||
}
|
||||
imageViewFavourite.setOnClickListener(this@DetailsFragment)
|
||||
if (manga.rating == Manga.NO_RATING) {
|
||||
ratingContainer.isVisible = false
|
||||
} else {
|
||||
textViewRating.text = String.format("%.1f", manga.rating * 5)
|
||||
ratingContainer.isVisible = true
|
||||
}
|
||||
val file = manga.url.toUri().toFileOrNull()
|
||||
if (file != null) {
|
||||
viewLifecycleScope.launch {
|
||||
val size = withContext(Dispatchers.IO) {
|
||||
file.length()
|
||||
}
|
||||
textViewSize.text = FileSizeUtils.formatBytes(requireContext(), size)
|
||||
}
|
||||
sizeContainer.isVisible = true
|
||||
} else {
|
||||
sizeContainer.isVisible = false
|
||||
}
|
||||
buttonFavorite.setOnClickListener(this@DetailsFragment)
|
||||
buttonRead.setOnClickListener(this@DetailsFragment)
|
||||
buttonRead.setOnLongClickListener(this@DetailsFragment)
|
||||
buttonRead.isEnabled = !manga.chapters.isNullOrEmpty()
|
||||
@@ -91,13 +117,13 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
|
||||
}
|
||||
|
||||
private fun onFavouriteChanged(isFavourite: Boolean) {
|
||||
binding.imageViewFavourite.setImageResource(
|
||||
with(binding.buttonFavorite) {
|
||||
if (isFavourite) {
|
||||
R.drawable.ic_heart
|
||||
this.setIconResource(R.drawable.ic_heart)
|
||||
} else {
|
||||
R.drawable.ic_heart_outline
|
||||
this.setIconResource(R.drawable.ic_heart_outline)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
@@ -107,7 +133,7 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
|
||||
override fun onClick(v: View) {
|
||||
val manga = viewModel.manga.value
|
||||
when (v.id) {
|
||||
R.id.imageView_favourite -> {
|
||||
R.id.button_favorite -> {
|
||||
FavouriteCategoriesDialog.show(childFragmentManager, manga ?: return)
|
||||
}
|
||||
R.id.button_read -> {
|
||||
@@ -163,31 +189,10 @@ class DetailsFragment : BaseFragment<FragmentDetailsBinding>(), View.OnClickList
|
||||
tagsJob?.cancel()
|
||||
tagsJob = viewLifecycleScope.launch {
|
||||
val tags = ArrayList<ChipsView.ChipModel>(manga.tags.size + 2)
|
||||
if (manga.author != null) {
|
||||
tags += ChipsView.ChipModel(
|
||||
title = manga.author,
|
||||
icon = R.drawable.ic_chip_user
|
||||
)
|
||||
}
|
||||
for (tag in manga.tags) {
|
||||
tags += ChipsView.ChipModel(
|
||||
title = tag.title,
|
||||
icon = R.drawable.ic_chip_tag
|
||||
)
|
||||
}
|
||||
val file = manga.url.toUri().toFileOrNull()
|
||||
if (file != null) {
|
||||
val size = withContext(Dispatchers.IO) {
|
||||
file.length()
|
||||
}
|
||||
tags += ChipsView.ChipModel(
|
||||
title = FileSizeUtils.formatBytes(requireContext(), size),
|
||||
icon = R.drawable.ic_chip_storage
|
||||
)
|
||||
} else {
|
||||
tags += ChipsView.ChipModel(
|
||||
title = manga.source.title,
|
||||
icon = R.drawable.ic_chip_web
|
||||
icon = 0
|
||||
)
|
||||
}
|
||||
binding.chipsTags.setChips(tags)
|
||||
|
||||
@@ -4,16 +4,15 @@ import android.os.Bundle
|
||||
import android.view.*
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -81,6 +80,10 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
addOnScrollListener(paginationListener!!)
|
||||
}
|
||||
with(binding.swipeRefreshLayout) {
|
||||
setColorSchemeColors(
|
||||
ContextCompat.getColor(context, R.color.color_primary),
|
||||
ContextCompat.getColor(context, R.color.color_primary_variant)
|
||||
)
|
||||
setOnRefreshListener(this@MangaListFragment)
|
||||
isEnabled = isSwipeRefreshEnabled
|
||||
}
|
||||
@@ -246,13 +249,9 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
|
||||
when (mode) {
|
||||
ListMode.LIST -> {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
context,
|
||||
RecyclerView.VERTICAL
|
||||
)
|
||||
)
|
||||
updatePadding(left = 0, right = 0)
|
||||
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
||||
addItemDecoration(SpacingItemDecoration(spacing))
|
||||
updatePadding(left = spacing, right = spacing)
|
||||
}
|
||||
ListMode.DETAILED_LIST -> {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
|
||||
import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun Manga.toListModel() = MangaListModel(
|
||||
id = id,
|
||||
@@ -20,7 +19,7 @@ fun Manga.toListDetailedModel() = MangaListDetailedModel(
|
||||
id = id,
|
||||
title = title,
|
||||
subtitle = altTitle,
|
||||
rating = if (rating == Manga.NO_RATING) null else "${(rating * 10).roundToInt()}/10",
|
||||
rating = if (rating == Manga.NO_RATING) null else String.format("%.1f", rating * 5),
|
||||
tags = tags.joinToString(", ") { it.title },
|
||||
coverUrl = coverUrl,
|
||||
manga = this
|
||||
|
||||
@@ -6,10 +6,7 @@ import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.graphics.Insets
|
||||
@@ -17,6 +14,7 @@ import androidx.core.view.*
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -28,6 +26,7 @@ import org.koitharu.kotatsu.core.model.Manga
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSection
|
||||
import org.koitharu.kotatsu.databinding.ActivityMainBinding
|
||||
import org.koitharu.kotatsu.databinding.NavigationHeaderBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment
|
||||
import org.koitharu.kotatsu.history.ui.HistoryListFragment
|
||||
@@ -46,6 +45,7 @@ import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
||||
import org.koitharu.kotatsu.tracker.ui.FeedFragment
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.navigationItemBackground
|
||||
import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||
|
||||
class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
@@ -57,12 +57,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
mode = LazyThreadSafetyMode.NONE
|
||||
)
|
||||
|
||||
private lateinit var navHeaderBinding: NavigationHeaderBinding
|
||||
private lateinit var drawerToggle: ActionBarDrawerToggle
|
||||
private var searchUi: SearchUI? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityMainBinding.inflate(layoutInflater))
|
||||
navHeaderBinding = NavigationHeaderBinding.inflate(layoutInflater)
|
||||
drawerToggle = ActionBarDrawerToggle(
|
||||
this,
|
||||
binding.drawer,
|
||||
@@ -73,7 +75,18 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
|
||||
binding.drawer.addDrawerListener(drawerToggle)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
binding.navigationView.setNavigationItemSelectedListener(this)
|
||||
binding.navigationView.apply {
|
||||
val menuView = findViewById<RecyclerView>(com.google.android.material.R.id.design_navigation_view)
|
||||
navHeaderBinding.root.setOnApplyWindowInsetsListener { v, insets ->
|
||||
v.updatePadding(top = insets.systemWindowInsetTop)
|
||||
// NavigationView doesn't dispatch insets to the menu view, so pad the bottom here.
|
||||
menuView.updatePadding(bottom = insets.systemWindowInsetBottom)
|
||||
insets
|
||||
}
|
||||
addHeaderView(navHeaderBinding.root)
|
||||
itemBackground = navigationItemBackground(context)
|
||||
setNavigationItemSelectedListener(this@MainActivity)
|
||||
}
|
||||
|
||||
with(binding.fab) {
|
||||
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.wetoon.WebtoonReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.PagesThumbnailsSheet
|
||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PointF
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.koitharu.kotatsu.reader.ui.pager.wetoon
|
||||
package org.koitharu.kotatsu.reader.ui.pager.webtoon
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
@@ -47,9 +47,6 @@ class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListen
|
||||
feedAdapter = FeedAdapter(get(), viewLifecycleOwner, this)
|
||||
with(binding.recyclerView) {
|
||||
adapter = feedAdapter
|
||||
addItemDecoration(
|
||||
SpacingItemDecoration(resources.getDimensionPixelOffset(R.dimen.grid_spacing))
|
||||
)
|
||||
setHasFixedSize(true)
|
||||
addOnScrollListener(PaginationScrollListener(4, this@FeedFragment))
|
||||
}
|
||||
@@ -134,7 +131,7 @@ class FeedFragment : BaseFragment<FragmentFeedBinding>(), PaginationScrollListen
|
||||
return
|
||||
}
|
||||
val summaryText = getString(
|
||||
R.string.chapers_checking_progress,
|
||||
R.string.chapters_checking_progress,
|
||||
progress.value + 1,
|
||||
progress.total
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ fun feedItemAD(
|
||||
.lifecycle(lifecycleOwner)
|
||||
.enqueueWith(coil)
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.textViewSubtitle.text = item.subtitle
|
||||
binding.badge.text = item.subtitle
|
||||
binding.textViewChapters.text = item.chapters
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package org.koitharu.kotatsu.tracker.ui.model
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.text.format.DateUtils
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.utils.ext.formatRelative
|
||||
|
||||
fun TrackingLogItem.toFeedItem(resources: Resources): FeedItem {
|
||||
val chaptersString = if (chapters.size > MAX_CHAPTERS) {
|
||||
@@ -23,17 +21,7 @@ fun TrackingLogItem.toFeedItem(resources: Resources): FeedItem {
|
||||
id = id,
|
||||
imageUrl = manga.coverUrl,
|
||||
title = manga.title,
|
||||
subtitle = buildString {
|
||||
append(createdAt.formatRelative(DateUtils.DAY_IN_MILLIS))
|
||||
append(" ")
|
||||
append(
|
||||
resources.getQuantityString(
|
||||
R.plurals.new_chapters,
|
||||
chapters.size,
|
||||
chapters.size
|
||||
)
|
||||
)
|
||||
},
|
||||
subtitle = chapters.size.toString(),
|
||||
chapters = chaptersString,
|
||||
manga = manga
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun ImageView.newImageRequest(url: String) = ImageRequest.Builder(context)
|
||||
.data(url)
|
||||
.crossfade(true)
|
||||
.target(this)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
21
app/src/main/java/org/koitharu/kotatsu/utils/ext/UiExt.kt
Normal file
21
app/src/main/java/org/koitharu/kotatsu/utils/ext/UiExt.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
fun navigationItemBackground(context: Context): Drawable? {
|
||||
// Need to inflate the drawable and CSL via AppCompatResources to work on Lollipop
|
||||
// From Google I/O repo (https://github.com/google/iosched)
|
||||
var background = AppCompatResources.getDrawable(context, R.drawable.navigation_item_background)
|
||||
if (background != null) {
|
||||
val tint = AppCompatResources.getColorStateList(
|
||||
context, R.color.navigation_item_background_tint
|
||||
)
|
||||
background = DrawableCompat.wrap(background.mutate())
|
||||
background.setTintList(tint)
|
||||
}
|
||||
return background
|
||||
}
|
||||
Reference in New Issue
Block a user