Show Identicon for offerers or trading peers onion address and indicate repeated trades, Display additional info for Altcoins at buy/sell buttons

This commit is contained in:
Manfred Karrer 2016-03-17 17:58:37 +01:00
parent a3aaee811d
commit e6e8fc56fc
39 changed files with 303 additions and 25 deletions

View File

@ -34,7 +34,6 @@
-fx-image: url("../../../images/buy_white.png");
}
#image-sell {
-fx-image: url("../../../images/sell.png");
}
@ -213,3 +212,64 @@
-fx-image: url("../../../images/link.png");
}
#avatar_1 {
-fx-image: url("../../../images/avatars/avatar_1.png");
}
#avatar_2 {
-fx-image: url("../../../images/avatars/avatar_2.png");
}
#avatar_3 {
-fx-image: url("../../../images/avatars/avatar_3.png");
}
#avatar_4 {
-fx-image: url("../../../images/avatars/avatar_4.png");
}
#avatar_5 {
-fx-image: url("../../../images/avatars/avatar_5.png");
}
#avatar_6 {
-fx-image: url("../../../images/avatars/avatar_6.png");
}
#avatar_7 {
-fx-image: url("../../../images/avatars/avatar_7.png");
}
#avatar_8 {
-fx-image: url("../../../images/avatars/avatar_8.png");
}
#avatar_9 {
-fx-image: url("../../../images/avatars/avatar_9.png");
}
#avatar_10 {
-fx-image: url("../../../images/avatars/avatar_10.png");
}
#avatar_11 {
-fx-image: url("../../../images/avatars/avatar_11.png");
}
#avatar_12 {
-fx-image: url("../../../images/avatars/avatar_12.png");
}
#avatar_13 {
-fx-image: url("../../../images/avatars/avatar_13.png");
}
#avatar_14 {
-fx-image: url("../../../images/avatars/avatar_14.png");
}
#avatar_15 {
-fx-image: url("../../../images/avatars/avatar_15.png");
}

View File

@ -32,6 +32,7 @@ import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView;
import io.bitsquare.gui.main.offer.OfferView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.CryptoCurrency;
@ -43,6 +44,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
@ -153,6 +155,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
paymentMethodColumn = getPaymentMethodColumn();
tableView.getColumns().add(paymentMethodColumn);
tableView.getColumns().add(getActionColumn());
tableView.getColumns().add(getAvatarColumn());
tableView.getSortOrder().add(priceColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
@ -192,17 +195,18 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
paymentMethodComboBox.setOnAction(e -> model.onSetPaymentMethod(paymentMethodComboBox.getSelectionModel().getSelectedItem()));
createOfferButton.setOnAction(e -> onCreateOffer());
priceColumn.textProperty().bind(createStringBinding(
() -> !model.showAllTradeCurrenciesProperty.get() ?
"Price in " + model.tradeCurrencyCode.get() + "/BTC" :
"Price",
model.tradeCurrencyCode,
model.showAllTradeCurrenciesProperty));
volumeColumn.textProperty().bind(createStringBinding(
() -> !model.showAllTradeCurrenciesProperty.get() ?
"Amount in " + model.tradeCurrencyCode.get() + " (Min.)" :
"Amount (Min.)",
() -> {
setDirectionTitles();
String tradeCurrencyCode = model.tradeCurrencyCode.get();
boolean showAllTradeCurrencies = model.showAllTradeCurrenciesProperty.get();
priceColumn.setText(!showAllTradeCurrencies ?
"Price in " + tradeCurrencyCode + "/BTC" :
"Price");
return !showAllTradeCurrencies ?
"Amount in " + tradeCurrencyCode + " (Min.)" :
"Amount (Min.)";
},
model.tradeCurrencyCode,
model.showAllTradeCurrenciesProperty));
@ -212,6 +216,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
tableView.setItems(model.getOfferList());
priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
tableView.sort();
}
@Override
@ -237,17 +242,22 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
ImageView iconView = new ImageView();
createOfferButton.setGraphic(iconView);
if (direction == Offer.Direction.SELL) {
offerBookTitle.setText("Offers for buying bitcoin ");
createOfferButton.setId("sell-button-big");
createOfferButton.setText("Create new offer for selling bitcoin");
iconView.setId("image-sell-white");
} else {
offerBookTitle.setText("Offers for selling bitcoin ");
createOfferButton.setId("buy-button-big");
createOfferButton.setText("Create new offer for buying bitcoin");
iconView.setId("image-buy-white");
}
iconView.setId(direction == Offer.Direction.SELL ? "image-sell-white" : "image-buy-white");
createOfferButton.setId(direction == Offer.Direction.SELL ? "sell-button-big" : "buy-button-big");
setDirectionTitles();
}
private void setDirectionTitles() {
Offer.Direction direction = model.getDirection();
String directionText = direction == Offer.Direction.BUY ? "buying" : "selling";
String mirroredDirectionText = direction == Offer.Direction.SELL ? "buying" : "selling";
TradeCurrency selectedTradeCurrency = model.getSelectedTradeCurrency();
String postFix = selectedTradeCurrency instanceof FiatCurrency || model.showAllTradeCurrenciesProperty.get() ? "" :
" (" + mirroredDirectionText + " " + selectedTradeCurrency.getName() + ")";
offerBookTitle.setText("Offers for " + directionText + " bitcoin" + postFix);
createOfferButton.setText("Create new offer for " + directionText + " bitcoin" + postFix);
}
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
@ -568,5 +578,44 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getAvatarColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>("") {
{
setMinWidth(32);
setMaxWidth(32);
setSortable(true);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
@Override
public void updateItem(final OfferBookListItem newItem, boolean empty) {
super.updateItem(newItem, empty);
if (newItem != null && !empty) {
String hostName = newItem.getOffer().getOwnerNodeAddress().hostName;
int numPastTrades = model.getNumPastTrades(newItem.getOffer());
boolean hasTraded = numPastTrades > 0;
String tooltipText = hasTraded ? "Offerers onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that offerer." : "Offerers onion address: " + hostName;
Node identIcon = ImageUtil.getIdentIcon(hostName, tooltipText, hasTraded);
if (identIcon != null)
setGraphic(identIcon);
} else {
setGraphic(null);
}
}
};
}
});
return column;
}
}

