package ch.threema.app.ui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.PopupWindow;
import org.koin.java.KoinJavaComponent;
import org.slf4j.Logger;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import ch.threema.app.R;
import ch.threema.app.qrcodes.ContactUrlUtil;
import ch.threema.app.qrcodes.QrCodeGenerator;
import ch.threema.app.services.UserService;
import ch.threema.app.utils.AnimationUtil;
import static ch.threema.base.utils.LoggingKt.getThreemaLogger;
public class QRCodePopup extends DimmingPopupWindow implements DefaultLifecycleObserver {
private static final Logger logger = getThreemaLogger("QRCodePopup" );
private ImageView imageView;
private View topLayout;
private View parentView;
private final int [] location = new int [2];
private @Nullable Bitmap bitmap;
public QRCodePopup(Context context, View parentView, LifecycleOwner lifecycleOwner) {
super (context);
if (lifecycleOwner != null ) {
lifecycleOwner.getLifecycle().addObserver(this );
}
init(context, parentView);
}
private void init(@NonNull Context context, View parentView) {
this .parentView = parentView;
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
topLayout = layoutInflater.inflate(R.layout.popup_qrcode, null , true );
this .imageView = topLayout.findViewById(R.id.thumbnail_view);
setContentView(topLayout);
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
setBackgroundDrawable(new BitmapDrawable());
setAnimationStyle(0);
setElevation(0);
setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
}
/**
* Show a popup with a QR code
*
* @param sourceView starting point for animation
* @param text text to display as QR code, or null to display a QR code for the user's identity and public key
*/
public void show(@NonNull final View sourceView, @Nullable String text) {
QrCodeGenerator qrCodeGenerator = KoinJavaComponent.get(QrCodeGenerator.class );
if (text != null ) {
bitmap = qrCodeGenerator.generateWithUnicodeSupport(text);
} else {
UserService userService = KoinJavaComponent.get(UserService.class );
ContactUrlUtil contactUrlUtil = KoinJavaComponent.get(ContactUrlUtil.class );
var content = contactUrlUtil.generate(
userService.getIdentity(),
userService.getPublicKey()
);
bitmap = qrCodeGenerator.generate(content);
}
if (bitmap == null ) {
logger.debug("Unable to get qr code bitmap" );
return ;
}
final BitmapDrawable bitmapDrawable = new BitmapDrawable(getContext().getResources(), bitmap);
bitmapDrawable.setFilterBitmap(false );
this .imageView.setImageDrawable(bitmapDrawable);
showAtLocation(parentView, Gravity.CENTER, 0, 0);
dimBackground();
getContentView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getContentView().getViewTreeObserver().removeOnGlobalLayoutListener(this );
AnimationUtil.getViewCenter(sourceView, getContentView(), location);
AnimationSet animation = new AnimationSet(true );
Animation scale = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, Animation.ABSOLUTE, location[0], Animation.ABSOLUTE, location[1]);
Animation fade = new AlphaAnimation(0.0f, 1.0f);
animation.addAnimation(scale);
animation.addAnimation(fade);
animation.setInterpolator(new DecelerateInterpolator());
animation.setDuration(150);
getContentView().startAnimation(animation);
}
});
topLayout.setOnClickListener(v -> dismiss());
}
@Override
public void dismiss() {
AnimationUtil.popupAnimateOut(getContentView(), () -> {
QRCodePopup.super .dismiss();
if (bitmap != null ) {
bitmap.recycle();
bitmap = null ;
}
});
}
/**
* Notifies that {@code ON_PAUSE} event occurred.
* <p>
* This method will be called before the {@link LifecycleOwner}'s {@code onPause} method
* is called.
*
* @param owner the component, whose state was changed
*/
@Override
public void onPause(@NonNull LifecycleOwner owner) {
QRCodePopup.super .dismiss();
}
}
Messung V0.5 in Prozent C=95 H=96 G=95
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-04-27)
¤
*© Formatika GbR, Deutschland