From 9781afd8003124de23525b3228aaee8b6b4b7ce8 Mon Sep 17 00:00:00 2001 From: leewyatt Date: Wed, 17 Apr 2024 04:38:59 +0900 Subject: [PATCH 1/2] Implement i18n, optimize property initialization, and fix focus loss in InfoCenterView --- .../gemsfx/infocenter/InfoCenterView.java | 129 +++++++++++++++--- .../gemsfx/infocenter/NotificationView.java | 16 ++- .../dlsc/gemsfx/skins/InfoCenterViewSkin.java | 42 +++--- .../gemsfx/util/ResourceBundleManager.java | 107 +++++++++++++++ .../resources/info-center-view.properties | 9 ++ .../resources/info-center-view_zh.properties | 9 ++ .../resources/notification-view.properties | 6 + .../resources/notification-view_zh.properties | 6 + 8 files changed, 284 insertions(+), 40 deletions(-) create mode 100644 gemsfx/src/main/java/com/dlsc/gemsfx/util/ResourceBundleManager.java create mode 100644 gemsfx/src/main/resources/info-center-view.properties create mode 100644 gemsfx/src/main/resources/info-center-view_zh.properties create mode 100644 gemsfx/src/main/resources/notification-view.properties create mode 100644 gemsfx/src/main/resources/notification-view_zh.properties diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterView.java b/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterView.java index 06341b08..1bf1c929 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterView.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterView.java @@ -4,17 +4,24 @@ import javafx.beans.InvalidationListener; import javafx.beans.WeakInvalidationListener; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import javafx.css.CssMetaData; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableProperty; +import javafx.css.converter.SizeConverter; import javafx.scene.control.Control; import javafx.scene.control.Skin; import javafx.util.Duration; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -31,6 +38,11 @@ public class InfoCenterView extends Control { private static final String TRANSPARENT = "transparent"; + private static final double DEFAULT_NOTIFICATION_SPACING = 10.0; + private static final Duration DEFAULT_EXPAND_DURATION = Duration.millis(300); + private static final Duration DEFAULT_SLIDE_IN_DURATION = Duration.millis(500); + private static final boolean DEFAULT_AUTO_OPEN_GROUP = false; + private static final boolean DEFAULT_TRANSPARENT = false; private final InvalidationListener updateNotificationsListener = it -> updateNotificationsList(); @@ -70,8 +82,6 @@ public InfoCenterView() { getGroups().addListener(groupListListener); setFocusTraversable(false); - - transparentProperty().addListener(it -> updateStyle()); } @Override @@ -171,8 +181,6 @@ private void updatePinnedAndUnpinnedGroupsList() { * notifications of that group. Applications can choose to display the notifications in * a different place, e.g. in a dialog. * - * @see NotificationGroup#maximumNumberOfNotificationsProperty() - * * @return the callback that gets invoked when requesting to see all notifications of a group */ public final ObjectProperty>> onShowAllGroupNotificationsProperty() { @@ -183,10 +191,10 @@ public final void setOnShowAllGroupNotifications(Consumer> notifications = FXCollections.observableArrayList(); @@ -281,10 +292,10 @@ public final void setShowAllFadeDuration(Duration showAllFadeDuration) { this.showAllFadeDuration.set(showAllFadeDuration); } - private final ObjectProperty expandDuration = new SimpleObjectProperty<>(this, "expandDuration", Duration.millis(300)); + private ObjectProperty expandDuration; public final Duration getExpandDuration() { - return expandDuration.get(); + return expandDuration == null ? DEFAULT_EXPAND_DURATION : expandDuration.get(); } /** @@ -294,17 +305,20 @@ public final Duration getExpandDuration() { * @return the expand / collapse animation duration */ public final ObjectProperty expandDurationProperty() { + if (expandDuration == null) { + expandDuration = new SimpleObjectProperty<>(this, "expandDuration", DEFAULT_EXPAND_DURATION); + } return expandDuration; } public final void setExpandDuration(Duration expandDuration) { - this.expandDuration.set(expandDuration); + expandDurationProperty().set(expandDuration); } - private final ObjectProperty slideInDuration = new SimpleObjectProperty<>(this, "slideInDuration", Duration.millis(500)); + private ObjectProperty slideInDuration; public final Duration getSlideInDuration() { - return slideInDuration.get(); + return slideInDuration == null ? DEFAULT_SLIDE_IN_DURATION : slideInDuration.get(); } /** @@ -313,17 +327,20 @@ public final Duration getSlideInDuration() { * @return the slide-in duration of a new notification */ public final ObjectProperty slideInDurationProperty() { + if (slideInDuration == null) { + slideInDuration = new SimpleObjectProperty<>(this, "slideInDuration", DEFAULT_SLIDE_IN_DURATION); + } return slideInDuration; } public final void setSlideInDuration(Duration slideInDuration) { - this.slideInDuration.set(slideInDuration); + slideInDurationProperty().set(slideInDuration); } - private final BooleanProperty transparent = new SimpleBooleanProperty(this, "transparent", false); + private BooleanProperty transparent; public final boolean isTransparent() { - return transparent.get(); + return transparent == null ? DEFAULT_TRANSPARENT : transparent.get(); } /** @@ -333,11 +350,91 @@ public final boolean isTransparent() { * @return true if the control is not transparent */ public final BooleanProperty transparentProperty() { + if (transparent == null) { + transparent = new SimpleBooleanProperty(this, "transparent", DEFAULT_TRANSPARENT) { + @Override + protected void invalidated() { + updateStyle(); + } + }; + } return transparent; } public final void setTransparent(boolean transparent) { - this.transparent.set(transparent); + transparentProperty().set(transparent); + } + + private DoubleProperty notificationSpacing; + + /** + * Represents the spacing between individual notification views within the same notification group. + * This property allows adjustment of the vertical gap depending on the layout of the notification container. + * The default value is 10.0. + * + * @return the DoubleProperty that controls the spacing between notifications in the same group. + */ + public final DoubleProperty notificationSpacingProperty() { + if (notificationSpacing == null) { + notificationSpacing = new StyleableDoubleProperty(DEFAULT_NOTIFICATION_SPACING) { + @Override + public Object getBean() { + return InfoCenterView.this; + } + + @Override + public String getName() { + return "notificationSpacing"; + } + + @Override + public CssMetaData getCssMetaData() { + return StyleableProperties.NOTIFICATION_SPACING; + } + }; + } + return notificationSpacing; + } + + public final double getNotificationSpacing() { + return notificationSpacing == null ? DEFAULT_NOTIFICATION_SPACING : notificationSpacing.get(); + } + + public final void setNotificationSpacing(double notificationSpacing) { + notificationSpacingProperty().set(notificationSpacing); + } + + private static class StyleableProperties { + + private static final CssMetaData NOTIFICATION_SPACING = + new CssMetaData<>("-fx-notification-spacing", SizeConverter.getInstance(), DEFAULT_NOTIFICATION_SPACING) { + @Override + public boolean isSettable(InfoCenterView view) { + return view.notificationSpacing == null || !view.notificationSpacing.isBound(); + } + + @Override + public StyleableProperty getStyleableProperty(InfoCenterView view) { + return (StyleableProperty) view.notificationSpacingProperty(); + } + }; + + private static final List> STYLEABLES; + + static { + final List> styleables = new ArrayList<>(Control.getClassCssMetaData()); + styleables.add(NOTIFICATION_SPACING); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + @Override + public List> getControlCssMetaData() { + return getClassCssMetaData(); + } + + public static List> getClassCssMetaData() { + return InfoCenterView.StyleableProperties.STYLEABLES; } public void clearAll() { diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/NotificationView.java b/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/NotificationView.java index 9eef384b..ed9b824b 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/NotificationView.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/NotificationView.java @@ -1,6 +1,7 @@ package com.dlsc.gemsfx.infocenter; import com.dlsc.gemsfx.infocenter.Notification.OnClickBehaviour; +import com.dlsc.gemsfx.util.ResourceBundleManager; import javafx.animation.FadeTransition; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -45,6 +46,8 @@ public class NotificationView> extends StackPane { private static final PseudoClass PSEUDO_CLASS_EXPANDED = PseudoClass.getPseudoClass("expanded"); + private static final DateTimeFormatter SHORT_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); + private final S notification; private final ContentPane contentPane; private final StackPane stackNotification1; @@ -211,23 +214,22 @@ public final void setShowContent(boolean showContent) { private static final StringConverter DEFAULT_TIME_CONVERTER = new StringConverter<>() { @Override public String toString(ZonedDateTime dateTime) { - System.out.println(">> 0"); if (dateTime != null) { Duration between = Duration.between(dateTime, ZonedDateTime.now()); if (between.toDays() == 0) { if (between.toHours() > 2) { return DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(dateTime.toLocalTime()); } else if (between.toHours() > 0) { - return MessageFormat.format("{0}h ago", between.toHours()); + return MessageFormat.format("{0}{1}", between.toHours(), ResourceBundleManager.getString(ResourceBundleManager.Type.NOTIFICATION_VIEW, "time.hours.ago")); } else if (between.toMinutes() > 0) { - return MessageFormat.format("{0}m ago", between.toMinutes()); + return MessageFormat.format("{0}{1}", between.toMinutes(), ResourceBundleManager.getString(ResourceBundleManager.Type.NOTIFICATION_VIEW, "time.minutes.ago")); } else { - return "now"; + return ResourceBundleManager.getString(ResourceBundleManager.Type.NOTIFICATION_VIEW, "time.now"); } } else if (between.toDays() == 1) { - return MessageFormat.format("Yesterday, {0}", DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(dateTime.toLocalTime())); + return MessageFormat.format("{0}, {1}", ResourceBundleManager.getString(ResourceBundleManager.Type.NOTIFICATION_VIEW,"time.yesterday"), SHORT_TIME_FORMATTER.format(dateTime.toLocalTime())); } else if (between.toDays() < 7) { - return MessageFormat.format("{0} days ago", between.toDays()); + return MessageFormat.format("{0} {1}", between.toDays(), ResourceBundleManager.getString(ResourceBundleManager.Type.NOTIFICATION_VIEW, "time.days.ago")); } else { return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).format(dateTime); } @@ -346,7 +348,7 @@ public ContentPane() { contentProperty().addListener(it -> updateCenterNode(center)); showContentProperty().addListener(it -> updateCenterNode(center)); - Label clearAllLabel = new Label("Clear All"); + Label clearAllLabel = new Label(ResourceBundleManager.getString(ResourceBundleManager.Type.NOTIFICATION_VIEW, "group.clear.all")); clearAllLabel.getStyleClass().add("clear-all"); clearAllLabel.setMouseTransparent(true); diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/InfoCenterViewSkin.java b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/InfoCenterViewSkin.java index 97ce2a43..a1a406cf 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/skins/InfoCenterViewSkin.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/skins/InfoCenterViewSkin.java @@ -6,6 +6,7 @@ import com.dlsc.gemsfx.infocenter.Notification.OnClickBehaviour; import com.dlsc.gemsfx.infocenter.NotificationGroup; import com.dlsc.gemsfx.infocenter.NotificationView; +import com.dlsc.gemsfx.util.ResourceBundleManager; import javafx.animation.AnimationTimer; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; @@ -238,8 +239,8 @@ public void selectNext() { groupNameLabel.setMaxWidth(Double.MAX_VALUE); HBox.setHgrow(groupNameLabel, Priority.ALWAYS); - Button closeShowAllButton = new Button("Close Group"); - closeShowAllButton.setTooltip(new Tooltip("Switch back to view with all groups")); + Button closeShowAllButton = new Button(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"single.group.header.close")); + closeShowAllButton.setTooltip(new Tooltip(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"single.group.header.close.tip"))); closeShowAllButton.getStyleClass().add("close-show-all-button"); closeShowAllButton.setOnAction(evt -> view.setShowAllGroup(null)); @@ -247,7 +248,7 @@ public void selectNext() { clearAllButton.setGraphic(new FontIcon()); clearAllButton.getStyleClass().add("clear-all-button"); clearAllButton.setOnAction(evt -> view.getShowAllGroup().getNotifications().clear()); - clearAllButton.setTooltip(new Tooltip("Remove all notifications")); + clearAllButton.setTooltip(new Tooltip(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"single.group.header.remove.all"))); HBox singleGroupHeader = new HBox(groupNameLabel, closeShowAllButton, clearAllButton); singleGroupHeader.getStyleClass().add("single-group-header"); @@ -427,12 +428,13 @@ class GroupView> extends Pane { private final HBox headerBox = new HBox(); - private final int SPACING = 10; - private final InvalidationListener requestedNotificationListener = it -> showNotificationView(); - private final WeakInvalidationListener weakRequestedNotificationListener = new WeakInvalidationListener(requestedNotificationListener); + private final InvalidationListener spacingListener = it -> layoutChildren(); + private final WeakInvalidationListener weakSpacingListener = new WeakInvalidationListener(spacingListener); + + public GroupView(NotificationGroup group) { this.group = group; getStyleClass().add("group-view"); @@ -444,24 +446,28 @@ public GroupView(NotificationGroup group) { groupNameLabel.setMaxWidth(Double.MAX_VALUE); HBox.setHgrow(groupNameLabel, Priority.ALWAYS); - Button showLessButton = new Button("Show Less"); + Button showLessButton = new Button(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW, "group.header.show.less")); showLessButton.getStyleClass().add("show-less-button"); - showLessButton.setOnAction(evt -> group.setExpanded(false)); - showLessButton.setTooltip(new Tooltip("Stack the notifications")); + showLessButton.setOnAction(evt -> { + requestFocus(); + group.setExpanded(false); + }); + showLessButton.setTooltip(new Tooltip(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"group.header.show.less.tip"))); InfoCenterView infoCenterView = getSkinnable(); + infoCenterView.notificationSpacingProperty().addListener(weakSpacingListener); Button showAllButton = new Button(); - showAllButton.textProperty().bind(Bindings.createStringBinding(() -> MessageFormat.format("Show All {0}", group.getNotifications().size()), group.getNotifications())); + showAllButton.textProperty().bind(Bindings.createStringBinding(() -> MessageFormat.format("{0} {1}", ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"group.header.show.all"), group.getNotifications().size()), group.getNotifications())); showAllButton.getStyleClass().add("show-all-button"); - showAllButton.setTooltip(new Tooltip("Show all notifications of this group in a list")); + showAllButton.setTooltip(new Tooltip(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"group.header.show.all.tip"))); showAllButton.setOnAction(evt -> infoCenterView.getOnShowAllGroupNotifications().accept(group)); showAllButton.visibleProperty().bind(Bindings.createBooleanBinding(() -> infoCenterView.getOnShowAllGroupNotifications() != null && group.getNotifications().size() > group.getMaximumNumberOfNotifications(), group.maximumNumberOfNotificationsProperty(), group.getNotifications(), infoCenterView.onShowAllGroupNotificationsProperty())); Button clearButton = new Button(); clearButton.getStyleClass().add("clear-button"); - clearButton.setTooltip(new Tooltip("Remove all notifications from the group")); + clearButton.setTooltip(new Tooltip(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"group.header.remove.all.tip"))); clearButton.setGraphic(new FontIcon()); clearButton.setOnAction(evt -> { group.setExpanded(false); @@ -470,7 +476,7 @@ public GroupView(NotificationGroup group) { ToggleButton pinButton = new ToggleButton(); pinButton.getStyleClass().add("pin-button"); - pinButton.setTooltip(new Tooltip("Pin the group at the top")); + pinButton.setTooltip(new Tooltip(ResourceBundleManager.getString(ResourceBundleManager.Type.INFO_CENTER_VIEW,"group.header.pin.tip"))); pinButton.setGraphic(new FontIcon()); pinButton.visibleProperty().bind(group.pinnableProperty()); pinButton.managedProperty().bind(group.pinnableProperty()); @@ -568,9 +574,10 @@ protected double computePrefHeight(double width) { width = width - getInsets().getLeft() - getInsets().getRight(); + double spacing = getSkinnable().getNotificationSpacing(); if (headerBox.isVisible()) { h += headerBox.prefHeight(width); - h += SPACING; + h += spacing; } List notificationViews = getChildren().stream().filter(node -> node instanceof NotificationView).collect(Collectors.toList()); @@ -586,7 +593,7 @@ protected double computePrefHeight(double width) { if (notificationViews.size() > 1) { for (int i = notificationViews.size() - 2; i >= 0; i--) { Node node = notificationViews.get(i); - innerHeight += SPACING; + innerHeight += spacing; innerHeight += node.prefHeight(width); } } @@ -607,11 +614,12 @@ protected void layoutChildren() { List notificationViews = getChildren().stream().filter(node -> node instanceof NotificationView).collect(Collectors.toList()); int size = notificationViews.size(); + double spacing = getSkinnable().getNotificationSpacing(); if (headerBox.isVisible()) { double boxHeight = headerBox.prefHeight(w); headerBox.resizeRelocate(x, y, w, boxHeight); y += boxHeight; - y += SPACING; + y += spacing; } for (int i = size - 1; i >= 0; i--) { @@ -622,7 +630,7 @@ protected void layoutChildren() { y += h; if (i > 0) { - y += SPACING; + y += spacing; } } } diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/util/ResourceBundleManager.java b/gemsfx/src/main/java/com/dlsc/gemsfx/util/ResourceBundleManager.java new file mode 100644 index 00000000..20b472ad --- /dev/null +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/util/ResourceBundleManager.java @@ -0,0 +1,107 @@ +package com.dlsc.gemsfx.util; + +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class provides a centralized mechanism to retrieve localized strings + * from property files based on the current locale settings. It caches the resource bundles + * to avoid repetitive and unnecessary loading of the properties files. + * + *

Usage involves retrieving strings via base names of the resource bundles + * or by using predefined types which represent specific views or components + * in the application.

+ */ +public class ResourceBundleManager { + + private static final Logger LOG = Logger.getLogger(ResourceBundleManager.class.getName()); + private static final Map BUNDLES = new ConcurrentHashMap<>(); + private static Locale currentLocale = Locale.getDefault(); + + public enum Type { + INFO_CENTER_VIEW("info-center-view"), + NOTIFICATION_VIEW("notification-view"); + + private final String baseName; + + Type(String baseName) { + this.baseName = baseName; + } + + public String getBaseName() { + return baseName; + } + } + + private ResourceBundleManager() { + } + + /** + * Retrieves the resource bundle for the specified base name and the current application locale. + * This method will return a cached bundle if it exists, or load a new bundle if it does not. + * + * @param baseName the base name of the resource bundle. + * @return the requested resource bundle. + */ + public static ResourceBundle getBundle(String baseName) { + return BUNDLES.computeIfAbsent(key(baseName, currentLocale), + k -> ResourceBundle.getBundle(baseName, currentLocale, ResourceBundleManager.class.getClassLoader())); + } + + /** + * Sets the current locale of the application. If the locale is changed, + * the method clears the cache of loaded resource bundles. + * + * @param locale the new locale to set as the current. + */ + public static void setLocale(Locale locale) { + if (!locale.equals(currentLocale)) { + currentLocale = locale; + // Clear cache as locale has changed + BUNDLES.clear(); + } + } + + + /** + * Generates a unique key based on the base name and locale for caching purposes. + */ + private static String key(String baseName, Locale locale) { + return baseName + "_" + locale.toString(); + } + + /** + * Retrieves a localized string from the resource bundle specified by the base name. + * If the key is not found, it logs a warning and returns the key itself. + * + * @param baseName the base name of the resource bundle. + * @param key the key for the desired string in the bundle. + * @return the localized string. + */ + public static String getString(String baseName, String key) { + try { + ResourceBundle bundle = getBundle(baseName); + return bundle.getString(key); + } catch (MissingResourceException ex) { + LOG.log(Level.WARNING, "Missing resource key: " + key, ex); + return key; + } + } + + /** + * Retrieves a localized string from the resource bundle associated with a given type. + * + * @param type the type of the resource bundle. + * @param key the key for the desired string in the bundle. + * @return the localized string. + */ + public static String getString(Type type, String key) { + return getString(type.getBaseName(), key); + } + +} diff --git a/gemsfx/src/main/resources/info-center-view.properties b/gemsfx/src/main/resources/info-center-view.properties new file mode 100644 index 00000000..7b801549 --- /dev/null +++ b/gemsfx/src/main/resources/info-center-view.properties @@ -0,0 +1,9 @@ +group.header.show.all=Show All +group.header.show.all.tip=Show all notifications of this group in a list +group.header.show.less=Show Less +group.header.show.less.tip=Stack the notifications +group.header.remove.all.tip=Remove all notifications from the group +group.header.pin.tip=Pin the group at the top +single.group.header.close=Close Group +single.group.header.close.tip=Switch back to view with all groups +single.group.header.remove.all=Remove all notifications \ No newline at end of file diff --git a/gemsfx/src/main/resources/info-center-view_zh.properties b/gemsfx/src/main/resources/info-center-view_zh.properties new file mode 100644 index 00000000..eba38de4 --- /dev/null +++ b/gemsfx/src/main/resources/info-center-view_zh.properties @@ -0,0 +1,9 @@ +group.header.show.all=\u5168\u90e8\u663e\u793a +group.header.show.all.tip=\u5728\u5217\u8868\u4e2d\u663e\u793a\u672c\u7ec4\u5168\u90e8\u901a\u77e5 +group.header.show.less=\u6298\u53e0\u901a\u77e5 +group.header.show.less.tip=\u6536\u8d77\u672c\u7ec4\u901a\u77e5 +group.header.remove.all.tip=\u5220\u9664\u672c\u7ec4\u5168\u90e8\u901a\u77e5 +group.header.pin.tip=\u56fa\u5b9a\u672c\u7ec4\u901a\u77e5\u5230\u9876\u90e8 +single.group.header.close=\u5173\u95ed +single.group.header.close.tip=\u8fd4\u56de\u67e5\u770b\u6240\u6709\u7ec4\u7684\u901a\u77e5 +single.group.header.remove.all=\u5220\u9664\u672c\u7ec4\u5168\u90e8\u901a\u77e5 \ No newline at end of file diff --git a/gemsfx/src/main/resources/notification-view.properties b/gemsfx/src/main/resources/notification-view.properties new file mode 100644 index 00000000..d3dc81d3 --- /dev/null +++ b/gemsfx/src/main/resources/notification-view.properties @@ -0,0 +1,6 @@ +group.clear.all=Clear All +time.now=now +time.hours.ago=h ago +time.minutes.ago=m ago +time.yesterday=Yesterday +time.days.ago=days ago \ No newline at end of file diff --git a/gemsfx/src/main/resources/notification-view_zh.properties b/gemsfx/src/main/resources/notification-view_zh.properties new file mode 100644 index 00000000..887f3314 --- /dev/null +++ b/gemsfx/src/main/resources/notification-view_zh.properties @@ -0,0 +1,6 @@ +group.clear.all=\u6e05\u7a7a +time.now=\u73b0\u5728 +time.hours.ago=\u5c0f\u65f6\u524d +time.minutes.ago=\u5206\u949f\u524d +time.yesterday=\u6628\u5929 +time.days.ago=\u5929\u524d \ No newline at end of file From 63177674ff3ee12aff2bc80afe383ac1823a4bf1 Mon Sep 17 00:00:00 2001 From: leewyatt Date: Wed, 17 Apr 2024 04:55:34 +0900 Subject: [PATCH 2/2] Rename slideInDuration() to slideInDurationProperty() and lazy initialization --- .../dlsc/gemsfx/infocenter/InfoCenterPane.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterPane.java b/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterPane.java index 26e4efb4..2345ff12 100644 --- a/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterPane.java +++ b/gemsfx/src/main/java/com/dlsc/gemsfx/infocenter/InfoCenterPane.java @@ -25,6 +25,8 @@ */ public class InfoCenterPane extends Control { + private static final Duration DEFAULT_SLIDE_IN_DURATION = Duration.millis(200); + /* * The managed info center instance. */ @@ -161,7 +163,7 @@ public void handle(long now) { } }; - TreeShowing.treeShowing(this).addListener((p,o,n) -> { + TreeShowing.treeShowing(this).addListener((p, o, n) -> { if (n) { timer.start(); } else { @@ -231,9 +233,8 @@ public final boolean isAutoHide() { * A flag that determines if the info center view should automatically disappear again * after a certain timeout duration. * - * @see #autoHideDuration - * * @return true if the info center hides automatically after a certain period of time + * @see #autoHideDuration */ public final BooleanProperty autoHideProperty() { return autoHide; @@ -269,7 +270,7 @@ public final void setContent(Node content) { // slide in / slide out duration - private final ObjectProperty slideInDuration = new SimpleObjectProperty<>(this, "slideDuration", Duration.millis(200)); //$NON-NLS-1$ + private ObjectProperty slideInDuration; //$NON-NLS-1$ /** * The duration used for the "slide in" / "slide out" animation when the info center view @@ -277,16 +278,19 @@ public final void setContent(Node content) { * * @return animation duration for the sliding in and out of the info center view */ - public final ObjectProperty slideInDuration() { + public final ObjectProperty slideInDurationProperty() { + if (slideInDuration == null) { + slideInDuration = new SimpleObjectProperty<>(this, "slideInDuration", DEFAULT_SLIDE_IN_DURATION); + } return slideInDuration; } public final Duration getSlideInDuration() { - return slideInDuration.get(); + return slideInDuration == null ? DEFAULT_SLIDE_IN_DURATION : slideInDuration.get(); } public final void setSlideInDuration(Duration duration) { - slideInDuration.set(duration); + slideInDurationProperty().set(duration); } private final BooleanProperty showInfoCenter = new SimpleBooleanProperty(this, "showInfoCenter", false);