View File

@ -33,6 +33,8 @@ import io.bitsquare.locale.*;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.payment.*;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.user.Preferences;
@ -69,6 +71,7 @@ class OfferBookViewModel extends ActivatableViewModel {
private final Preferences preferences;
private final P2PService p2PService;
private final PriceFeed priceFeed;
private ClosedTradableManager closedTradableManager;
private Navigation navigation;
final BSFormatter formatter;
@ -101,6 +104,7 @@ class OfferBookViewModel extends ActivatableViewModel {
@Inject
public OfferBookViewModel(User user, OpenOfferManager openOfferManager, OfferBook offerBook,
Preferences preferences, P2PService p2PService, PriceFeed priceFeed,
ClosedTradableManager closedTradableManager,
Navigation navigation, BSFormatter formatter) {
super();
@ -110,6 +114,7 @@ class OfferBookViewModel extends ActivatableViewModel {
this.preferences = preferences;
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.closedTradableManager = closedTradableManager;
this.navigation = navigation;
this.formatter = formatter;
@ -435,4 +440,12 @@ class OfferBookViewModel extends ActivatableViewModel {
private boolean isEditEntry(String id) {
return id.equals(EDIT_FLAG);
}
public int getNumPastTrades(Offer offer) {
return closedTradableManager.getClosedTrades().stream()
.filter(e -> e instanceof Trade && ((Trade) e).getTradingPeerNodeAddress() != null &&
((Trade) e).getTradingPeerNodeAddress().hostName.equals(offer.getOffererNodeAddress().hostName))
.collect(Collectors.toSet())
.size();
}
}

View File

@ -35,6 +35,7 @@
<TableColumn text="Trade amount" fx:id="volumeColumn" minWidth="130"/>
<TableColumn text="Trade type" fx:id="directionColumn" minWidth="80"/>
<TableColumn text="State" fx:id="stateColumn" minWidth="80" sortable="false"/>
<TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/>
</columns>
</TableView>

View File

@ -23,11 +23,13 @@ import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.trade.Tradable;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.offer.OpenOffer;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
@ -41,7 +43,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
TableView<ClosedTradableListItem> table;
@FXML
TableColumn<ClosedTradableListItem, ClosedTradableListItem> priceColumn, amountColumn, volumeColumn,
directionColumn, dateColumn, tradeIdColumn, stateColumn;
directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn;
private final BSFormatter formatter;
private final OfferDetailsWindow offerDetailsWindow;
private final TradeDetailsWindow tradeDetailsWindow;
@ -63,6 +65,7 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
setVolumeColumnCellFactory();
setDateColumnCellFactory();
setStateColumnCellFactory();
setAvatarColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No closed trades available"));
@ -155,6 +158,34 @@ public class ClosedTradesView extends ActivatableViewAndModel<VBox, ClosedTrades
});
}
private TableColumn<ClosedTradableListItem, ClosedTradableListItem> setAvatarColumnCellFactory() {
avatarColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
avatarColumn.setCellFactory(
new Callback<TableColumn<ClosedTradableListItem, ClosedTradableListItem>, TableCell<ClosedTradableListItem,
ClosedTradableListItem>>() {
@Override
public TableCell<ClosedTradableListItem, ClosedTradableListItem> call(TableColumn<ClosedTradableListItem, ClosedTradableListItem> column) {
return new TableCell<ClosedTradableListItem, ClosedTradableListItem>() {
@Override
public void updateItem(final ClosedTradableListItem newItem, boolean empty) {
super.updateItem(newItem, empty);
if (newItem != null && !empty && newItem.getTradable() instanceof Trade) {
String hostName = ((Trade) newItem.getTradable()).getTradingPeerNodeAddress().hostName;
Node identIcon = ImageUtil.getIdentIcon(hostName, "Trading peers onion address: " + hostName, true);
if (identIcon != null)
setGraphic(identIcon);
} else {
setGraphic(null);
}
}
};
}
});
return avatarColumn;
}
private void setAmountColumnCellFactory() {
amountColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));

