time tracking: add overview tab

This commit is contained in:
takuyakanbr
2018-07-28 23:19:19 +08:00
parent 8435004956
commit a8c418d180
13 changed files with 452 additions and 76 deletions

View File

@@ -0,0 +1,128 @@
/*
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.timetracking;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.util.ImageUtil;
class OverviewItemPanel extends JPanel
{
private static final ImageIcon ARROW_RIGHT_ICON;
private static final Color HOVER_COLOR = ColorScheme.DARKER_GRAY_HOVER_COLOR;
private final JLabel statusLabel;
static
{
ARROW_RIGHT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "/util/arrow_right.png"));
}
OverviewItemPanel(ItemManager itemManager, TimeTrackingPanel pluginPanel, Tab tab, String title)
{
setBackground(ColorScheme.DARKER_GRAY_COLOR);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(7, 7, 7, 7));
JLabel iconLabel = new JLabel();
iconLabel.setMinimumSize(new Dimension(36, 32));
itemManager.getImage(tab.getItemID()).addTo(iconLabel);
add(iconLabel, BorderLayout.WEST);
JPanel textContainer = new JPanel();
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
textContainer.setLayout(new GridLayout(2, 1));
textContainer.setBorder(new EmptyBorder(5, 7, 5, 7));
addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent mouseEvent)
{
pluginPanel.switchTab(tab);
setBackground(ColorScheme.DARKER_GRAY_COLOR);
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
}
@Override
public void mouseReleased(MouseEvent e)
{
setBackground(HOVER_COLOR);
textContainer.setBackground(HOVER_COLOR);
}
@Override
public void mouseEntered(MouseEvent e)
{
setBackground(HOVER_COLOR);
textContainer.setBackground(HOVER_COLOR);
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
@Override
public void mouseExited(MouseEvent e)
{
setBackground(ColorScheme.DARKER_GRAY_COLOR);
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
});
JLabel titleLabel = new JLabel(title);
titleLabel.setForeground(Color.WHITE);
titleLabel.setFont(FontManager.getRunescapeSmallFont());
statusLabel = new JLabel();
statusLabel.setForeground(Color.GRAY);
statusLabel.setFont(FontManager.getRunescapeSmallFont());
textContainer.add(titleLabel);
textContainer.add(statusLabel);
add(textContainer, BorderLayout.CENTER);
JLabel arrowLabel = new JLabel(ARROW_RIGHT_ICON);
add(arrowLabel, BorderLayout.EAST);
}
void updateStatus(String text, Color color)
{
statusLabel.setText(text);
statusLabel.setForeground(color);
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.timetracking;
import java.awt.Color;
import java.awt.GridLayout;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.clocks.ClockManager;
import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
import net.runelite.client.plugins.timetracking.farming.PatchImplementation;
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
import net.runelite.client.ui.ColorScheme;
class OverviewTabPanel extends TabContentPanel
{
private final TimeTrackingConfig config;
private final FarmingTracker farmingTracker;
private final BirdHouseTracker birdHouseTracker;
private final ClockManager clockManager;
private final OverviewItemPanel timerOverview;
private final OverviewItemPanel stopwatchOverview;
private final Map<PatchImplementation, OverviewItemPanel> farmingOverviews;
private final OverviewItemPanel birdHouseOverview;
OverviewTabPanel(ItemManager itemManager, TimeTrackingConfig config, TimeTrackingPanel pluginPanel,
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager)
{
this.config = config;
this.farmingTracker = farmingTracker;
this.birdHouseTracker = birdHouseTracker;
this.clockManager = clockManager;
setLayout(new GridLayout(0, 1, 0, 8));
setBackground(ColorScheme.DARK_GRAY_COLOR);
timerOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.CLOCK, "Timers");
add(timerOverview);
stopwatchOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.CLOCK, "Stopwatches");
add(stopwatchOverview);
birdHouseOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.BIRD_HOUSE, "Bird Houses");
add(birdHouseOverview);
farmingOverviews = new LinkedHashMap<>();
farmingOverviews.put(PatchImplementation.ALLOTMENT, new OverviewItemPanel(itemManager, pluginPanel, Tab.ALLOTMENT, "Allotment Patches"));
farmingOverviews.put(PatchImplementation.FLOWER, new OverviewItemPanel(itemManager, pluginPanel, Tab.FLOWER, "Flower Patches"));
farmingOverviews.put(PatchImplementation.HERB, new OverviewItemPanel(itemManager, pluginPanel, Tab.HERB, "Herb Patches"));
farmingOverviews.put(PatchImplementation.TREE, new OverviewItemPanel(itemManager, pluginPanel, Tab.TREE, "Tree Patches"));
farmingOverviews.put(PatchImplementation.FRUIT_TREE, new OverviewItemPanel(itemManager, pluginPanel, Tab.FRUIT_TREE, "Fruit Tree Patches"));
farmingOverviews.put(PatchImplementation.HOPS, new OverviewItemPanel(itemManager, pluginPanel, Tab.HOPS, "Hops Patches"));
farmingOverviews.put(PatchImplementation.BUSH, new OverviewItemPanel(itemManager, pluginPanel, Tab.BUSH, "Bush Patches"));
farmingOverviews.put(PatchImplementation.GRAPES, new OverviewItemPanel(itemManager, pluginPanel, Tab.GRAPE, "Grape Patches"));
for (OverviewItemPanel panel : farmingOverviews.values())
{
add(panel);
}
}
@Override
public int getUpdateInterval()
{
return 50; // 10 seconds
}
@Override
public void update()
{
final long timers = clockManager.getActiveTimerCount();
final long stopwatches = clockManager.getActiveStopwatchCount();
if (timers == 0)
{
timerOverview.updateStatus("No active timers", Color.GRAY);
}
else
{
timerOverview.updateStatus(timers + " active timer" + (timers == 1 ? "" : "s"), ColorScheme.PROGRESS_COMPLETE_COLOR);
}
if (stopwatches == 0)
{
stopwatchOverview.updateStatus("No active stopwatches", Color.GRAY);
}
else
{
stopwatchOverview.updateStatus(stopwatches + " active stopwatch" + (stopwatches == 1 ? "" : "es"), ColorScheme.PROGRESS_COMPLETE_COLOR);
}
farmingOverviews.forEach((patchType, panel) -> updateItemPanel(panel, farmingTracker.getCompletionTime(patchType)));
updateItemPanel(birdHouseOverview, birdHouseTracker.getCompletionTime());
}
private void updateItemPanel(OverviewItemPanel panel, long completionTime)
{
long duration = completionTime - Instant.now().getEpochSecond();
if (completionTime < 0)
{
panel.updateStatus("Unknown", Color.GRAY);
}
else if (duration <= 0)
{
panel.updateStatus("Ready", ColorScheme.PROGRESS_COMPLETE_COLOR);
}
else
{
panel.updateStatus("Ready " + getFormattedEstimate(duration, config.estimateRelative()), Color.GRAY);
}
}
}

View File

@@ -32,6 +32,7 @@ import net.runelite.api.ItemID;
@Getter
public enum Tab
{
OVERVIEW("Overview", ItemID.OLD_NOTES),
CLOCK("Timers & Stopwatches", ItemID.WATCH),
BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE),
ALLOTMENT("Allotment Patches", ItemID.CABBAGE),

View File

@@ -25,6 +25,10 @@
package net.runelite.client.plugins.timetracking;
import java.awt.Dimension;
import java.time.LocalDateTime;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
import javax.swing.JPanel;
public abstract class TabContentPanel extends JPanel
@@ -43,4 +47,41 @@ public abstract class TabContentPanel extends JPanel
{
return super.getPreferredSize();
}
protected static String getFormattedEstimate(long remainingSeconds, boolean useRelativeTime)
{
if (useRelativeTime)
{
StringBuilder sb = new StringBuilder("in ");
long duration = (remainingSeconds + 59) / 60;
long minutes = duration % 60;
long hours = (duration / 60) % 24;
long days = duration / (60 * 24);
if (days > 0)
{
sb.append(days).append("d ");
}
if (hours > 0)
{
sb.append(hours).append("h ");
}
if (minutes > 0)
{
sb.append(minutes).append("m ");
}
return sb.toString();
}
else
{
StringBuilder sb = new StringBuilder();
LocalDateTime endTime = LocalDateTime.now().plus(remainingSeconds, ChronoUnit.SECONDS);
LocalDateTime currentTime = LocalDateTime.now();
if (endTime.getDayOfWeek() != currentTime.getDayOfWeek())
{
sb.append(endTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" ");
}
sb.append(String.format("at %d:%02d", endTime.getHour(), endTime.getMinute()));
return sb.toString();
}
}
}

View File

@@ -30,6 +30,8 @@ import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
@@ -55,6 +57,7 @@ class TimeTrackingPanel extends PluginPanel
/* This is the panel the tabs' respective panels will be displayed on. */
private final JPanel display = new JPanel();
private final Map<Tab, MaterialTab> uiTabs = new HashMap<>();
private final MaterialTabGroup tabGroup = new MaterialTabGroup(display);
private boolean active;
@@ -81,6 +84,7 @@ class TimeTrackingPanel extends PluginPanel
add(tabGroup, BorderLayout.NORTH);
add(display, BorderLayout.CENTER);
addTab(Tab.OVERVIEW, new OverviewTabPanel(itemManager, config, this, farmingTracker, birdHouseTracker, clockManager));
addTab(Tab.CLOCK, clockManager.getClockTabPanel());
addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel());
@@ -126,13 +130,20 @@ class TimeTrackingPanel extends PluginPanel
return true;
});
uiTabs.put(tab, materialTab);
tabGroup.addTab(materialTab);
if (config.activeTab() == tab)
{
tabGroup.select(materialTab);
}
}
void switchTab(Tab tab)
{
tabGroup.select(uiTabs.get(tab));
}
/**
* Gets the update interval of the active tab panel, in units of 200 milliseconds.
*/

