Spracherkennung für: .kt vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
package ch.threema.app.ui
import android.app.Activity
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager.BadTokenException
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.PopupWindow
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
import androidx.core.view.isVisible
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import ch.threema.app.R
import ch.threema.app.emojis.EmojiTextView
import ch.threema.app.preference.service.PreferenceService
import ch.threema.app.utils.ConfigUtils
import ch.threema.app.utils.TestUtil
import com.google.android.material.card.MaterialCardView
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class TooltipPopup
@JvmOverloads
constructor(
private val context: Context,
@StringRes
private val preferenceKey: Int = 0,
lifecycleOwner: LifecycleOwner? = null,
@DrawableRes
private val icon: Int = 0,
private val showCloseButton: Boolean = true,
) : PopupWindow(context), DefaultLifecycleObserver, KoinComponent {
private val preferenceService: PreferenceService by inject()
private var popupLayout: View
private var titleView: EmojiTextView
private var textView: EmojiTextView
private var timeoutHandler: Handler? = null
private val dismissRunnable = Runnable {
listener.onTimedOut(this)
dismiss(false)
}
var listener: TooltipPopupListener = TooltipPopupListener()
init {
lifecycleOwner?.lifecycle?.addObserver(this)
val layoutInflater = context.getSystemService<LayoutInflater>()!!
popupLayout = layoutInflater.inflate(R.layout.popup_tooltip, null, false)!!
titleView = popupLayout.findViewById(R.id.title)
textView = popupLayout.findViewById(R.id.label)
contentView = popupLayout
inputMethodMode = INPUT_METHOD_NOT_NEEDED
animationStyle = R.style.TooltipAnimation
isFocusable = false
isTouchable = true
isOutsideTouchable = false
setBackgroundDrawable(ColorDrawable())
popupLayout.setOnClickListener {
listener.onClicked(this)
}
popupLayout.findViewById<View>(R.id.close_button)
?.let { closeButton ->
closeButton.isVisible = showCloseButton
closeButton.setOnClickListener {
listener.onCloseButtonClicked(this)
}
}
}
fun dismissForever() {
if (preferenceKey != 0) {
preferenceService.setTooltipPopupDismissed(preferenceKey, true)
}
dismiss(false)
}
fun dismiss(immediate: Boolean) {
if (immediate) {
animationStyle = 0
}
timeoutHandler?.removeCallbacks(dismissRunnable)
timeoutHandler = null
this.dismiss()
listener.onDismissed(this)
}
/**
* Show a tooltip at the specified location pointing to a specified anchor view
*
* @param activity Activity context
* @param anchor Anchor / parent view to of this tooltip
* @param title Optional title text to show in tooltip
* @param text Text to show in tooltip
* @param alignment Where to align the tooltip and where the arrow should be shown
* @param originLocation The location on screen where the tip of the arrow should point to
* @param timeoutMs How long the tooltip should be shown until it fades out
*/
fun show(
activity: Activity,
anchor: View,
title: String? = null,
text: String?,
alignment: Alignment,
originLocation: IntArray,
timeoutMs: Int = 0,
) {
if (isForeverDismissed() || TestUtil.isInDeviceTest()) {
return
}
if (title != null) {
titleView.text = title
titleView.isVisible = true
} else {
titleView.isVisible = false
}
textView.text = text
val resources = context.resources
val screenHeight: Int
val screenWidth: Int
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowMetrics = activity.windowManager.currentWindowMetrics
val rect = windowMetrics.bounds
screenWidth = rect.right
screenHeight = rect.bottom
} else {
screenWidth = resources.displayMetrics.widthPixels
screenHeight = resources.displayMetrics.heightPixels + ConfigUtils.getNavigationBarHeight(activity)
}
val maxWidth = resources.getDimensionPixelSize(R.dimen.tooltip_max_width)
val arrowInset = resources.getDimensionPixelSize(R.dimen.tooltip_popup_arrow_inset)
val marginOnOtherEdge = resources.getDimensionPixelSize(R.dimen.tooltip_margin_on_other_edge)
val arrowOffset = (resources.getDimensionPixelSize(R.dimen.identity_popup_arrow_width) / 2) + arrowInset
var popupX: Int
val popupY: Int
val popupWidth: Int
val anchorGravity: Int
val contentGravity: Int
when (alignment) {
Alignment.ABOVE_ANCHOR_ARROW_LEFT -> {
popupLayout.findViewById<View>(R.id.arrow_bottom_left).isVisible = true
popupX = (originLocation[0] - arrowOffset).coerceAtLeast(0)
popupY = screenHeight - originLocation[1]
popupWidth = (screenWidth - popupX - marginOnOtherEdge).coerceAtMost(maxWidth)
anchorGravity = Gravity.LEFT or Gravity.BOTTOM
contentGravity = Gravity.LEFT
}
Alignment.ABOVE_ANCHOR_ARROW_RIGHT -> {
popupLayout.findViewById<View>(R.id.arrow_bottom_right).isVisible = true
popupX = (originLocation[0] + arrowOffset).coerceAtMost(screenWidth)
popupY = screenHeight - originLocation[1]
popupWidth = (popupX - marginOnOtherEdge).coerceAtMost(maxWidth)
popupX -= popupWidth
anchorGravity = Gravity.LEFT or Gravity.BOTTOM
contentGravity = Gravity.RIGHT
}
Alignment.BELOW_ANCHOR_ARROW_LEFT -> {
popupLayout.findViewById<View>(R.id.arrow_top_left).isVisible = true
popupX = (originLocation[0] - arrowOffset).coerceAtLeast(0)
popupY = originLocation[1]
popupWidth = (screenWidth - popupX - marginOnOtherEdge).coerceAtMost(maxWidth)
anchorGravity = Gravity.LEFT or Gravity.TOP
contentGravity = Gravity.LEFT
}
Alignment.BELOW_ANCHOR_ARROW_RIGHT -> {
popupLayout.findViewById<View>(R.id.arrow_top_right).isVisible = true
popupX = (originLocation[0] + arrowOffset).coerceAtMost(screenWidth)
popupY = originLocation[1]
popupWidth = (popupX - marginOnOtherEdge).coerceAtMost(maxWidth)
popupX -= popupWidth
anchorGravity = Gravity.LEFT or Gravity.TOP
contentGravity = Gravity.RIGHT
}
}
this.width = popupWidth
this.height = ViewGroup.LayoutParams.WRAP_CONTENT
val contentLayout = popupLayout.findViewById<MaterialCardView>(R.id.content)
val params = contentLayout.layoutParams as FrameLayout.LayoutParams
params.gravity = contentGravity
contentLayout.layoutParams = params
if (activity.isFinishing || activity.isDestroyed) {
return
}
try {
showAtLocation(anchor, anchorGravity, popupX, popupY)
} catch (e: BadTokenException) {
return
}
val iconView = popupLayout.findViewById<ImageView>(R.id.icon)
if (icon != 0) {
iconView.setImageResource(icon)
iconView.isVisible = true
} else {
iconView.isVisible = false
}
listener.onShown(this)
if (timeoutMs > 0) {
if (timeoutHandler == null) {
timeoutHandler = Handler(Looper.getMainLooper())
}
timeoutHandler?.removeCallbacks(dismissRunnable)
timeoutHandler?.postDelayed(dismissRunnable, timeoutMs.toLong())
}
}
private fun isForeverDismissed(): Boolean {
if (preferenceKey == 0) {
return false
}
return preferenceService.isTooltipPopupDismissed(preferenceKey)
}
/**
* Notifies that `ON_PAUSE` event occurred.
*
* This method will be called before the [LifecycleOwner]'s `onPause` method
* is called.
*
* @param owner the component, whose state was changed
*/
override fun onPause(owner: LifecycleOwner) {
dismiss(true)
}
open class TooltipPopupListener {
open fun onShown(tooltipPopup: TooltipPopup) {}
open fun onClicked(tooltipPopup: TooltipPopup) {
tooltipPopup.dismissForever()
}
open fun onCloseButtonClicked(tooltipPopup: TooltipPopup) {
tooltipPopup.dismissForever()
}
open fun onTimedOut(tooltipPopup: TooltipPopup) {}
open fun onDismissed(tooltipPopup: TooltipPopup) {}
}
enum class Alignment {
ABOVE_ANCHOR_ARROW_LEFT,
ABOVE_ANCHOR_ARROW_RIGHT,
BELOW_ANCHOR_ARROW_LEFT,
BELOW_ANCHOR_ARROW_RIGHT,
}
}
[Dauer der Verarbeitung: 0.19 Sekunden, vorverarbeitet 2026-04-27]