Remove unused legacy views

This commit is contained in:
MM20 2022-03-29 18:16:44 +02:00
parent 956af82f79
commit 537cde60a5
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 0 additions and 1073 deletions

View File

@ -1,29 +0,0 @@
package de.mm20.launcher2.ui.legacy.data
import android.content.Context
import android.graphics.drawable.ColorDrawable
import androidx.core.content.ContextCompat
import de.mm20.launcher2.graphics.TextDrawable
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.sp
import de.mm20.launcher2.search.data.Searchable
/**
* A helper searchable that is used to display text inside [de.mm20.launcher2.ui.search2.SearchGridViews]
*/
class InformationText(
override val label: String,
val clickAction: (() -> Unit)? = null
) : Searchable() {
override val key: String
get() = "text://${label}"
override fun getPlaceholderIcon(context: Context): LauncherIcon {
return LauncherIcon(
foreground = TextDrawable("i", fontSize = 40 * context.sp),
background = ColorDrawable(ContextCompat.getColor(context, R.color.grey))
)
}
}

View File

@ -1,72 +0,0 @@
package de.mm20.launcher2.ui.legacy.transition
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import androidx.transition.Transition
import androidx.transition.TransitionValues
import de.mm20.launcher2.ui.legacy.view.LauncherCardView
class LauncherCards : Transition() {
override fun captureStartValues(transitionValues: TransitionValues) {
val view = transitionValues.view
if (view is LauncherCardView) {
transitionValues.values[PROP_ELEVATION] = view.cardElevation
transitionValues.values[PROP_BG_OPACITY] = view.backgroundOpacity
}
}
override fun captureEndValues(transitionValues: TransitionValues) {
val view = transitionValues.view
if (view is LauncherCardView) {
transitionValues.values[PROP_ELEVATION] = view.cardElevation
transitionValues.values[PROP_BG_OPACITY] = view.backgroundOpacity
}
}
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
if (startValues == null || endValues == null) return null
if (startValues.view !is LauncherCardView) return null
val endView = endValues.view as? LauncherCardView
?: return null
val startElevation = startValues.values[PROP_ELEVATION] as Float
val endElevation = endValues.values[PROP_ELEVATION] as Float
val startBgOpacity = startValues.values[PROP_BG_OPACITY] as Int
val endBgOpacity = endValues.values[PROP_BG_OPACITY] as Int
if(startBgOpacity < endBgOpacity) {
return AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(endView, "cardElevation", startElevation, startElevation, endElevation).apply {
interpolator = AccelerateInterpolator()
},
ObjectAnimator.ofInt(endView, "backgroundOpacity", startBgOpacity, endBgOpacity).apply {
interpolator = DecelerateInterpolator()
}
)
}
}
return AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(endView, "cardElevation", startElevation, endElevation, endElevation).apply {
interpolator = DecelerateInterpolator()
},
ObjectAnimator.ofInt(endView, "backgroundOpacity", startBgOpacity, endBgOpacity).apply {
interpolator = AccelerateInterpolator()
}
)
}
}
companion object {
private const val PROP_ELEVATION = "mm20:app:elevation"
private const val PROP_BG_OPACITY = "mm20:app:bg_opacity"
}
}

View File

@ -1,32 +0,0 @@
package de.mm20.launcher2.ui.legacy.view
import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import de.mm20.launcher2.ui.R
class AspectRationImageView : AppCompatImageView {
var aspectRatio = 1f
var fixedHeight = false
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes) {
attrs?.let {
val ta = context.theme.obtainStyledAttributes(it, R.styleable.AspectRatioImageView, 0, defStyleRes)
aspectRatio = ta.getFloat(R.styleable.AspectRatioImageView_aspectRatio, 1f)
fixedHeight = ta.getBoolean(R.styleable.AspectRatioImageView_fixedHeight, false)
ta.recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (fixedHeight) {
setMeasuredDimension((measuredHeight / aspectRatio).toInt(), measuredHeight)
} else {
setMeasuredDimension(measuredWidth, (measuredWidth * aspectRatio).toInt())
}
}
}

View File