View File

@@ -111,6 +111,7 @@ public class TimeTrackingPlugin extends Plugin
clockManager.loadTimers();
clockManager.loadStopwatches();
birdHouseTracker.loadFromConfig();
farmingTracker.loadCompletionTimes();
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png");
@@ -205,6 +206,7 @@ public class TimeTrackingPlugin extends Plugin
public void onUsernameChanged(UsernameChanged e)
{
farmingTracker.migrateConfiguration();
farmingTracker.loadCompletionTimes();
birdHouseTracker.loadFromConfig();
panel.update();
}

View File

@@ -93,6 +93,16 @@ public class ClockManager
SwingUtilities.invokeLater(clockTabPanel::rebuild);
}
public long getActiveTimerCount()
{
return timers.stream().filter(Timer::isActive).count();
}
public long getActiveStopwatchCount()
{
return stopwatches.stream().filter(Stopwatch::isActive).count();
}
/**
* Checks if any timers have completed, and send notifications if required.
*/

View File

@@ -29,12 +29,8 @@ import com.google.common.base.Strings;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.swing.JLabel;
import javax.swing.border.EmptyBorder;
@@ -215,40 +211,9 @@ public class FarmingTabPanel extends TabContentPanel
{
panel.getEstimate().setText("Done");
}
else if (config.estimateRelative())
{
int remaining = (int) (59 + doneEstimate - unixNow) / 60;
StringBuilder f = new StringBuilder();
f.append("Done in ");
int min = remaining % 60;
int hours = (remaining / 60) % 24;
int days = remaining / (60 * 24);
if (days > 0)
{
f.append(days).append("d ");
}
if (hours > 0)
{
f.append(hours).append("h ");
}
if (min > 0)
{
f.append(min).append("m ");
}
panel.getEstimate().setText(f.toString());
}
else
{
StringBuilder f = new StringBuilder();
LocalDateTime ldtTime = LocalDateTime.ofEpochSecond(doneEstimate, 0, OffsetDateTime.now().getOffset());
LocalDateTime ldtNow = LocalDateTime.now();
f.append("Done ");
if (ldtTime.getDayOfWeek() != ldtNow.getDayOfWeek())
{
f.append(ldtTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" ");
}
f.append(String.format("at %d:%02d", ldtTime.getHour(), ldtTime.getMinute()));
panel.getEstimate().setText(f.toString());
panel.getEstimate().setText("Done " + getFormattedEstimate(doneEstimate - unixNow, config.estimateRelative()));
}
}
else

