Nicer Share Pics

This commit is contained in:
Tim Zöller 2026-04-08 11:06:26 +02:00
parent 48ac2e444b
commit 07fbcf8064
5 changed files with 1045 additions and 563 deletions

View file

@ -473,7 +473,12 @@ public class ActivityPubController {
imageAttachment.put("type", "Image"); imageAttachment.put("type", "Image");
imageAttachment.put("mediaType", "image/png"); imageAttachment.put("mediaType", "image/png");
imageAttachment.put("url", imageUrl); imageAttachment.put("url", imageUrl);
imageAttachment.put("name", "Activity map showing " + activity.getActivityType() + " route"); // The "name" field on an Image attachment is what Mastodon, other
// ActivityPub servers, and screen readers expose as the image
// description. Build a real prose description from the activity
// data instead of the previous "Activity map showing X route"
// placeholder. See ActivityImageService.buildImageAltText.
imageAttachment.put("name", activityImageService.buildImageAltText(activity));
noteObject.put("attachment", List.of(imageAttachment)); noteObject.put("attachment", List.of(imageAttachment));
} }

View file

@ -233,7 +233,11 @@ public class ActivityPostProcessingService {
imageAttachment.put("type", "Image"); imageAttachment.put("type", "Image");
imageAttachment.put("mediaType", "image/png"); imageAttachment.put("mediaType", "image/png");
imageAttachment.put("url", imageUrl); imageAttachment.put("url", imageUrl);
imageAttachment.put("name", "Activity map showing " + activity.getActivityType() + " route"); // The "name" field on an Image attachment is what Mastodon and
// other ActivityPub servers expose as the image description /
// alt text. Build it from the same data the visual renderer
// uses so the two stay in sync.
imageAttachment.put("name", activityImageService.buildImageAltText(activity));
noteObject.put("attachment", List.of(imageAttachment)); noteObject.put("attachment", List.of(imageAttachment));
} }

View file

@ -191,8 +191,11 @@ public class OsmTileRenderer {
drawY = 0; drawY = 0;
} }
// Fill background with neutral gray // Fill the letterbox margins with the FitPub dark background so the
g.setColor(new Color(240, 240, 240)); // square share-image map blends seamlessly into the dark stats panel.
// (Was previously a neutral light gray that produced visible bars
// around non-square routes.)
g.setColor(new Color(0x0f, 0x05, 0x20));
g.fillRect(0, 0, width, height); g.fillRect(0, 0, width, height);
// Draw scaled image centered with preserved aspect ratio // Draw scaled image centered with preserved aspect ratio

View file

@ -82,10 +82,15 @@ class ActivityImageServiceTest {
System.out.println(" Total distance: " + parsedData.getTotalDistance() + " m"); System.out.println(" Total distance: " + parsedData.getTotalDistance() + " m");
System.out.println(" Total duration: " + parsedData.getTotalDuration()); System.out.println(" Total duration: " + parsedData.getTotalDuration());
// Create a test user with required fields // Create a test user with required fields. Both username and email are
// suffixed with the current time so re-running the test doesn't collide
// with rows left behind by previous (failed) runs the test is not
// @Transactional and intentionally leaves the user/activity in the DB
// so the generated image can be inspected against real persisted state.
long uniq = System.currentTimeMillis();
User testUser = new User(); User testUser = new User();
testUser.setUsername("testuser_" + System.currentTimeMillis()); testUser.setUsername("testuser_" + uniq);
testUser.setEmail("test@example.com"); testUser.setEmail("test_" + uniq + "@example.com");
testUser.setPasswordHash("hashedpassword"); testUser.setPasswordHash("hashedpassword");
testUser.setDisplayName("Test User"); testUser.setDisplayName("Test User");
testUser.setEnabled(true); testUser.setEnabled(true);
@ -94,7 +99,9 @@ class ActivityImageServiceTest {
testUser.setPrivateKey("-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC\n-----END PRIVATE KEY-----"); testUser.setPrivateKey("-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC\n-----END PRIVATE KEY-----");
testUser = userRepository.save(testUser); testUser = userRepository.save(testUser);
// Create a test activity entity // Create a test activity entity. sourceFileFormat is required by the
// schema (V15 made it NOT NULL with a CHECK constraint allowing only
// 'FIT' or 'GPX'); this test parses a FIT file so 'FIT' is correct.
Activity activity = Activity.builder() Activity activity = Activity.builder()
.id(UUID.randomUUID()) .id(UUID.randomUUID())
.userId(testUser.getId()) .userId(testUser.getId())
@ -105,6 +112,7 @@ class ActivityImageServiceTest {
.endedAt(parsedData.getEndTime()) .endedAt(parsedData.getEndTime())
.timezone(parsedData.getTimezone()) .timezone(parsedData.getTimezone())
.visibility(Activity.Visibility.PUBLIC) .visibility(Activity.Visibility.PUBLIC)
.sourceFileFormat("FIT")
.totalDistance(parsedData.getTotalDistance()) .totalDistance(parsedData.getTotalDistance())
.totalDurationSeconds(parsedData.getTotalDuration() != null ? parsedData.getTotalDuration().getSeconds() : null) .totalDurationSeconds(parsedData.getTotalDuration() != null ? parsedData.getTotalDuration().getSeconds() : null)
.elevationGain(parsedData.getElevationGain()) .elevationGain(parsedData.getElevationGain())