Merge pull request #1705 from SiboVG/updated_updater
Updated software updater layout
This commit is contained in:
commit
2e96683509
@ -306,7 +306,7 @@ pref.dlg.Allthrustcurvefiles = All thrust curve files (*.eng; *.rse; *.zip; dire
|
|||||||
pref.dlg.RASPfiles = RASP motor files (*.eng)
|
pref.dlg.RASPfiles = RASP motor files (*.eng)
|
||||||
pref.dlg.RockSimfiles = RockSim engine files (*.rse)
|
pref.dlg.RockSimfiles = RockSim engine files (*.rse)
|
||||||
pref.dlg.ZIParchives = ZIP archives (*.zip)
|
pref.dlg.ZIParchives = ZIP archives (*.zip)
|
||||||
pref.dlg.checkbox.Checkupdates = Check for software updates at startup
|
pref.dlg.checkbox.Checkupdates = Always check for software updates at startup
|
||||||
pref.dlg.checkbox.Checkupdates.ttip = Check for software updates every time you start up OpenRocket
|
pref.dlg.checkbox.Checkupdates.ttip = Check for software updates every time you start up OpenRocket
|
||||||
pref.dlg.checkbox.CheckBetaupdates = Also check for beta releases
|
pref.dlg.checkbox.CheckBetaupdates = Also check for beta releases
|
||||||
pref.dlg.checkbox.CheckBetaupdates.ttip = If checked, beta release updates are also notified. If unchecked, only official releases are considered.
|
pref.dlg.checkbox.CheckBetaupdates.ttip = If checked, beta release updates are also notified. If unchecked, only official releases are considered.
|
||||||
@ -363,11 +363,11 @@ update.dlg.latestVersion = You are running the latest version of OpenRocket, ver
|
|||||||
update.dlg.newerVersion.title = Newer version detected
|
update.dlg.newerVersion.title = Newer version detected
|
||||||
update.dlg.newerVersion = You are either running a test/unofficial release of OpenRocket, or you have a time machine and are running an official release from the future.\n\nYour version: %s\nLatest official release: %s
|
update.dlg.newerVersion = You are either running a test/unofficial release of OpenRocket, or you have a time machine and are running an official release from the future.\n\nYour version: %s\nLatest official release: %s
|
||||||
update.dlg.updateAvailable.title = Update available
|
update.dlg.updateAvailable.title = Update available
|
||||||
update.dlg.updateAvailable.txtPane.title = OpenRocket version %s available!
|
update.dlg.updateAvailable.lbl.title = A new version of OpenRocket is available!
|
||||||
update.dlg.updateAvailable.txtPane.yourVersion = Your current version: %s
|
update.dlg.updateAvailable.lbl.yourVersion = OpenRocket %s is available! \u2015 you have %s. Would you like to download it now?
|
||||||
update.dlg.updateAvailable.txtPane.changelog = Changelog
|
update.dlg.updateAvailable.lbl.releaseNotes = Release notes:
|
||||||
update.dlg.updateAvailable.txtPane.readMore = Read more on GitHub
|
update.dlg.updateAvailable.txtPane.readMore = Read more on GitHub
|
||||||
update.dlg.updateAvailable.but.install = Install update
|
update.dlg.updateAvailable.but.install = Install Update
|
||||||
update.dlg.updateAvailable.combo.noDownloads = No downloads available
|
update.dlg.updateAvailable.combo.noDownloads = No downloads available
|
||||||
update.fetcher.badResponse = Bad response code from server: %d
|
update.fetcher.badResponse = Bad response code from server: %d
|
||||||
update.fetcher.badConnection = Could not connect to the GitHub server. Please check your internet connection.
|
update.fetcher.badConnection = Could not connect to the GitHub server. Please check your internet connection.
|
||||||
|
@ -58,6 +58,7 @@ public abstract class Preferences implements ChangeSource {
|
|||||||
|
|
||||||
private static final String CHECK_UPDATES = "CheckUpdates";
|
private static final String CHECK_UPDATES = "CheckUpdates";
|
||||||
|
|
||||||
|
private static final String IGNORE_VERSIONS = "IgnoreVersions";
|
||||||
private static final String CHECK_BETA_UPDATES = "CheckBetaUpdates";
|
private static final String CHECK_BETA_UPDATES = "CheckBetaUpdates";
|
||||||
|
|
||||||
public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
|
public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
|
||||||
@ -150,6 +151,14 @@ public abstract class Preferences implements ChangeSource {
|
|||||||
this.putBoolean(CHECK_UPDATES, check);
|
this.putBoolean(CHECK_UPDATES, check);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final List<String> getIgnoreVersions() {
|
||||||
|
return List.of(this.getString(IGNORE_VERSIONS, "").split("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setIgnoreVersions(List<String> versions) {
|
||||||
|
this.putString(IGNORE_VERSIONS, String.join("\n", versions));
|
||||||
|
}
|
||||||
|
|
||||||
public final boolean getCheckBetaUpdates() {
|
public final boolean getCheckBetaUpdates() {
|
||||||
return this.getBoolean(CHECK_BETA_UPDATES, BuildProperties.getDefaultCheckBetaUpdates());
|
return this.getBoolean(CHECK_BETA_UPDATES, BuildProperties.getDefaultCheckBetaUpdates());
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ public class AssetHandler {
|
|||||||
mapExtensionToPlatform.put(".sh", new UpdatePlatform[] {UpdatePlatform.LINUX, UpdatePlatform.UNIX});
|
mapExtensionToPlatform.put(".sh", new UpdatePlatform[] {UpdatePlatform.LINUX, UpdatePlatform.UNIX});
|
||||||
mapExtensionToPlatform.put(".jar", new UpdatePlatform[] {UpdatePlatform.JAR});
|
mapExtensionToPlatform.put(".jar", new UpdatePlatform[] {UpdatePlatform.JAR});
|
||||||
|
|
||||||
mapPlatformToName.put(UpdatePlatform.MAC_OS, "Mac OS");
|
mapPlatformToName.put(UpdatePlatform.MAC_OS, "macOS");
|
||||||
mapPlatformToName.put(UpdatePlatform.WINDOWS, "Windows");
|
mapPlatformToName.put(UpdatePlatform.WINDOWS, "Windows");
|
||||||
mapPlatformToName.put(UpdatePlatform.LINUX, "Linux");
|
mapPlatformToName.put(UpdatePlatform.LINUX, "Linux");
|
||||||
mapPlatformToName.put(UpdatePlatform.UNIX, "Linux");
|
mapPlatformToName.put(UpdatePlatform.UNIX, "Linux");
|
||||||
@ -42,7 +42,7 @@ public class AssetHandler {
|
|||||||
/**
|
/**
|
||||||
* Maps a list of asset URLs to their respective operating platform name.
|
* Maps a list of asset URLs to their respective operating platform name.
|
||||||
* E.g. "https://github.com/openrocket/openrocket/releases/download/release-15.03/OpenRocket-15.03.dmg" is mapped a
|
* E.g. "https://github.com/openrocket/openrocket/releases/download/release-15.03/OpenRocket-15.03.dmg" is mapped a
|
||||||
* map element with "Mac OS" as key and the url as value.
|
* map element with "macOS" as key and the url as value.
|
||||||
* @param urls list of asset URLs
|
* @param urls list of asset URLs
|
||||||
* @return map with as key the operating platform name and as value the corresponding asset URL
|
* @return map with as key the operating platform name and as value the corresponding asset URL
|
||||||
*/
|
*/
|
||||||
@ -70,7 +70,7 @@ public class AssetHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of a platform (e.g. for Platform.MAC_OS, return "Mac OS")
|
* Get the name of a platform (e.g. for Platform.MAC_OS, return "macOS")
|
||||||
* @param platform platform to get the name from
|
* @param platform platform to get the name from
|
||||||
* @return name of the platform
|
* @return name of the platform
|
||||||
*/
|
*/
|
||||||
|
@ -7,6 +7,7 @@ import java.awt.Insets;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ import net.sf.openrocket.communication.AssetHandler;
|
|||||||
import net.sf.openrocket.communication.AssetHandler.UpdatePlatform;
|
import net.sf.openrocket.communication.AssetHandler.UpdatePlatform;
|
||||||
import net.sf.openrocket.communication.ReleaseInfo;
|
import net.sf.openrocket.communication.ReleaseInfo;
|
||||||
import net.sf.openrocket.communication.UpdateInfo;
|
import net.sf.openrocket.communication.UpdateInfo;
|
||||||
|
import net.sf.openrocket.gui.components.StyledLabel;
|
||||||
|
import net.sf.openrocket.gui.configdialog.CommonStrings;
|
||||||
import net.sf.openrocket.gui.util.GUIUtil;
|
import net.sf.openrocket.gui.util.GUIUtil;
|
||||||
import net.sf.openrocket.gui.util.Icons;
|
import net.sf.openrocket.gui.util.Icons;
|
||||||
import net.sf.openrocket.gui.util.SwingPreferences;
|
import net.sf.openrocket.gui.util.SwingPreferences;
|
||||||
@ -55,8 +58,19 @@ public class UpdateInfoDialog extends JDialog {
|
|||||||
|
|
||||||
JPanel panel = new JPanel(new MigLayout("insets n n 8px n, fill"));
|
JPanel panel = new JPanel(new MigLayout("insets n n 8px n, fill"));
|
||||||
|
|
||||||
panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")),
|
panel.add(new JLabel(Icons.getScaledIcon(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket"), 0.6)),
|
||||||
"split, span, top");
|
"spany, top, gapright 20px, cell 0 0");
|
||||||
|
|
||||||
|
// OpenRocket version available!
|
||||||
|
panel.add(new StyledLabel(trans.get("update.dlg.updateAvailable.lbl.title"), 8, StyledLabel.Style.BOLD), "spanx, wrap");
|
||||||
|
|
||||||
|
// Your version
|
||||||
|
ReleaseInfo release = info.getLatestRelease();
|
||||||
|
panel.add(new StyledLabel(String.format(trans.get("update.dlg.updateAvailable.lbl.yourVersion"),
|
||||||
|
release.getReleaseName(), BuildProperties.getVersion()), -1, StyledLabel.Style.PLAIN), "skip 1, spanx, wrap para");
|
||||||
|
|
||||||
|
// Release notes
|
||||||
|
panel.add(new StyledLabel(trans.get("update.dlg.updateAvailable.lbl.releaseNotes"), 1, StyledLabel.Style.BOLD), "spanx, wrap");
|
||||||
|
|
||||||
// Release information box
|
// Release information box
|
||||||
final JTextPane textPane = new JTextPane();
|
final JTextPane textPane = new JTextPane();
|
||||||
@ -65,18 +79,10 @@ public class UpdateInfoDialog extends JDialog {
|
|||||||
textPane.setMargin(new Insets(10, 10, 40, 10));
|
textPane.setMargin(new Insets(10, 10, 40, 10));
|
||||||
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
|
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
|
||||||
|
|
||||||
ReleaseInfo release = info.getLatestRelease();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
// OpenRocket version available!
|
|
||||||
sb.append("<html>");
|
sb.append("<html>");
|
||||||
sb.append(String.format("<h1>%s</h1>", String.format(trans.get("update.dlg.updateAvailable.txtPane.title"), release.getReleaseName())));
|
|
||||||
|
|
||||||
// Your version
|
// Release notes
|
||||||
sb.append(String.format("<i>%s</i> <br><br>", String.format(trans.get("update.dlg.updateAvailable.txtPane.yourVersion"), BuildProperties.getVersion())));
|
|
||||||
|
|
||||||
// Changelog
|
|
||||||
sb.append(String.format("<h2>%s</h2>", trans.get("update.dlg.updateAvailable.txtPane.changelog")));
|
|
||||||
String releaseNotes = release.getReleaseNotes();
|
String releaseNotes = release.getReleaseNotes();
|
||||||
releaseNotes = releaseNotes.replaceAll("^\"|\"$", ""); // Remove leading and trailing quotations
|
releaseNotes = releaseNotes.replaceAll("^\"|\"$", ""); // Remove leading and trailing quotations
|
||||||
sb.append(MarkdownUtil.toHtml(releaseNotes)).append("<br><br>");
|
sb.append(MarkdownUtil.toHtml(releaseNotes)).append("<br><br>");
|
||||||
@ -103,7 +109,7 @@ public class UpdateInfoDialog extends JDialog {
|
|||||||
textPane.setText(sb.toString());
|
textPane.setText(sb.toString());
|
||||||
textPane.setCaretPosition(0); // Scroll to the top
|
textPane.setCaretPosition(0); // Scroll to the top
|
||||||
|
|
||||||
panel.add(new JScrollPane(textPane), "left, grow, span, push, gapleft 40px, gapbottom 6px, wrap");
|
panel.add(new JScrollPane(textPane), "skip 1, left, spanx, grow, push, gapbottom 6px, wrap");
|
||||||
|
|
||||||
//// Check for software updates at startup
|
//// Check for software updates at startup
|
||||||
JCheckBox checkAtStartup = new JCheckBox(trans.get("pref.dlg.checkbox.Checkupdates"));
|
JCheckBox checkAtStartup = new JCheckBox(trans.get("pref.dlg.checkbox.Checkupdates"));
|
||||||
@ -116,9 +122,36 @@ public class UpdateInfoDialog extends JDialog {
|
|||||||
preferences.setCheckUpdates(checkAtStartup.isSelected());
|
preferences.setCheckUpdates(checkAtStartup.isSelected());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
panel.add(checkAtStartup);
|
panel.add(checkAtStartup, "skip 1, spanx, wrap");
|
||||||
|
|
||||||
// Install operating system combo box
|
// Lower row buttons
|
||||||
|
//// Remind me later button
|
||||||
|
JButton btnLater = new SelectColorButton("Remind Me Later");
|
||||||
|
btnLater.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
UpdateInfoDialog.this.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
panel.add(btnLater, "skip 1, split 2");
|
||||||
|
|
||||||
|
//// Skip this version button
|
||||||
|
JButton btnSkip = new SelectColorButton("Skip This Version");
|
||||||
|
btnSkip.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
List<String> ignoreVersions = new ArrayList<>(preferences.getIgnoreVersions());
|
||||||
|
String ignore = release.getReleaseName();
|
||||||
|
if (!ignoreVersions.contains(ignore)) {
|
||||||
|
ignoreVersions.add(ignore);
|
||||||
|
preferences.setIgnoreVersions(ignoreVersions);
|
||||||
|
}
|
||||||
|
UpdateInfoDialog.this.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
panel.add(btnSkip);
|
||||||
|
|
||||||
|
//// Install operating system combo box
|
||||||
List<String> assetURLs = release.getAssetURLs();
|
List<String> assetURLs = release.getAssetURLs();
|
||||||
Map<UpdatePlatform, String> mappedAssets = AssetHandler.mapURLToPlatform(assetURLs);
|
Map<UpdatePlatform, String> mappedAssets = AssetHandler.mapURLToPlatform(assetURLs);
|
||||||
JComboBox<Object> comboBox;
|
JComboBox<Object> comboBox;
|
||||||
@ -141,7 +174,7 @@ public class UpdateInfoDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
panel.add(comboBox, "pushx, right");
|
panel.add(comboBox, "pushx, right");
|
||||||
|
|
||||||
// Install update button
|
//// Install update button
|
||||||
JButton btnInstall = new SelectColorButton(trans.get("update.dlg.updateAvailable.but.install"));
|
JButton btnInstall = new SelectColorButton(trans.get("update.dlg.updateAvailable.but.install"));
|
||||||
btnInstall.addActionListener(new ActionListener() {
|
btnInstall.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -159,25 +192,15 @@ public class UpdateInfoDialog extends JDialog {
|
|||||||
if (mappedAssets == null || mappedAssets.size() == 0) {
|
if (mappedAssets == null || mappedAssets.size() == 0) {
|
||||||
btnInstall.setEnabled(false);
|
btnInstall.setEnabled(false);
|
||||||
}
|
}
|
||||||
panel.add(btnInstall, "gapright 20");
|
panel.add(btnInstall, "wrap");
|
||||||
|
|
||||||
// Cancel button
|
panel.setPreferredSize(new Dimension(850, 700));
|
||||||
JButton btnCancel = new SelectColorButton(trans.get("button.cancel"));
|
|
||||||
btnCancel.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
UpdateInfoDialog.this.dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
panel.add(btnCancel);
|
|
||||||
|
|
||||||
panel.setPreferredSize(new Dimension(900, 600));
|
|
||||||
|
|
||||||
this.add(panel);
|
this.add(panel);
|
||||||
|
|
||||||
this.pack();
|
this.pack();
|
||||||
this.setLocationRelativeTo(null);
|
this.setLocationRelativeTo(null);
|
||||||
GUIUtil.setDisposableDialogOptions(this, btnCancel);
|
GUIUtil.setDisposableDialogOptions(this, btnLater);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -326,6 +326,13 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
|
|||||||
// Nothing went wrong (yay!)
|
// Nothing went wrong (yay!)
|
||||||
ReleaseStatus status = info.getReleaseStatus();
|
ReleaseStatus status = info.getReleaseStatus();
|
||||||
ReleaseInfo release = info.getLatestRelease();
|
ReleaseInfo release = info.getLatestRelease();
|
||||||
|
|
||||||
|
// Do nothing if the release is part of the ignore versions
|
||||||
|
if (preferences.getIgnoreVersions().contains(release.getReleaseName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display software updater dialog, based on the current build version status
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case LATEST:
|
case LATEST:
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
|
@ -7,6 +7,7 @@ import java.awt.desktop.PreferencesHandler;
|
|||||||
import java.awt.desktop.QuitHandler;
|
import java.awt.desktop.QuitHandler;
|
||||||
import java.awt.desktop.AppReopenedListener;
|
import java.awt.desktop.AppReopenedListener;
|
||||||
|
|
||||||
|
import net.sf.openrocket.communication.UpdateInfoRetriever;
|
||||||
import net.sf.openrocket.gui.util.DummyFrameMenuOSX;
|
import net.sf.openrocket.gui.util.DummyFrameMenuOSX;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -56,6 +57,10 @@ final class OSXSetup {
|
|||||||
if (BasicFrame.isFramesEmpty()) {
|
if (BasicFrame.isFramesEmpty()) {
|
||||||
log.info("App re-opened");
|
log.info("App re-opened");
|
||||||
BasicFrame.reopen();
|
BasicFrame.reopen();
|
||||||
|
|
||||||
|
// Also check for software updates
|
||||||
|
final UpdateInfoRetriever updateRetriever = SwingStartup.startUpdateChecker();
|
||||||
|
SwingStartup.checkUpdateStatus(updateRetriever);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -193,15 +193,7 @@ public class SwingStartup {
|
|||||||
guiModule.startLoader();
|
guiModule.startLoader();
|
||||||
|
|
||||||
// Start update info fetching
|
// Start update info fetching
|
||||||
final UpdateInfoRetriever updateRetriever;
|
final UpdateInfoRetriever updateRetriever = startUpdateChecker();
|
||||||
if (Application.getPreferences().getCheckUpdates()) {
|
|
||||||
log.info("Starting update check");
|
|
||||||
updateRetriever = new UpdateInfoRetriever();
|
|
||||||
updateRetriever.startFetchUpdateInfo();
|
|
||||||
} else {
|
|
||||||
log.info("Update check disabled");
|
|
||||||
updateRetriever = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the best available look-and-feel
|
// Set the best available look-and-feel
|
||||||
log.info("Setting best LAF");
|
log.info("Setting best LAF");
|
||||||
@ -249,8 +241,20 @@ public class SwingStartup {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UpdateInfoRetriever startUpdateChecker() {
|
||||||
|
final UpdateInfoRetriever updateRetriever;
|
||||||
|
if (Application.getPreferences().getCheckUpdates()) {
|
||||||
|
log.info("Starting update check");
|
||||||
|
updateRetriever = new UpdateInfoRetriever();
|
||||||
|
updateRetriever.startFetchUpdateInfo();
|
||||||
|
} else {
|
||||||
|
log.info("Update check disabled");
|
||||||
|
updateRetriever = null;
|
||||||
|
}
|
||||||
|
return updateRetriever;
|
||||||
|
}
|
||||||
|
|
||||||
private void checkUpdateStatus(final UpdateInfoRetriever updateRetriever) {
|
public static void checkUpdateStatus(final UpdateInfoRetriever updateRetriever) {
|
||||||
if (updateRetriever == null)
|
if (updateRetriever == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -268,10 +272,12 @@ public class SwingStartup {
|
|||||||
if (!updateRetriever.isRunning()) {
|
if (!updateRetriever.isRunning()) {
|
||||||
timer.stop();
|
timer.stop();
|
||||||
|
|
||||||
|
final SwingPreferences preferences = (SwingPreferences) Application.getPreferences();
|
||||||
UpdateInfo info = updateRetriever.getUpdateInfo();
|
UpdateInfo info = updateRetriever.getUpdateInfo();
|
||||||
|
|
||||||
// Only display something when an update is found
|
// Only display something when an update is found
|
||||||
if (info != null && info.getException() == null && info.getReleaseStatus() == ReleaseStatus.OLDER) {
|
if (info != null && info.getException() == null && info.getReleaseStatus() == ReleaseStatus.OLDER &&
|
||||||
|
!preferences.getIgnoreVersions().contains(info.getLatestRelease().getReleaseName())) {
|
||||||
UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
|
UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
|
||||||
infoDialog.setVisible(true);
|
infoDialog.setVisible(true);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user