Skip to content

Commit 5e199f0

Browse files
committed
Enhance Collection GUI to display parcel items and delivery information
1 parent 72b1a85 commit 5e199f0

12 files changed

Lines changed: 176 additions & 29 deletions

File tree

src/main/java/com/eternalcode/parcellockers/ParcelLockers.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ public void onEnable() {
155155
lockerManager,
156156
userManager,
157157
itemStorageManager,
158-
parcelDispatchService
158+
parcelDispatchService,
159+
parcelContentManager,
160+
deliveryManager
159161
);
160162

161163
MainGui mainGUI = new MainGui(

src/main/java/com/eternalcode/parcellockers/command/debug/DebugCommand.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ void getRandomItem(@Sender Player player, @Arg int stacks) {
8686

8787
List<Material> itemMaterials = Arrays.stream(Material.values()).filter(Material::isItem).toList();
8888

89+
// FIXME: Use Registry API
8990
if (itemMaterials.isEmpty()) { // should never happen
9091
this.noticeService.player(player.getUniqueId(), messages -> Notice.chat("&cNo valid item materials found."));
9192
return;

src/main/java/com/eternalcode/parcellockers/configuration/implementation/PluginConfig.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public static class GuiSettings extends OkaeriConfig {
160160
public ConfigItem cornerItem = new ConfigItem()
161161
.name("")
162162
.lore(Collections.emptyList())
163-
.type(Material.ORANGE_STAINED_GLASS_PANE);
163+
.type(Material.BLUE_STAINED_GLASS_PANE);
164164

165165
@Comment({ "", "# The item of the parcel submit button" })
166166
public ConfigItem submitParcelItem = new ConfigItem()
@@ -216,6 +216,9 @@ public static class GuiSettings extends OkaeriConfig {
216216
)
217217
.type(Material.CHEST_MINECART);
218218

219+
@Comment({ "", "# The lore line showing when the parcel will arrive. Placeholders: {DURATION} - time remaining, {DATE} - arrival date" })
220+
public String parcelArrivingLine = "&6Arriving in: &e{DURATION} &7({DATE})";
221+
219222
@Comment({ "", "# The item of the parcel item storage button" })
220223
public ConfigItem parcelStorageItem = new ConfigItem()
221224
.name("&6\uD83D\uDCBE Parcel storage")
@@ -328,6 +331,12 @@ public static class GuiSettings extends OkaeriConfig {
328331
Material.END_PORTAL_FRAME
329332
);
330333

334+
@Comment({ "", "# The first line of lore when the parcel contains items in the collection GUI."})
335+
public String parcelItemsCollectionGui = "&6Items:";
336+
337+
@Comment({ "", "# The line of lore containing the item name and amount when the parcel contains items in the collection GUI."})
338+
public String parcelItemCollectionFormat = "&6- <gradient:#f6d14a:#862f51>{AMOUNT}x {ITEM}</gradient>";
339+
331340
@Comment({ "", "# The item of the parcel item in the collection GUI" })
332341
public ConfigItem parcelCollectionItem = new ConfigItem()
333342
.name("&a{NAME}")
@@ -344,5 +353,8 @@ public static class GuiSettings extends OkaeriConfig {
344353
.name("&4✘ &cNo parcels found")
345354
.lore(List.of("&cYou don't have any parcels to collect."))
346355
.type(Material.STRUCTURE_VOID);
356+
357+
@Comment({ "", "# The lore line showing when the parcel has arrived. Placeholders: {DATE} - arrival date" })
358+
public String parcelArrivedLine = "&aArrived on: &2{DATE}";
347359
}
348360
}

src/main/java/com/eternalcode/parcellockers/delivery/DeliveryManager.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.github.benmanes.caffeine.cache.Caffeine;
66
import java.time.Duration;
77
import java.time.Instant;
8+
import java.util.Optional;
89
import java.util.UUID;
910
import java.util.concurrent.CompletableFuture;
1011

@@ -29,6 +30,17 @@ public Delivery getOrCreate(UUID parcel, Instant deliveryTimestamp) {
2930
return this.deliveryCache.get(parcel, key -> this.create(key, deliveryTimestamp));
3031
}
3132

33+
public CompletableFuture<Optional<Delivery>> get(UUID parcel) {
34+
Delivery cached = this.deliveryCache.getIfPresent(parcel);
35+
if (cached != null) {
36+
return CompletableFuture.completedFuture(Optional.of(cached));
37+
}
38+
return this.deliveryRepository.fetch(parcel).thenApply(optional -> {
39+
optional.ifPresent(delivery -> this.deliveryCache.put(parcel, delivery));
40+
return optional;
41+
});
42+
}
43+
3244
public Delivery create(UUID parcel, Instant deliveryTimestamp) {
3345
Delivery delivery = new Delivery(parcel, deliveryTimestamp);
3446
if (this.deliveryCache.getIfPresent(parcel) != null) {

src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.eternalcode.parcellockers.gui;
22

3+
import com.eternalcode.parcellockers.content.ParcelContent;
4+
import com.eternalcode.parcellockers.content.ParcelContentManager;
5+
import com.eternalcode.parcellockers.delivery.Delivery;
6+
import com.eternalcode.parcellockers.delivery.DeliveryManager;
37
import com.eternalcode.parcellockers.itemstorage.ItemStorage;
48
import com.eternalcode.parcellockers.itemstorage.ItemStorageManager;
59
import com.eternalcode.parcellockers.locker.Locker;
@@ -25,19 +29,25 @@ public class GuiManager {
2529
private final UserManager userManager;
2630
private final ItemStorageManager itemStorageManager;
2731
private final ParcelDispatchService parcelDispatchService;
32+
private final ParcelContentManager parcelContentManager;
33+
private final DeliveryManager deliveryManager;
2834

2935
public GuiManager(
3036
ParcelService parcelService,
3137
LockerManager lockerManager,
3238
UserManager userManager,
3339
ItemStorageManager itemStorageManager,
34-
ParcelDispatchService parcelDispatchService
40+
ParcelDispatchService parcelDispatchService,
41+
ParcelContentManager parcelContentManager,
42+
DeliveryManager deliveryManager
3543
) {
3644
this.parcelService = parcelService;
3745
this.lockerManager = lockerManager;
3846
this.userManager = userManager;
3947
this.itemStorageManager = itemStorageManager;
4048
this.parcelDispatchService = parcelDispatchService;
49+
this.parcelContentManager = parcelContentManager;
50+
this.deliveryManager = deliveryManager;
4151
}
4252

4353
public void sendParcel(Player sender, Parcel parcel, List<ItemStack> items) {
@@ -84,4 +94,12 @@ public void saveItemStorage(UUID player, List<ItemStack> items) {
8494
public CompletableFuture<Boolean> deleteItemStorage(UUID owner) {
8595
return this.itemStorageManager.delete(owner);
8696
}
97+
98+
public CompletableFuture<Optional<ParcelContent>> getParcelContent(UUID parcelId) {
99+
return this.parcelContentManager.get(parcelId);
100+
}
101+
102+
public CompletableFuture<Optional<Delivery>> getDelivery(UUID parcelId) {
103+
return this.deliveryManager.get(parcelId);
104+
}
87105
}

src/main/java/com/eternalcode/parcellockers/gui/implementation/locker/CollectionGui.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@
1010
import com.eternalcode.parcellockers.parcel.ParcelStatus;
1111
import com.eternalcode.parcellockers.parcel.util.PlaceholderUtil;
1212
import com.eternalcode.parcellockers.shared.Page;
13+
import com.eternalcode.parcellockers.util.MaterialUtil;
1314
import com.spotify.futures.CompletableFutures;
1415
import dev.triumphteam.gui.guis.Gui;
1516
import dev.triumphteam.gui.guis.GuiItem;
1617
import dev.triumphteam.gui.guis.PaginatedGui;
18+
import java.util.ArrayList;
19+
import java.util.List;
1720
import java.util.concurrent.CompletableFuture;
1821
import java.util.function.Supplier;
1922
import net.kyori.adventure.text.Component;
2023
import net.kyori.adventure.text.minimessage.MiniMessage;
2124
import org.bukkit.entity.Player;
25+
import org.bukkit.inventory.ItemStack;
2226

2327
public class CollectionGui implements GuiView {
2428

@@ -112,18 +116,32 @@ private CompletableFuture<Supplier<GuiItem>> createParcelItemAsync(
112116
Player player,
113117
PaginatedGuiRefresher refresher
114118
) {
119+
CompletableFuture<List<String>> loreFuture = PlaceholderUtil.replaceParcelPlaceholdersAsync(parcel, parcelItem.lore(), this.guiManager);
120+
CompletableFuture<List<ItemStack>> contentFuture = this.guiManager.getParcelContent(parcel.uuid())
121+
.thenApply(optional -> optional.map(content -> content.items()).orElse(List.of()));
122+
123+
return loreFuture.thenCombine(contentFuture, (processedLore, items) -> () -> {
124+
ConfigItem item = parcelItem.clone();
125+
item.name(item.name().replace("{NAME}", parcel.name()));
126+
127+
List<String> loreWithItems = new ArrayList<>(processedLore);
128+
if (!items.isEmpty()) {
129+
loreWithItems.add(this.guiSettings.parcelItemsCollectionGui);
130+
for (ItemStack itemStack : items) {
131+
loreWithItems.add(this.guiSettings.parcelItemCollectionFormat
132+
.replace("{ITEM}", MaterialUtil.format(itemStack.getType()))
133+
.replace("{AMOUNT}", Integer.toString(itemStack.getAmount()))
134+
);
135+
}
136+
}
115137

116-
return PlaceholderUtil.replaceParcelPlaceholdersAsync(parcel, parcelItem.lore(), this.guiManager)
117-
.thenApply(processedLore -> () -> {
118-
ConfigItem item = parcelItem.clone();
119-
item.name(item.name().replace("{NAME}", parcel.name()));
120-
item.lore(processedLore);
121-
item.glow(true);
138+
item.lore(loreWithItems);
139+
item.glow(true);
122140

123-
return item.toGuiItem(event -> {
124-
this.guiManager.collectParcel(player, parcel);
125-
refresher.removeItemBySlot(event.getSlot());
126-
});
141+
return item.toGuiItem(event -> {
142+
this.guiManager.collectParcel(player, parcel);
143+
refresher.removeItemBySlot(event.getSlot());
127144
});
145+
});
128146
}
129147
}

src/main/java/com/eternalcode/parcellockers/gui/implementation/locker/SendingGuiState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public SendingGuiState() {
2929
this.priority = false;
3030
this.entryLocker = UUID.randomUUID();
3131
this.destinationLocker = null;
32-
this.status = ParcelStatus.IN_PROGRESS;
32+
this.status = ParcelStatus.SENT;
3333
}
3434

3535
}

src/main/java/com/eternalcode/parcellockers/gui/implementation/remote/ParcelListGui.java

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,25 @@
55
import com.eternalcode.commons.scheduler.Scheduler;
66
import com.eternalcode.parcellockers.configuration.implementation.PluginConfig.GuiSettings;
77
import com.eternalcode.parcellockers.configuration.serializable.ConfigItem;
8+
import com.eternalcode.parcellockers.delivery.Delivery;
89
import com.eternalcode.parcellockers.gui.GuiManager;
910
import com.eternalcode.parcellockers.gui.GuiView;
11+
import com.eternalcode.parcellockers.parcel.Parcel;
1012
import com.eternalcode.parcellockers.parcel.util.PlaceholderUtil;
1113
import com.eternalcode.parcellockers.shared.Page;
14+
import com.eternalcode.parcellockers.util.DurationUtil;
1215
import com.spotify.futures.CompletableFutures;
1316
import dev.triumphteam.gui.builder.item.PaperItemBuilder;
1417
import dev.triumphteam.gui.guis.Gui;
1518
import dev.triumphteam.gui.guis.GuiItem;
1619
import dev.triumphteam.gui.guis.PaginatedGui;
20+
import java.time.Duration;
21+
import java.time.Instant;
22+
import java.time.ZoneId;
23+
import java.time.format.DateTimeFormatter;
24+
import java.util.ArrayList;
1725
import java.util.List;
26+
import java.util.Optional;
1827
import java.util.concurrent.CompletableFuture;
1928
import net.kyori.adventure.text.Component;
2029
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -25,6 +34,8 @@ public class ParcelListGui implements GuiView {
2534
private static final int WIDTH = 7;
2635
private static final int HEIGHT = 4;
2736
private static final Page FIRST_PAGE = new Page(0, WIDTH * HEIGHT);
37+
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
38+
2839
private final Scheduler scheduler;
2940
private final MiniMessage miniMessage;
3041
private final GuiSettings guiSettings;
@@ -75,19 +86,7 @@ public void show(Player player, Page page) {
7586
ConfigItem item = this.guiSettings.parcelItem;
7687

7788
List<CompletableFuture<GuiItem>> itemFutures = result.items().stream()
78-
.map(parcel -> PlaceholderUtil.replaceParcelPlaceholdersAsync(parcel, item.lore(), this.guiManager)
79-
.thenApply(processedLore -> {
80-
PaperItemBuilder parcelItem = item.toBuilder();
81-
82-
List<Component> newLore = processedLore.stream()
83-
.map(line -> resetItalic(this.miniMessage.deserialize(line)))
84-
.toList();
85-
86-
parcelItem.lore(newLore);
87-
parcelItem.name(resetItalic(this.miniMessage.deserialize(item.name().replace("{NAME}", parcel.name()))));
88-
89-
return parcelItem.asGuiItem();
90-
}))
89+
.map(parcel -> this.createParcelItemAsync(parcel, item))
9190
.toList();
9291

9392
CompletableFutures.allAsList(itemFutures)
@@ -119,4 +118,49 @@ private void setupStaticItems(Player player, PaginatedGui gui) {
119118

120119
gui.setItem(49, closeItem);
121120
}
121+
122+
private CompletableFuture<GuiItem> createParcelItemAsync(Parcel parcel, ConfigItem item) {
123+
CompletableFuture<List<String>> loreFuture = PlaceholderUtil.replaceParcelPlaceholdersAsync(
124+
parcel, item.lore(), this.guiManager
125+
);
126+
CompletableFuture<Optional<Delivery>> deliveryFuture = this.guiManager.getDelivery(parcel.uuid());
127+
128+
return loreFuture.thenCombine(deliveryFuture, (processedLore, deliveryOptional) -> {
129+
PaperItemBuilder parcelItem = item.toBuilder();
130+
131+
List<String> loreWithArrival = new ArrayList<>(processedLore);
132+
133+
if (deliveryOptional.isPresent()) {
134+
Delivery delivery = deliveryOptional.get();
135+
Instant arrivalTime = delivery.deliveryTimestamp();
136+
Instant now = Instant.now();
137+
138+
if (arrivalTime.isAfter(now)) {
139+
Duration remaining = Duration.between(now, arrivalTime);
140+
String formattedDuration = DurationUtil.format(remaining);
141+
String formattedDate = DATE_FORMATTER.format(arrivalTime.atZone(ZoneId.systemDefault()));
142+
143+
String arrivingLine = this.guiSettings.parcelArrivingLine
144+
.replace("{DURATION}", formattedDuration)
145+
.replace("{DATE}", formattedDate);
146+
147+
loreWithArrival.add(arrivingLine);
148+
} else if (arrivalTime.isBefore(now)) { // not supported rn, because deliveries are deleted on arrival, so the if is always false
149+
String arrivedLine = this.guiSettings.parcelArrivedLine
150+
.replace("{DATE}", DATE_FORMATTER.format(arrivalTime.atZone(ZoneId.systemDefault())));
151+
loreWithArrival.add(arrivedLine);
152+
}
153+
}
154+
155+
List<Component> newLore = loreWithArrival.stream()
156+
.map(line -> resetItalic(this.miniMessage.deserialize(line)))
157+
.toList();
158+
159+
parcelItem.lore(newLore);
160+
parcelItem.name(resetItalic(this.miniMessage.deserialize(item.name().replace("{NAME}", parcel.name()))));
161+
162+
return parcelItem.asGuiItem();
163+
});
164+
}
165+
122166
}

src/main/java/com/eternalcode/parcellockers/parcel/ParcelStatus.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
public enum ParcelStatus {
44

5-
IN_PROGRESS,
5+
SENT,
66
DELIVERED
77
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.eternalcode.parcellockers.util;
2+
3+
import com.eternalcode.commons.time.DurationParser;
4+
import com.eternalcode.commons.time.TemporalAmountParser;
5+
import java.time.Duration;
6+
import java.time.temporal.ChronoUnit;
7+
import java.util.regex.Pattern;
8+
9+
public final class DurationUtil {
10+
11+
private static final Duration ONE_SECOND = Duration.ofSeconds(1);
12+
private static final Pattern REFORMAT_PATTERN = Pattern.compile("(\\d+)([dhms]+)");
13+
private static final String REFORMAT_REPLACEMENT = "$1$2 ";
14+
15+
private static final TemporalAmountParser<Duration> DURATION = new DurationParser()
16+
.withUnit("s", ChronoUnit.SECONDS)
17+
.withUnit("m", ChronoUnit.MINUTES)
18+
.withUnit("h", ChronoUnit.HOURS)
19+
.withUnit("d", ChronoUnit.DAYS);
20+
21+
private DurationUtil() {
22+
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
23+
}
24+
25+
public static String format(Duration duration) {
26+
if (duration.compareTo(ONE_SECOND) < 0) {
27+
return "0s";
28+
}
29+
return reformat(DURATION.format(duration));
30+
}
31+
32+
33+
private static String reformat(String input) {
34+
return REFORMAT_PATTERN.matcher(input).replaceAll(REFORMAT_REPLACEMENT).trim();
35+
}
36+
}

0 commit comments

Comments
 (0)