View File

@ -52,6 +52,7 @@
</TableColumn>
<TableColumn text="Payment method" fx:id="paymentMethodColumn" minWidth="120"/>
<TableColumn text="My role" fx:id="roleColumn" minWidth="120" maxWidth="120"/>
<TableColumn text="" fx:id="avatarColumn" minWidth="32" maxWidth="32"/>
</columns>
</TableView>
</VBox>

View File

@ -25,9 +25,11 @@ import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.ImageUtil;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
@ -56,7 +58,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
@FXML
TableColumn<PendingTradesListItem, Fiat> priceColumn, tradeVolumeColumn;
@FXML
TableColumn<PendingTradesListItem, PendingTradesListItem> roleColumn, paymentMethodColumn, idColumn, dateColumn;
TableColumn<PendingTradesListItem, PendingTradesListItem> avatarColumn, roleColumn, paymentMethodColumn, idColumn, dateColumn;
@FXML
TableColumn<PendingTradesListItem, Coin> tradeAmountColumn;
@ -88,6 +90,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
setVolumeColumnCellFactory();
setPaymentMethodColumnCellFactory();
setRoleColumnCellFactory();
setAvatarColumnCellFactory();
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setPlaceholder(new Label("No pending trades available"));
@ -365,5 +368,37 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
}
});
}
private TableColumn<PendingTradesListItem, PendingTradesListItem> setAvatarColumnCellFactory() {
avatarColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
avatarColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem newItem, boolean empty) {
super.updateItem(newItem, empty);
if (newItem != null && !empty && newItem.getTrade().getTradingPeerNodeAddress() != null) {
String hostName = newItem.getTrade().getTradingPeerNodeAddress().hostName;
int numPastTrades = model.getNumPastTrades(newItem.getTrade());
boolean hasTraded = numPastTrades > 0;
String tooltipText = hasTraded ? "Trading peers onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that peer." : "Trading peers onion address: " + hostName;
Node identIcon = ImageUtil.getIdentIcon(hostName, tooltipText, hasTraded);
if (identIcon != null)
setGraphic(identIcon);
} else {
setGraphic(null);
}
}
};
}
});
return avatarColumn;
}
}

View File

