diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfig.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfig.java
index 056df000b4..f6dc5f14b9 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfig.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfig.java
@@ -17,87 +17,20 @@
package bisq.desktop.components.cathash;
-import bisq.common.util.MathUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
@Slf4j
-public class BucketConfig {
+public abstract class BucketConfig {
+ static final String CURRENT_VERSION = "v1";
static final String DIGIT = "#";
static final String SHAPE_NUMBER = "#SHAPE_NUMBER#";
- private static final Bucket BG = new Bucket(16, 0);
- private static final Bucket BG_OVERLAY = new Bucket(32, 1);
- private static final Bucket BODY_AND_FACE = new Bucket(16, 2);
- private static final Bucket CHEST_AND_EARS = new Bucket(16, 3);
- private static final Bucket CHEST_OVERLAY = new Bucket(3, 4);
- private static final Bucket EARS_OVERLAY = new Bucket(3, 5);
- private static final Bucket FACE_OVERLAY = new Bucket(17, 6);
- private static final Bucket EYES = new Bucket(16, 7);
- private static final Bucket NOSE = new Bucket(6, 8);
- private static final Bucket WHISKERS = new Bucket(7, 9);
- private static final Bucket BODY_SHAPE = new Bucket(2, 10);
- private static final Bucket CHEST_SHAPE = new Bucket(2, 11);
- private static final Bucket EARS_SHAPE = new Bucket(2, 12);
- private static final Bucket FACE_SHAPE = new Bucket(5, 13);
+ abstract int[] getBucketSizes();
- private static final int[] BUCKET_SIZES = new int[]{
- BG.getCount(),
- BG_OVERLAY.getCount(),
- BODY_AND_FACE.getCount(),
- CHEST_AND_EARS.getCount(),
- CHEST_OVERLAY.getCount(),
- EARS_OVERLAY.getCount(),
- FACE_OVERLAY.getCount(),
- EYES.getCount(),
- NOSE.getCount(),
- WHISKERS.getCount(),
- BODY_SHAPE.getCount(),
- CHEST_SHAPE.getCount(),
- EARS_SHAPE.getCount(),
- FACE_SHAPE.getCount()
- };
-
- private static final PathDetails[] PATH_TEMPLATES;
-
- static {
- String postFix = ".png";
- PATH_TEMPLATES = new PathDetails[]{
- new PathDetails("bg/bg_0/" + DIGIT + postFix, BG.getIdx()),
- new PathDetails("bg/bg_1/" + DIGIT + postFix, BG_OVERLAY.getIdx()),
- new PathDetails("body/body" + SHAPE_NUMBER + "/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), BODY_SHAPE.getIdx()),
- new PathDetails("chest/chest" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), CHEST_SHAPE.getIdx()),
- new PathDetails("chest/chest" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, CHEST_OVERLAY.getIdx(), CHEST_SHAPE.getIdx()),
- new PathDetails("ears/ears" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), EARS_SHAPE.getIdx()),
- new PathDetails("ears/ears" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, EARS_OVERLAY.getIdx(), EARS_SHAPE.getIdx()),
- new PathDetails("face/face" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), FACE_SHAPE.getIdx()),
- new PathDetails("face/face" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, FACE_OVERLAY.getIdx(), FACE_SHAPE.getIdx()),
- new PathDetails("eyes/" + DIGIT + postFix, EYES.getIdx()),
- new PathDetails("nose/" + DIGIT + postFix, NOSE.getIdx()),
- new PathDetails("whiskers/" + DIGIT + postFix, WHISKERS.getIdx())
- };
-
- long numCombinations = getNumCombinations();
- log.info("Number of combinations: 2^{} = {}", MathUtils.getLog2(numCombinations), numCombinations);
- }
-
- static int[] getBucketSizes() {
- return BUCKET_SIZES;
- }
-
- static PathDetails[] getPathTemplates() {
- return PATH_TEMPLATES;
- }
-
- static long getNumCombinations() {
- long result = 1;
- for (int bucketSize : BUCKET_SIZES) {
- result *= bucketSize;
- }
- return result;
- }
+ abstract PathDetails[] getPathTemplates();
@Getter
static class Bucket {
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfigV1.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfigV1.java
new file mode 100644
index 0000000000..74eedfc31a
--- /dev/null
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/BucketConfigV1.java
@@ -0,0 +1,86 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.desktop.components.cathash;
+
+import bisq.common.util.MathUtils;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public final class BucketConfigV1 extends BucketConfig {
+ private static final Bucket BG = new Bucket(16, 0);
+ private static final Bucket BG_OVERLAY = new Bucket(32, 1);
+ private static final Bucket BODY_AND_FACE = new Bucket(16, 2);
+ private static final Bucket CHEST_AND_EARS = new Bucket(16, 3);
+ private static final Bucket CHEST_OVERLAY = new Bucket(3, 4);
+ private static final Bucket EARS_OVERLAY = new Bucket(3, 5);
+ private static final Bucket FACE_OVERLAY = new Bucket(17, 6);
+ private static final Bucket EYES = new Bucket(16, 7);
+ private static final Bucket NOSE = new Bucket(6, 8);
+ private static final Bucket WHISKERS = new Bucket(7, 9);
+ private static final Bucket BODY_SHAPE = new Bucket(2, 10);
+ private static final Bucket CHEST_SHAPE = new Bucket(2, 11);
+ private static final Bucket EARS_SHAPE = new Bucket(2, 12);
+ private static final Bucket FACE_SHAPE = new Bucket(5, 13);
+
+ private static final int[] BUCKET_SIZES = new int[]{
+ BG.getCount(),
+ BG_OVERLAY.getCount(),
+ BODY_AND_FACE.getCount(),
+ CHEST_AND_EARS.getCount(),
+ CHEST_OVERLAY.getCount(),
+ EARS_OVERLAY.getCount(),
+ FACE_OVERLAY.getCount(),
+ EYES.getCount(),
+ NOSE.getCount(),
+ WHISKERS.getCount(),
+ BODY_SHAPE.getCount(),
+ CHEST_SHAPE.getCount(),
+ EARS_SHAPE.getCount(),
+ FACE_SHAPE.getCount()
+ };
+
+ private static final PathDetails[] PATH_TEMPLATES;
+
+ static {
+ String postFix = ".png";
+ PATH_TEMPLATES = new PathDetails[]{
+ new PathDetails("bg/bg_0/" + DIGIT + postFix, BG.getIdx()),
+ new PathDetails("bg/bg_1/" + DIGIT + postFix, BG_OVERLAY.getIdx()),
+ new PathDetails("body/body" + SHAPE_NUMBER + "/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), BODY_SHAPE.getIdx()),
+ new PathDetails("chest/chest" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), CHEST_SHAPE.getIdx()),
+ new PathDetails("chest/chest" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, CHEST_OVERLAY.getIdx(), CHEST_SHAPE.getIdx()),
+ new PathDetails("ears/ears" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, CHEST_AND_EARS.getIdx(), EARS_SHAPE.getIdx()),
+ new PathDetails("ears/ears" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, EARS_OVERLAY.getIdx(), EARS_SHAPE.getIdx()),
+ new PathDetails("face/face" + SHAPE_NUMBER + "_0/" + DIGIT + postFix, BODY_AND_FACE.getIdx(), FACE_SHAPE.getIdx()),
+ new PathDetails("face/face" + SHAPE_NUMBER + "_1/" + DIGIT + postFix, FACE_OVERLAY.getIdx(), FACE_SHAPE.getIdx()),
+ new PathDetails("eyes/" + DIGIT + postFix, EYES.getIdx()),
+ new PathDetails("nose/" + DIGIT + postFix, NOSE.getIdx()),
+ new PathDetails("whiskers/" + DIGIT + postFix, WHISKERS.getIdx())
+ };
+ }
+
+ @Override
+ int[] getBucketSizes() {
+ return BUCKET_SIZES;
+ }
+
+ @Override
+ PathDetails[] getPathTemplates() {
+ return PATH_TEMPLATES;
+ }
+}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/CatHash.java b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/CatHash.java
index 4af1837820..f3d8a76475 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/CatHash.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/components/cathash/CatHash.java
@@ -34,26 +34,49 @@ public class CatHash {
private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
public static Image getImage(UserProfile userProfile) {
- return getImage(userProfile.getPubKeyHash(), userProfile.getProofOfWork().getSolution(), true);
+ return getImage(userProfile.getPubKeyHash(), userProfile.getProofOfWork().getSolution(),
+ userProfile.getAvatarVersion(), true);
}
- public static Image getImage(byte[] pubKeyHash, byte[] powSolution) {
- return getImage(pubKeyHash, powSolution, true);
+ public static Image getImage(byte[] pubKeyHash, byte[] powSolution, String avatarVersion) {
+ return getImage(pubKeyHash, powSolution, avatarVersion, true);
}
- public static Image getImage(byte[] pubKeyHash, byte[] powSolution, boolean useCache) {
+ public static Image getImage(byte[] pubKeyHash, byte[] powSolution, String avatarVersion, boolean useCache) {
byte[] combined = ByteArrayUtils.concat(powSolution, pubKeyHash);
BigInteger input = new BigInteger(combined);
if (useCache && CACHE.containsKey(input)) {
return CACHE.get(input);
}
- int[] buckets = BucketEncoder.encode(input, BucketConfig.getBucketSizes());
- String[] paths = BucketEncoder.toPaths(buckets, BucketConfig.getPathTemplates());
+ BucketConfig bucketConfig = getBucketConfig(avatarVersion);
+ log.info("Getting user avatar image using class {}", bucketConfig.getClass().getName());
+
+ int[] buckets = BucketEncoder.encode(input, bucketConfig.getBucketSizes());
+ String[] paths = BucketEncoder.toPaths(buckets, bucketConfig.getPathTemplates());
Image image = ImageUtil.composeImage(paths, SIZE, SIZE);
if (useCache && CACHE.size() < MAX_CACHE_SIZE) {
CACHE.put(input, image);
}
return image;
}
+
+ public static String currentAvatarsVersion() {
+ return BucketConfig.CURRENT_VERSION;
+ }
+
+ private static BucketConfig getBucketConfig(String avatarVersion) {
+ BucketConfig bucketConfig;
+ switch (avatarVersion) {
+ case "v1": {
+ bucketConfig = new BucketConfigV1();
+ log.info("Creating v1 BucketConfig: {}", bucketConfig.getClass().getName());
+ }
+ default: {
+ bucketConfig = new BucketConfigV1();
+ log.info("Falling to default BucketConfig: {}", bucketConfig.getClass().getName());
+ }
+ }
+ return bucketConfig;
+ }
}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileDisplay.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileDisplay.java
index 6c5eae1dd4..143010624a 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileDisplay.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/components/UserProfileDisplay.java
@@ -56,6 +56,7 @@ public UserProfileDisplay(@Nullable UserProfile userProfile) {
public UserProfileDisplay(@Nullable UserProfile userProfile, double size) {
super(10);
+
setAlignment(Pos.CENTER_LEFT);
userProfileIcon = new UserProfileIcon(size);
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2Controller.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2Controller.java
index 5efe9a4017..3a86bf3869 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2Controller.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2Controller.java
@@ -38,6 +38,8 @@
@Slf4j
public class CreateNewProfileStep2Controller implements InitWithDataController {
+ private static final String CURRENT_AVATARS_VERSION = CatHash.currentAvatarsVersion();
+
@Getter
@ToString
@EqualsAndHashCode
@@ -90,7 +92,7 @@ public void initWithData(InitData data) {
model.setProofOfWork(data.getProofOfWork());
model.getNickName().set(data.getNickName());
model.getNym().set(data.getNym());
- model.getCatHashImage().set(CatHash.getImage(data.getPubKeyHash(), data.getProofOfWork().getSolution()));
+ model.getCatHashImage().set(CatHash.getImage(data.getPubKeyHash(), data.getProofOfWork().getSolution(), CURRENT_AVATARS_VERSION));
}
@Override
@@ -133,6 +135,7 @@ protected void onSave() {
model.getKeyPair(),
model.getPubKeyHash(),
model.getProofOfWork(),
+ CURRENT_AVATARS_VERSION,
model.getTerms().get(),
model.getStatement().get())
.whenComplete((chatUserIdentity, throwable) -> UIThread.run(() -> {
@@ -148,4 +151,4 @@ protected void onSave() {
protected void close() {
OverlayController.hide();
}
-}
\ No newline at end of file
+}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2View.java b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2View.java
index abd16fdc49..9f3b4126e6 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2View.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/main/content/user/user_profile/create/step2/CreateNewProfileStep2View.java
@@ -128,5 +128,7 @@ protected void onViewDetached() {
nym.textProperty().unbind();
terms.textProperty().unbindBidirectional(model.getTerms());
statement.textProperty().unbindBidirectional(model.getStatement());
+ saveButton.setOnAction(null);
+ cancelButton.setOnAction(null);
}
}
diff --git a/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java b/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java
index b7636073bb..cc64ca0560 100644
--- a/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java
+++ b/apps/desktop/desktop/src/main/java/bisq/desktop/overlay/onboarding/create_profile/CreateProfileController.java
@@ -46,6 +46,8 @@
@Slf4j
public class CreateProfileController implements Controller {
+ private static final String CURRENT_AVATARS_VERSION = CatHash.currentAvatarsVersion();
+
protected final CreateProfileModel model;
@Getter
protected final CreateProfileView view;
@@ -119,6 +121,7 @@ protected void onCreateUserProfile() {
model.getKeyPair().orElseThrow(),
model.getPubKeyHash().orElseThrow(),
model.getProofOfWork().orElseThrow(),
+ CURRENT_AVATARS_VERSION,
"",
"")
.whenComplete((chatUserIdentity, throwable) -> UIThread.run(() -> {
@@ -155,7 +158,7 @@ private CompletableFuture createProofOfWork(byte[] pubKeyHash) {
model.setProofOfWork(Optional.of(proofOfWork));
byte[] powSolution = proofOfWork.getSolution();
String nym = NymIdGenerator.generate(pubKeyHash, powSolution);
- Image image = CatHash.getImage(pubKeyHash, powSolution);
+ Image image = CatHash.getImage(pubKeyHash, powSolution, CURRENT_AVATARS_VERSION);
model.getNym().set(nym);
model.getCatHashImage().set(image);
model.getPowProgress().set(0);
diff --git a/user/src/main/java/bisq/user/identity/UserIdentityService.java b/user/src/main/java/bisq/user/identity/UserIdentityService.java
index 54e52dcb5b..94be4e81b2 100644
--- a/user/src/main/java/bisq/user/identity/UserIdentityService.java
+++ b/user/src/main/java/bisq/user/identity/UserIdentityService.java
@@ -174,11 +174,12 @@ public CompletableFuture createAndPublishNewUserProfile(String nic
KeyPair keyPair,
byte[] pubKeyHash,
ProofOfWork proofOfWork,
+ String avatarVersion,
String terms,
String statement) {
String identityTag = nickName + "-" + Hex.encode(pubKeyHash);
return identityService.createNewActiveIdentity(identityTag, keyPair)
- .thenApply(identity -> createUserIdentity(nickName, proofOfWork, terms, statement, identity))
+ .thenApply(identity -> createUserIdentity(nickName, proofOfWork, avatarVersion, terms, statement, identity))
.thenApply(userIdentity -> {
publishPublicUserProfile(userIdentity.getUserProfile(), userIdentity.getIdentity().getNetworkIdWithKeyPair().getKeyPair());
return userIdentity;
@@ -315,12 +316,14 @@ private CompletableFuture publishPublicUserProfile(UserProfile
private UserIdentity createUserIdentity(String nickName,
ProofOfWork proofOfWork,
+ String avatarVersion,
String terms,
String statement,
Identity identity) {
checkArgument(nickName.equals(nickName.trim()) && !nickName.isEmpty(),
"Nickname must not have leading or trailing spaces and must not be empty.");
- UserProfile userProfile = new UserProfile(nickName, proofOfWork, identity.getNetworkIdWithKeyPair().getNetworkId(), terms, statement);
+ UserProfile userProfile = new UserProfile(nickName, proofOfWork, avatarVersion,
+ identity.getNetworkIdWithKeyPair().getNetworkId(), terms, statement);
UserIdentity userIdentity = new UserIdentity(identity, userProfile);
synchronized (lock) {
diff --git a/user/src/main/java/bisq/user/profile/UserProfile.java b/user/src/main/java/bisq/user/profile/UserProfile.java
index 3de9b752b0..9f33bf4384 100644
--- a/user/src/main/java/bisq/user/profile/UserProfile.java
+++ b/user/src/main/java/bisq/user/profile/UserProfile.java
@@ -55,7 +55,8 @@ public final class UserProfile implements DistributedData {
public static final int MAX_LENGTH_STATEMENT = 100;
public static UserProfile from(UserProfile userProfile, String terms, String statement) {
- return new UserProfile(userProfile.getNickName(), userProfile.getProofOfWork(), userProfile.getNetworkId(), terms, statement);
+ return new UserProfile(userProfile.getNickName(), userProfile.getProofOfWork(), userProfile.getAvatarVersion(),
+ userProfile.getNetworkId(), terms, statement);
}
// We give a bit longer TTL than the chat messages to ensure the chat user is available as long the messages are
@@ -63,6 +64,7 @@ public static UserProfile from(UserProfile userProfile, String terms, String sta
private final String nickName;
// We need the proofOfWork for verification of the nym and cathash icon
private final ProofOfWork proofOfWork;
+ private final String avatarVersion;
private final NetworkId networkId;
private final String terms;
private final String statement;
@@ -73,11 +75,13 @@ public static UserProfile from(UserProfile userProfile, String terms, String sta
public UserProfile(String nickName,
ProofOfWork proofOfWork,
+ String avatarVersion,
NetworkId networkId,
String terms,
String statement) {
this.nickName = nickName;
this.proofOfWork = proofOfWork;
+ this.avatarVersion = avatarVersion;
this.networkId = networkId;
this.terms = terms;
this.statement = statement;
@@ -98,6 +102,7 @@ public bisq.user.protobuf.UserProfile toProto() {
.setNickName(nickName)
.setTerms(terms)
.setStatement(statement)
+ .setAvatarVersion(avatarVersion)
.setProofOfWork(proofOfWork.toProto())
.setNetworkId(networkId.toProto())
.build();
@@ -106,6 +111,7 @@ public bisq.user.protobuf.UserProfile toProto() {
public static UserProfile fromProto(bisq.user.protobuf.UserProfile proto) {
return new UserProfile(proto.getNickName(),
ProofOfWork.fromProto(proto.getProofOfWork()),
+ proto.getAvatarVersion(),
NetworkId.fromProto(proto.getNetworkId()),
proto.getTerms(),
proto.getStatement());
@@ -205,6 +211,7 @@ public String toString() {
return "UserProfile{" +
"\r\n nickName='" + nickName + '\'' +
",\r\n proofOfWork=" + proofOfWork +
+ ",\r\n avatarVersion=" + avatarVersion +
",\r\n networkId=" + networkId +
",\r\n terms='" + terms + '\'' +
",\r\n statement='" + statement + '\'' +
@@ -213,4 +220,4 @@ public String toString() {
",\r\n bondedReputationHash=" + bondedReputationHash +
"\r\n}";
}
-}
\ No newline at end of file
+}
diff --git a/user/src/main/proto/user.proto b/user/src/main/proto/user.proto
index 1408cd3286..9196a7e852 100644
--- a/user/src/main/proto/user.proto
+++ b/user/src/main/proto/user.proto
@@ -30,6 +30,7 @@ message UserProfile {
security.ProofOfWork proofOfWork = 3;
string terms = 4;
string statement = 5;
+ string avatarVersion = 6;
}
message UserIdentity {