Merge "Animate LockScreen and AOD clock text for charging events" into sc-dev
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
android:typeface="monospace"
|
android:typeface="monospace"
|
||||||
android:elegantTextHeight="false"
|
android:elegantTextHeight="false"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
|
chargeAnimationDelay="350"
|
||||||
dozeWeight="200"
|
dozeWeight="200"
|
||||||
lockScreenWeight="400"
|
lockScreenWeight="400"
|
||||||
/>
|
/>
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
android:fontFamily="@font/clock"
|
android:fontFamily="@font/clock"
|
||||||
android:typeface="monospace"
|
android:typeface="monospace"
|
||||||
android:elegantTextHeight="false"
|
android:elegantTextHeight="false"
|
||||||
|
chargeAnimationDelay="200"
|
||||||
dozeWeight="200"
|
dozeWeight="200"
|
||||||
lockScreenWeight="400"
|
lockScreenWeight="400"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -45,5 +45,6 @@
|
|||||||
<declare-styleable name="AnimatableClockView">
|
<declare-styleable name="AnimatableClockView">
|
||||||
<attr name="dozeWeight" format="integer" />
|
<attr name="dozeWeight" format="integer" />
|
||||||
<attr name="lockScreenWeight" format="integer" />
|
<attr name="lockScreenWeight" format="integer" />
|
||||||
|
<attr name="chargeAnimationDelay" format="integer" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.android.settingslib.Utils;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher;
|
import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||||
|
import com.android.systemui.statusbar.policy.BatteryController;
|
||||||
import com.android.systemui.util.ViewController;
|
import com.android.systemui.util.ViewController;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -45,6 +46,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
|
|||||||
private int mLockScreenColor;
|
private int mLockScreenColor;
|
||||||
|
|
||||||
private boolean mIsDozing;
|
private boolean mIsDozing;
|
||||||
|
private boolean mIsCharging;
|
||||||
private float mDozeAmount;
|
private float mDozeAmount;
|
||||||
private Locale mLocale;
|
private Locale mLocale;
|
||||||
|
|
||||||
@@ -56,7 +58,8 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
|
|||||||
public AnimatableClockController(
|
public AnimatableClockController(
|
||||||
AnimatableClockView view,
|
AnimatableClockView view,
|
||||||
StatusBarStateController statusBarStateController,
|
StatusBarStateController statusBarStateController,
|
||||||
BroadcastDispatcher broadcastDispatcher) {
|
BroadcastDispatcher broadcastDispatcher,
|
||||||
|
BatteryController batteryController) {
|
||||||
super(view);
|
super(view);
|
||||||
mStatusBarStateController = statusBarStateController;
|
mStatusBarStateController = statusBarStateController;
|
||||||
mIsDozing = mStatusBarStateController.isDozing();
|
mIsDozing = mStatusBarStateController.isDozing();
|
||||||
@@ -68,6 +71,16 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
|
|||||||
R.dimen.keyguard_clock_line_spacing_scale_burmese);
|
R.dimen.keyguard_clock_line_spacing_scale_burmese);
|
||||||
mDefaultLineSpacing = getContext().getResources().getFloat(
|
mDefaultLineSpacing = getContext().getResources().getFloat(
|
||||||
R.dimen.keyguard_clock_line_spacing_scale);
|
R.dimen.keyguard_clock_line_spacing_scale);
|
||||||
|
|
||||||
|
batteryController.addCallback(new BatteryController.BatteryStateChangeCallback() {
|
||||||
|
@Override
|
||||||
|
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
|
||||||
|
if (!mIsCharging && charging) {
|
||||||
|
mView.animateCharge(mIsDozing);
|
||||||
|
}
|
||||||
|
mIsCharging = charging;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
|
private BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ public class AnimatableClockView extends TextView {
|
|||||||
private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
|
private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
|
||||||
private static final CharSequence SINGLE_LINE_FORMAT_12_HOUR = "h:mm";
|
private static final CharSequence SINGLE_LINE_FORMAT_12_HOUR = "h:mm";
|
||||||
private static final CharSequence SINGLE_LINE_FORMAT_24_HOUR = "H:mm";
|
private static final CharSequence SINGLE_LINE_FORMAT_24_HOUR = "H:mm";
|
||||||
private static final long ANIM_DURATION = 300;
|
private static final long DOZE_ANIM_DURATION = 300;
|
||||||
|
private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
|
||||||
|
private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000;
|
||||||
|
|
||||||
private final Calendar mTime = Calendar.getInstance();
|
private final Calendar mTime = Calendar.getInstance();
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ public class AnimatableClockView extends TextView {
|
|||||||
private int mDozingColor;
|
private int mDozingColor;
|
||||||
private int mLockScreenColor;
|
private int mLockScreenColor;
|
||||||
private float mLineSpacingScale = 1f;
|
private float mLineSpacingScale = 1f;
|
||||||
|
private int mChargeAnimationDelay = 0;
|
||||||
|
|
||||||
private TextAnimator mTextAnimator = null;
|
private TextAnimator mTextAnimator = null;
|
||||||
private Runnable mOnTextAnimatorInitialized;
|
private Runnable mOnTextAnimatorInitialized;
|
||||||
@@ -79,6 +82,8 @@ public class AnimatableClockView extends TextView {
|
|||||||
try {
|
try {
|
||||||
mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
|
mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
|
||||||
mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
|
mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
|
||||||
|
mChargeAnimationDelay = ta.getInt(
|
||||||
|
R.styleable.AnimatableClockView_chargeAnimationDelay, 200);
|
||||||
} finally {
|
} finally {
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
}
|
}
|
||||||
@@ -150,11 +155,36 @@ public class AnimatableClockView extends TextView {
|
|||||||
mLockScreenColor = lockScreenColor;
|
mLockScreenColor = lockScreenColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void animateCharge(boolean isDozing) {
|
||||||
|
if (mTextAnimator == null || mTextAnimator.isRunning()) {
|
||||||
|
// Skip charge animation if dozing animation is already playing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Runnable startAnimPhase2 = () -> setTextStyle(
|
||||||
|
isDozing ? mDozingWeight : mLockScreenWeight/* weight */,
|
||||||
|
-1,
|
||||||
|
null,
|
||||||
|
true /* animate */,
|
||||||
|
CHARGE_ANIM_DURATION_PHASE_1,
|
||||||
|
0 /* delay */,
|
||||||
|
null /* onAnimationEnd */);
|
||||||
|
setTextStyle(isDozing ? mLockScreenWeight : mDozingWeight/* weight */,
|
||||||
|
-1,
|
||||||
|
null,
|
||||||
|
true /* animate */,
|
||||||
|
CHARGE_ANIM_DURATION_PHASE_0,
|
||||||
|
mChargeAnimationDelay,
|
||||||
|
startAnimPhase2);
|
||||||
|
}
|
||||||
|
|
||||||
void animateDoze(boolean isDozing, boolean animate) {
|
void animateDoze(boolean isDozing, boolean animate) {
|
||||||
setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
|
setTextStyle(isDozing ? mDozingWeight : mLockScreenWeight /* weight */,
|
||||||
-1,
|
-1,
|
||||||
isDozing ? mDozingColor : mLockScreenColor,
|
isDozing ? mDozingColor : mLockScreenColor,
|
||||||
animate);
|
animate,
|
||||||
|
DOZE_ANIM_DURATION,
|
||||||
|
0 /* delay */,
|
||||||
|
null /* onAnimationEnd */);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,15 +200,20 @@ public class AnimatableClockView extends TextView {
|
|||||||
private void setTextStyle(
|
private void setTextStyle(
|
||||||
@IntRange(from = 0, to = 1000) int weight,
|
@IntRange(from = 0, to = 1000) int weight,
|
||||||
@FloatRange(from = 0) float textSize,
|
@FloatRange(from = 0) float textSize,
|
||||||
int color,
|
Integer color,
|
||||||
boolean animate) {
|
boolean animate,
|
||||||
|
long duration,
|
||||||
|
long delay,
|
||||||
|
Runnable onAnimationEnd) {
|
||||||
if (mTextAnimator != null) {
|
if (mTextAnimator != null) {
|
||||||
mTextAnimator.setTextStyle(weight, textSize, color, animate, ANIM_DURATION, null);
|
mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null,
|
||||||
|
delay, onAnimationEnd);
|
||||||
} else {
|
} else {
|
||||||
// when the text animator is set, update its start values
|
// when the text animator is set, update its start values
|
||||||
mOnTextAnimatorInitialized =
|
mOnTextAnimatorInitialized =
|
||||||
() -> mTextAnimator.setTextStyle(
|
() -> mTextAnimator.setTextStyle(
|
||||||
weight, textSize, color, false, ANIM_DURATION, null);
|
weight, textSize, color, false, duration, null,
|
||||||
|
delay, onAnimationEnd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator;
|
|||||||
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
|
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
|
||||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
|
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
|
||||||
import com.android.systemui.statusbar.phone.NotificationIconContainer;
|
import com.android.systemui.statusbar.phone.NotificationIconContainer;
|
||||||
|
import com.android.systemui.statusbar.policy.BatteryController;
|
||||||
import com.android.systemui.util.ViewController;
|
import com.android.systemui.util.ViewController;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -70,6 +71,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
|||||||
private final KeyguardSliceViewController mKeyguardSliceViewController;
|
private final KeyguardSliceViewController mKeyguardSliceViewController;
|
||||||
private final NotificationIconAreaController mNotificationIconAreaController;
|
private final NotificationIconAreaController mNotificationIconAreaController;
|
||||||
private final BroadcastDispatcher mBroadcastDispatcher;
|
private final BroadcastDispatcher mBroadcastDispatcher;
|
||||||
|
private final BatteryController mBatteryController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clock for both small and large sizes
|
* Clock for both small and large sizes
|
||||||
@@ -118,7 +120,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
|||||||
BroadcastDispatcher broadcastDispatcher,
|
BroadcastDispatcher broadcastDispatcher,
|
||||||
PluginManager pluginManager,
|
PluginManager pluginManager,
|
||||||
FeatureFlags featureFlags,
|
FeatureFlags featureFlags,
|
||||||
@Main Executor uiExecutor) {
|
@Main Executor uiExecutor,
|
||||||
|
BatteryController batteryController) {
|
||||||
super(keyguardClockSwitch);
|
super(keyguardClockSwitch);
|
||||||
mResources = resources;
|
mResources = resources;
|
||||||
mStatusBarStateController = statusBarStateController;
|
mStatusBarStateController = statusBarStateController;
|
||||||
@@ -130,6 +133,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
|||||||
mPluginManager = pluginManager;
|
mPluginManager = pluginManager;
|
||||||
mIsSmartspaceEnabled = featureFlags.isSmartspaceEnabled();
|
mIsSmartspaceEnabled = featureFlags.isSmartspaceEnabled();
|
||||||
mUiExecutor = uiExecutor;
|
mUiExecutor = uiExecutor;
|
||||||
|
mBatteryController = batteryController;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,14 +160,16 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
|||||||
new AnimatableClockController(
|
new AnimatableClockController(
|
||||||
mView.findViewById(R.id.animatable_clock_view),
|
mView.findViewById(R.id.animatable_clock_view),
|
||||||
mStatusBarStateController,
|
mStatusBarStateController,
|
||||||
mBroadcastDispatcher);
|
mBroadcastDispatcher,
|
||||||
|
mBatteryController);
|
||||||
mClockViewController.init();
|
mClockViewController.init();
|
||||||
|
|
||||||
mLargeClockViewController =
|
mLargeClockViewController =
|
||||||
new AnimatableClockController(
|
new AnimatableClockController(
|
||||||
mView.findViewById(R.id.animatable_clock_view_large),
|
mView.findViewById(R.id.animatable_clock_view_large),
|
||||||
mStatusBarStateController,
|
mStatusBarStateController,
|
||||||
mBroadcastDispatcher);
|
mBroadcastDispatcher,
|
||||||
|
mBatteryController);
|
||||||
mLargeClockViewController.init();
|
mLargeClockViewController.init();
|
||||||
|
|
||||||
// If a smartspace plugin is detected, replace the existing smartspace
|
// If a smartspace plugin is detected, replace the existing smartspace
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ class TextAnimator(
|
|||||||
invalidateCallback()
|
invalidateCallback()
|
||||||
}
|
}
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
addListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
textInterpolator.rebase()
|
||||||
|
}
|
||||||
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
|
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,10 @@ class TextAnimator(
|
|||||||
textInterpolator.layout = layout
|
textInterpolator.layout = layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isRunning(): Boolean {
|
||||||
|
return animator.isRunning
|
||||||
|
}
|
||||||
|
|
||||||
fun draw(c: Canvas) = textInterpolator.draw(c)
|
fun draw(c: Canvas) = textInterpolator.draw(c)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +107,9 @@ class TextAnimator(
|
|||||||
color: Int? = null,
|
color: Int? = null,
|
||||||
animate: Boolean = true,
|
animate: Boolean = true,
|
||||||
duration: Long = -1L,
|
duration: Long = -1L,
|
||||||
interpolator: TimeInterpolator? = null
|
interpolator: TimeInterpolator? = null,
|
||||||
|
delay: Long = 0,
|
||||||
|
onAnimationEnd: Runnable? = null
|
||||||
) {
|
) {
|
||||||
if (animate) {
|
if (animate) {
|
||||||
animator.cancel()
|
animator.cancel()
|
||||||
@@ -120,12 +128,25 @@ class TextAnimator(
|
|||||||
textInterpolator.onTargetPaintModified()
|
textInterpolator.onTargetPaintModified()
|
||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
|
animator.startDelay = delay
|
||||||
animator.duration = if (duration == -1L) {
|
animator.duration = if (duration == -1L) {
|
||||||
DEFAULT_ANIMATION_DURATION
|
DEFAULT_ANIMATION_DURATION
|
||||||
} else {
|
} else {
|
||||||
duration
|
duration
|
||||||
}
|
}
|
||||||
interpolator?.let { animator.interpolator = it }
|
interpolator?.let { animator.interpolator = it }
|
||||||
|
if (onAnimationEnd != null) {
|
||||||
|
val listener = object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
onAnimationEnd.run()
|
||||||
|
animator.removeListener(this)
|
||||||
|
}
|
||||||
|
override fun onAnimationCancel(animation: Animator?) {
|
||||||
|
animator.removeListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animator.addListener(listener)
|
||||||
|
}
|
||||||
animator.start()
|
animator.start()
|
||||||
} else {
|
} else {
|
||||||
// No animation is requested, thus set base and target state to the same state.
|
// No animation is requested, thus set base and target state to the same state.
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.FeatureFlags;
|
|||||||
import com.android.systemui.statusbar.StatusBarState;
|
import com.android.systemui.statusbar.StatusBarState;
|
||||||
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
|
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
|
||||||
import com.android.systemui.statusbar.phone.NotificationIconContainer;
|
import com.android.systemui.statusbar.phone.NotificationIconContainer;
|
||||||
|
import com.android.systemui.statusbar.policy.BatteryController;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -96,6 +97,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
|
|||||||
private AnimatableClockView mClockView;
|
private AnimatableClockView mClockView;
|
||||||
@Mock
|
@Mock
|
||||||
private AnimatableClockView mLargeClockView;
|
private AnimatableClockView mLargeClockView;
|
||||||
|
@Mock
|
||||||
|
BatteryController mBatteryController;
|
||||||
|
|
||||||
private KeyguardClockSwitchController mController;
|
private KeyguardClockSwitchController mController;
|
||||||
|
|
||||||
@@ -127,7 +130,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
|
|||||||
mBroadcastDispatcher,
|
mBroadcastDispatcher,
|
||||||
mPluginManager,
|
mPluginManager,
|
||||||
mFeatureFlags,
|
mFeatureFlags,
|
||||||
mExecutor);
|
mExecutor,
|
||||||
|
mBatteryController);
|
||||||
|
|
||||||
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
|
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
|
||||||
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
|
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.keyguard
|
package com.android.keyguard
|
||||||
|
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.text.Layout
|
import android.text.Layout
|
||||||
@@ -25,7 +26,9 @@ import androidx.test.filters.SmallTest
|
|||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.Mockito.eq
|
||||||
import org.mockito.Mockito.inOrder
|
import org.mockito.Mockito.inOrder
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
@@ -107,4 +110,34 @@ class TextAnimatorTest : SysuiTestCase() {
|
|||||||
// Then, animation start should not be called.
|
// Then, animation start should not be called.
|
||||||
verify(valueAnimator, never()).start()
|
verify(valueAnimator, never()).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAnimationEnded() {
|
||||||
|
val layout = makeLayout("Hello, World", PAINT)
|
||||||
|
val valueAnimator = mock(ValueAnimator::class.java)
|
||||||
|
val textInterpolator = mock(TextInterpolator::class.java)
|
||||||
|
val paint = mock(TextPaint::class.java)
|
||||||
|
`when`(textInterpolator.targetPaint).thenReturn(paint)
|
||||||
|
val animationEndCallback = mock(Runnable::class.java)
|
||||||
|
|
||||||
|
val textAnimator = TextAnimator(layout, {}).apply {
|
||||||
|
this.textInterpolator = textInterpolator
|
||||||
|
this.animator = valueAnimator
|
||||||
|
}
|
||||||
|
|
||||||
|
textAnimator.setTextStyle(
|
||||||
|
weight = 400,
|
||||||
|
animate = true,
|
||||||
|
onAnimationEnd = animationEndCallback
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify animationEnd callback has been added.
|
||||||
|
val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java)
|
||||||
|
verify(valueAnimator).addListener(captor.capture())
|
||||||
|
captor.value.onAnimationEnd(valueAnimator)
|
||||||
|
|
||||||
|
// Verify animationEnd callback has been invoked and removed.
|
||||||
|
verify(animationEndCallback).run()
|
||||||
|
verify(valueAnimator).removeListener(eq(captor.value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user