@ -30,6 +30,7 @@ import io.bitsquare.p2p.P2PService;
import io.bitsquare.payment.PaymentMethod;
import io.bitsquare.trade.Contract;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.User;
import javafx.beans.property.*;
@ -38,6 +39,8 @@ import org.bitcoinj.core.BlockChainListener;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import java.util.stream.Collectors;
import static io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel.SellerState.*;
public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataModel> implements ViewModel {
@ -70,6 +73,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
public final P2PService p2PService;
public final User user;
private ClosedTradableManager closedTradableManager;
public final Clock clock;
private final ObjectProperty<BuyerState> buyerState = new SimpleObjectProperty<>();
@ -88,6 +92,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
BtcAddressValidator btcAddressValidator,
P2PService p2PService,
User user,
ClosedTradableManager closedTradableManager,
Clock clock) {
super(dataModel);
@ -95,6 +100,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
this.btcAddressValidator = btcAddressValidator;
this.p2PService = p2PService;
this.user = user;
this.closedTradableManager = closedTradableManager;
this.clock = clock;
}
@ -256,6 +262,13 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
return dataModel.getOffer() != null && dataModel.getOffer().getPaymentMethod().equals(PaymentMethod.BLOCK_CHAINS);
}
public int getNumPastTrades(Trade trade) {
return closedTradableManager.getClosedTrades().stream()
.filter(e -> e instanceof Trade && ((Trade) e).getTradingPeerNodeAddress() != null &&
((Trade) e).getTradingPeerNodeAddress().hostName.equals(trade.getTradingPeerNodeAddress().hostName))
.collect(Collectors.toSet())
.size();
}
///////////////////////////////////////////////////////////////////////////////////////////
// States

View File

@ -19,11 +19,21 @@ package io.bitsquare.gui.util;
import com.sun.javafx.tk.quantum.QuantumToolkit;
import io.bitsquare.locale.Country;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class ImageUtil {
private static final Logger log = LoggerFactory.getLogger(ImageUtil.class);
@ -55,6 +65,71 @@ public class ImageUtil {
}
public static boolean isRetina() {
return ((QuantumToolkit) QuantumToolkit.getToolkit()).getMaxRenderScale() > 1.9f;
float maxRenderScale = ((QuantumToolkit) QuantumToolkit.getToolkit()).getMaxRenderScale();
boolean isRetina = maxRenderScale > 1.9f;
log.info("isRetina=" + isRetina + " / maxRenderScale=" + maxRenderScale);
return isRetina;
}
public static Node getIdentIcon(String hostName, String tooltipText, boolean hasTraded) {
if (!hostName.isEmpty()) {
// for testing locally we use a random hostname to get dif. colors
if (hostName.startsWith("localhost"))
hostName = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
int maxIndices = 15;
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] bytes = md.digest(hostName.getBytes());
int intValue = Math.abs(((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16)
| ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF));
int index = (intValue % maxIndices) + 1;
int red = intValue % 256;
int green = (intValue >> 8) % 64; // we use green for marking repeated trades, so avoid it in main bg color
int blue = (intValue >> 16) % 256;
ImageView iconView = new ImageView();
iconView.setId("avatar_" + index);
iconView.setScaleX(intValue % 2 == 0 ? 1d : -1d);
double size = 26;
Group iconGroup = new Group();
Color color = Color.rgb(red, green, blue);
color = color.deriveColor(1, 0.6, 1, 1); // reduce saturation
if (hasTraded) {
Canvas outerBg = new Canvas(size, size);
GraphicsContext gc = outerBg.getGraphicsContext2D();
gc.setFill(Color.rgb(0, 170, 51)); // green
gc.fillOval(0, 0, size, size);
outerBg.setLayoutY(1);
Canvas innerBg = new Canvas(size, size);
GraphicsContext gc2 = innerBg.getGraphicsContext2D();
gc2.setFill(color);
gc2.fillOval(2, 2, size - 4, size - 4);
innerBg.setLayoutY(1);
iconGroup.getChildren().addAll(outerBg, innerBg, iconView);
} else {
Canvas bg = new Canvas(size, size);
GraphicsContext gc = bg.getGraphicsContext2D();
gc.setFill(color);
gc.fillOval(0, 0, size, size);
bg.setLayoutY(1);
iconGroup.getChildren().addAll(bg, iconView);
}
Tooltip.install(iconGroup, new Tooltip(tooltipText));
return iconGroup;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
log.error(e.toString());
return null;
}
} else {
return null;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB