package org.telegram.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; import android.provider.Settings; import android.os.Vibrator; import android.text.TextUtils; import android.util.Property; import android.util.TypedValue; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.OvershootInterpolator; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.ListUpdateCallback; import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AccountInstance; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLocation; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.messenger.voip.VoIPBaseService; import org.telegram.messenger.voip.VoIPService; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.ActionBarMenuSubItem; import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Cells.GroupCallInvitedCell; import org.telegram.ui.Cells.GroupCallTextCell; import org.telegram.ui.Cells.GroupCallUserCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AnimationProperties; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.BlobDrawable; import org.telegram.ui.Components.CheckBoxSquare; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.EditTextBoldCursor; import org.telegram.ui.Components.FillLastLinearLayoutManager; import org.telegram.ui.Components.GroupVoipInviteAlert; import org.telegram.ui.Components.GroupCallPip; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RLottieDrawable; import org.telegram.ui.Components.RLottieImageView; import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.ShareAlert; import org.telegram.ui.Components.UndoView; import org.telegram.ui.Components.WaveDrawable; import org.telegram.ui.Components.voip.VoIPToggleButton; import java.util.ArrayList; import java.util.Locale; public class GroupCallActivity extends BottomSheet implements NotificationCenter.NotificationCenterDelegate, VoIPBaseService.StateListener { private static final int eveyone_can_speak_item = 1; private static final int admin_can_speak_item = 2; private static final int share_invite_link_item = 3; private static final int leave_item = 4; private static final int start_record = 5; private static final int MUTE_BUTTON_STATE_UNMUTE = 0; private static final int MUTE_BUTTON_STATE_MUTE = 1; private static final int MUTE_BUTTON_STATE_MUTED_BY_ADMIN = 2; private static final int MUTE_BUTTON_STATE_CONNECTING = 3; public static GroupCallActivity groupCallInstance; public static boolean groupCallUiVisible; private AccountInstance accountInstance; private View actionBarBackground; private ActionBar actionBar; private ListAdapter listAdapter; private RecyclerListView listView; private FillLastLinearLayoutManager layoutManager; private VoIPToggleButton soundButton; private VoIPToggleButton leaveButton; private RLottieImageView muteButton; private TextView[] muteLabel = new TextView[2]; private TextView[] muteSubLabel = new TextView[2]; private FrameLayout buttonsContainer; private RadialProgressView radialProgressView; private Drawable shadowDrawable; private View actionBarShadow; private AnimatorSet actionBarAnimation; private LaunchActivity parentActivity; private UndoView[] undoView = new UndoView[2]; private ShareAlert shareAlert; private boolean delayedGroupCallUpdated; private RectF rect = new RectF(); private boolean enterEventSent; private boolean anyEnterEventSent; private float scrollOffsetY; private boolean scrolling; private TLRPC.TL_groupCallParticipant selfDummyParticipant; private Paint listViewBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private ArrayList oldParticipants = new ArrayList<>(); private ArrayList oldInvited = new ArrayList<>(); private int oldCount; private RLottieDrawable bigMicDrawable; private final BlobDrawable tinyWaveDrawable; private final BlobDrawable bigWaveDrawable; private float amplitude; private float animateToAmplitude; private float animateAmplitudeDiff; private RadialGradient radialGradient; private final Matrix radialMatrix; private final Paint radialPaint; private ValueAnimator muteButtonAnimator; public TLRPC.Chat currentChat; public ChatObject.Call call; private RecordCallDrawable recordCallDrawable; private TextView titleTextView; private ActionBarMenuItem otherItem; private ActionBarMenuItem pipItem; private ActionBarMenuSubItem inviteItem; private ActionBarMenuSubItem recordItem; private ActionBarMenuSubItem everyoneItem; private ActionBarMenuSubItem adminItem; private ActionBarMenuSubItem leaveItem; private View dividerItem; private final LinearLayout menuItemsContainer; private GroupVoipInviteAlert groupVoipInviteAlert; private int muteButtonState = MUTE_BUTTON_STATE_UNMUTE; private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); private Paint paintTmp = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); private Paint leaveBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private WeavingState[] states = new WeavingState[4]; private float switchProgress = 1.0f; private WeavingState prevState; private WeavingState currentState; private long lastUpdateTime; private int shaderBitmapSize = 200; private float showWavesProgress; private float showLightingProgress; private boolean scheduled; private boolean pressed; private int currentCallState; private float colorProgress; private int backgroundColor; private boolean invalidateColors = true; private final int[] colorsTmp = new int[3]; private Runnable unmuteRunnable = () -> VoIPService.getSharedInstance().setMicMute(false, true, false); private Runnable pressRunnable = () -> { if (!scheduled || VoIPService.getSharedInstance() == null) { return; } muteButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); updateMuteButton(MUTE_BUTTON_STATE_MUTE, true); AndroidUtilities.runOnUIThread(unmuteRunnable, 80); scheduled = false; pressed = true; }; public static final Property COLOR_PROGRESS = new AnimationProperties.FloatProperty("colorProgress") { @Override public void setValue(GroupCallActivity object, float value) { object.setColorProgress(value); } @Override public Float get(GroupCallActivity object) { return object.getColorProgress(); } }; private static class RecordCallDrawable extends Drawable { private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG); private long lastUpdateTime; private float alpha = 1.0f; private int state; private boolean recording; private View parentView; public RecordCallDrawable() { super(); paint.setColor(0xffffffff); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(AndroidUtilities.dp(1.5f)); } public void setParentView(View view) { parentView = view; } @Override public int getIntrinsicWidth() { return AndroidUtilities.dp(24); } @Override public int getIntrinsicHeight() { return AndroidUtilities.dp(24); } public boolean isRecording() { return recording; } public void setRecording(boolean value) { recording = value; alpha = 1.0f; invalidateSelf(); } @Override public void draw(Canvas canvas) { int cx = getBounds().centerX(); int cy = getBounds().centerY(); canvas.drawCircle(cx, cy, AndroidUtilities.dp(10), paint); paint2.setColor(recording ? 0xffEE7D79 : 0xffffffff); paint2.setAlpha((int) (255 * alpha)); canvas.drawCircle(cx, cy, AndroidUtilities.dp(5), paint2); if (recording) { long newTime = SystemClock.elapsedRealtime(); long dt = newTime - lastUpdateTime; if (dt > 17) { dt = 17; } lastUpdateTime = newTime; if (state == 0) { alpha += dt / 500.0f; if (alpha >= 1.0f) { alpha = 1.0f; state = 1; } } else if (state == 1) { alpha -= dt / 500.0f; if (alpha < 0.5f) { alpha = 0.5f; state = 0; } } parentView.invalidate(); } } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter colorFilter) { } @Override public int getOpacity() { return PixelFormat.TRANSPARENT; } } private class VolumeSlider extends FrameLayout { private RLottieImageView imageView; private TextView textView; private TLRPC.TL_groupCallParticipant currentParticipant; private RLottieDrawable speakerDrawable; private boolean captured; private float sx, sy; private int thumbX; private double currentProgress; private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG); private Path path = new Path(); private float[] radii = new float[8]; private RectF rect = new RectF(); private int currentColor; private int oldColor; private float colorChangeProgress; private long lastUpdateTime; private float[] volumeAlphas = new float[3]; public VolumeSlider(Context context, TLRPC.TL_groupCallParticipant participant) { super(context); setWillNotDraw(false); currentParticipant = participant; currentProgress = ChatObject.getParticipantVolume(participant) / 20000.0f; colorChangeProgress = 1.0f; setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); speakerDrawable = new RLottieDrawable(R.raw.speaker, "" + R.raw.speaker, AndroidUtilities.dp(24), AndroidUtilities.dp(24), true, null); imageView = new RLottieImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setAnimation(speakerDrawable); imageView.setTag(currentProgress == 0 ? 1 : null); addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 40, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 0, 0, 0, 0)); speakerDrawable.setCustomEndFrame(currentProgress == 0 ? 17 : 34); speakerDrawable.setCurrentFrame(speakerDrawable.getCustomEndFrame() - 1, false, true); textView = new TextView(context); textView.setLines(1); textView.setSingleLine(true); textView.setGravity(Gravity.LEFT); textView.setEllipsize(TextUtils.TruncateAt.END); textView.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); double vol = ChatObject.getParticipantVolume(currentParticipant) / 100.0; textView.setText(String.format(Locale.US, "%d%%", (int) (vol > 0 ? Math.max(vol, 1) : 0))); textView.setPadding(LocaleController.isRTL ? 0 : AndroidUtilities.dp(43), 0, LocaleController.isRTL ? AndroidUtilities.dp(43) : 0, 0); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL)); paint2.setStyle(Paint.Style.STROKE); paint2.setStrokeWidth(AndroidUtilities.dp(1.5f)); paint2.setStrokeCap(Paint.Cap.ROUND); paint2.setColor(0xffffffff); int percent = (int) (ChatObject.getParticipantVolume(currentParticipant) / 100.0); for (int a = 0; a < volumeAlphas.length; a++) { int p; if (a == 0) { p = 0; } else if (a == 1) { p = 50; } else { p = 150; } if (percent > p) { volumeAlphas[a] = 1.0f; } else { volumeAlphas[a] = 0.0f; } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), View.MeasureSpec.EXACTLY)); thumbX = (int) (MeasureSpec.getSize(widthMeasureSpec) * currentProgress); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return onTouch(ev); } @Override public boolean onTouchEvent(MotionEvent event) { return onTouch(event); } boolean onTouch(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { sx = ev.getX(); sy = ev.getY(); return true; } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { captured = false; if (ev.getAction() == MotionEvent.ACTION_UP) { final ViewConfiguration vc = ViewConfiguration.get(getContext()); if (Math.abs(ev.getY() - sy) < vc.getScaledTouchSlop()) { thumbX = (int) ev.getX(); if (thumbX < 0) { thumbX = 0; } else if (thumbX > getMeasuredWidth()) { thumbX = getMeasuredWidth(); } pressed = true; } } if (pressed) { if (ev.getAction() == MotionEvent.ACTION_UP) { onSeekBarDrag(thumbX / (double) getMeasuredWidth(), true); } pressed = false; invalidate(); return true; } } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { if (!captured) { final ViewConfiguration vc = ViewConfiguration.get(getContext()); if (Math.abs(ev.getY() - sy) > vc.getScaledTouchSlop()) { return false; } if (Math.abs(ev.getX() - sx) > vc.getScaledTouchSlop()) { captured = true; getParent().requestDisallowInterceptTouchEvent(true); if (ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) { thumbX = (int) ev.getX(); if (thumbX < 0) { thumbX = 0; } else if (thumbX > getMeasuredWidth()) { thumbX = getMeasuredWidth(); } pressed = true; invalidate(); return true; } } } else { if (pressed) { thumbX = (int) ev.getX(); if (thumbX < 0) { thumbX = 0; } else if (thumbX > getMeasuredWidth()) { thumbX = getMeasuredWidth(); } onSeekBarDrag(thumbX / (double) getMeasuredWidth(), false); invalidate(); return true; } } } return false; } private void onSeekBarDrag(double progress, boolean finalMove) { if (VoIPService.getSharedInstance() == null) { return; } currentProgress = progress; currentParticipant.volume = (int) (progress * 20000); currentParticipant.flags |= 128; double vol = ChatObject.getParticipantVolume(currentParticipant) / 100.0; textView.setText(String.format(Locale.US, "%d%%", (int) (vol > 0 ? Math.max(vol, 1) : 0))); VoIPService.getSharedInstance().setParticipantVolume(currentParticipant.source, currentParticipant.volume); if (finalMove) { TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(currentParticipant.user_id); if (currentParticipant.volume == 0) { scrimPopupWindow.dismiss(); scrimPopupWindow = null; scrimPopupWindowItems = null; processSelectedOption(currentParticipant, currentParticipant.user_id, ChatObject.canManageCalls(currentChat) ? 0 : 5); } else { VoIPService.getSharedInstance().editCallMember(user, false, currentParticipant.volume); } } Integer newTag = currentProgress == 0 ? 1 : null; if (imageView.getTag() == null && newTag != null || imageView.getTag() != null && newTag == null) { speakerDrawable.setCustomEndFrame(currentProgress == 0 ? 17 : 34); speakerDrawable.setCurrentFrame(currentProgress == 0 ? 0 : 17); speakerDrawable.start(); imageView.setTag(newTag); } } @Override protected void onDraw(Canvas canvas) { int prevColor = currentColor; if (currentProgress < 0.25f) { currentColor = 0xffCC5757; } else if (currentProgress > 0.25f && currentProgress < 0.5f) { currentColor = 0xffC9A53B; } else if (currentProgress >= 0.5f && currentProgress <= 0.75f) { currentColor = 0xff57BC6B; } else { currentColor = 0xff4DA6DF; } int color = AndroidUtilities.getOffsetColor(oldColor, prevColor, colorChangeProgress, 1.0f); if (prevColor != 0 && prevColor != currentColor) { colorChangeProgress = 0.0f; oldColor = color; } paint.setColor(color); long newTime = SystemClock.elapsedRealtime(); long dt = newTime - lastUpdateTime; if (dt > 17) { dt = 17; } lastUpdateTime = newTime; if (colorChangeProgress < 1.0f) { colorChangeProgress += dt / 200.0f; if (colorChangeProgress > 1.0f) { colorChangeProgress = 1.0f; } else { invalidate(); } } path.reset(); radii[0] = radii[1] = radii[6] = radii[7] = AndroidUtilities.dp(6); float rad = thumbX < AndroidUtilities.dp(12) ? Math.max(0, (thumbX - AndroidUtilities.dp(6)) / (float) AndroidUtilities.dp(6)) : 1.0f; radii[2] = radii[3] = radii[4] = radii[5] = AndroidUtilities.dp(6) * rad; rect.set(0, 0, thumbX, getMeasuredHeight()); path.addRoundRect(rect, radii, Path.Direction.CW); path.close(); canvas.drawPath(path, paint); int percent = (int) (ChatObject.getParticipantVolume(currentParticipant) / 100.0); int cx = imageView.getLeft() + imageView.getMeasuredWidth() / 2 + AndroidUtilities.dp(5); int cy = imageView.getTop() + imageView.getMeasuredHeight() / 2; for (int a = 0; a < volumeAlphas.length; a++) { int p; if (a == 0) { p = 0; rad = AndroidUtilities.dp(6); } else if (a == 1) { p = 50; rad = AndroidUtilities.dp(10); } else { p = 150; rad = AndroidUtilities.dp(14); } float offset = (AndroidUtilities.dp(2) * (1.0f - volumeAlphas[a])); paint2.setAlpha((int) (255 * volumeAlphas[a])); rect.set(cx - rad + offset, cy - rad + offset, cx + rad - offset, cy + rad - offset); canvas.drawArc(rect, -50, 100, false, paint2); if (percent > p) { if (volumeAlphas[a] < 1.0f) { volumeAlphas[a] += dt / 180.0f; if (volumeAlphas[a] > 1.0f) { volumeAlphas[a] = 1.0f; } invalidate(); } } else { if (volumeAlphas[a] > 0.0f) { volumeAlphas[a] -= dt / 180.0f; if (volumeAlphas[a] < 0.0f) { volumeAlphas[a] = 0.0f; } invalidate(); } } } } } private class WeavingState { private float targetX = -1f; private float targetY = -1f; private float startX; private float startY; private float duration; private float time; private Shader shader; private Matrix matrix = new Matrix(); private int currentState; public WeavingState(int state) { currentState = state; } public void update(int top, int left, int size, long dt) { if (shader == null) { return; } if (duration == 0 || time >= duration) { duration = Utilities.random.nextInt(700) + 500; time = 0; if (targetX == -1f) { setTarget(); } startX = targetX; startY = targetY; setTarget(); } time += dt * (0.5f + BlobDrawable.GRADIENT_SPEED_MIN) + dt * (BlobDrawable.GRADIENT_SPEED_MAX * 2) * amplitude; if (time > duration) { time = duration; } float interpolation = CubicBezierInterpolator.EASE_OUT.getInterpolation(time / duration); float x = left + size * (startX + (targetX - startX) * interpolation) - 200; float y = top + size * (startY + (targetY - startY) * interpolation) - 200; float s; if (currentState == MUTE_BUTTON_STATE_MUTED_BY_ADMIN) { s = 1f; } else { s = currentState == MUTE_BUTTON_STATE_MUTE ? 4 : 2.5f; } float scale = AndroidUtilities.dp(122) / 400.0f * s; matrix.reset(); matrix.postTranslate(x, y); matrix.postScale(scale, scale, x + 200, y + 200); shader.setLocalMatrix(matrix); } private void setTarget() { if (currentState == MUTE_BUTTON_STATE_MUTED_BY_ADMIN) { targetX = 0.85f + 0.25f * Utilities.random.nextInt(100) / 100f; targetY = 1f; } else if (currentState == MUTE_BUTTON_STATE_MUTE) { targetX = 0.2f + 0.3f * Utilities.random.nextInt(100) / 100f; targetY = 0.7f + 0.3f * Utilities.random.nextInt(100) / 100f; } else { targetX = 0.8f + 0.2f * (Utilities.random.nextInt(100) / 100f); targetY = Utilities.random.nextInt(100) / 100f; } } } @SuppressWarnings("FieldCanBeLocal") private static class LabeledButton extends FrameLayout { private ImageView imageView; private TextView textView; public LabeledButton(Context context, String text, int resId, int color) { super(context); imageView = new ImageView(context); if (Build.VERSION.SDK_INT >= 21) { imageView.setBackground(Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(50), color, 0x1fffffff)); } else { imageView.setBackground(Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(50), color, color)); } imageView.setImageResource(resId); imageView.setScaleType(ImageView.ScaleType.CENTER); addView(imageView, LayoutHelper.createFrame(50, 50, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); textView = new TextView(context); textView.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); textView.setGravity(Gravity.CENTER_HORIZONTAL); textView.setText(text); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 50 + 5, 0, 0)); } public void setColor(int color) { Theme.setSelectorDrawableColor(imageView.getBackground(), color, false); if (Build.VERSION.SDK_INT < 21) { Theme.setSelectorDrawableColor(imageView.getBackground(), color, true); } imageView.invalidate(); } } @Override protected boolean canDismissWithSwipe() { return false; } @Override protected boolean onCustomOpenAnimation() { groupCallUiVisible = true; NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.groupCallVisibilityChanged); GroupCallPip.updateVisibility(getContext()); return super.onCustomOpenAnimation(); } @Override public void dismiss() { groupCallUiVisible = false; if (groupVoipInviteAlert != null) { groupVoipInviteAlert.dismiss(); } delayedGroupCallUpdated = true; NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.groupCallVisibilityChanged); accountInstance.getNotificationCenter().removeObserver(this, NotificationCenter.needShowAlert); accountInstance.getNotificationCenter().removeObserver(this, NotificationCenter.groupCallUpdated); accountInstance.getNotificationCenter().removeObserver(this, NotificationCenter.chatInfoDidLoad); accountInstance.getNotificationCenter().removeObserver(this, NotificationCenter.didLoadChatAdmins); accountInstance.getNotificationCenter().removeObserver(this, NotificationCenter.applyGroupCallVisibleParticipants); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.webRtcMicAmplitudeEvent); NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didEndCall); super.dismiss(); } @Override public void didReceivedNotification(int id, int account, Object... args) { if (id == NotificationCenter.groupCallUpdated) { Long callId = (Long) args[1]; if (call != null && call.call.id == callId) { if (call.call instanceof TLRPC.TL_groupCallDiscarded) { dismiss(); } else { updateItems(); for (int a = 0, N = listView.getChildCount(); a < N; a++) { View child = listView.getChildAt(a); if (child instanceof GroupCallUserCell) { ((GroupCallUserCell) child).applyParticipantChanges(true); } } if (scrimView != null) { delayedGroupCallUpdated = true; } else { applyCallParticipantUpdates(); } if (actionBar != null) { int count = call.call.participants_count + (listAdapter.addSelfToCounter() ? 1 : 0); actionBar.setSubtitle(LocaleController.formatPluralString("Members", count)); } updateState(true, (Boolean) args[2]); } } } else if (id == NotificationCenter.webRtcMicAmplitudeEvent) { float amplitude = (float) args[0]; setAmplitude(amplitude * 4000.0f); if (listView != null) { TLRPC.TL_groupCallParticipant participant = call.participants.get(accountInstance.getUserConfig().getClientUserId()); if (participant != null) { ArrayList array = delayedGroupCallUpdated ? oldParticipants : call.sortedParticipants; int idx = array.indexOf(participant); if (idx >= 0) { RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(idx + listAdapter.usersStartRow); if (holder != null && holder.itemView instanceof GroupCallUserCell) { GroupCallUserCell cell = (GroupCallUserCell) holder.itemView; cell.setAmplitude(amplitude * 15.0f); if (holder.itemView == scrimView) { containerView.invalidate(); } } } } } } else if (id == NotificationCenter.needShowAlert) { int num = (Integer) args[0]; if (num == 6) { String text = (String) args[1]; String error; if ("ANONYMOUS_CALLS_DISABLED".equals(text) || "GROUPCALL_ANONYMOUS_FORBIDDEN".equals(text)) { error = LocaleController.getString("VoipGroupJoinAnonymousAdmin", R.string.VoipGroupJoinAnonymousAdmin); } else { error = LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + text; } AlertDialog.Builder builder = AlertsCreator.createSimpleAlert(getContext(), LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat), error); builder.setOnDismissListener(dialog -> dismiss()); try { builder.show(); } catch (Exception e) { FileLog.e(e); } } } else if (id == NotificationCenter.didEndCall) { if (VoIPService.getSharedInstance() == null) { dismiss(); } } else if (id == NotificationCenter.chatInfoDidLoad) { TLRPC.ChatFull chatFull = (TLRPC.ChatFull) args[0]; if (chatFull.id == currentChat.id) { updateItems(); updateState(isShowing(), false); } } else if (id == NotificationCenter.didLoadChatAdmins) { int chatId = (Integer) args[0]; if (chatId == currentChat.id) { updateItems(); updateState(isShowing(), false); } } else if (id == NotificationCenter.applyGroupCallVisibleParticipants) { int count = listView.getChildCount(); long time = (Long) args[0]; for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); RecyclerView.ViewHolder holder = listView.findContainingViewHolder(child); if (holder != null && holder.itemView instanceof GroupCallUserCell) { GroupCallUserCell cell = (GroupCallUserCell) holder.itemView; cell.getParticipant().lastVisibleDate = time; } } } } private void applyCallParticipantUpdates() { int count = listView.getChildCount(); View minChild = null; int minPosition = 0; for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); RecyclerView.ViewHolder holder = listView.findContainingViewHolder(child); if (holder != null && holder.getAdapterPosition() != RecyclerView.NO_POSITION) { if (minChild == null || minPosition > holder.getAdapterPosition()) { minChild = child; minPosition = holder.getAdapterPosition(); } } } try { UpdateCallback updateCallback = new UpdateCallback(listAdapter); setOldRows(listAdapter.addMemberRow, listAdapter.selfUserRow, listAdapter.usersStartRow, listAdapter.usersEndRow, listAdapter.invitedStartRow, listAdapter.invitedEndRow); listAdapter.updateRows(); DiffUtil.calculateDiff(diffUtilsCallback).dispatchUpdatesTo(updateCallback); } catch (Exception e) { FileLog.e(e); listAdapter.notifyDataSetChanged(); } call.saveActiveDates(); if (minChild != null) { FileLog.d("scroll to " + minPosition + " top = " + (minChild.getTop() - listView.getPaddingTop())); layoutManager.scrollToPositionWithOffset(minPosition, minChild.getTop() - listView.getPaddingTop()); } oldParticipants.clear(); oldParticipants.addAll(call.sortedParticipants); oldInvited.clear(); oldInvited.addAll(call.invitedUsers); oldCount = listAdapter.getItemCount(); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); if (child instanceof GroupCallUserCell || child instanceof GroupCallInvitedCell) { RecyclerView.ViewHolder holder = listView.findContainingViewHolder(child); if (holder != null) { if (child instanceof GroupCallUserCell) { ((GroupCallUserCell) child).setDrawDivider(holder.getAdapterPosition() != listAdapter.getItemCount() - 2); } else { ((GroupCallInvitedCell) child).setDrawDivider(holder.getAdapterPosition() != listAdapter.getItemCount() - 2); } } } } } private void updateItems() { TLRPC.Chat newChat = accountInstance.getMessagesController().getChat(currentChat.id); if (newChat != null) { currentChat = newChat; } boolean anyVisible = false; if (ChatObject.canUserDoAdminAction(currentChat, ChatObject.ACTION_INVITE)) { inviteItem.setVisibility(View.VISIBLE); anyVisible = true; } else { inviteItem.setVisibility(View.GONE); } if (ChatObject.canManageCalls(currentChat)) { leaveItem.setVisibility(View.VISIBLE); anyVisible = true; } else { leaveItem.setVisibility(View.GONE); } if (ChatObject.canManageCalls(currentChat) && call.call.can_change_join_muted) { everyoneItem.setVisibility(View.VISIBLE); adminItem.setVisibility(View.VISIBLE); dividerItem.setVisibility(View.VISIBLE); anyVisible = true; } else { everyoneItem.setVisibility(View.GONE); adminItem.setVisibility(View.GONE); dividerItem.setVisibility(View.GONE); } otherItem.setVisibility(anyVisible ? View.VISIBLE : View.GONE); ((FrameLayout.LayoutParams) menuItemsContainer.getLayoutParams()).rightMargin = anyVisible ? 0 : AndroidUtilities.dp(6); actionBar.setTitleRightMargin(AndroidUtilities.dp(48) * (anyVisible ? 2 : 1)); } protected void makeFocusable(EditTextBoldCursor editText, boolean showKeyboard) { if (!enterEventSent) { BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); if (fragment instanceof ChatActivity) { boolean keyboardVisible = ((ChatActivity) fragment).needEnterText(); enterEventSent = true; anyEnterEventSent = true; AndroidUtilities.runOnUIThread(() -> { if (groupVoipInviteAlert != null) { groupVoipInviteAlert.setFocusable(true); editText.requestFocus(); if (showKeyboard) { AndroidUtilities.runOnUIThread(() -> AndroidUtilities.showKeyboard(editText)); } } }, keyboardVisible ? 200 : 0); } else { enterEventSent = true; anyEnterEventSent = true; groupVoipInviteAlert.setFocusable(true); editText.requestFocus(); if (showKeyboard) { AndroidUtilities.runOnUIThread(() -> AndroidUtilities.showKeyboard(editText)); } } } } public static void create(LaunchActivity activity, AccountInstance account) { if (groupCallInstance != null || VoIPService.getSharedInstance() == null) { return; } ChatObject.Call call = VoIPService.getSharedInstance().groupCall; if (call == null) { return; } TLRPC.Chat chat = account.getMessagesController().getChat(call.chatId); if (chat == null) { return; } groupCallInstance = new GroupCallActivity(activity, account, call, chat); groupCallInstance.parentActivity = activity; groupCallInstance.show(); } public GroupCallActivity(Context context, AccountInstance account, ChatObject.Call call, TLRPC.Chat chat) { super(context, false); this.accountInstance = account; this.call = call; this.currentChat = chat; this.currentAccount = account.getCurrentAccount(); drawNavigationBar = true; scrollNavBar = true; navBarColorKey = null; //useLightNavBar = true; scrimPaint = new Paint() { @Override public void setAlpha(int a) { super.setAlpha(a); if (containerView != null) { containerView.invalidate(); } } }; setOnDismissListener(dialog -> { BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); if (anyEnterEventSent) { if (fragment instanceof ChatActivity) { ((ChatActivity) fragment).onEditTextDialogClose(true); } } }); setDimBehindAlpha(75); oldParticipants.addAll(call.sortedParticipants); oldInvited.addAll(call.invitedUsers); selfDummyParticipant = new TLRPC.TL_groupCallParticipant(); selfDummyParticipant.user_id = accountInstance.getUserConfig().getClientUserId(); selfDummyParticipant.muted = true; selfDummyParticipant.can_self_unmute = true; selfDummyParticipant.date = accountInstance.getConnectionsManager().getCurrentTime(); currentCallState = VoIPService.getSharedInstance().getCallState(); VoIPService.audioLevelsCallback = (uids, levels, voice) -> { for (int a = 0; a < uids.length; a++) { TLRPC.TL_groupCallParticipant participant = call.participantsBySources.get(uids[a]); if (participant != null) { ArrayList array = delayedGroupCallUpdated ? oldParticipants : call.sortedParticipants; int idx = array.indexOf(participant); if (idx >= 0) { RecyclerView.ViewHolder holder = listView.findViewHolderForAdapterPosition(idx + listAdapter.usersStartRow); if (holder != null && holder.itemView instanceof GroupCallUserCell) { GroupCallUserCell cell = (GroupCallUserCell) holder.itemView; cell.setAmplitude(levels[a] * 15.0f); if (holder.itemView == scrimView) { containerView.invalidate(); } } } } } }; accountInstance.getNotificationCenter().addObserver(this, NotificationCenter.groupCallUpdated); accountInstance.getNotificationCenter().addObserver(this, NotificationCenter.needShowAlert); accountInstance.getNotificationCenter().addObserver(this, NotificationCenter.chatInfoDidLoad); accountInstance.getNotificationCenter().addObserver(this, NotificationCenter.didLoadChatAdmins); accountInstance.getNotificationCenter().addObserver(this, NotificationCenter.applyGroupCallVisibleParticipants); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.webRtcMicAmplitudeEvent); NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didEndCall); shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow_round).mutate(); bigMicDrawable = new RLottieDrawable(R.raw.voice_outlined, "" + R.raw.voice_outlined, AndroidUtilities.dp(28), AndroidUtilities.dp(38), true, null); containerView = new FrameLayout(context) { private boolean ignoreLayout = false; private RectF rect = new RectF(); @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int totalHeight = MeasureSpec.getSize(heightMeasureSpec); if (Build.VERSION.SDK_INT >= 21) { ignoreLayout = true; setPadding(backgroundPaddingLeft, AndroidUtilities.statusBarHeight, backgroundPaddingLeft, 0); ignoreLayout = false; } int availableHeight = totalHeight - getPaddingTop() - AndroidUtilities.dp(14 + 231); LayoutParams layoutParams = (LayoutParams) listView.getLayoutParams(); layoutParams.topMargin = ActionBar.getCurrentActionBarHeight() + AndroidUtilities.dp(14); layoutParams = (LayoutParams) actionBarShadow.getLayoutParams(); layoutParams.topMargin = ActionBar.getCurrentActionBarHeight(); int contentSize = Math.max(AndroidUtilities.dp(64 + 50 + 58 * 2.5f), availableHeight / 5 * 3); int padding = Math.max(0, availableHeight - contentSize + AndroidUtilities.dp(8)); if (listView.getPaddingTop() != padding) { ignoreLayout = true; listView.setPadding(0, padding, 0, 0); ignoreLayout = false; } super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); updateLayout(false); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY - AndroidUtilities.dp(37) && actionBar.getAlpha() == 0.0f) { dismiss(); return true; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent e) { return !isDismissed() && super.onTouchEvent(e); } @Override public void requestLayout() { if (ignoreLayout) { return; } super.requestLayout(); } @Override protected void onDraw(Canvas canvas) { int offset = AndroidUtilities.dp(74); float top = scrollOffsetY - offset; int height = getMeasuredHeight() + AndroidUtilities.dp(15) + backgroundPaddingTop; float rad = 1.0f; if (top + backgroundPaddingTop < ActionBar.getCurrentActionBarHeight()) { int willMoveUpTo = offset - backgroundPaddingTop - AndroidUtilities.dp(14); float moveProgress = Math.min(1.0f, (ActionBar.getCurrentActionBarHeight() - top - backgroundPaddingTop) / willMoveUpTo); int diff = (int) ((ActionBar.getCurrentActionBarHeight() - willMoveUpTo) * moveProgress); top -= diff; height += diff; rad = 1.0f - moveProgress; } top += getPaddingTop(); shadowDrawable.setBounds(0, (int) top, getMeasuredWidth(), height); shadowDrawable.draw(canvas); if (rad != 1.0f) { Theme.dialogs_onlineCirclePaint.setColor(backgroundColor); rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + AndroidUtilities.dp(24)); canvas.drawRoundRect(rect, AndroidUtilities.dp(12) * rad, AndroidUtilities.dp(12) * rad, Theme.dialogs_onlineCirclePaint); } int finalColor = Color.argb((int) (255 * actionBar.getAlpha()), (int) (Color.red(backgroundColor) * 0.8f), (int) (Color.green(backgroundColor) * 0.8f), (int) (Color.blue(backgroundColor) * 0.8f)); Theme.dialogs_onlineCirclePaint.setColor(finalColor); canvas.drawRect(backgroundPaddingLeft, 0, getMeasuredWidth() - backgroundPaddingLeft, AndroidUtilities.statusBarHeight, Theme.dialogs_onlineCirclePaint); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (scrimView != null) { canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), scrimPaint); float listTop = listView.getY(); boolean groupedBackgroundWasDraw = false; int count = listView.getChildCount(); for (int num = 0; num < count; num++) { View child = listView.getChildAt(num); if (child != scrimView) { continue; } float viewClipLeft = Math.max(listView.getLeft(), listView.getLeft() + child.getX()); float viewClipTop = Math.max(listTop, listView.getTop() + child.getY()); float viewClipRight = Math.min(listView.getRight(), listView.getLeft() + child.getX() + child.getMeasuredWidth()); float viewClipBottom = Math.min(listView.getY() + listView.getMeasuredHeight(), listView.getY() + child.getY() + child.getMeasuredHeight()); if (viewClipTop < viewClipBottom) { if (child.getAlpha() != 1f) { canvas.saveLayerAlpha(viewClipLeft, viewClipTop, viewClipRight, viewClipBottom, (int) (255 * child.getAlpha()), Canvas.ALL_SAVE_FLAG); } else { canvas.save(); } canvas.clipRect(viewClipLeft, viewClipTop, viewClipRight, viewClipBottom); canvas.translate(listView.getLeft() + child.getX(), listView.getY() + child.getY()); rect.set(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); canvas.drawRoundRect(rect, AndroidUtilities.dp(13), AndroidUtilities.dp(13), listViewBackgroundPaint); child.draw(canvas); canvas.restore(); } } } } }; containerView.setWillNotDraw(false); containerView.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); containerView.setKeepScreenOn(true); containerView.setClipChildren(false); listView = new RecyclerListView(context) { @Override public boolean drawChild(Canvas canvas, View child, long drawingTime) { if (child == scrimView) { return false; } return super.drawChild(canvas, child, drawingTime); } @Override protected void dispatchDraw(Canvas canvas) { float maxBottom = 0; float minTop = 0; for (int a = 0, N = getChildCount(); a < N; a++) { View child = getChildAt(a); ViewHolder holder = findContainingViewHolder(child); if (holder == null || holder.getItemViewType() == 3) { continue; } maxBottom = Math.max(maxBottom, child.getY() + child.getMeasuredHeight()); if (a == 0) { minTop = Math.max(0, child.getY()); } else { minTop = Math.min(minTop, Math.max(0, child.getY())); } } rect.set(0, minTop, getMeasuredWidth(), Math.min(getMeasuredHeight(), maxBottom)); canvas.drawRoundRect(rect, AndroidUtilities.dp(13), AndroidUtilities.dp(13), listViewBackgroundPaint); canvas.save(); canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight()); super.dispatchDraw(canvas); canvas.restore(); } }; listView.setClipToPadding(false); listView.setClipChildren(false); DefaultItemAnimator itemAnimator = new DefaultItemAnimator() { @Override protected void onMoveAnimationUpdate(RecyclerView.ViewHolder holder) { listView.invalidate(); updateLayout(true); } }; itemAnimator.setDelayAnimations(false); listView.setItemAnimator(itemAnimator); listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (listView.getChildCount() <= 0) { return; } if (!call.loadingMembers && !call.membersLoadEndReached && layoutManager.findLastVisibleItemPosition() > listAdapter.getItemCount() - 5) { call.loadMembers(false); } updateLayout(true); } @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { int offset = AndroidUtilities.dp(74); float top = scrollOffsetY - offset; if (top + backgroundPaddingTop < ActionBar.getCurrentActionBarHeight() && listView.canScrollVertically(1)) { View child = listView.getChildAt(0); RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findViewHolderForAdapterPosition(0); if (holder != null && holder.itemView.getTop() > 0) { listView.smoothScrollBy(0, holder.itemView.getTop()); } } } scrolling = newState == RecyclerView.SCROLL_STATE_DRAGGING; } }); listView.setVerticalScrollBarEnabled(false); listView.setLayoutManager(layoutManager = new FillLastLinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false, 0, listView)); layoutManager.setBind(false); containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 14, 14, 14, 231)); listView.setAdapter(listAdapter = new ListAdapter(context)); listView.setTopBottomSelectorRadius(13); listView.setSelectorDrawableColor(Theme.getColor(Theme.key_voipgroup_listSelector)); listView.setOnItemClickListener((view, position, x, y) -> { if (view instanceof GroupCallUserCell) { GroupCallUserCell cell = (GroupCallUserCell) view; if (cell.isSelfUser()) { return; } showMenuForCell(cell); } else if (view instanceof GroupCallInvitedCell) { GroupCallInvitedCell cell = (GroupCallInvitedCell) view; if (cell.getUser() == null) { return; } Bundle args = new Bundle(); args.putInt("user_id", cell.getUser().id); parentActivity.presentFragment(new ProfileActivity(args)); dismiss(); } else if (position == listAdapter.addMemberRow) { TLRPC.ChatFull chatFull = accountInstance.getMessagesController().getChatFull(currentChat.id); if (chatFull == null) { return; } enterEventSent = false; groupVoipInviteAlert = new GroupVoipInviteAlert(getContext(), accountInstance.getCurrentAccount(), currentChat, chatFull, call.participants, call.invitedUsersMap); groupVoipInviteAlert.setOnDismissListener(dialog -> groupVoipInviteAlert = null); groupVoipInviteAlert.setDelegate(new GroupVoipInviteAlert.GroupVoipInviteAlertDelegate() { @Override public void copyInviteLink() { getLink(true); } @Override public void inviteUser(int id) { inviteUserToCall(id, true); } @Override public void needOpenSearch(MotionEvent ev, EditTextBoldCursor editText) { if (!enterEventSent) { if (ev.getX() > editText.getLeft() && ev.getX() < editText.getRight() && ev.getY() > editText.getTop() && ev.getY() < editText.getBottom()) { makeFocusable(editText, true); } else { makeFocusable(editText, false); } } } }); groupVoipInviteAlert.show(); } }); listView.setOnItemLongClickListener((view, position) -> { if (view instanceof GroupCallUserCell) { updateItems(); GroupCallUserCell cell = (GroupCallUserCell) view; return cell.clickMuteButton(); } return false; }); buttonsContainer = new FrameLayout(context) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int cw = AndroidUtilities.dp(122); int w = (getMeasuredWidth() - cw) / 2; int h = getMeasuredHeight(); int x = (w - soundButton.getMeasuredWidth()) / 2; int y = (h - leaveButton.getMeasuredHeight()) / 2 - AndroidUtilities.dp(9); soundButton.layout(x, y, x + soundButton.getMeasuredWidth(), y + soundButton.getMeasuredHeight()); x = getMeasuredWidth() - w + (w - leaveButton.getMeasuredWidth()) / 2; leaveButton.layout(x, y, x + leaveButton.getMeasuredWidth(), y + leaveButton.getMeasuredHeight()); x = (getMeasuredWidth() - muteButton.getMeasuredWidth()) / 2; y = (h - muteButton.getMeasuredHeight()) / 2 - AndroidUtilities.dp(18); muteButton.layout(x, y, x + muteButton.getMeasuredWidth(), y + muteButton.getMeasuredHeight()); for (int a = 0; a < 2; a++) { x = (getMeasuredWidth() - muteLabel[a].getMeasuredWidth()) / 2; y = h - AndroidUtilities.dp(35) - muteLabel[a].getMeasuredHeight(); muteLabel[a].layout(x, y, x + muteLabel[a].getMeasuredWidth(), y + muteLabel[a].getMeasuredHeight()); x = (getMeasuredWidth() - muteSubLabel[a].getMeasuredWidth()) / 2; y = h - AndroidUtilities.dp(17) - muteSubLabel[a].getMeasuredHeight(); muteSubLabel[a].layout(x, y, x + muteSubLabel[a].getMeasuredWidth(), y + muteSubLabel[a].getMeasuredHeight()); } } final OvershootInterpolator overshootInterpolator = new OvershootInterpolator(1.5f); int currentLightColor; @SuppressLint("DrawAllocation") @Override protected void dispatchDraw(Canvas canvas) { int offset = (getMeasuredWidth() - getMeasuredHeight()) / 2; long newTime = SystemClock.elapsedRealtime(); long dt = newTime - lastUpdateTime; lastUpdateTime = newTime; if (dt > 20) { dt = 17; } if (currentState != null) { currentState.update(0, offset, getMeasuredHeight(), dt); } tinyWaveDrawable.minRadius = AndroidUtilities.dp(62); tinyWaveDrawable.maxRadius = AndroidUtilities.dp(62) + AndroidUtilities.dp(20) * BlobDrawable.FORM_SMALL_MAX; bigWaveDrawable.minRadius = AndroidUtilities.dp(65); bigWaveDrawable.maxRadius = AndroidUtilities.dp(65) + AndroidUtilities.dp(20) * BlobDrawable.FORM_BIG_MAX; if (animateToAmplitude != amplitude) { amplitude += animateAmplitudeDiff * dt; if (animateAmplitudeDiff > 0) { if (amplitude > animateToAmplitude) { amplitude = animateToAmplitude; } } else { if (amplitude < animateToAmplitude) { amplitude = animateToAmplitude; } } invalidate(); } boolean canSwitchProgress = true; if (prevState != null && prevState.currentState == MUTE_BUTTON_STATE_CONNECTING) { radialProgressView.toCircle(true, true); if (!radialProgressView.isCircle()) { canSwitchProgress = false; } } else if (prevState != null && currentState != null && currentState.currentState == MUTE_BUTTON_STATE_CONNECTING) { radialProgressView.toCircle(true, false); } if (canSwitchProgress) { if (switchProgress != 1f) { if (prevState != null && prevState.currentState == MUTE_BUTTON_STATE_CONNECTING) { switchProgress += dt / 100f; } else { switchProgress += dt / 180f; } if (switchProgress >= 1.0f) { switchProgress = 1f; prevState = null; if (currentState != null && currentState.currentState == MUTE_BUTTON_STATE_CONNECTING) { radialProgressView.toCircle(false, true); } } invalidateColors = true; invalidate(); } if (invalidateColors && currentState != null) { invalidateColors = false; int lightingColor; int soundButtonColor; int soundButtonColorChecked; if (prevState != null) { fillColors(prevState.currentState, colorsTmp); int oldLight = colorsTmp[0]; int oldSound = colorsTmp[1]; int oldSound2 = colorsTmp[2]; fillColors(currentState.currentState, colorsTmp); lightingColor = ColorUtils.blendARGB(oldLight, colorsTmp[0], switchProgress); soundButtonColorChecked = ColorUtils.blendARGB(oldSound, colorsTmp[1], switchProgress); soundButtonColor = ColorUtils.blendARGB(oldSound2, colorsTmp[2], switchProgress); } else { fillColors(currentState.currentState, colorsTmp); lightingColor = colorsTmp[0]; soundButtonColorChecked = colorsTmp[1]; soundButtonColor = colorsTmp[2]; } if (currentLightColor != lightingColor) { radialGradient = new RadialGradient(0, 0, AndroidUtilities.dp(100), new int[]{ColorUtils.setAlphaComponent(lightingColor, 60), ColorUtils.setAlphaComponent(lightingColor, 0)}, null, Shader.TileMode.CLAMP); radialPaint.setShader(radialGradient); currentLightColor = lightingColor; } soundButton.setBackgroundColor(soundButtonColor, soundButtonColorChecked); } boolean showWaves = false; boolean showLighting = false; if (currentState != null) { showWaves = currentState.currentState == MUTE_BUTTON_STATE_MUTE || currentState.currentState == MUTE_BUTTON_STATE_UNMUTE; showLighting = currentState.currentState != MUTE_BUTTON_STATE_CONNECTING; } if (prevState != null && currentState != null && currentState.currentState == MUTE_BUTTON_STATE_CONNECTING) { showWavesProgress -= dt / 180f; if (showWavesProgress < 0f) { showWavesProgress = 0f; } invalidate(); } else { if (showWaves && showWavesProgress != 1f) { showWavesProgress += dt / 350f; if (showWavesProgress > 1f) { showWavesProgress = 1f; } invalidate(); } else if (!showWaves && showWavesProgress != 0) { showWavesProgress -= dt / 350f; if (showWavesProgress < 0f) { showWavesProgress = 0f; } invalidate(); } } if (showLighting && showLightingProgress != 1f) { showLightingProgress += dt / 350f; if (showLightingProgress > 1f) { showLightingProgress = 1f; } invalidate(); } else if (!showLighting && showLightingProgress != 0) { showLightingProgress -= dt / 350f; if (showLightingProgress < 0f) { showLightingProgress = 0f; } invalidate(); } } float showWavesProgressInterpolated = overshootInterpolator.getInterpolation(GroupCallActivity.this.showWavesProgress); showWavesProgressInterpolated = 0.4f + 0.6f * showWavesProgressInterpolated; bigWaveDrawable.update(amplitude, 1f); tinyWaveDrawable.update(amplitude, 1f); if (prevState != null && currentState != null && (currentState.currentState == MUTE_BUTTON_STATE_CONNECTING || prevState.currentState == MUTE_BUTTON_STATE_CONNECTING)) { float progress; if (currentState.currentState == MUTE_BUTTON_STATE_CONNECTING) { progress = switchProgress; paint.setShader(prevState.shader); } else { progress = 1f - switchProgress; paint.setShader(currentState.shader); } paintTmp.setColor(AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_listViewBackgroundUnscrolled), Theme.getColor(Theme.key_voipgroup_disabledButton), colorProgress, 1.0f)); int cx = muteButton.getLeft() + muteButton.getMeasuredWidth() / 2; int cy = muteButton.getTop() + muteButton.getMeasuredHeight() / 2; radialMatrix.setTranslate(cx, cy); radialGradient.setLocalMatrix(radialMatrix); paint.setAlpha(76); float radius = AndroidUtilities.dp(52) / 2f; canvas.drawCircle(leaveButton.getX() + leaveButton.getMeasuredWidth() / 2f, leaveButton.getY() + radius, radius, leaveBackgroundPaint); canvas.save(); canvas.scale(BlobDrawable.GLOBAL_SCALE, BlobDrawable.GLOBAL_SCALE, cx, cy); canvas.save(); float scale = BlobDrawable.SCALE_BIG_MIN + BlobDrawable.SCALE_BIG * amplitude * 0.5f; canvas.scale(scale * showLightingProgress, scale * showLightingProgress, cx, cy); float scaleLight = 0.7f + BlobDrawable.LIGHT_GRADIENT_SIZE; canvas.save(); canvas.scale(scaleLight, scaleLight, cx, cy); canvas.drawCircle(cx, cy, AndroidUtilities.dp(160), radialPaint); canvas.restore(); canvas.restore(); canvas.save(); scale = BlobDrawable.SCALE_BIG_MIN + BlobDrawable.SCALE_BIG * amplitude; canvas.scale(scale * showWavesProgressInterpolated, scale * showWavesProgressInterpolated, cx, cy); bigWaveDrawable.draw(cx, cy, canvas, paint); canvas.restore(); canvas.save(); scale = BlobDrawable.SCALE_SMALL_MIN + BlobDrawable.SCALE_SMALL * amplitude; canvas.scale(scale * showWavesProgressInterpolated, scale * showWavesProgressInterpolated, cx, cy); tinyWaveDrawable.draw(cx, cy, canvas, paint); canvas.restore(); paint.setAlpha(255); if (canSwitchProgress) { canvas.drawCircle(cx, cy, AndroidUtilities.dp(57), paint); paint.setColor(Theme.getColor(Theme.key_voipgroup_connectingProgress)); if (progress != 0) { paint.setAlpha((int) (255 * progress)); paint.setShader(null); canvas.drawCircle(cx, cy, AndroidUtilities.dp(57), paint); } } canvas.drawCircle(cx, cy, AndroidUtilities.dp(55) * progress, paintTmp); if (!canSwitchProgress) { radialProgressView.draw(canvas, cx, cy); } canvas.restore(); invalidate(); } else { for (int i = 0; i < 2; i++) { float alpha; float buttonRadius = AndroidUtilities.dp(57); if (i == 0 && prevState != null) { paint.setShader(prevState.shader); alpha = 1f - switchProgress; if (prevState.currentState == MUTE_BUTTON_STATE_CONNECTING) { buttonRadius -= alpha * AndroidUtilities.dp(2); } } else if (i == 1) { paint.setShader(currentState.shader); alpha = switchProgress; if (currentState.currentState == MUTE_BUTTON_STATE_CONNECTING) { buttonRadius -= alpha * AndroidUtilities.dp(2); } } else { continue; } if (paint.getShader() == null) { paint.setColor(AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_listViewBackgroundUnscrolled), Theme.getColor(Theme.key_voipgroup_disabledButton), colorProgress, 1.0f)); } int cx = muteButton.getLeft() + muteButton.getMeasuredWidth() / 2; int cy = muteButton.getTop() + muteButton.getMeasuredHeight() / 2; radialMatrix.setTranslate(cx, cy); radialGradient.setLocalMatrix(radialMatrix); paint.setAlpha((int) (76 * alpha)); if (i == 1) { float radius = AndroidUtilities.dp(52) / 2f; canvas.drawCircle(leaveButton.getX() + leaveButton.getMeasuredWidth() / 2, leaveButton.getY() + radius, radius, leaveBackgroundPaint); } canvas.save(); canvas.scale(BlobDrawable.GLOBAL_SCALE, BlobDrawable.GLOBAL_SCALE, cx, cy); canvas.save(); float scale = BlobDrawable.SCALE_BIG_MIN + BlobDrawable.SCALE_BIG * amplitude * 0.5f; canvas.scale(scale * showLightingProgress, scale * showLightingProgress, cx, cy); if (i == 1) { float scaleLight = 0.7f + BlobDrawable.LIGHT_GRADIENT_SIZE; canvas.save(); canvas.scale(scaleLight, scaleLight, cx, cy); canvas.drawCircle(cx, cy, AndroidUtilities.dp(160), radialPaint); canvas.restore(); } canvas.restore(); canvas.save(); scale = BlobDrawable.SCALE_BIG_MIN + BlobDrawable.SCALE_BIG * amplitude; canvas.scale(scale * showWavesProgressInterpolated, scale * showWavesProgressInterpolated, cx, cy); bigWaveDrawable.draw(cx, cy, canvas, paint); canvas.restore(); canvas.save(); scale = BlobDrawable.SCALE_SMALL_MIN + BlobDrawable.SCALE_SMALL * amplitude; canvas.scale(scale * showWavesProgressInterpolated, scale * showWavesProgressInterpolated, cx, cy); tinyWaveDrawable.draw(cx, cy, canvas, paint); canvas.restore(); if (i == 0) { paint.setAlpha(255); } else { paint.setAlpha((int) (255 * alpha)); } canvas.drawCircle(cx, cy, buttonRadius, paint); canvas.restore(); if (i == 1 && currentState.currentState == MUTE_BUTTON_STATE_CONNECTING) { radialProgressView.draw(canvas, cx, cy); } } invalidate(); } super.dispatchDraw(canvas); } }; buttonsContainer.setWillNotDraw(false); containerView.addView(buttonsContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 231, Gravity.LEFT | Gravity.BOTTOM)); int color = Theme.getColor(Theme.key_voipgroup_unmuteButton2); int r = Color.red(color); int g = Color.green(color); int b = Color.blue(color); radialMatrix = new Matrix(); radialGradient = new RadialGradient(0, 0, AndroidUtilities.dp(160), new int[]{Color.argb(50, r, g, b), Color.argb(0, r, g, b)}, null, Shader.TileMode.CLAMP); radialPaint = new Paint(Paint.ANTI_ALIAS_FLAG); radialPaint.setShader(radialGradient); tinyWaveDrawable = new BlobDrawable(9); bigWaveDrawable = new BlobDrawable(12); tinyWaveDrawable.minRadius = AndroidUtilities.dp(62); tinyWaveDrawable.maxRadius = AndroidUtilities.dp(72); tinyWaveDrawable.generateBlob(); bigWaveDrawable.minRadius = AndroidUtilities.dp(65); bigWaveDrawable.maxRadius = AndroidUtilities.dp(75); bigWaveDrawable.generateBlob(); tinyWaveDrawable.paint.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_unmuteButton), (int) (255 * WaveDrawable.CIRCLE_ALPHA_2))); bigWaveDrawable.paint.setColor(ColorUtils.setAlphaComponent(Theme.getColor(Theme.key_voipgroup_unmuteButton), (int) (255 * WaveDrawable.CIRCLE_ALPHA_1))); soundButton = new VoIPToggleButton(context); soundButton.setCheckable(true); soundButton.setTextSize(12); buttonsContainer.addView(soundButton, LayoutHelper.createFrame(68, 90)); soundButton.setOnClickListener(v -> { if (VoIPService.getSharedInstance() == null) { return; } VoIPService.getSharedInstance().toggleSpeakerphoneOrShowRouteSheet(getContext(), false); }); leaveButton = new VoIPToggleButton(context); leaveButton.setDrawBackground(false); leaveButton.setTextSize(12); leaveButton.setData(R.drawable.calls_decline, 0xffffffff, Theme.getColor(Theme.key_voipgroup_leaveButton), 0.3f, false, LocaleController.getString("VoipGroupLeave", R.string.VoipGroupLeave), false, false); buttonsContainer.addView(leaveButton, LayoutHelper.createFrame(68, 80)); leaveButton.setOnClickListener(v -> { updateItems(); onLeaveClick(context, this::dismiss, false); }); muteButton = new RLottieImageView(context) { @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN && muteButtonState == MUTE_BUTTON_STATE_UNMUTE) { AndroidUtilities.runOnUIThread(pressRunnable, 300); scheduled = true; } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { if (scheduled) { AndroidUtilities.cancelRunOnUIThread(pressRunnable); scheduled = false; } else if (pressed) { AndroidUtilities.cancelRunOnUIThread(unmuteRunnable); updateMuteButton(MUTE_BUTTON_STATE_UNMUTE, true); if (VoIPService.getSharedInstance() != null) { VoIPService.getSharedInstance().setMicMute(true, true, false); muteButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } pressed = false; MotionEvent cancel = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); super.onTouchEvent(cancel); cancel.recycle(); return true; } } return super.onTouchEvent(event); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(Button.class.getName()); info.setEnabled(muteButtonState == MUTE_BUTTON_STATE_UNMUTE || muteButtonState == MUTE_BUTTON_STATE_MUTE); if (muteButtonState == MUTE_BUTTON_STATE_MUTE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { info.addAction(new AccessibilityNodeInfo.AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, LocaleController.getString("VoipMute", R.string.VoipMute))); } } }; muteButton.setAnimation(bigMicDrawable); muteButton.setScaleType(ImageView.ScaleType.CENTER); buttonsContainer.addView(muteButton, LayoutHelper.createFrame(122, 122, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); muteButton.setOnClickListener(v -> { if (VoIPService.getSharedInstance() == null || muteButtonState == MUTE_BUTTON_STATE_CONNECTING) { return; } if (muteButtonState == MUTE_BUTTON_STATE_MUTED_BY_ADMIN) { AndroidUtilities.shakeView(muteLabel[0], 2, 0); AndroidUtilities.shakeView(muteSubLabel[0], 2, 0); try { Vibrator vibrator = (Vibrator) parentActivity.getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) { vibrator.vibrate(200); } } catch (Exception e) { FileLog.e(e); } return; } if (muteButtonState == MUTE_BUTTON_STATE_UNMUTE) { updateMuteButton(MUTE_BUTTON_STATE_MUTE, true); VoIPService.getSharedInstance().setMicMute(false, false, true); muteButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } else { updateMuteButton(MUTE_BUTTON_STATE_UNMUTE, true); VoIPService.getSharedInstance().setMicMute(true, false, true); muteButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } }); radialProgressView = new RadialProgressView(context); radialProgressView.setSize(AndroidUtilities.dp(110)); radialProgressView.setStrokeWidth(4); radialProgressView.setProgressColor(Theme.getColor(Theme.key_voipgroup_connectingProgress)); //buttonsContainer.addView(radialProgressView, LayoutHelper.createFrame(126, 126, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); for (int a = 0; a < 2; a++) { muteLabel[a] = new TextView(context); muteLabel[a].setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); muteLabel[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); muteLabel[a].setGravity(Gravity.CENTER_HORIZONTAL); buttonsContainer.addView(muteLabel[a], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 0, 0, 26)); muteSubLabel[a] = new TextView(context); muteSubLabel[a].setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); muteSubLabel[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); muteSubLabel[a].setGravity(Gravity.CENTER_HORIZONTAL); buttonsContainer.addView(muteSubLabel[a], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 0, 0, 10)); if (a == 1) { muteLabel[a].setVisibility(View.INVISIBLE); muteSubLabel[a].setVisibility(View.INVISIBLE); } } actionBar = new ActionBar(context) { @Override public void setAlpha(float alpha) { super.setAlpha(alpha); containerView.invalidate(); } }; actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setOccupyStatusBar(false); actionBar.setAllowOverlayTitle(false); actionBar.setItemsColor(Theme.getColor(Theme.key_voipgroup_actionBarItems), false); actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), false); actionBar.setTitleColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); actionBar.setSubtitleColor(Theme.getColor(Theme.key_voipgroup_lastSeenTextUnscrolled)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { dismiss(); } else if (id == eveyone_can_speak_item) { call.call.join_muted = false; toggleAdminSpeak(); } else if (id == admin_can_speak_item) { call.call.join_muted = true; toggleAdminSpeak(); } else if (id == share_invite_link_item) { getLink(false); } else if (id == leave_item) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(LocaleController.getString("VoipGroupEndAlertTitle", R.string.VoipGroupEndAlertTitle)); builder.setMessage(LocaleController.getString("VoipGroupEndAlertText", R.string.VoipGroupEndAlertText)); builder.setPositiveButton(LocaleController.getString("VoipGroupEnd", R.string.VoipGroupEnd), (dialogInterface, i) -> { if (VoIPService.getSharedInstance() != null) { VoIPService.getSharedInstance().hangUp(1); } dismiss(); NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didStartedCall); }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); AlertDialog dialog = builder.create(); dialog.setBackgroundColor(Theme.getColor(Theme.key_voipgroup_dialogBackground)); dialog.show(); TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); if (button != null) { button.setTextColor(Theme.getColor(Theme.key_voipgroup_leaveCallMenu)); } dialog.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); } else if (id == start_record) { recordCallDrawable.setRecording(!recordCallDrawable.isRecording()); recordItem.setText(recordCallDrawable.isRecording() ? LocaleController.getString("VoipGroupStopRecordCall", R.string.VoipGroupStopRecordCall) : LocaleController.getString("VoipGroupRecordCall", R.string.VoipGroupRecordCall)); recordItem.setSubtext(recordCallDrawable.isRecording() ? "00:01:00" : null); } } }); actionBar.setAlpha(0.0f); actionBar.getBackButton().setScaleX(0.9f); actionBar.getBackButton().setScaleY(0.9f); actionBar.getBackButton().setTranslationX(-AndroidUtilities.dp(14)); actionBar.getTitleTextView().setTranslationY(AndroidUtilities.dp(23)); actionBar.getSubtitleTextView().setTranslationY(AndroidUtilities.dp(20)); otherItem = new ActionBarMenuItem(context, null, 0, Theme.getColor(Theme.key_voipgroup_actionBarItems)); otherItem.setLongClickEnabled(false); otherItem.setIcon(R.drawable.ic_ab_other); otherItem.setContentDescription(LocaleController.getString("AccDescrMoreOptions", R.string.AccDescrMoreOptions)); otherItem.setSubMenuOpenSide(2); otherItem.setDelegate(id -> actionBar.getActionBarMenuOnItemClick().onItemClick(id)); otherItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_voipgroup_actionBarItemsSelector), 6)); otherItem.setOnClickListener(v -> { updateItems(); if (call.call.join_muted) { everyoneItem.setColors(Theme.getColor(Theme.key_voipgroup_actionBarItems), Theme.getColor(Theme.key_voipgroup_actionBarItems)); everyoneItem.setChecked(false); adminItem.setColors(Theme.getColor(Theme.key_voipgroup_checkMenu), Theme.getColor(Theme.key_voipgroup_checkMenu)); adminItem.setChecked(true); } else { everyoneItem.setColors(Theme.getColor(Theme.key_voipgroup_checkMenu), Theme.getColor(Theme.key_voipgroup_checkMenu)); everyoneItem.setChecked(true); adminItem.setColors(Theme.getColor(Theme.key_voipgroup_actionBarItems), Theme.getColor(Theme.key_voipgroup_actionBarItems)); adminItem.setChecked(false); } otherItem.toggleSubMenu(); }); otherItem.setPopupItemsColor(Theme.getColor(Theme.key_voipgroup_actionBarItems), false); otherItem.setPopupItemsColor(Theme.getColor(Theme.key_voipgroup_actionBarItems), true); pipItem = new ActionBarMenuItem(context, null, 0, Theme.getColor(Theme.key_voipgroup_actionBarItems)); pipItem.setLongClickEnabled(false); pipItem.setIcon(R.drawable.msg_voice_pip); pipItem.setContentDescription(LocaleController.getString("AccDescrPipMode", R.string.AccDescrPipMode)); pipItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_voipgroup_actionBarItemsSelector), 6)); pipItem.setOnClickListener(v -> { if (Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(parentActivity)) { GroupCallPip.clearForce(); dismiss(); } else { AlertsCreator.createDrawOverlayGroupCallPermissionDialog(getContext()).show(); } }); titleTextView = new TextView(context); titleTextView.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); titleTextView.setGravity(Gravity.LEFT | Gravity.TOP); titleTextView.setText(LocaleController.getString("VoipGroupVoiceChat", R.string.VoipGroupVoiceChat)); actionBarBackground = new View(context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), ActionBar.getCurrentActionBarHeight()); } }; actionBarBackground.setAlpha(0.0f); containerView.addView(actionBarBackground, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0)); containerView.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 23, 0, 48, 0)); containerView.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 0)); menuItemsContainer = new LinearLayout(context); menuItemsContainer.setOrientation(LinearLayout.HORIZONTAL); menuItemsContainer.addView(pipItem, LayoutHelper.createLinear(48, 48)); menuItemsContainer.addView(otherItem, LayoutHelper.createLinear(48, 48)); containerView.addView(menuItemsContainer, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.RIGHT)); actionBarShadow = new View(context); actionBarShadow.setAlpha(0.0f); actionBarShadow.setBackgroundColor(Theme.getColor(Theme.key_dialogShadowLine)); containerView.addView(actionBarShadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1)); for (int a = 0; a < 2; a++) { undoView[a] = new UndoView(context); undoView[a].setAdditionalTranslationY(AndroidUtilities.dp(10)); if (Build.VERSION.SDK_INT >= 21) { undoView[a].setTranslationZ(AndroidUtilities.dp(5)); } containerView.addView(undoView[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 8, 0, 8, 8)); } everyoneItem = otherItem.addSubItem(eveyone_can_speak_item, 0, LocaleController.getString("VoipGroupAllCanSpeak", R.string.VoipGroupAllCanSpeak), true); adminItem = otherItem.addSubItem(admin_can_speak_item, 0, LocaleController.getString("VoipGroupOnlyAdminsCanSpeak", R.string.VoipGroupOnlyAdminsCanSpeak), true); everyoneItem.setCheckColor(Theme.getColor(Theme.key_voipgroup_checkMenu)); everyoneItem.setColors(Theme.getColor(Theme.key_voipgroup_checkMenu), Theme.getColor(Theme.key_voipgroup_checkMenu)); adminItem.setCheckColor(Theme.getColor(Theme.key_voipgroup_checkMenu)); adminItem.setColors(Theme.getColor(Theme.key_voipgroup_checkMenu), Theme.getColor(Theme.key_voipgroup_checkMenu)); dividerItem = otherItem.addDivider(Theme.getColor(Theme.key_voipgroup_listViewBackground)); recordCallDrawable = new RecordCallDrawable(); recordItem = otherItem.addSubItem(start_record, 0, recordCallDrawable, LocaleController.getString("VoipGroupRecordCall", R.string.VoipGroupRecordCall), false); recordCallDrawable.setParentView(recordItem.getImageView()); inviteItem = otherItem.addSubItem(share_invite_link_item, R.drawable.msg_link, LocaleController.getString("VoipGroupShareInviteLink", R.string.VoipGroupShareInviteLink)); leaveItem = otherItem.addSubItem(leave_item, R.drawable.msg_endcall, LocaleController.getString("VoipGroupEndChat", R.string.VoipGroupEndChat)); otherItem.setPopupItemsSelectorColor(Theme.getColor(Theme.key_voipgroup_listSelector)); leaveItem.setColors(Theme.getColor(Theme.key_voipgroup_leaveCallMenu), Theme.getColor(Theme.key_voipgroup_leaveCallMenu)); inviteItem.setColors(Theme.getColor(Theme.key_voipgroup_actionBarItems), Theme.getColor(Theme.key_voipgroup_actionBarItems)); recordItem.setColors(Theme.getColor(Theme.key_voipgroup_actionBarItems), Theme.getColor(Theme.key_voipgroup_actionBarItems)); recordItem.setVisibility(View.GONE); listAdapter.notifyDataSetChanged(); oldCount = listAdapter.getItemCount(); actionBar.setTitle(currentChat.title); actionBar.setSubtitle(LocaleController.formatPluralString("Participants", call.call.participants_count + (listAdapter.addSelfToCounter() ? 1 : 0))); actionBar.setTitleRightMargin(AndroidUtilities.dp(48) * 2); call.saveActiveDates(); VoIPService.getSharedInstance().registerStateListener(this); updateItems(); updateSpeakerPhoneIcon(false); updateState(false, false); setColorProgress(0.0f); leaveBackgroundPaint.setColor(Theme.getColor(Theme.key_voipgroup_leaveButton)); } @Override public void dismissInternal() { super.dismissInternal(); if (VoIPService.getSharedInstance() != null) { VoIPService.getSharedInstance().unregisterStateListener(this); } if (groupCallInstance == this) { groupCallInstance = null; } groupCallUiVisible = false; VoIPService.audioLevelsCallback = null; GroupCallPip.updateVisibility(getContext()); } public final static float MAX_AMPLITUDE = 8_500f; private void setAmplitude(double value) { animateToAmplitude = (float) (Math.min(MAX_AMPLITUDE, value) / MAX_AMPLITUDE); animateAmplitudeDiff = (animateToAmplitude - amplitude) / (100 + 500.0f * BlobDrawable.AMPLITUDE_SPEED); } @Override public void onStateChanged(int state) { currentCallState = state; updateState(isShowing(), false); } private UndoView getUndoView() { if (undoView[0].getVisibility() == View.VISIBLE) { UndoView old = undoView[0]; undoView[0] = undoView[1]; undoView[1] = old; old.hide(true, 2); containerView.removeView(undoView[0]); containerView.addView(undoView[0]); } return undoView[0]; } private float getColorProgress() { return colorProgress; } private void setColorProgress(float progress) { colorProgress = progress; backgroundColor = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_actionBarUnscrolled), Theme.getColor(Theme.key_voipgroup_actionBar), progress, 1.0f); actionBarBackground.setBackgroundColor(backgroundColor); otherItem.redrawPopup(0xff232A33); shadowDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY)); navBarColor = backgroundColor; int color = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_listViewBackgroundUnscrolled), Theme.getColor(Theme.key_voipgroup_listViewBackground), progress, 1.0f); dividerItem.setBackgroundColor(color); listViewBackgroundPaint.setColor(color); listView.setGlowColor(color); if (muteButtonState == MUTE_BUTTON_STATE_CONNECTING || muteButtonState == MUTE_BUTTON_STATE_MUTED_BY_ADMIN) { muteButton.invalidate(); } color = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_leaveButton), Theme.getColor(Theme.key_voipgroup_leaveButtonScrolled), progress, 1.0f); leaveButton.setBackgroundColor(color, color); color = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_lastSeenTextUnscrolled), Theme.getColor(Theme.key_voipgroup_lastSeenText), progress, 1.0f); int color2 = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_mutedIconUnscrolled), Theme.getColor(Theme.key_voipgroup_mutedIcon), progress, 1.0f); for (int a = 0, N = listView.getChildCount(); a < N; a++) { View child = listView.getChildAt(a); if (child instanceof GroupCallTextCell) { GroupCallTextCell cell = (GroupCallTextCell) child; cell.setColors(color2, color); } else if (child instanceof GroupCallUserCell) { GroupCallUserCell cell = (GroupCallUserCell) child; cell.setGrayIconColor(actionBar.getTag() != null ? Theme.key_voipgroup_mutedIcon : Theme.key_voipgroup_mutedIconUnscrolled, color2); } else if (child instanceof GroupCallInvitedCell) { GroupCallInvitedCell cell = (GroupCallInvitedCell) child; cell.setGrayIconColor(actionBar.getTag() != null ? Theme.key_voipgroup_mutedIcon : Theme.key_voipgroup_mutedIconUnscrolled, color2); } } containerView.invalidate(); listView.invalidate(); container.invalidate(); } private void getLink(boolean copy) { TLRPC.ChatFull chatFull = accountInstance.getMessagesController().getChatFull(currentChat.id); String url; if (!TextUtils.isEmpty(currentChat.username)) { url = accountInstance.getMessagesController().linkPrefix + "/" + currentChat.username; } else { url = chatFull != null && chatFull.exported_invite != null ? chatFull.exported_invite.link : null; } if (TextUtils.isEmpty(url)) { TLRPC.TL_messages_exportChatInvite req = new TLRPC.TL_messages_exportChatInvite(); req.peer = MessagesController.getInputPeer(currentChat); accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (response instanceof TLRPC.TL_chatInviteExported) { TLRPC.TL_chatInviteExported invite = (TLRPC.TL_chatInviteExported) response; if (chatFull != null) { chatFull.exported_invite = invite; } openShareAlert(invite.link, copy); } })); } else { openShareAlert(url, copy); } } private void openShareAlert(String url, boolean copy) { if (copy) { AndroidUtilities.addToClipboard(url); getUndoView().showWithAction(0, UndoView.ACTION_VOIP_LINK_COPIED, null, null, null, null); } else { String message = LocaleController.formatString("VoipGroupInviteText", R.string.VoipGroupInviteText, url); boolean keyboardIsOpen = false; if (parentActivity != null) { BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); if (fragment instanceof ChatActivity) { keyboardIsOpen = ((ChatActivity) fragment).needEnterText(); anyEnterEventSent = true; enterEventSent = true; } } shareAlert = new ShareAlert(getContext(), null, message, false, url, false); shareAlert.setDelegate(new ShareAlert.ShareAlertDelegate() { @Override public boolean didCopy() { getUndoView().showWithAction(0, UndoView.ACTION_VOIP_LINK_COPIED, null, null, null, null); return true; } }); shareAlert.setOnDismissListener(dialog -> shareAlert = null); AndroidUtilities.runOnUIThread(() -> { if (shareAlert != null) { shareAlert.show(); } }, keyboardIsOpen ? 200 : 0); } } private void inviteUserToCall(int id, boolean shouldAdd) { TLRPC.User user = accountInstance.getMessagesController().getUser(id); if (user == null) { return; } final AlertDialog[] progressDialog = new AlertDialog[]{new AlertDialog(getContext(), 3)}; TLRPC.TL_phone_inviteToGroupCall req = new TLRPC.TL_phone_inviteToGroupCall(); req.call = call.getInputGroupCall(); TLRPC.TL_inputUser inputUser = new TLRPC.TL_inputUser(); inputUser.user_id = user.id; inputUser.access_hash = user.access_hash; req.users.add(inputUser); int requestId = accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { accountInstance.getMessagesController().processUpdates((TLRPC.Updates) response, false); AndroidUtilities.runOnUIThread(() -> { if (call != null && !delayedGroupCallUpdated) { call.addInvitedUser(id); applyCallParticipantUpdates(); if (groupVoipInviteAlert != null) { groupVoipInviteAlert.dismiss(); } try { progressDialog[0].dismiss(); } catch (Throwable ignore) { } progressDialog[0] = null; getUndoView().showWithAction(0, UndoView.ACTION_VOIP_INVITED, user, null, null, null); } }); } else { AndroidUtilities.runOnUIThread(() -> { try { progressDialog[0].dismiss(); } catch (Throwable ignore) { } progressDialog[0] = null; if (shouldAdd && "USER_NOT_PARTICIPANT".equals(error.text)) { processSelectedOption(null, id, 3); } else { BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); AlertsCreator.processError(currentAccount, error, fragment, req); } }); } }); if (requestId != 0) { AndroidUtilities.runOnUIThread(() -> { if (progressDialog[0] == null) { return; } progressDialog[0].setOnCancelListener(dialog -> accountInstance.getConnectionsManager().cancelRequest(requestId, true)); progressDialog[0].show(); }, 500); } } private void updateLayout(boolean animated) { if (listView.getChildCount() <= 0) { listView.setTopGlowOffset((int) (scrollOffsetY = listView.getPaddingTop())); containerView.invalidate(); return; } RecyclerListView.Holder holder = null; for (int a = 0, N = listView.getChildCount(); a < N; a++) { View child = listView.getChildAt(a); holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); if (holder != null) { if (holder.getAdapterPosition() == 0) { break; } else { holder = null; } } } float newOffset = holder != null ? Math.max(0, holder.itemView.getY()) : 0; boolean show = newOffset <= ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(14); if (show && actionBar.getTag() == null || !show && actionBar.getTag() != null) { actionBar.setTag(show ? 1 : null); if (actionBarAnimation != null) { actionBarAnimation.cancel(); actionBarAnimation = null; } setUseLightStatusBar(actionBar.getTag() == null); actionBar.getBackButton().animate() .scaleX(show ? 1.0f : 0.9f) .scaleY(show ? 1.0f : 0.9f) .translationX(show ? 0.0f : -AndroidUtilities.dp(14)) .setDuration(300) .setInterpolator(CubicBezierInterpolator.DEFAULT) .start(); actionBar.getTitleTextView().animate() .translationY(show ? 0.0f : AndroidUtilities.dp(23)) .setDuration(300) .setInterpolator(CubicBezierInterpolator.DEFAULT) .start(); actionBar.getSubtitleTextView().animate() .translationY(show ? 0.0f : AndroidUtilities.dp(20)) .setDuration(300) .setInterpolator(CubicBezierInterpolator.DEFAULT) .start(); /*titleTextView.animate() .scaleX(show ? 0.9f : 1.0f) .scaleY(show ? 0.9f : 1.0f) .alpha(show ? 0.0f : 1.0f) .setDuration(140) .setInterpolator(CubicBezierInterpolator.DEFAULT) .start();*/ actionBarAnimation = new AnimatorSet(); actionBarAnimation.setDuration(140); actionBarAnimation.playTogether( ObjectAnimator.ofFloat(actionBar, View.ALPHA, show ? 1.0f : 0.0f), ObjectAnimator.ofFloat(actionBarBackground, View.ALPHA, show ? 1.0f : 0.0f), ObjectAnimator.ofFloat(actionBarShadow, View.ALPHA, show ? 1.0f : 0.0f)); actionBarAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { actionBarAnimation = null; } }); actionBarAnimation.start(); } FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); newOffset += layoutParams.topMargin; if (scrollOffsetY != newOffset) { listView.setTopGlowOffset((int) ((scrollOffsetY = newOffset) - layoutParams.topMargin)); int offset = AndroidUtilities.dp(74); float t = scrollOffsetY - offset; int diff; if (t + backgroundPaddingTop < ActionBar.getCurrentActionBarHeight() * 2) { int willMoveUpTo = offset - backgroundPaddingTop - AndroidUtilities.dp(14) + ActionBar.getCurrentActionBarHeight(); float moveProgress = Math.min(1.0f, (ActionBar.getCurrentActionBarHeight() * 2 - t - backgroundPaddingTop) / willMoveUpTo); diff = (int) (AndroidUtilities.dp(AndroidUtilities.isTablet() ? 17 : 13) * moveProgress); float newProgress = Math.min(1.0f, moveProgress); if (Math.abs(newProgress - colorProgress) > 0.0001f) { setColorProgress(Math.min(1.0f, moveProgress)); } titleTextView.setScaleX(Math.max(0.9f, 1.0f - 0.1f * moveProgress * 1.2f)); titleTextView.setScaleY(Math.max(0.9f, 1.0f - 0.1f * moveProgress * 1.2f)); titleTextView.setAlpha(Math.max(0.0f, 1.0f - moveProgress * 1.2f)); } else { diff = 0; titleTextView.setScaleX(1.0f); titleTextView.setScaleY(1.0f); titleTextView.setAlpha(1.0f); if (colorProgress > 0.0001f) { setColorProgress(0.0f); } } menuItemsContainer.setTranslationY(Math.max(AndroidUtilities.dp(4), scrollOffsetY - AndroidUtilities.dp(53) - diff)); titleTextView.setTranslationY(Math.max(AndroidUtilities.dp(4), scrollOffsetY - AndroidUtilities.dp(44) - diff)); containerView.invalidate(); } } private void cancelMutePress() { if (scheduled) { scheduled = false; AndroidUtilities.cancelRunOnUIThread(pressRunnable); } if (pressed) { pressed = false; MotionEvent cancel = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); muteButton.onTouchEvent(cancel); cancel.recycle(); } } private void updateState(boolean animated, boolean selfUpdated) { if (currentCallState == VoIPService.STATE_WAIT_INIT || currentCallState == VoIPService.STATE_WAIT_INIT_ACK || currentCallState == VoIPService.STATE_CREATING || currentCallState == VoIPService.STATE_RECONNECTING) { cancelMutePress(); updateMuteButton(MUTE_BUTTON_STATE_CONNECTING, animated); } else { if (VoIPService.getSharedInstance() == null) { return; } TLRPC.TL_groupCallParticipant participant = call.participants.get(accountInstance.getUserConfig().getClientUserId()); if (participant != null && !participant.can_self_unmute && participant.muted && !ChatObject.canManageCalls(currentChat)) { cancelMutePress(); updateMuteButton(MUTE_BUTTON_STATE_MUTED_BY_ADMIN, animated); VoIPService.getSharedInstance().setMicMute(true, false, false); } else { boolean micMuted = VoIPService.getSharedInstance().isMicMute(); if (selfUpdated && participant != null && participant.muted && !micMuted) { cancelMutePress(); VoIPService.getSharedInstance().setMicMute(true, false, false); micMuted = true; } if (micMuted) { updateMuteButton(MUTE_BUTTON_STATE_UNMUTE, animated); } else { updateMuteButton(MUTE_BUTTON_STATE_MUTE, animated); } } } } @Override public void onAudioSettingsChanged() { updateSpeakerPhoneIcon(true); for (int a = 0, N = listView.getChildCount(); a < N; a++) { View child = listView.getChildAt(a); if (child instanceof GroupCallUserCell) { ((GroupCallUserCell) child).applyParticipantChanges(true); } } } private void updateSpeakerPhoneIcon(boolean animated) { if (soundButton == null) { return; } VoIPService service = VoIPService.getSharedInstance(); if (service == null) { return; } boolean bluetooth = service.isBluetoothOn() || service.isBluetoothWillOn(); boolean checked = !bluetooth && service.isSpeakerphoneOn(); if (bluetooth) { soundButton.setData(R.drawable.calls_bluetooth, Color.WHITE, 0, 0.1f, true, LocaleController.getString("VoipAudioRoutingBluetooth", R.string.VoipAudioRoutingBluetooth), false, animated); } else if (checked) { soundButton.setData(R.drawable.calls_speaker, Color.WHITE, 0, 0.3f, true, LocaleController.getString("VoipSpeaker", R.string.VoipSpeaker), false, animated); } else { if (service.isHeadsetPlugged()) { soundButton.setData(R.drawable.calls_headphones, Color.WHITE, 0, 0.1f, true, LocaleController.getString("VoipAudioRoutingHeadset", R.string.VoipAudioRoutingHeadset), false, animated); } else { soundButton.setData(R.drawable.calls_speaker, Color.WHITE, 0, 0.1f, true, LocaleController.getString("VoipSpeaker", R.string.VoipSpeaker), false, animated); } } soundButton.setChecked(checked, animated); } private void updateMuteButton(int state, boolean animated) { if (muteButtonState == state && animated) { return; } if (muteButtonAnimator != null) { muteButtonAnimator.cancel(); muteButtonAnimator = null; } String newText; String newSubtext; boolean changed; if (state == MUTE_BUTTON_STATE_UNMUTE) { newText = LocaleController.getString("VoipGroupUnmute", R.string.VoipGroupUnmute); newSubtext = LocaleController.getString("VoipHoldAndTalk", R.string.VoipHoldAndTalk); changed = bigMicDrawable.setCustomEndFrame(13); } else if (state == MUTE_BUTTON_STATE_MUTE) { newText = LocaleController.getString("VoipTapToMute", R.string.VoipTapToMute); newSubtext = ""; changed = bigMicDrawable.setCustomEndFrame(24); } else { if (state == MUTE_BUTTON_STATE_CONNECTING) { newText = LocaleController.getString("Connecting", R.string.Connecting); newSubtext = ""; } else { newText = LocaleController.getString("VoipMutedByAdmin", R.string.VoipMutedByAdmin); newSubtext = LocaleController.getString("VoipMutedByAdminInfo", R.string.VoipMutedByAdminInfo); } changed = bigMicDrawable.setCustomEndFrame(13); } final String contentDescription; if (!TextUtils.isEmpty(newSubtext)) { contentDescription = newText + " " + newSubtext; } else { contentDescription = newText; } muteButton.setContentDescription(contentDescription); if (animated) { if (changed) { if (state == MUTE_BUTTON_STATE_MUTE) { bigMicDrawable.setCurrentFrame(12); } else { bigMicDrawable.setCurrentFrame(0); } } muteButton.playAnimation(); muteLabel[1].setVisibility(View.VISIBLE); muteLabel[1].setAlpha(0.0f); muteLabel[1].setTranslationY(-AndroidUtilities.dp(5)); muteLabel[1].setText(newText); muteSubLabel[1].setVisibility(View.VISIBLE); muteSubLabel[1].setAlpha(0.0f); muteSubLabel[1].setTranslationY(-AndroidUtilities.dp(5)); muteSubLabel[1].setText(newSubtext); muteButtonAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); muteButtonAnimator.addUpdateListener(animation -> { float v = (float) animation.getAnimatedValue(); muteLabel[0].setAlpha(1.0f - v); muteLabel[0].setTranslationY(AndroidUtilities.dp(5) * v); muteSubLabel[0].setAlpha(1.0f - v); muteSubLabel[0].setTranslationY(AndroidUtilities.dp(5) * v); muteLabel[1].setAlpha(v); muteLabel[1].setTranslationY(AndroidUtilities.dp(-5 + 5 * v)); muteSubLabel[1].setAlpha(v); muteSubLabel[1].setTranslationY(AndroidUtilities.dp(-5 + 5 * v)); }); muteButtonAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { muteButtonAnimator = null; TextView temp = muteLabel[0]; muteLabel[0] = muteLabel[1]; muteLabel[1] = temp; temp.setVisibility(View.INVISIBLE); temp = muteSubLabel[0]; muteSubLabel[0] = muteSubLabel[1]; muteSubLabel[1] = temp; temp.setVisibility(View.INVISIBLE); for (int a = 0; a < 2; a++) { muteLabel[a].setTranslationY(0); muteSubLabel[a].setTranslationY(0); } } }); muteButtonAnimator.setDuration(180); muteButtonAnimator.start(); muteButtonState = state; } else { muteButtonState = state; bigMicDrawable.setCurrentFrame(bigMicDrawable.getCustomEndFrame() - 1, false, true); muteLabel[0].setText(newText); muteSubLabel[0].setText(newSubtext); } updateMuteButtonState(animated); } private void fillColors(int state, int[] colorsToSet) { if (state == MUTE_BUTTON_STATE_UNMUTE) { colorsToSet[0] = Theme.getColor(Theme.key_voipgroup_unmuteButton2); colorsToSet[1] = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_soundButtonActive), Theme.getColor(Theme.key_voipgroup_soundButtonActiveScrolled), colorProgress, 1.0f); colorsToSet[2] = Theme.getColor(Theme.key_voipgroup_soundButton); } else if (state == MUTE_BUTTON_STATE_MUTE) { colorsToSet[0] = Theme.getColor(Theme.key_voipgroup_muteButton2); colorsToSet[1] = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_soundButtonActive2), Theme.getColor(Theme.key_voipgroup_soundButtonActive2Scrolled), colorProgress, 1.0f); colorsToSet[2] = Theme.getColor(Theme.key_voipgroup_soundButton2); } else if (state == MUTE_BUTTON_STATE_MUTED_BY_ADMIN) { colorsToSet[0] = Theme.getColor(Theme.key_voipgroup_mutedByAdminGradient3); colorsToSet[1] = Theme.getColor(Theme.key_voipgroup_mutedByAdminMuteButton); colorsToSet[2] = Theme.getColor(Theme.key_voipgroup_mutedByAdminMuteButtonDisabled); } else { colorsToSet[0] = Theme.getColor(Theme.key_voipgroup_disabledButton); colorsToSet[1] = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_disabledButtonActive), Theme.getColor(Theme.key_voipgroup_disabledButtonActiveScrolled), colorProgress, 1.0f); colorsToSet[2] = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_listViewBackgroundUnscrolled), Theme.getColor(Theme.key_voipgroup_disabledButton), colorProgress, 1.0f); } } private void updateMuteButtonState(boolean animated) { muteButton.invalidate(); if (states[muteButtonState] == null) { states[muteButtonState] = new WeavingState(muteButtonState); if (muteButtonState == MUTE_BUTTON_STATE_CONNECTING) { states[muteButtonState].shader = null; } else { if (muteButtonState == MUTE_BUTTON_STATE_MUTED_BY_ADMIN) { states[muteButtonState].shader = new LinearGradient(0,400, 400, 0, new int[]{Theme.getColor(Theme.key_voipgroup_mutedByAdminGradient),Theme.getColor(Theme.key_voipgroup_mutedByAdminGradient3), Theme.getColor(Theme.key_voipgroup_mutedByAdminGradient2)}, null, Shader.TileMode.CLAMP); } else if (muteButtonState == MUTE_BUTTON_STATE_MUTE) { states[muteButtonState].shader = new RadialGradient(200, 200, 200, new int[]{Theme.getColor(Theme.key_voipgroup_muteButton), Theme.getColor(Theme.key_voipgroup_muteButton3)}, null, Shader.TileMode.CLAMP); } else { states[muteButtonState].shader = new RadialGradient(200, 200, 200, new int[]{Theme.getColor(Theme.key_voipgroup_unmuteButton2), Theme.getColor(Theme.key_voipgroup_unmuteButton)}, null, Shader.TileMode.CLAMP); } } } if (states[muteButtonState] != currentState) { prevState = currentState; currentState = states[muteButtonState]; if (prevState == null || !animated) { switchProgress = 1; prevState = null; } else { switchProgress = 0; } } if (!animated) { boolean showWaves = false; boolean showLighting = false; if (currentState != null) { showWaves = currentState.currentState == MUTE_BUTTON_STATE_MUTE || currentState.currentState == MUTE_BUTTON_STATE_UNMUTE; showLighting = currentState.currentState != MUTE_BUTTON_STATE_CONNECTING; } showWavesProgress = showWaves ? 1f : 0f; showLightingProgress = showLighting ? 1f : 0f; } buttonsContainer.invalidate(); } private static void processOnLeave(ChatObject.Call call, boolean discard, Runnable onLeave) { if (VoIPService.getSharedInstance() != null) { VoIPService.getSharedInstance().hangUp(discard ? 1 : 0); } if (call != null) { int selfUserId = call.currentAccount.getUserConfig().clientUserId; TLRPC.TL_groupCallParticipant participant = call.participants.get(selfUserId); if (participant != null) { call.participants.delete(selfUserId); call.sortedParticipants.remove(participant); call.call.participants_count--; } } if (onLeave != null) { onLeave.run(); } NotificationCenter.getGlobalInstance().postNotificationName(NotificationCenter.didStartedCall); } public static void onLeaveClick(Context context, Runnable onLeave, boolean fromOverlayWindow) { VoIPService service = VoIPService.getSharedInstance(); if (service == null) { return; } TLRPC.Chat currentChat = service.getChat(); ChatObject.Call call = service.groupCall; if (!ChatObject.canManageCalls(currentChat)) { processOnLeave(call, false, onLeave); return; } AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(LocaleController.getString("VoipGroupLeaveAlertTitle", R.string.VoipGroupLeaveAlertTitle)); builder.setMessage(LocaleController.getString("VoipGroupLeaveAlertText", R.string.VoipGroupLeaveAlertText)); int currentAccount = service.getAccount(); CheckBoxCell[] cells = new CheckBoxCell[1]; LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(LinearLayout.VERTICAL); cells[0] = new CheckBoxCell(context, 1); cells[0].setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (fromOverlayWindow) { cells[0].setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); } else { cells[0].setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); CheckBoxSquare checkBox = (CheckBoxSquare) cells[0].getCheckBoxView(); checkBox.setColors(Theme.key_voipgroup_mutedIcon, Theme.key_voipgroup_listeningText, Theme.key_voipgroup_nameText); } cells[0].setTag(0); cells[0].setText(LocaleController.getString("VoipGroupLeaveAlertEndChat", R.string.VoipGroupLeaveAlertEndChat), "", false, false); cells[0].setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); linearLayout.addView(cells[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); cells[0].setOnClickListener(v -> { Integer num = (Integer) v.getTag(); cells[num].setChecked(!cells[num].isChecked(), true); }); builder.setCustomViewOffset(12); builder.setView(linearLayout); builder.setPositiveButton(LocaleController.getString("VoipGroupLeave", R.string.VoipGroupLeave), (dialogInterface, position) -> processOnLeave(call, cells[0].isChecked(), onLeave)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); if (fromOverlayWindow) { builder.setDimEnabled(false); } AlertDialog dialog = builder.create(); if (fromOverlayWindow) { if (Build.VERSION.SDK_INT >= 26) { dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); } else { dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); } dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); } if (!fromOverlayWindow) { dialog.setBackgroundColor(Theme.getColor(Theme.key_voipgroup_dialogBackground)); } dialog.show(); if (!fromOverlayWindow) { TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); if (button != null) { button.setTextColor(Theme.getColor(Theme.key_voipgroup_leaveCallMenu)); } dialog.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); } } private Paint scrimPaint; private View scrimView; private int popupAnimationIndex = -1; private AnimatorSet scrimAnimatorSet; private ActionBarPopupWindow scrimPopupWindow; private ActionBarMenuSubItem[] scrimPopupWindowItems; private void processSelectedOption(TLRPC.TL_groupCallParticipant participant, int userId, int option) { TLRPC.User user = accountInstance.getMessagesController().getUser(userId); if (option == 0 || option == 2 || option == 3) { if (option == 0) { if (VoIPService.getSharedInstance() == null) { return; } VoIPService.getSharedInstance().editCallMember(user, true, -1); getUndoView().showWithAction(0, UndoView.ACTION_VOIP_MUTED, user, null, null, null); return; } AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); TextView messageTextView = new TextView(getContext()); messageTextView.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); FrameLayout frameLayout = new FrameLayout(getContext()); builder.setView(frameLayout); AvatarDrawable avatarDrawable = new AvatarDrawable(); avatarDrawable.setTextSize(AndroidUtilities.dp(12)); BackupImageView imageView = new BackupImageView(getContext()); imageView.setRoundRadius(AndroidUtilities.dp(20)); frameLayout.addView(imageView, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 22, 5, 22, 0)); avatarDrawable.setInfo(user); imageView.setImage(ImageLocation.getForUser(user, false), "50_50", avatarDrawable, user); String name = UserObject.getFirstName(user); TextView textView = new TextView(getContext()); textView.setTextColor(Theme.getColor(Theme.key_voipgroup_actionBarItems)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setLines(1); textView.setMaxLines(1); textView.setSingleLine(true); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); textView.setEllipsize(TextUtils.TruncateAt.END); if (option == 2) { textView.setText(LocaleController.getString("VoipGroupRemoveMemberAlertTitle", R.string.VoipGroupRemoveMemberAlertTitle)); messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("VoipGroupRemoveMemberAlertText", R.string.VoipGroupRemoveMemberAlertText, name))); } else { textView.setText(LocaleController.getString("VoipGroupAddMemberTitle", R.string.VoipGroupAddMemberTitle)); messageTextView.setText(AndroidUtilities.replaceTags(LocaleController.formatString("VoipGroupAddMemberText", R.string.VoipGroupAddMemberText, name, currentChat.title))); } frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 21 : 76), 11, (LocaleController.isRTL ? 76 : 21), 0)); frameLayout.addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 57, 24, 9)); if (option == 2) { builder.setPositiveButton(LocaleController.getString("VoipGroupUserRemove", R.string.VoipGroupUserRemove), (dialogInterface, i) -> { accountInstance.getMessagesController().deleteUserFromChat(currentChat.id, user, null); getUndoView().showWithAction(0, UndoView.ACTION_VOIP_REMOVED, user, null, null, null); }); } else { builder.setPositiveButton(LocaleController.getString("VoipGroupAdd", R.string.VoipGroupAdd), (dialogInterface, i) -> { BaseFragment fragment = parentActivity.getActionBarLayout().fragmentsStack.get(parentActivity.getActionBarLayout().fragmentsStack.size() - 1); accountInstance.getMessagesController().addUserToChat(currentChat.id, user, 0, null, fragment, () -> inviteUserToCall(userId, false)); }); } builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); AlertDialog dialog = builder.create(); dialog.setBackgroundColor(Theme.getColor(Theme.key_voipgroup_dialogBackground)); dialog.show(); if (option == 2) { TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); if (button != null) { button.setTextColor(Theme.getColor(Theme.key_voipgroup_leaveCallMenu)); } } } else if (option == 6) { Bundle args = new Bundle(); args.putInt("user_id", participant.user_id); parentActivity.presentFragment(new ProfileActivity(args)); dismiss(); } else { if (option == 5) { VoIPService.getSharedInstance().editCallMember(user, true, -1); getUndoView().showWithAction(0, UndoView.ACTION_VOIP_MUTED_FOR_YOU, user, null, null, null); VoIPService.getSharedInstance().setParticipantVolume(participant.source, 0); } else { if ((participant.flags & 128) != 0 && participant.volume == 0) { participant.volume = 10000; VoIPService.getSharedInstance().editCallMember(user, false, participant.volume); } else { VoIPService.getSharedInstance().editCallMember(user, false, -1); } VoIPService.getSharedInstance().setParticipantVolume(participant.source, ChatObject.getParticipantVolume(participant)); getUndoView().showWithAction(0, option == 1 ? UndoView.ACTION_VOIP_UNMUTED : UndoView.ACTION_VOIP_UNMUTED_FOR_YOU, user, null, null, null); } } } private boolean showMenuForCell(GroupCallUserCell view) { if (scrimPopupWindow != null) { scrimPopupWindow.dismiss(); scrimPopupWindow = null; scrimPopupWindowItems = null; return false; } TLRPC.TL_groupCallParticipant participant = view.getParticipant(); Rect rect = new Rect(); ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout = new ActionBarPopupWindow.ActionBarPopupWindowLayout(getContext()); popupLayout.setBackgroundDrawable(null); popupLayout.setPadding(0, 0, 0, 0); popupLayout.setOnTouchListener(new View.OnTouchListener() { private int[] pos = new int[2]; @Override public boolean onTouch(View v, MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { if (scrimPopupWindow != null && scrimPopupWindow.isShowing()) { View contentView = scrimPopupWindow.getContentView(); contentView.getLocationInWindow(pos); rect.set(pos[0], pos[1], pos[0] + contentView.getMeasuredWidth(), pos[1] + contentView.getMeasuredHeight()); if (!rect.contains((int) event.getX(), (int) event.getY())) { scrimPopupWindow.dismiss(); } } } else if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { if (scrimPopupWindow != null && scrimPopupWindow.isShowing()) { scrimPopupWindow.dismiss(); } } return false; } }); popupLayout.setDispatchKeyEventListener(keyEvent -> { if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK && keyEvent.getRepeatCount() == 0 && scrimPopupWindow != null && scrimPopupWindow.isShowing()) { scrimPopupWindow.dismiss(); } }); LinearLayout buttonsLayout = new LinearLayout(getContext()); LinearLayout volumeLayout = !participant.muted_by_you ? new LinearLayout(getContext()) : null; LinearLayout linearLayout = new LinearLayout(getContext()) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); buttonsLayout.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); if (volumeLayout != null) { volumeLayout.measure(MeasureSpec.makeMeasureSpec(buttonsLayout.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); setMeasuredDimension(buttonsLayout.getMeasuredWidth(), buttonsLayout.getMeasuredHeight() + volumeLayout.getMeasuredHeight()); } else { setMeasuredDimension(buttonsLayout.getMeasuredWidth(), buttonsLayout.getMeasuredHeight()); } } }; linearLayout.setMinimumWidth(AndroidUtilities.dp(240)); linearLayout.setOrientation(LinearLayout.VERTICAL); VolumeSlider volumeSlider = null; if (!participant.muted_by_you && (!participant.muted || participant.can_self_unmute)) { Drawable shadowDrawable = getContext().getResources().getDrawable(R.drawable.popup_fixed_alert).mutate(); shadowDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY)); volumeLayout.setBackgroundDrawable(shadowDrawable); linearLayout.addView(volumeLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 0)); volumeSlider = new VolumeSlider(getContext(), participant); volumeLayout.addView(volumeSlider, LayoutHelper.MATCH_PARENT, 48); } buttonsLayout.setMinimumWidth(AndroidUtilities.dp(240)); buttonsLayout.setOrientation(LinearLayout.VERTICAL); Drawable shadowDrawable = getContext().getResources().getDrawable(R.drawable.popup_fixed_alert).mutate(); shadowDrawable.setColorFilter(new PorterDuffColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY)); buttonsLayout.setBackgroundDrawable(shadowDrawable); linearLayout.addView(buttonsLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 0, volumeSlider != null ? -8 : 0, 0, 0)); ScrollView scrollView; if (Build.VERSION.SDK_INT >= 21) { scrollView = new ScrollView(getContext(), null, 0, R.style.scrollbarShapeStyle) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(linearLayout.getMeasuredWidth(), getMeasuredHeight()); } }; } else { scrollView = new ScrollView(getContext()); } scrollView.setClipToPadding(false); popupLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); ArrayList items = new ArrayList<>(2); ArrayList icons = new ArrayList<>(2); ArrayList options = new ArrayList<>(2); boolean isAdmin = false; if (currentChat.megagroup) { isAdmin = accountInstance.getMessagesController().getAdminRank(currentChat.id, participant.user_id) != null; } else { TLRPC.ChatFull chatFull = accountInstance.getMessagesController().getChatFull(currentChat.id); if (chatFull != null) { for (int a = 0, N = chatFull.participants.participants.size(); a < N; a++) { TLRPC.ChatParticipant chatParticipant = chatFull.participants.participants.get(a); if (chatParticipant.user_id == participant.user_id) { isAdmin = chatParticipant instanceof TLRPC.TL_chatParticipantAdmin || chatParticipant instanceof TLRPC.TL_chatParticipantCreator; break; } } } } if (ChatObject.canManageCalls(currentChat)) { if (!isAdmin || !participant.muted) { if (!participant.muted || participant.can_self_unmute) { items.add(LocaleController.getString("VoipGroupMute", R.string.VoipGroupMute)); icons.add(R.drawable.msg_voice_muted); options.add(0); } else { items.add(LocaleController.getString("VoipGroupAllowToSpeak", R.string.VoipGroupAllowToSpeak)); icons.add(R.drawable.msg_voice_unmuted); options.add(1); } } items.add(LocaleController.getString("VoipGroupOpenProfile", R.string.VoipGroupOpenProfile)); icons.add(R.drawable.msg_openprofile); options.add(6); if (!isAdmin) { items.add(LocaleController.getString("VoipGroupUserRemove", R.string.VoipGroupUserRemove)); icons.add(R.drawable.msg_block2); options.add(2); } } else { if (participant.muted_by_you) { items.add(LocaleController.getString("VoipGroupUnmuteForMe", R.string.VoipGroupUnmuteForMe)); icons.add(R.drawable.msg_voice_unmuted); options.add(4); } else { items.add(LocaleController.getString("VoipGroupMuteForMe", R.string.VoipGroupMuteForMe)); icons.add(R.drawable.msg_voice_muted); options.add(5); } items.add(LocaleController.getString("VoipGroupOpenProfile", R.string.VoipGroupOpenProfile)); icons.add(R.drawable.msg_openprofile); options.add(6); } scrimPopupWindowItems = new ActionBarMenuSubItem[items.size()]; for (int a = 0, N = items.size(); a < N; a++) { ActionBarMenuSubItem cell = new ActionBarMenuSubItem(getContext(), a == 0, a == N - 1); if (options.get(a) != 2) { cell.setColors(Theme.getColor(Theme.key_voipgroup_actionBarItems), Theme.getColor(Theme.key_voipgroup_actionBarItems)); } else { cell.setColors(Theme.getColor(Theme.key_voipgroup_leaveCallMenu), Theme.getColor(Theme.key_voipgroup_leaveCallMenu)); } cell.setSelectorColor(Theme.getColor(Theme.key_voipgroup_listSelector)); cell.setTextAndIcon(items.get(a), icons.get(a)); scrimPopupWindowItems[a] = cell; buttonsLayout.addView(cell); final int i = a; cell.setOnClickListener(v1 -> { if (i >= options.size()) { return; } processSelectedOption(participant, participant.user_id, options.get(i)); if (scrimPopupWindow != null) { scrimPopupWindow.dismiss(); } }); } scrollView.addView(linearLayout, LayoutHelper.createScroll(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP)); scrimPopupWindow = new ActionBarPopupWindow(popupLayout, LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT) { @Override public void dismiss() { super.dismiss(); if (scrimPopupWindow != this) { return; } scrimPopupWindow = null; scrimPopupWindowItems = null; if (scrimAnimatorSet != null) { scrimAnimatorSet.cancel(); scrimAnimatorSet = null; } layoutManager.setCanScrollVertically(true); scrimAnimatorSet = new AnimatorSet(); ArrayList animators = new ArrayList<>(); animators.add(ObjectAnimator.ofInt(scrimPaint, AnimationProperties.PAINT_ALPHA, 0)); scrimAnimatorSet.playTogether(animators); scrimAnimatorSet.setDuration(220); scrimAnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { scrimView = null; containerView.invalidate(); listView.invalidate(); if (delayedGroupCallUpdated) { delayedGroupCallUpdated = false; applyCallParticipantUpdates(); } } }); scrimAnimatorSet.start(); } }; scrimPopupWindow.setPauseNotifications(true); scrimPopupWindow.setDismissAnimationDuration(220); scrimPopupWindow.setOutsideTouchable(true); scrimPopupWindow.setClippingEnabled(true); scrimPopupWindow.setAnimationStyle(R.style.PopupContextAnimation); scrimPopupWindow.setFocusable(true); popupLayout.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), View.MeasureSpec.AT_MOST)); scrimPopupWindow.setInputMethodMode(ActionBarPopupWindow.INPUT_METHOD_NOT_NEEDED); scrimPopupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED); scrimPopupWindow.getContentView().setFocusableInTouchMode(true); int popupX = AndroidUtilities.dp(14) + listView.getMeasuredWidth() + AndroidUtilities.dp(8) - popupLayout.getMeasuredWidth(); int popupY = (int) (listView.getY() + view.getY() + view.getMeasuredHeight()); scrimPopupWindow.showAtLocation(listView, Gravity.LEFT | Gravity.TOP, popupX, popupY); listView.stopScroll(); layoutManager.setCanScrollVertically(false); scrimView = view; containerView.invalidate(); listView.invalidate(); if (scrimAnimatorSet != null) { scrimAnimatorSet.cancel(); } scrimAnimatorSet = new AnimatorSet(); ArrayList animators = new ArrayList<>(); animators.add(ObjectAnimator.ofInt(scrimPaint, AnimationProperties.PAINT_ALPHA, 0, 100)); scrimAnimatorSet.playTogether(animators); scrimAnimatorSet.setDuration(150); scrimAnimatorSet.start(); return true; } private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private int usersStartRow; private int usersEndRow; private int invitedStartRow; private int invitedEndRow; private int addMemberRow; private int selfUserRow; private int lastRow; private int rowsCount; public ListAdapter(Context context) { mContext = context; } public boolean addSelfToCounter() { if (selfUserRow < 0) { return false; } if (VoIPService.getSharedInstance() == null) { return false; } return !VoIPService.getSharedInstance().isJoined(); } @Override public int getItemCount() { return rowsCount; } private void updateRows() { if (delayedGroupCallUpdated) { return; } rowsCount = 0; if (ChatObject.canWriteToChat(currentChat)) { addMemberRow = rowsCount++; } else { addMemberRow = -1; } if (call.participants.indexOfKey(selfDummyParticipant.user_id) < 0) { selfUserRow = rowsCount++; } else { selfUserRow = -1; } usersStartRow = rowsCount; rowsCount += call.sortedParticipants.size(); usersEndRow = rowsCount; if (call.invitedUsers.isEmpty()) { invitedStartRow = -1; invitedEndRow = -1; } else { invitedStartRow = rowsCount; rowsCount += call.invitedUsers.size(); invitedEndRow = rowsCount; } lastRow = rowsCount++; } @Override public void notifyDataSetChanged() { updateRows(); super.notifyDataSetChanged(); } @Override public void notifyItemChanged(int position) { updateRows(); super.notifyItemChanged(position); } @Override public void notifyItemChanged(int position, @Nullable Object payload) { updateRows(); super.notifyItemChanged(position, payload); } @Override public void notifyItemRangeChanged(int positionStart, int itemCount) { updateRows(); super.notifyItemRangeChanged(positionStart, itemCount); } @Override public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { updateRows(); super.notifyItemRangeChanged(positionStart, itemCount, payload); } @Override public void notifyItemInserted(int position) { updateRows(); super.notifyItemInserted(position); } @Override public void notifyItemMoved(int fromPosition, int toPosition) { updateRows(); super.notifyItemMoved(fromPosition, toPosition); } @Override public void notifyItemRangeInserted(int positionStart, int itemCount) { updateRows(); super.notifyItemRangeInserted(positionStart, itemCount); } @Override public void notifyItemRemoved(int position) { updateRows(); super.notifyItemRemoved(position); } @Override public void notifyItemRangeRemoved(int positionStart, int itemCount) { updateRows(); super.notifyItemRangeRemoved(positionStart, itemCount); } @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { int type = holder.getItemViewType(); if (type == 1) { GroupCallUserCell cell = (GroupCallUserCell) holder.itemView; String key = actionBar.getTag() != null ? Theme.key_voipgroup_mutedIcon : Theme.key_voipgroup_mutedIconUnscrolled; cell.setGrayIconColor(key, Theme.getColor(key)); cell.setDrawDivider(holder.getAdapterPosition() != getItemCount() - 2); } else if (type == 2) { GroupCallInvitedCell cell = (GroupCallInvitedCell) holder.itemView; String key = actionBar.getTag() != null ? Theme.key_voipgroup_mutedIcon : Theme.key_voipgroup_mutedIconUnscrolled; cell.setGrayIconColor(key, Theme.getColor(key)); cell.setDrawDivider(holder.getAdapterPosition() != getItemCount() - 2); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { case 0: GroupCallTextCell textCell = (GroupCallTextCell) holder.itemView; int color = AndroidUtilities.getOffsetColor(Theme.getColor(Theme.key_voipgroup_lastSeenTextUnscrolled), Theme.getColor(Theme.key_voipgroup_lastSeenText), actionBar.getTag() != null ? 1.0f : 0.0f, 1.0f); textCell.setColors(color, color); textCell.setTextAndIcon(LocaleController.getString("VoipGroupInviteMember", R.string.VoipGroupInviteMember), R.drawable.actions_addmember2, true); break; case 1: GroupCallUserCell userCell = (GroupCallUserCell) holder.itemView; TLRPC.TL_groupCallParticipant participant; if (position == selfUserRow) { participant = selfDummyParticipant; } else { int row = position - usersStartRow; if (delayedGroupCallUpdated) { if (row >= 0 && row < oldParticipants.size()) { participant = oldParticipants.get(row); } else { participant = null; } } else { if (row >= 0 && row < call.sortedParticipants.size()) { participant = call.sortedParticipants.get(row); } else { participant = null; } } } if (participant != null) { userCell.setData(accountInstance, participant, call); } break; case 2: GroupCallInvitedCell invitedCell = (GroupCallInvitedCell) holder.itemView; Integer uid; int row = position - invitedStartRow; if (delayedGroupCallUpdated) { if (row >= 0 && row < oldInvited.size()) { uid = oldInvited.get(row); } else { uid = null; } } else { if (row >= 0 && row < call.invitedUsers.size()) { uid = call.invitedUsers.get(row); } else { uid = null; } } if (uid != null) { invitedCell.setData(currentAccount, uid); } break; } } @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { int type = holder.getItemViewType(); if (type == 1) { GroupCallUserCell userCell = (GroupCallUserCell) holder.itemView; return !userCell.isSelfUser(); } else if (type == 3) { return false; } return true; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; switch (viewType) { case 0: view = new GroupCallTextCell(mContext); break; case 1: view = new GroupCallUserCell(mContext) { @Override protected void onMuteClick(GroupCallUserCell cell) { showMenuForCell(cell); } }; break; case 2: view = new GroupCallInvitedCell(mContext); break; case 3: default: view = new View(mContext); break; } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new RecyclerListView.Holder(view); } @Override public int getItemViewType(int position) { if (position == lastRow) { return 3; } if (position == addMemberRow) { return 0; } if (position == selfUserRow || position >= usersStartRow && position < usersEndRow) { return 1; } return 2; } } private int oldAddMemberRow; private int oldSelfUserRow; private int oldUsersStartRow; private int oldUsersEndRow; private int oldInvitedStartRow; private int oldInvitedEndRow; public void setOldRows(int addMemberRow, int selfUserRow, int usersStartRow, int usersEndRow, int invitedStartRow, int invitedEndRow) { oldAddMemberRow = addMemberRow; oldSelfUserRow = selfUserRow; oldUsersStartRow = usersStartRow; oldUsersEndRow = usersEndRow; oldInvitedStartRow = invitedStartRow; oldInvitedEndRow = invitedEndRow; } private DiffUtil.Callback diffUtilsCallback = new DiffUtil.Callback() { @Override public int getOldListSize() { return oldCount; } @Override public int getNewListSize() { return listAdapter.rowsCount; } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { if (listAdapter.addMemberRow >= 0) { if (oldItemPosition == oldAddMemberRow && newItemPosition == listAdapter.addMemberRow) { return true; } else if (oldItemPosition == oldAddMemberRow && newItemPosition != listAdapter.addMemberRow || oldItemPosition != oldAddMemberRow && newItemPosition == listAdapter.addMemberRow) { return false; } } if (oldItemPosition == oldCount - 1 && newItemPosition == listAdapter.rowsCount - 1) { return true; } else if (oldItemPosition == oldCount - 1 || newItemPosition == listAdapter.rowsCount - 1) { return false; } if ((newItemPosition == listAdapter.selfUserRow || newItemPosition >= listAdapter.usersStartRow && newItemPosition < listAdapter.usersEndRow) && (oldItemPosition == oldSelfUserRow || oldItemPosition >= oldUsersStartRow && oldItemPosition < oldUsersEndRow)) { TLRPC.TL_groupCallParticipant oldItem; TLRPC.TL_groupCallParticipant newItem; if (oldItemPosition == oldSelfUserRow) { oldItem = selfDummyParticipant; } else { oldItem = oldParticipants.get(oldItemPosition - oldUsersStartRow); } if (newItemPosition == listAdapter.selfUserRow) { newItem = selfDummyParticipant; } else { newItem = call.sortedParticipants.get(newItemPosition - listAdapter.usersStartRow); } return oldItem.user_id == newItem.user_id && oldItem.lastActiveDate == oldItem.active_date; } else if (newItemPosition >= listAdapter.invitedStartRow && newItemPosition < listAdapter.invitedEndRow && oldItemPosition >= oldInvitedStartRow && oldItemPosition < oldInvitedEndRow) { Integer oldItem = oldInvited.get(oldItemPosition - oldInvitedStartRow); Integer newItem = call.invitedUsers.get(newItemPosition - listAdapter.invitedStartRow); return oldItem.equals(newItem); } return false; } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return true; } }; private static class UpdateCallback implements ListUpdateCallback { final RecyclerView.Adapter adapter; boolean changed; private UpdateCallback(RecyclerView.Adapter adapter) { this.adapter = adapter; } @Override public void onInserted(int position, int count) { changed = true; adapter.notifyItemRangeInserted(position, count); } @Override public void onRemoved(int position, int count) { changed = true; adapter.notifyItemRangeRemoved(position, count); } @Override public void onMoved(int fromPosition, int toPosition) { changed = true; adapter.notifyItemMoved(fromPosition, toPosition); } @Override public void onChanged(int position, int count, @Nullable Object payload) { adapter.notifyItemRangeChanged(position, count, payload); } } private void toggleAdminSpeak() { TLRPC.TL_phone_toggleGroupCallSettings req = new TLRPC.TL_phone_toggleGroupCallSettings(); req.call = call.getInputGroupCall(); req.join_muted = call.call.join_muted; req.flags |= 1; accountInstance.getConnectionsManager().sendRequest(req, (response, error) -> { if (response != null) { accountInstance.getMessagesController().processUpdates((TLRPC.Updates) response, false); } }); } @Override public ArrayList getThemeDescriptions() { return new ArrayList<>(); } }