View File

@@ -27,6 +27,8 @@ package net.runelite.client.plugins.timetracking.farming;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.time.Instant;
import java.util.EnumMap;
import java.util.Map;
import net.runelite.api.Client;
import net.runelite.api.Varbits;
import net.runelite.api.coords.WorldPoint;
@@ -59,6 +61,15 @@ public class FarmingTracker
}
/**
* The time at which all patches of a particular type will be ready to be harvested,
* or {@code -1} if we have no data about any patch of the given type.
*
* Each value is set to {@code 0} if all patches of that type have already completed
* when updating the value.
*/
private Map<PatchImplementation, Long> completionTimes = new EnumMap<>(PatchImplementation.class);
public FarmingTabPanel createTabPanel(Tab tab)
{
return new FarmingTabPanel(client, itemManager, configManager, config, farmingWorld.getTabs().get(tab));
@@ -88,9 +99,10 @@ public class FarmingTracker
// timetracking.<login-username>.<regionID>.<VarbitID>=<varbitValue>:<unix time>
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID();
long unixNow = Instant.now().getEpochSecond();
for (Varbits varbit : region.getVarbits())
for (FarmingPatch patch : region.getPatches())
{
// Write the config value if it doesn't match what is current, or it is more than 5 minutes old
Varbits varbit = patch.getVarbit();
String key = Integer.toString(varbit.getId());
String strVarbit = Integer.toString(client.getVar(varbit));
String storedValue = configManager.getConfiguration(group, key);
@@ -118,6 +130,7 @@ public class FarmingTracker
String value = strVarbit + ":" + unixNow;
configManager.setConfiguration(group, key, value);
updateCompletionTime(patch.getImplementation());
changed = true;
}
}
@@ -125,6 +138,97 @@ public class FarmingTracker
return changed;
}
public void loadCompletionTimes()
{
completionTimes.clear();
for (PatchImplementation patchType : PatchImplementation.values())
{
updateCompletionTime(patchType);
}
}
/**
* Gets the overall completion time for the given patch type.
* @see #completionTimes
*/
public long getCompletionTime(PatchImplementation patchType)
{
Long completionTime = completionTimes.get(patchType);
return completionTime == null ? -1 : completionTime;
}
/**
* Updates the overall completion time for the given patch type.
* @see #completionTimes
*/
private void updateCompletionTime(PatchImplementation patchType)
{
long maxCompletionTime = 0;
boolean allUnknown = true;
for (FarmingPatch patch : farmingWorld.getPatchTypes().get(patchType))
{
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + patch.getRegion().getRegionID();
String key = Integer.toString(patch.getVarbit().getId());
String storedValue = configManager.getConfiguration(group, key);
long unixTime = 0;
int value = 0;
if (storedValue != null)
{
String[] parts = storedValue.split(":");
if (parts.length == 2)
{
try
{
value = Integer.parseInt(parts[0]);
unixTime = Long.parseLong(parts[1]);
}
catch (NumberFormatException e)
{
// ignored
}
}
}
PatchState state = unixTime <= 0 ? null : patch.getImplementation().forVarbitValue(value);
if (state == null || state.getProduce().getItemID() < 0)
{
continue; // unknown state
}
int tickrate = state.getTickRate() * 60;
int stage = state.getStage();
int stages = state.getStages();
if (state.getProduce() != Produce.WEEDS && state.getProduce() != Produce.SCARECROW)
{
// update max duration if this patch takes longer to grow
if (tickrate > 0)
{
long tickTime = unixTime / tickrate;
long doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate;
maxCompletionTime = Math.max(maxCompletionTime, doneEstimate);
}
else if (state.getCropState() == CropState.GROWING && stage != stages - 1)
{
continue; // unknown state
}
}
allUnknown = false;
}
if (allUnknown)
{
completionTimes.put(patchType, -1L);
return;
}
completionTimes.put(patchType, (maxCompletionTime <= Instant.now().getEpochSecond()) ? 0 : maxCompletionTime);
}
/**
* Migrates configuration data from {@code "farmingTracker"} key to {@code "timetracking"} key.
* This method should be removed after a reasonable amount of time.

View File

@@ -26,9 +26,12 @@
package net.runelite.client.plugins.timetracking.farming;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -47,6 +50,9 @@ class FarmingWorld
@Getter
private Map<Tab, Set<FarmingPatch>> tabs = new HashMap<>();
@Getter
private Map<PatchImplementation, List<FarmingPatch>> patchTypes = new EnumMap<>(PatchImplementation.class);
private final Comparator<FarmingPatch> tabSorter = Comparator
.comparing(FarmingPatch::getImplementation)
.thenComparing((FarmingPatch p) -> p.getRegion().getName())
@@ -243,6 +249,10 @@ class FarmingWorld
tabs
.computeIfAbsent(p.getImplementation().getTab(), k -> new TreeSet<>(tabSorter))
.add(p);
patchTypes
.computeIfAbsent(p.getImplementation(), k -> new ArrayList<>())
.add(p);
}
}
}

View File

@@ -28,12 +28,8 @@ package net.runelite.client.plugins.timetracking.hunter;
import java.awt.Color;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import net.runelite.api.ItemID;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.timetracking.TabContentPanel;
@@ -130,48 +126,17 @@ public class BirdHouseTabPanel extends TabContentPanel
panel.getEstimate().setText("Built");
break;
case SEEDED:
long doneEstimate = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION;
if (doneEstimate < unixNow)
long remainingTime = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION - unixNow;
if (remainingTime <= 0)
{
panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION);
panel.getProgress().setForeground(COMPLETED_COLOR);
panel.getEstimate().setText("Done");
}
else if (config.estimateRelative())
{
int remainingSeconds = (int) (59 + doneEstimate - unixNow);
int remaining = remainingSeconds / 60;
panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION - remainingSeconds);
StringBuilder f = new StringBuilder("Done in ");
int min = remaining % 60;
int hours = (remaining / 60) % 24;
int days = remaining / (60 * 24);
if (days > 0)
{
f.append(days).append("d ");
}
if (hours > 0)
{
f.append(hours).append("h ");
}
if (min > 0)
{
f.append(min).append("m ");
}
panel.getEstimate().setText(f.toString());
}
else
{
StringBuilder f = new StringBuilder();
LocalDateTime ldtTime = LocalDateTime.ofEpochSecond(doneEstimate, 0, OffsetDateTime.now().getOffset());
LocalDateTime ldtNow = LocalDateTime.now();
f.append("Done ");
if (ldtTime.getDayOfWeek() != ldtNow.getDayOfWeek())
{
f.append(ldtTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" ");
}
f.append(String.format("at %d:%02d", ldtTime.getHour(), ldtTime.getMinute()));
panel.getEstimate().setText(f.toString());
panel.getProgress().setValue((int) (BirdHouseTracker.BIRD_HOUSE_DURATION - remainingTime));
panel.getEstimate().setText("Done " + getFormattedEstimate(remainingTime, config.estimateRelative()));
}
break;
default:

View File

@@ -78,6 +78,7 @@ public class BirdHouseTracker
* This is set to {@code 0} if the bird houses have already completed
* when updating it.
*/
@Getter
private long completionTime = -1;
public BirdHouseTabPanel createBirdHouseTabPanel()