@ -1,52 +0,0 @@
package de.mm20.launcher2.ui.legacy.view
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.card.MaterialCardView
import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
open class InnerCardView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.materialCardViewStyle
) : MaterialCardView(context, attrs, defStyleAttr), KoinComponent {
init {
radius = LauncherCardView.currentCardStyle.radius * dp
strokeColor = ContextCompat.getColor(context, R.color.color_divider)
strokeWidth = (1 * dp).toInt()
cardElevation = 2 * dp
outlineProvider = null
}
private val dataStore: LauncherDataStore by inject()
private var job: Job? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
job?.cancel()
job = lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
dataStore.data.map { it.cards.radius }.distinctUntilChanged().collectLatest {
radius = it * dp
}
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
job?.cancel()
}
}

View File

@ -1,110 +0,0 @@
package de.mm20.launcher2.ui.legacy.view
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.card.MaterialCardView
import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.math.roundToInt
/**
* A card view implementation that solves the following issues of MaterialCardView:
* (1) Content clipping in transitions
* (2) Elevation overlay color
*/
open class LauncherCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.materialCardViewStyle
) : MaterialCardView(context, attrs, defStyleAttr), KoinComponent {
var backgroundOpacity: Int = (currentCardStyle.opacity * 255).roundToInt()
set(value) {
setCardBackgroundColor(cardBackgroundColor.defaultColor.let {
ColorStateList.valueOf((it and 0xFFFFFF) or (value shl 24))
})
field = value
}
private var strokeOpacity: Int = if (currentCardStyle.borderWidth > 0) 0xFF else 0
set(value) {
setStrokeColor(strokeColorStateList?.defaultColor?.let {
ColorStateList.valueOf((it and 0xFFFFFF) or (value shl 24))
})
field = value
}
private var overrideBackgroundOpacity = false
private var actualElevation: Float
init {
strokeColor = cardBackgroundColor.defaultColor
strokeWidth = (currentCardStyle.borderWidth * dp).roundToInt()
radius = currentCardStyle.radius * dp
context.theme.obtainStyledAttributes(
attrs,
R.styleable.LauncherCardView,
0, 0
).apply {
try {
if (hasValue(R.styleable.LauncherCardView_backgroundOpacity)) {
overrideBackgroundOpacity = true
backgroundOpacity = getInt(R.styleable.LauncherCardView_backgroundOpacity, 255)
}
} finally {
recycle()
}
}
strokeOpacity = if (backgroundOpacity == 0) 0 else 0xFF
actualElevation = context.resources.getDimension(R.dimen.card_elevation)
cardElevation = if (backgroundOpacity == 255) actualElevation else 0f
}
private val dataStore: LauncherDataStore by inject()
private var job: Job? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
job?.cancel()
job = lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
dataStore.data.map { it.cards }.distinctUntilChanged().collectLatest {
currentCardStyle = it
if (!overrideBackgroundOpacity) {
backgroundOpacity = (it.opacity * 255).roundToInt()
strokeOpacity = if (backgroundOpacity == 0) 0 else 0xFF
cardElevation = if (backgroundOpacity == 255) actualElevation else 0f
}
strokeWidth = (it.borderWidth * dp).roundToInt()
radius = it.radius * dp
}
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
job?.cancel()
}
companion object {
internal var currentCardStyle = Settings.CardSettings.getDefaultInstance()
}
}

View File

