Remove unused legacy views
This commit is contained in:
parent
956af82f79
commit
537cde60a5
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user