diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/data/InformationText.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/data/InformationText.kt deleted file mode 100644 index bea8e211..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/data/InformationText.kt +++ /dev/null @@ -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)) - ) - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/transition/LauncherCards.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/transition/LauncherCards.kt deleted file mode 100644 index 307d8513..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/transition/LauncherCards.kt +++ /dev/null @@ -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" - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/AspectRationImageView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/AspectRationImageView.kt deleted file mode 100644 index 27130fd4..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/AspectRationImageView.kt +++ /dev/null @@ -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()) - } - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt deleted file mode 100644 index 82c722b6..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/InnerCardView.kt +++ /dev/null @@ -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() - } - -} diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt deleted file mode 100644 index 6d4a79cd..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherCardView.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt deleted file mode 100644 index 9b09a2f6..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt +++ /dev/null @@ -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 - } - } - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt deleted file mode 100644 index 2635d343..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt +++ /dev/null @@ -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() - private val rightActions = mutableListOf() - - 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 = 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 - } - } - } - } - } - - -} \ No newline at end of file