@ -1,475 +0,0 @@
package de.mm20.launcher2.ui.legacy.view
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.*
import android.view.animation.AccelerateInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import androidx.lifecycle.*
import com.google.android.material.card.MaterialCardView
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.ktx.lifecycleOwner
import de.mm20.launcher2.ktx.lifecycleScope
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.transition.ChangingLayoutTransition
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import kotlin.math.abs
class SwipeCardView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MaterialCardView(context, attrs, defStyleAttr), KoinComponent {
private val backdrop = FrameLayout(context)
private val icon = ImageView(context)
private val content = MaterialCardView(context)
private val iconColor = ContextCompat.getColor(context, R.color.swipe_card_icon_color)
private val iconColorActive =
ContextCompat.getColor(context, R.color.swipe_card_icon_color_active)
init {
super.addView(backdrop)
super.addView(icon, LayoutParams((40 * dp).toInt(), (24 * dp).toInt()))
icon.setColorFilter(iconColor)
super.addView(content)
content.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
content.transitionName = "SwipeCardView/content"
radius = LauncherCardView.currentCardStyle.radius * dp
content.radius = radius
super.setCardBackgroundColor(
ContextCompat.getColor(
context,
R.color.swipe_cardview_background
)
)
content.layoutTransition = ChangingLayoutTransition()
val ta = context.obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground))
content.foreground = ta.getDrawable(0)
ta.recycle()
}
var leftAction: SwipeAction? = null
var rightAction: SwipeAction? = null
private var leftThreshold = false
set(value) {
if (value == field) return
if (value) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
leftAction?.color?.let { backdrop.setBackgroundColor(it) }
AnimatorSet().also {
it.playTogether(
ViewAnimationUtils.createCircularReveal(
backdrop,
(28 * dp).toInt(),
(height * 0.5).toInt(),
0f,
width.toFloat()
)
.setDuration(300),
ObjectAnimator.ofArgb(icon, "colorFilter", iconColor, iconColorActive)
.apply {
duration = 150
startDelay = 100
},
ObjectAnimator.ofFloat(icon, "scaleX", 1.2f).apply {
duration = 200
},
ObjectAnimator.ofFloat(icon, "scaleY", 1.2f).apply {
duration = 200
}
)
}.start()
} else {
AnimatorSet().also {
it.playTogether(
ViewAnimationUtils.createCircularReveal(
backdrop,
(28 * dp).toInt(),
(height * 0.5).toInt(),
width.toFloat(),
0f
).apply {
doOnEnd {
if (!rightThreshold && !leftThreshold) backdrop.setBackgroundColor(0)
}
duration = 300
},
ObjectAnimator.ofArgb(icon, "colorFilter", iconColorActive, iconColor)
.apply {
duration = 150
startDelay = 100
},
ObjectAnimator.ofFloat(icon, "scaleX", 1f).apply {
duration = 200
},
ObjectAnimator.ofFloat(icon, "scaleY", 1f).apply {
duration = 200
}
)
}.start()
}
field = value
}
private var rightThreshold = false
set(value) {
if (value == field) return
if (value) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
rightAction?.color?.let { backdrop.setBackgroundColor(it) }
AnimatorSet().also {
it.playTogether(
ViewAnimationUtils.createCircularReveal(
backdrop,
(width - 28 * dp).toInt(),
(height * 0.5).toInt(),
0f,
width.toFloat()
)
.setDuration(300),
ObjectAnimator.ofArgb(icon, "colorFilter", iconColor, iconColorActive)
.apply {
duration = 150
startDelay = 100
},
ObjectAnimator.ofFloat(icon, "scaleX", 1.2f).apply {
duration = 200
},
ObjectAnimator.ofFloat(icon, "scaleY", 1.2f).apply {
duration = 200
}
)
}.start()
} else {
AnimatorSet().also {
it.playTogether(
ViewAnimationUtils.createCircularReveal(
backdrop,
(width - 28 * dp).toInt(),
(height * 0.5).toInt(),
width.toFloat(),
0f
).apply {
doOnEnd {
if (!rightThreshold && !leftThreshold) backdrop.setBackgroundColor(0)
}
duration = 300
},
ObjectAnimator.ofArgb(icon, "colorFilter", iconColorActive, iconColor)
.apply {
duration = 150
startDelay = 100
},
ObjectAnimator.ofFloat(icon, "scaleX", 1f).apply {
duration = 200
},
ObjectAnimator.ofFloat(icon, "scaleY", 1f).apply {
duration = 200
}
)
}.start()
}
field = value
}
private var swipeDirectionLeft: Boolean? = null
set(value) {
if (field == value) return
backdrop.setBackgroundColor(0)
if (value == true) {
leftAction?.icon?.let { icon.setImageResource(it) }
icon.setPadding((16 * dp).toInt(), 0, 0, 0)
icon.layoutParams = (icon.layoutParams as LayoutParams).also {
it.gravity = Gravity.CENTER_VERTICAL or Gravity.START
}
icon.pivotX = 28 * dp
} else if (value == false) {
rightAction?.icon?.let { icon.setImageResource(it) }
icon.setPadding(0, 0, (16 * dp).toInt(), 0)
icon.layoutParams = (icon.layoutParams as LayoutParams).also {
it.gravity = Gravity.CENTER_VERTICAL or Gravity.END
}
icon.pivotX = 12 * dp
}
field = value
}
override fun setCardBackgroundColor(color: Int) {
content.setCardBackgroundColor(color)
}
override fun setCardBackgroundColor(color: ColorStateList?) {
content.setCardBackgroundColor(color)
}
override fun addView(child: View?) {
content.addView(child)
}
override fun addView(child: View?, params: ViewGroup.LayoutParams?) {
content.addView(child, params)
}
override fun addView(child: View?, width: Int, height: Int) {
content.addView(child, width, height)
}
override fun setRadius(radius: Float) {
super.setRadius(radius)
content?.radius = radius
}
override fun removeAllViews() {
content.removeAllViews()
}
override fun removeAllViewsInLayout() {
content.removeAllViewsInLayout()
}
override fun removeView(view: View?) {
content.removeView(view)
}
override fun removeViewAt(index: Int) {
content.removeViewAt(index)
}
override fun removeViewInLayout(view: View?) {
content.removeViewInLayout(view)
}
override fun removeViews(start: Int, count: Int) {
content.removeViews(start, count)
}
override fun removeViewsInLayout(start: Int, count: Int) {
content.removeViewsInLayout(start, count)
}
private var downX = 0f
private var downY = 0f
private var isClick = false
private var isLongClick = false
private val longClickRunnable = Runnable {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
performLongClick()
isClick = false
isLongClick = true
content.foreground?.state = intArrayOf(android.R.attr.state_enabled)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return when (event?.action) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downY = event.y
isClick = true
isLongClick = false
handler?.postDelayed(
longClickRunnable,
ViewConfiguration.getLongPressTimeout().toLong()
)
content.foreground?.setHotspot(event.x, event.y)
content.foreground?.state =
intArrayOf(android.R.attr.state_pressed, android.R.attr.state_enabled)
true
}
MotionEvent.ACTION_MOVE -> {
if (abs(event.x - downX) > abs(event.y - downY)) parent.requestDisallowInterceptTouchEvent(
true
)
if (isLongClick) return false
swipeDirectionLeft = event.x - downX > 0
if (isClick && abs(event.x - downX) < 4 * dp) {
return true
}
isClick = false
handler?.removeCallbacks(longClickRunnable)
content.translationX = event.x - downX
leftThreshold = content.translationX > 0.5f * width
rightThreshold = content.translationX < -0.5f * width
content.foreground?.state = intArrayOf(android.R.attr.state_enabled)
true
}
MotionEvent.ACTION_UP -> {
when {
isClick -> {
performClick()
content.foreground?.state = intArrayOf(android.R.attr.state_enabled)
return false
}
leftThreshold -> {
if (leftAction?.action?.invoke() == true) {
content.animate().translationX(width.toFloat())
.setDuration(200)
.setInterpolator(AccelerateInterpolator())
.start()
} else {
content.animate().translationX(0f)
.setDuration(300)
.start()
}
}
rightThreshold -> {
if (rightAction?.action?.invoke() == true) {
content.animate().translationX(-width.toFloat())
.setDuration(200)
.setInterpolator(AccelerateInterpolator())
.start()
} else {
content.animate().translationX(0f)
.setDuration(300)
.start()
}
}
else -> {
content.animate().translationX(0f).setDuration(300).start()
}
}
true
}
MotionEvent.ACTION_CANCEL -> {
content.animate().translationX(0f).setDuration(300).start()
handler?.removeCallbacks(longClickRunnable)
content.foreground?.state = intArrayOf(android.R.attr.state_enabled)
false
}
MotionEvent.ACTION_OUTSIDE -> {
handler?.removeCallbacks(longClickRunnable)
content.foreground?.state = intArrayOf(android.R.attr.state_enabled)
true
}
else -> false
}
}
private val dataStore: LauncherDataStore by inject()
private var job: Job? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
job?.cancel()
job = lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
dataStore.data.map { it.cards.radius }.distinctUntilChanged().collectLatest {
radius = it * dp
}
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
job?.cancel()
}
open class SwipeAction(
@DrawableRes var icon: Int,
var color: Int,
/**
* Action that is performed after a swipe.
* returns true if the card should be animated out or false if it should be animated back.
*/
var action: () -> Boolean
)
}
class FavoriteSwipeAction(val context: Context, val searchable: Searchable) :
SwipeCardView.SwipeAction(
R.drawable.ic_star_solid,
ContextCompat.getColor(context, R.color.amber),
{ false }
), KoinComponent {
private val repository: FavoritesRepository by inject()
private val pinned = repository.isPinned(searchable)
.asLiveData((context as AppCompatActivity).lifecycleScope.coroutineContext)
init {
pinned.observe(context as LifecycleOwner) {
setPinned(it)
}
}
private fun setPinned(pinned: Boolean) {
if (pinned) {
icon = R.drawable.ic_star_outline
action = {
repository.unpinItem(
searchable
)
false
}
} else {
icon = R.drawable.ic_star_solid
action = {
repository.pinItem(
searchable
)
false
}
}
}
}
class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeCardView.SwipeAction(
R.drawable.ic_visibility_off,
ContextCompat.getColor(context, R.color.blue),
{ false }
), KoinComponent {
private val repository: FavoritesRepository by inject()
private val hidden = repository.isPinned(searchable)
.asLiveData((context as AppCompatActivity).lifecycleScope.coroutineContext)
init {
hidden.observe(context as LifecycleOwner) {
setHidden(it)
}
}
private fun setHidden(hidden: Boolean) {
if (hidden) {
icon = R.drawable.ic_visibility
action = {
repository.unhideItem(
searchable
)
true
}
} else {
icon = R.drawable.ic_visibility_off
action = {
repository.hideItem(
searchable
)
true
}
}
}
}

