Merge branch 'feature/m3' into devel
commit
8a08d58ed7
@ -0,0 +1,8 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
interface RecyclerViewOwner {
|
||||
|
||||
val recyclerView: RecyclerView
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
||||
class WindowInsetsDelegate(
|
||||
private val listener: WindowInsetsListener,
|
||||
) : OnApplyWindowInsetsListener, View.OnLayoutChangeListener {
|
||||
|
||||
var handleImeInsets: Boolean = false
|
||||
|
||||
var interceptingWindowInsetsListener: OnApplyWindowInsetsListener? = null
|
||||
|
||||
private var lastInsets: Insets? = null
|
||||
|
||||
override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat?): WindowInsetsCompat? {
|
||||
if (insets == null) {
|
||||
return null
|
||||
}
|
||||
val handledInsets = interceptingWindowInsetsListener?.onApplyWindowInsets(v, insets) ?: insets
|
||||
val newInsets = if (handleImeInsets) {
|
||||
Insets.max(
|
||||
handledInsets.getInsets(WindowInsetsCompat.Type.systemBars()),
|
||||
handledInsets.getInsets(WindowInsetsCompat.Type.ime()),
|
||||
)
|
||||
} else {
|
||||
handledInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
if (newInsets != lastInsets) {
|
||||
listener.onWindowInsetsChanged(newInsets)
|
||||
lastInsets = newInsets
|
||||
}
|
||||
return handledInsets
|
||||
}
|
||||
|
||||
override fun onLayoutChange(
|
||||
view: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int,
|
||||
) {
|
||||
view.removeOnLayoutChangeListener(this)
|
||||
if (lastInsets == null) { // Listener may not be called
|
||||
onApplyWindowInsets(view, ViewCompat.getRootWindowInsets(view))
|
||||
}
|
||||
}
|
||||
|
||||
fun onViewCreated(view: View) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(view, this)
|
||||
view.addOnLayoutChangeListener(this)
|
||||
}
|
||||
|
||||
fun onDestroyView() {
|
||||
lastInsets = null
|
||||
}
|
||||
|
||||
interface WindowInsetsListener {
|
||||
|
||||
fun onWindowInsetsChanged(insets: Insets)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
package org.koitharu.kotatsu.base.ui.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.RectShape
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.google.android.material.shape.ShapeAppearanceModel
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class ListItemTextView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = R.attr.listItemTextViewStyle,
|
||||
) : AppCompatCheckedTextView(context, attrs, defStyleAttr) {
|
||||
|
||||
private var checkedDrawableStart: Drawable? = null
|
||||
private var checkedDrawableEnd: Drawable? = null
|
||||
private var isInitialized = false
|
||||
private var isCheckDrawablesVisible: Boolean = false
|
||||
private var defaultPaddingStart: Int = 0
|
||||
private var defaultPaddingEnd: Int = 0
|
||||
|
||||
init {
|
||||
context.withStyledAttributes(attrs, R.styleable.ListItemTextView, defStyleAttr) {
|
||||
background = RippleDrawable(
|
||||
getColorStateList(R.styleable.ListItemTextView_rippleColor) ?: getRippleColorFallback(context),
|
||||
createShapeDrawable(this),
|
||||
ShapeDrawable(RectShape()),
|
||||
)
|
||||
checkedDrawableStart = getDrawable(R.styleable.ListItemTextView_checkedDrawableStart)
|
||||
checkedDrawableEnd = getDrawable(R.styleable.ListItemTextView_checkedDrawableEnd)
|
||||
}
|
||||
checkedDrawableStart?.setTintList(textColors)
|
||||
checkedDrawableEnd?.setTintList(textColors)
|
||||
defaultPaddingStart = paddingStart
|
||||
defaultPaddingEnd = paddingEnd
|
||||
isInitialized = true
|
||||
adjustCheckDrawables()
|
||||
}
|
||||
|
||||
override fun refreshDrawableState() {
|
||||
super.refreshDrawableState()
|
||||
adjustCheckDrawables()
|
||||
}
|
||||
|
||||
override fun setTextColor(colors: ColorStateList?) {
|
||||
checkedDrawableStart?.setTintList(colors)
|
||||
checkedDrawableEnd?.setTintList(colors)
|
||||
super.setTextColor(colors)
|
||||
}
|
||||
|
||||
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
|
||||
defaultPaddingStart = start
|
||||
defaultPaddingEnd = end
|
||||
super.setPaddingRelative(start, top, end, bottom)
|
||||
}
|
||||
|
||||
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
|
||||
defaultPaddingStart = if (isRtl) right else left
|
||||
defaultPaddingEnd = if (isRtl) left else right
|
||||
super.setPadding(left, top, right, bottom)
|
||||
}
|
||||
|
||||
private fun adjustCheckDrawables() {
|
||||
if (isInitialized && isCheckDrawablesVisible != isChecked) {
|
||||
setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
if (isChecked) checkedDrawableStart else null,
|
||||
null,
|
||||
if (isChecked) checkedDrawableEnd else null,
|
||||
null,
|
||||
)
|
||||
super.setPaddingRelative(
|
||||
if (isChecked && checkedDrawableStart != null) {
|
||||
defaultPaddingStart + compoundDrawablePadding
|
||||
} else defaultPaddingStart,
|
||||
paddingTop,
|
||||
if (isChecked && checkedDrawableEnd != null) {
|
||||
defaultPaddingEnd + compoundDrawablePadding
|
||||
} else defaultPaddingEnd,
|
||||
paddingBottom,
|
||||
)
|
||||
isCheckDrawablesVisible = isChecked
|
||||
}
|
||||
}
|
||||
|
||||
private fun createShapeDrawable(ta: TypedArray): InsetDrawable {
|
||||
val shapeAppearance = ShapeAppearanceModel.builder(
|
||||
context,
|
||||
ta.getResourceId(R.styleable.ListItemTextView_shapeAppearance, 0),
|
||||
ta.getResourceId(R.styleable.ListItemTextView_shapeAppearanceOverlay, 0),
|
||||
).build()
|
||||
val shapeDrawable = MaterialShapeDrawable(shapeAppearance)
|
||||
shapeDrawable.fillColor = ta.getColorStateList(R.styleable.ListItemTextView_backgroundTint)
|
||||
return InsetDrawable(
|
||||
shapeDrawable,
|
||||
ta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetLeft, 0),
|
||||
ta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetTop, 0),
|
||||
ta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetRight, 0),
|
||||
ta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetBottom, 0),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRippleColorFallback(context: Context): ColorStateList {
|
||||
return context.obtainStyledAttributes(intArrayOf(android.R.attr.colorControlHighlight)).use {
|
||||
it.getColorStateList(0)
|
||||
} ?: ColorStateList.valueOf(Color.TRANSPARENT)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:alpha="0.3" android:color="?attr/colorPrimary" android:state_checked="true"/>
|
||||
<item android:color="@android:color/transparent"/>
|
||||
</selector>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?attr/colorOnSurfaceVariant" android:state_enabled="false" />
|
||||
<item android:color="?attr/colorPrimaryDark" android:state_checked="true" />
|
||||
<item android:color="?attr/colorOnSurfaceVariant" />
|
||||
</selector>
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorPrimaryContainer" android:state_checked="true" />
|
||||
<item android:color="?colorSurfaceVariant" android:state_checked="false" />
|
||||
</selector>
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorPrimary" android:state_checked="true" />
|
||||
<item android:color="?colorOnSurfaceVariant" android:state_checked="false" />
|
||||
</selector>
|
||||
@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41L9,16.17z" />
|
||||
</vector>
|
||||
@ -1,5 +1,7 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
|
||||
</vector>
|
||||
<rotate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/abc_ic_arrow_drop_right_black_24dp"
|
||||
android:fromDegrees="-90"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="-90" />
|
||||
@ -1,11 +1,7 @@
|
||||
<vector
|
||||
<rotate
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z" />
|
||||
</vector>
|
||||
android:drawable="@drawable/abc_ic_arrow_drop_right_black_24dp"
|
||||
android:fromDegrees="90"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toDegrees="90" />
|
||||
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:bottom="4dp"
|
||||
android:left="4dp"
|
||||
android:right="4dp"
|
||||
android:top="4dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#000000" />
|
||||
<size
|
||||
android:width="20dp"
|
||||
android:height="20dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#000000" />
|
||||
<corners android:radius="56dp" />
|
||||
<size
|
||||
android:width="64dp"
|
||||
android:height="28dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ListItemTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
android:textAppearance="?attr/textAppearanceButton"
|
||||
app:rippleColor="?colorSecondaryContainer"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
Loading…
Reference in New Issue