mirror of
https://github.com/JFormDesigner/FlatLaf.git
synced 2025-12-06 22:10:54 +03:00
FlatAnimatorTest: added test for wheel scrolling (including chart)
This commit is contained in:
@@ -17,7 +17,12 @@
|
|||||||
package com.formdev.flatlaf.testing;
|
package com.formdev.flatlaf.testing;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.awt.event.MouseWheelListener;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import com.formdev.flatlaf.testing.FlatSmoothScrollingTest.LineChartPanel;
|
||||||
|
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||||
import com.formdev.flatlaf.util.Animator;
|
import com.formdev.flatlaf.util.Animator;
|
||||||
import com.formdev.flatlaf.util.CubicBezierEasing;
|
import com.formdev.flatlaf.util.CubicBezierEasing;
|
||||||
import net.miginfocom.swing.*;
|
import net.miginfocom.swing.*;
|
||||||
@@ -40,6 +45,9 @@ public class FlatAnimatorTest
|
|||||||
|
|
||||||
FlatAnimatorTest() {
|
FlatAnimatorTest() {
|
||||||
initComponents();
|
initComponents();
|
||||||
|
|
||||||
|
lineChartPanel.setSecondWidth( 500 );
|
||||||
|
mouseWheelTestPanel.lineChartPanel = lineChartPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
@@ -72,6 +80,14 @@ public class FlatAnimatorTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateChartDelayedChanged() {
|
||||||
|
lineChartPanel.setUpdateDelayed( updateChartDelayedCheckBox.isSelected() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearChart() {
|
||||||
|
lineChartPanel.clear();
|
||||||
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||||
JLabel label1 = new JLabel();
|
JLabel label1 = new JLabel();
|
||||||
@@ -79,6 +95,13 @@ public class FlatAnimatorTest
|
|||||||
JLabel label2 = new JLabel();
|
JLabel label2 = new JLabel();
|
||||||
easeInOutScrollBar = new JScrollBar();
|
easeInOutScrollBar = new JScrollBar();
|
||||||
startButton = new JButton();
|
startButton = new JButton();
|
||||||
|
JLabel label3 = new JLabel();
|
||||||
|
mouseWheelTestPanel = new FlatAnimatorTest.MouseWheelTestPanel();
|
||||||
|
JScrollPane scrollPane1 = new JScrollPane();
|
||||||
|
lineChartPanel = new FlatSmoothScrollingTest.LineChartPanel();
|
||||||
|
JLabel label4 = new JLabel();
|
||||||
|
updateChartDelayedCheckBox = new JCheckBox();
|
||||||
|
JButton clearChartButton = new JButton();
|
||||||
|
|
||||||
//======== this ========
|
//======== this ========
|
||||||
setLayout(new MigLayout(
|
setLayout(new MigLayout(
|
||||||
@@ -89,6 +112,10 @@ public class FlatAnimatorTest
|
|||||||
// rows
|
// rows
|
||||||
"[]" +
|
"[]" +
|
||||||
"[]" +
|
"[]" +
|
||||||
|
"[]" +
|
||||||
|
"[]" +
|
||||||
|
"[top]" +
|
||||||
|
"[400,grow,fill]" +
|
||||||
"[]"));
|
"[]"));
|
||||||
|
|
||||||
//---- label1 ----
|
//---- label1 ----
|
||||||
@@ -113,6 +140,37 @@ public class FlatAnimatorTest
|
|||||||
startButton.setText("Start");
|
startButton.setText("Start");
|
||||||
startButton.addActionListener(e -> start());
|
startButton.addActionListener(e -> start());
|
||||||
add(startButton, "cell 0 2");
|
add(startButton, "cell 0 2");
|
||||||
|
|
||||||
|
//---- label3 ----
|
||||||
|
label3.setText("Mouse wheel test:");
|
||||||
|
add(label3, "cell 0 4");
|
||||||
|
|
||||||
|
//---- mouseWheelTestPanel ----
|
||||||
|
mouseWheelTestPanel.setBorder(new LineBorder(Color.red));
|
||||||
|
add(mouseWheelTestPanel, "cell 1 4,height 100");
|
||||||
|
|
||||||
|
//======== scrollPane1 ========
|
||||||
|
{
|
||||||
|
scrollPane1.setViewportView(lineChartPanel);
|
||||||
|
}
|
||||||
|
add(scrollPane1, "cell 0 5 2 1");
|
||||||
|
|
||||||
|
//---- label4 ----
|
||||||
|
label4.setText("X: time (500ms per line) / Y: value (10% per line)");
|
||||||
|
add(label4, "cell 0 6 2 1");
|
||||||
|
|
||||||
|
//---- updateChartDelayedCheckBox ----
|
||||||
|
updateChartDelayedCheckBox.setText("Update chart delayed");
|
||||||
|
updateChartDelayedCheckBox.setMnemonic('U');
|
||||||
|
updateChartDelayedCheckBox.setSelected(true);
|
||||||
|
updateChartDelayedCheckBox.addActionListener(e -> updateChartDelayedChanged());
|
||||||
|
add(updateChartDelayedCheckBox, "cell 0 6 2 1,alignx right,growx 0");
|
||||||
|
|
||||||
|
//---- clearChartButton ----
|
||||||
|
clearChartButton.setText("Clear Chart");
|
||||||
|
clearChartButton.setMnemonic('C');
|
||||||
|
clearChartButton.addActionListener(e -> clearChart());
|
||||||
|
add(clearChartButton, "cell 0 6 2 1,alignx right,growx 0");
|
||||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,5 +178,68 @@ public class FlatAnimatorTest
|
|||||||
private JScrollBar linearScrollBar;
|
private JScrollBar linearScrollBar;
|
||||||
private JScrollBar easeInOutScrollBar;
|
private JScrollBar easeInOutScrollBar;
|
||||||
private JButton startButton;
|
private JButton startButton;
|
||||||
|
private FlatAnimatorTest.MouseWheelTestPanel mouseWheelTestPanel;
|
||||||
|
private FlatSmoothScrollingTest.LineChartPanel lineChartPanel;
|
||||||
|
private JCheckBox updateChartDelayedCheckBox;
|
||||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||||
|
|
||||||
|
//---- class MouseWheelTestPanel ------------------------------------------
|
||||||
|
|
||||||
|
static class MouseWheelTestPanel
|
||||||
|
extends JPanel
|
||||||
|
implements MouseWheelListener
|
||||||
|
{
|
||||||
|
private static final int MAX_VALUE = 1000;
|
||||||
|
private static final int STEP = 100;
|
||||||
|
|
||||||
|
private final JLabel valueLabel;
|
||||||
|
private final Animator animator;
|
||||||
|
|
||||||
|
LineChartPanel lineChartPanel;
|
||||||
|
|
||||||
|
private int value;
|
||||||
|
private int startValue;
|
||||||
|
private int targetValue = -1;
|
||||||
|
|
||||||
|
MouseWheelTestPanel() {
|
||||||
|
super( new BorderLayout() );
|
||||||
|
valueLabel = new JLabel( String.valueOf( value ), SwingConstants.CENTER );
|
||||||
|
valueLabel.setFont( valueLabel.getFont().deriveFont( (float) valueLabel.getFont().getSize() * 2 ) );
|
||||||
|
add( valueLabel, BorderLayout.CENTER );
|
||||||
|
add( new JLabel( " " ), BorderLayout.NORTH );
|
||||||
|
add( new JLabel( "(move mouse into rectangle and rotate mouse wheel)", SwingConstants.CENTER ), BorderLayout.SOUTH );
|
||||||
|
|
||||||
|
int duration = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.duration", 200 );
|
||||||
|
int resolution = FlatUIUtils.getUIInt( "ScrollPane.smoothScrolling.resolution", 10 );
|
||||||
|
|
||||||
|
animator = new Animator( duration, fraction -> {
|
||||||
|
value = startValue + Math.round( (targetValue - startValue) * fraction );
|
||||||
|
valueLabel.setText( String.valueOf( value ) );
|
||||||
|
|
||||||
|
lineChartPanel.addValue( value / (double) MAX_VALUE, Color.red );
|
||||||
|
}, () -> {
|
||||||
|
targetValue = -1;
|
||||||
|
} );
|
||||||
|
animator.setResolution( resolution );
|
||||||
|
animator.setInterpolator( new CubicBezierEasing( 0.5f, 0.5f, 0.5f, 1 ) );
|
||||||
|
|
||||||
|
addMouseWheelListener( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseWheelMoved( MouseWheelEvent e ) {
|
||||||
|
lineChartPanel.addValue( 0.5 + (e.getWheelRotation() / 10.), true, Color.red );
|
||||||
|
|
||||||
|
// start next animation at the current value
|
||||||
|
startValue = value;
|
||||||
|
|
||||||
|
// increase/decrease target value if animation is in progress
|
||||||
|
targetValue = (targetValue < 0 ? value : targetValue) + (STEP * e.getWheelRotation());
|
||||||
|
targetValue = Math.min( Math.max( targetValue, 0 ), MAX_VALUE );
|
||||||
|
|
||||||
|
// restart animator
|
||||||
|
animator.cancel();
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
JFDML JFormDesigner: "7.0.2.0.298" Java: "14.0.2" encoding: "UTF-8"
|
JFDML JFormDesigner: "7.0.2.0.298" Java: "15" encoding: "UTF-8"
|
||||||
|
|
||||||
new FormModel {
|
new FormModel {
|
||||||
contentType: "form/swing"
|
contentType: "form/swing"
|
||||||
@@ -9,7 +9,7 @@ new FormModel {
|
|||||||
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||||
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
|
"$layoutConstraints": "ltr,insets dialog,hidemode 3"
|
||||||
"$columnConstraints": "[fill][grow,fill]"
|
"$columnConstraints": "[fill][grow,fill]"
|
||||||
"$rowConstraints": "[][][]"
|
"$rowConstraints": "[][][][][top][400,grow,fill][]"
|
||||||
} ) {
|
} ) {
|
||||||
name: "this"
|
name: "this"
|
||||||
add( new FormComponent( "javax.swing.JLabel" ) {
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
@@ -54,9 +54,61 @@ new FormModel {
|
|||||||
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
"value": "cell 0 2"
|
"value": "cell 0 2"
|
||||||
} )
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
|
name: "label3"
|
||||||
|
"text": "Mouse wheel test:"
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 4"
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "com.formdev.flatlaf.testing.FlatAnimatorTest$MouseWheelTestPanel" ) {
|
||||||
|
name: "mouseWheelTestPanel"
|
||||||
|
"border": new javax.swing.border.LineBorder( sfield java.awt.Color red, 1, false )
|
||||||
|
auxiliary() {
|
||||||
|
"JavaCodeGenerator.variableLocal": false
|
||||||
|
}
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 1 4,height 100"
|
||||||
|
} )
|
||||||
|
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||||
|
name: "scrollPane1"
|
||||||
|
add( new FormComponent( "com.formdev.flatlaf.testing.FlatSmoothScrollingTest$LineChartPanel" ) {
|
||||||
|
name: "lineChartPanel"
|
||||||
|
auxiliary() {
|
||||||
|
"JavaCodeGenerator.variableLocal": false
|
||||||
|
}
|
||||||
|
} )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 5 2 1"
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
|
name: "label4"
|
||||||
|
"text": "X: time (500ms per line) / Y: value (10% per line)"
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 6 2 1"
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JCheckBox" ) {
|
||||||
|
name: "updateChartDelayedCheckBox"
|
||||||
|
"text": "Update chart delayed"
|
||||||
|
"mnemonic": 85
|
||||||
|
"selected": true
|
||||||
|
auxiliary() {
|
||||||
|
"JavaCodeGenerator.variableLocal": false
|
||||||
|
}
|
||||||
|
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "updateChartDelayedChanged", false ) )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 6 2 1,alignx right,growx 0"
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JButton" ) {
|
||||||
|
name: "clearChartButton"
|
||||||
|
"text": "Clear Chart"
|
||||||
|
"mnemonic": 67
|
||||||
|
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "clearChart", false ) )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 6 2 1,alignx right,growx 0"
|
||||||
|
} )
|
||||||
}, new FormLayoutConstraints( null ) {
|
}, new FormLayoutConstraints( null ) {
|
||||||
"location": new java.awt.Point( 0, 0 )
|
"location": new java.awt.Point( 0, 0 )
|
||||||
"size": new java.awt.Dimension( 415, 350 )
|
"size": new java.awt.Dimension( 625, 625 )
|
||||||
} )
|
} )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,20 +460,23 @@ public class FlatSmoothScrollingTest
|
|||||||
|
|
||||||
//---- class LineChartPanel -----------------------------------------------
|
//---- class LineChartPanel -----------------------------------------------
|
||||||
|
|
||||||
private static class LineChartPanel
|
static class LineChartPanel
|
||||||
extends JComponent
|
extends JComponent
|
||||||
implements Scrollable
|
implements Scrollable
|
||||||
{
|
{
|
||||||
private static final int SECOND_WIDTH = 200;
|
|
||||||
private static final int NEW_SEQUENCE_TIME_LAG = 500;
|
private static final int NEW_SEQUENCE_TIME_LAG = 500;
|
||||||
private static final int NEW_SEQUENCE_GAP = 20;
|
private static final int NEW_SEQUENCE_GAP = 20;
|
||||||
|
|
||||||
|
private int secondWidth = 1000;
|
||||||
|
|
||||||
private static class Data {
|
private static class Data {
|
||||||
final double value;
|
final double value;
|
||||||
|
final boolean dot;
|
||||||
final long time; // in milliseconds
|
final long time; // in milliseconds
|
||||||
|
|
||||||
Data( double value, long time ) {
|
Data( double value, boolean dot, long time ) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.dot = dot;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,8 +500,12 @@ public class FlatSmoothScrollingTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
void addValue( double value, Color chartColor ) {
|
void addValue( double value, Color chartColor ) {
|
||||||
|
addValue( value, false, chartColor );
|
||||||
|
}
|
||||||
|
|
||||||
|
void addValue( double value, boolean dot, Color chartColor ) {
|
||||||
List<Data> chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() );
|
List<Data> chartData = color2dataMap.computeIfAbsent( chartColor, k -> new ArrayList<>() );
|
||||||
chartData.add( new Data( value, System.nanoTime() / 1000000) );
|
chartData.add( new Data( value, dot, System.nanoTime() / 1000000) );
|
||||||
|
|
||||||
lastUsedChartColor = chartColor;
|
lastUsedChartColor = chartColor;
|
||||||
|
|
||||||
@@ -521,6 +528,10 @@ public class FlatSmoothScrollingTest
|
|||||||
this.updateDelayed = updateDelayed;
|
this.updateDelayed = updateDelayed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSecondWidth( int secondWidth ) {
|
||||||
|
this.secondWidth = secondWidth;
|
||||||
|
}
|
||||||
|
|
||||||
private void repaintAndRevalidate() {
|
private void repaintAndRevalidate() {
|
||||||
repaint();
|
repaint();
|
||||||
revalidate();
|
revalidate();
|
||||||
@@ -546,7 +557,7 @@ public class FlatSmoothScrollingTest
|
|||||||
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
|
private void paintImpl( Graphics2D g, int x, int y, int width, int height, double scaleFactor ) {
|
||||||
FlatUIUtils.setRenderingHints( g );
|
FlatUIUtils.setRenderingHints( g );
|
||||||
|
|
||||||
int secondWidth = (int) (SECOND_WIDTH * scaleFactor);
|
int secondWidth = (int) (this.secondWidth * scaleFactor);
|
||||||
int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor);
|
int seqGapWidth = (int) (NEW_SEQUENCE_GAP * scaleFactor);
|
||||||
|
|
||||||
Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray );
|
Color lineColor = FlatUIUtils.getUIColor( "Component.borderColor", Color.lightGray );
|
||||||
@@ -591,20 +602,32 @@ public class FlatSmoothScrollingTest
|
|||||||
|
|
||||||
g.setColor( chartColor );
|
g.setColor( chartColor );
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
int size = chartData.size();
|
int size = chartData.size();
|
||||||
for( int i = 0; i < size; i++ ) {
|
for( int i = 0; i < size; i++ ) {
|
||||||
Data data = chartData.get( i );
|
Data data = chartData.get( i );
|
||||||
int dy = (int) ((height - 1) * data.value);
|
int dy = (int) ((height - 1) * data.value);
|
||||||
|
|
||||||
|
if( data.dot ) {
|
||||||
|
int dotx = px;
|
||||||
|
if( i > 0 && data.time > ptime + NEW_SEQUENCE_TIME_LAG )
|
||||||
|
dotx += seqGapWidth;
|
||||||
|
int o = UIScale.scale( 1 );
|
||||||
|
int s = UIScale.scale( 3 );
|
||||||
|
g.fillRect( dotx - o, dy - o, s, s );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) {
|
if( data.time > ptime + NEW_SEQUENCE_TIME_LAG ) {
|
||||||
if( i > 0 && pcount == 0 )
|
if( !first && pcount == 0 )
|
||||||
g.drawLine( px, py, px + (int) (4 * scaleFactor), py );
|
g.drawLine( px, py, px + (int) (4 * scaleFactor), py );
|
||||||
|
|
||||||
// start new sequence
|
// start new sequence
|
||||||
seqTime = data.time;
|
seqTime = data.time;
|
||||||
seqX = (i > 0) ? px + seqGapWidth : 0;
|
seqX = !first ? px + seqGapWidth : 0;
|
||||||
px = seqX;
|
px = seqX;
|
||||||
pcount = 0;
|
pcount = 0;
|
||||||
|
first = false;
|
||||||
} else {
|
} else {
|
||||||
boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 );
|
boolean isTemporaryValue = isTemporaryValue( chartData, i ) || isTemporaryValue( chartData, i - 1 );
|
||||||
if( isTemporaryValue )
|
if( isTemporaryValue )
|
||||||
@@ -634,8 +657,14 @@ public class FlatSmoothScrollingTest
|
|||||||
if( i == 0 || i == chartData.size() - 1 )
|
if( i == 0 || i == chartData.size() - 1 )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
double valueBefore = chartData.get( i - 1 ).value;
|
Data dataBefore = chartData.get( i - 1 );
|
||||||
double valueAfter = chartData.get( i + 1 ).value;
|
Data dataAfter = chartData.get( i + 1 );
|
||||||
|
|
||||||
|
if( dataBefore.dot || dataAfter.dot )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
double valueBefore = dataBefore.value;
|
||||||
|
double valueAfter = dataAfter.value;
|
||||||
|
|
||||||
return valueBefore == valueAfter ||
|
return valueBefore == valueAfter ||
|
||||||
(i < chartData.size() - 2 && valueBefore == chartData.get( i + 2 ).value) ||
|
(i < chartData.size() - 2 && valueBefore == chartData.get( i + 2 ).value) ||
|
||||||
@@ -666,7 +695,7 @@ public class FlatSmoothScrollingTest
|
|||||||
px = seqX;
|
px = seqX;
|
||||||
} else {
|
} else {
|
||||||
// line in sequence
|
// line in sequence
|
||||||
int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * SECOND_WIDTH));
|
int dx = (int) (seqX + (((data.time - seqTime) / 1000.) * secondWidth));
|
||||||
px = dx;
|
px = dx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,7 +720,7 @@ public class FlatSmoothScrollingTest
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) {
|
public int getScrollableUnitIncrement( Rectangle visibleRect, int orientation, int direction ) {
|
||||||
return SECOND_WIDTH;
|
return secondWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user