View File

@ -1,303 +0,0 @@
package de.mm20.launcher2.ui.legacy.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.TooltipCompat
import androidx.core.view.setPadding
import androidx.lifecycle.*
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class ToolbarView : LinearLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(
context,
attrs,
defStyleRes
)
private val slots = context.resources.getInteger(R.integer.config_toolbarSlots)
private var leftOverflowIcon: ImageView? = null
private var rightOverflowIcon: ImageView? = null
private val leftActions = mutableListOf<ToolbarAction>()
private val rightActions = mutableListOf<ToolbarAction>()
var iconStyle = R.style.LauncherTheme_IconStyle
init {
orientation = HORIZONTAL
clipChildren = false
clipToPadding = false
val spacer = View(context)
spacer.layoutParams = LayoutParams(0, LayoutParams.MATCH_PARENT, 1f)
addView(spacer)
}
fun addAction(action: ToolbarAction, placement: Int) {
val useOverflowMenu = (leftActions.size >= slots && placement == PLACEMENT_START) ||
(rightActions.size >= slots && placement == PLACEMENT_END)
if (useOverflowMenu) {
if (placement == PLACEMENT_START) {
if (leftOverflowIcon == null) {
val overflowMenuIcon = ImageView(context, null, R.attr.iconStyle)
overflowMenuIcon.isClickable = true
overflowMenuIcon.isFocusable = true
overflowMenuIcon.setPadding((12 * dp).toInt())
overflowMenuIcon.layoutParams =
LayoutParams((48 * dp).toInt(), (48 * dp).toInt())
overflowMenuIcon.setImageResource(R.drawable.ic_more_vert)
removeViewAt(leftActions.size - 1)
addView(overflowMenuIcon, leftActions.size - 1)
leftOverflowIcon = overflowMenuIcon
}
leftActions.add(action)
val popup = PopupMenu(context, leftOverflowIcon!!)
for (i in slots - 1 until leftActions.size) {
if (leftActions[i].subActions.isNotEmpty()) {
val submenu = popup.menu.addSubMenu(leftActions[i].title)
for ((j, sa) in leftActions[i].subActions.withIndex()) {
submenu.add(i, j, 0, sa.title)
}
} else {
popup.menu.add(i, 0, 0, leftActions[i].title)
}
}
popup.setOnMenuItemClickListener {
if (leftActions[it.groupId].subActions.isEmpty()) {
leftActions[it.groupId].clickAction?.invoke()
} else {
leftActions[it.groupId].subActions[it.itemId].clickAction.invoke()
}
true
}
leftOverflowIcon?.setOnClickListener {
popup.show()
}
} else {
if (rightOverflowIcon == null) {
val overflowMenuIcon = ImageView(context, null, R.attr.iconStyle)
overflowMenuIcon.isClickable = true
overflowMenuIcon.isFocusable = true
overflowMenuIcon.setPadding((12 * dp).toInt())
overflowMenuIcon.layoutParams =
LayoutParams((48 * dp).toInt(), (48 * dp).toInt())
overflowMenuIcon.setImageResource(R.drawable.ic_more_vert)
removeViewAt(childCount - 1)
addView(overflowMenuIcon)
rightOverflowIcon = overflowMenuIcon
}
rightActions.add(action)
val popup = PopupMenu(context, rightOverflowIcon!!)
for (i in slots - 1 until rightActions.size) {
if (rightActions[i].subActions.isNotEmpty()) {
val submenu = popup.menu.addSubMenu(i, -1, 0, rightActions[i].title)
for ((j, sa) in rightActions[i].subActions.withIndex()) {
submenu.add(i, j, 0, sa.title)
}
} else {
val item = popup.menu.add(i, -1, 0, rightActions[i].title)
rightActions[i].titleChanged = {
item.title = rightActions[i].title
}
}
}
popup.setOnMenuItemClickListener {
if (rightActions[it.groupId].subActions.isEmpty()) {
rightActions[it.groupId].clickAction?.invoke()
} else if (it.itemId != -1) {
rightActions[it.groupId].subActions[it.itemId].clickAction.invoke()
}
true
}
rightOverflowIcon?.setOnClickListener {
popup.show()
}
}
} else {
val imageView = getIconView(action)
if (placement == PLACEMENT_START) {
addView(imageView, leftActions.size)
leftActions.add(action)
} else {
addView(imageView)
rightActions.add(action)
}
}
}
private fun getIconView(action: ToolbarAction): ImageView {
val imageView = ImageView(context, null, 0, iconStyle)
imageView.setImageResource(action.icon)
imageView.isClickable = true
imageView.isFocusable = true
action.iconChanged = {
imageView.setImageResource(action.icon)
}
action.titleChanged = {
TooltipCompat.setTooltipText(imageView, action.title)
}
TooltipCompat.setTooltipText(imageView, action.title)
val submenu =
if (action.subActions.isEmpty()) null else PopupMenu(context, imageView).apply {
for ((i, subAction) in action.subActions.withIndex()) {
menu.add(0, i, 0, subAction.title)
}
setOnMenuItemClickListener {
action.subActions[it.itemId].clickAction.invoke()
true
}
}
imageView.setOnClickListener { _ ->
if (submenu != null) {
submenu.show()
} else {
action.clickAction?.invoke()
}
}
imageView.setPadding((12 * dp).toInt())
imageView.layoutParams = LayoutParams((48 * dp).toInt(), (48 * dp).toInt())
return imageView
}
fun clear() {
removeAllViews()
leftActions.clear()
rightActions.clear()
leftOverflowIcon = null
rightOverflowIcon = null
val spacer = View(context)
spacer.layoutParams = LayoutParams(0, LayoutParams.MATCH_PARENT, 1f)
addView(spacer)
}
companion object {
val PLACEMENT_START = 0
val PLACEMENT_END = 1
}
}
open class ToolbarAction(icon: Int, title: String) {
@DrawableRes
var icon: Int = icon
set(@DrawableRes value) {
field = value
iconChanged?.invoke()
}
var title = title
set(value) {
field = value
titleChanged?.invoke()
}
var subActions: MutableList<ToolbarSubaction> = mutableListOf()
var clickAction: (() -> Unit)? = null
internal var iconChanged: (() -> Unit)? = null
internal var titleChanged: (() -> Unit)? = null
}
open class ToolbarSubaction(val title: String, var clickAction: (() -> Unit)) {
}
class FavoriteToolbarAction(val context: Context, val item: Searchable) : ToolbarAction(
R.drawable.ic_star_outline,
context.getString(R.string.menu_favorites_pin)
), KoinComponent {
private val repository: FavoritesRepository by inject()
private var isPinned = false
set(value) {
field = value
if (value) {
title = context.getString(R.string.menu_favorites_unpin)
icon = R.drawable.ic_star_solid
} else {
title = context.getString(R.string.menu_favorites_pin)
icon = R.drawable.ic_star_outline
}
}
init {
clickAction = {
if (isPinned) {
repository.unpinItem(item)
} else {
repository.pinItem(item)
}
}
(context as LifecycleOwner).apply {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
repository.isPinned(item).collectLatest {
isPinned = it
}
}
}
}
}
}
class VisibilityToolbarAction(val context: Context, val item: Searchable) : ToolbarAction(
R.drawable.ic_visibility,
context.getString(R.string.menu_hide)
), KoinComponent {
private val repository: FavoritesRepository by inject()
private var isHidden = false
set(value) {
field = value
if (value) {
title = context.getString(R.string.menu_unhide)
icon = R.drawable.ic_visibility
} else {
title = context.getString(R.string.menu_hide)
icon = R.drawable.ic_visibility_off
}
}
init {
clickAction = {
if (isHidden) {
repository.unhideItem(item)
} else {
repository.hideItem(item)
}
}
(context as LifecycleOwner).apply {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
repository.isHidden(item).collectLatest {
isHidden = it
}
}
}
}
}
}