diff --git a/app/src/main/java/com/dlsc/jfxcentral2/app/JFXCentral2MobileApp.java b/app/src/main/java/com/dlsc/jfxcentral2/app/JFXCentral2MobileApp.java index 2b97b5a6..16316b99 100644 --- a/app/src/main/java/com/dlsc/jfxcentral2/app/JFXCentral2MobileApp.java +++ b/app/src/main/java/com/dlsc/jfxcentral2/app/JFXCentral2MobileApp.java @@ -51,6 +51,7 @@ import com.dlsc.jfxcentral2.model.Size; import com.dlsc.jfxcentral2.utils.EventBusUtil; import com.dlsc.jfxcentral2.utils.MobileLinkUtil; +import com.dlsc.jfxcentral2.utils.MobileResponse; import com.dlsc.jfxcentral2.utils.MobileRoute; import com.dlsc.jfxcentral2.utils.MobileRouter; import com.dlsc.jfxcentral2.utils.NodeUtil; @@ -189,16 +190,16 @@ private MobileRouter createMobileRouter() { if (repositoryUpdated) { MobileHomePage mobileHomePage = MobileHomePage.getInstance(); mobileHomePage.sizeProperty().bind(size); - return mobileHomePage; + return MobileResponse.view(r, mobileHomePage); } else { - return new MobileRefreshPage(size); + return MobileResponse.redirect(r, PagePath.REFRESH); } })) - .and(MobileRoute.get(PagePath.REFRESH, r -> new MobileRefreshPage(size))) + .and(MobileRoute.get(PagePath.REFRESH, r -> MobileResponse.view(r, new MobileRefreshPage(size)))) .and(MobileRoute.redirect("/index", PagePath.HOME)) .and(MobileRoute.redirect("/home", PagePath.HOME)) - .and(MobileRoute.get(PagePath.LINKS, r -> new MobileLinksOfTheWeekPage(size))) - .and(MobileRoute.get(PagePath.DOCUMENTATION, r -> new MobileDocPage(size))) + .and(MobileRoute.get(PagePath.LINKS, r -> MobileResponse.view(r, new MobileLinksOfTheWeekPage(size)))) + .and(MobileRoute.get(PagePath.DOCUMENTATION, r -> MobileResponse.view(r, new MobileDocPage(size)))) .and(createCategoryOrDetailRoute(PagePath.SHOWCASES, RealWorldApp.class, () -> new MobileShowcasesCategoryPage(size), id -> new MobileShowcaseMobileDetailsPage(size, id))) .and(createCategoryOrDetailRoute(PagePath.REAL_WORLD, RealWorldApp.class, () -> new MobileShowcasesCategoryPage(size), id -> new MobileShowcaseMobileDetailsPage(size, id))) .and(createCategoryOrDetailRoute(PagePath.LIBRARIES, Library.class, () -> new MobileLibrariesCategoryPage(size), id -> new MobileLibraryDetailsPage(size, id))) @@ -221,13 +222,12 @@ private MobileRoute createCategoryOrDetailRoute(String path, Class 0 && clazz != null) { String id = url.substring(index + 1).trim(); if (!DataRepository2.getInstance().isValidId(clazz, id)) { - return new Label("Error: 404"); + return MobileResponse.view(url, new Label("Error: 404")); } - return detailedResponse.call(id); + return MobileResponse.view(url, detailedResponse.call(id)); } - return categoryResponse.get(); + return MobileResponse.view(url, categoryResponse.get()); }); - } public static void main(String[] args) { diff --git a/components/src/main/java/com/dlsc/jfxcentral2/components/MobilePageBase.java b/components/src/main/java/com/dlsc/jfxcentral2/components/MobilePageBase.java new file mode 100644 index 00000000..fbbd68cd --- /dev/null +++ b/components/src/main/java/com/dlsc/jfxcentral2/components/MobilePageBase.java @@ -0,0 +1,247 @@ +package com.dlsc.jfxcentral2.components; + +import com.dlsc.jfxcentral2.model.Size; +import com.dlsc.jfxcentral2.utils.MobileRouter; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.VBox; + +public class MobilePageBase extends VBox { + + private static final String DEFAULT_STYLE_CLASS = "mobile-page-base"; + + /** + * Default value indicating whether swipe back is enabled. + * If enabled, the user can swipe from left to right to go back to the previous view. + */ + private static final boolean DEFAULT_SWIPE_BACK_ENABLED = true; + + /** + * Default value indicating whether swipe forward is enabled. + * If enabled, the user can swipe from right to left to go forward to the next view. + */ + private static final boolean DEFAULT_SWIPE_FORWARD_ENABLED = true; + + /** + * Default horizontal threshold for swipe gestures. + * If the horizontal movement is greater than this value, the swipe gesture is recognized. + */ + private static final double SWIPE_HOR_THRESHOLD = 60; + + /** + * Default vertical threshold for swipe gestures. + * If the vertical movement is greater than this value, the horizontal swipe gesture is ignored. + */ + private static final double SWIPE_VERTICAL_THRESHOLD = 30; + + private final MobileRouter mobileRouter = MobileRouter.getInstance(); + private final SizeSupport sizeSupport = new SizeSupport(this); + + private double initialX; + private double initialY; + private boolean isSwiping; + + public MobilePageBase() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + + this.addEventFilter(MouseEvent.MOUSE_PRESSED, this::handleMousePressed); + this.addEventFilter(MouseEvent.MOUSE_RELEASED, this::handleMouseReleased); + } + + private void handleMousePressed(MouseEvent event) { + initialX = event.getSceneX(); + initialY = event.getSceneY(); + isSwiping = true; + } + + private void handleMouseReleased(MouseEvent event) { + if (!isSwiping) { + return; + } + + double deltaX = event.getSceneX() - initialX; + double deltaY = event.getSceneY() - initialY; + + // If the vertical movement is greater, ignore the horizontal movement. + if (Math.abs(deltaY) > SWIPE_VERTICAL_THRESHOLD) { + isSwiping = false; + return; + } + + if (Math.abs(deltaX) > SWIPE_HOR_THRESHOLD) { + if (deltaX > 0 && isSwipeBackEnabled() && mobileRouter.canGoBackProperty().get()) { + onSwipeRight(); + } else if (deltaX < 0 && isSwipeForwardEnabled() && mobileRouter.canGoForwardProperty().get()) { + onSwipeLeft(); + } + } + isSwiping = false; + } + protected void onSwipeLeft() { + mobileRouter.goToForward(); + } + + protected void onSwipeRight() { + mobileRouter.goToBack(); + } + + // size + + public final ObjectProperty sizeProperty() { + return sizeSupport.sizeProperty(); + } + + public final Size getSize() { + return sizeSupport.getSize(); + } + + public final void setSize(Size size) { + sizeSupport.setSize(size); + } + + // swipe back enabled + + private BooleanProperty swipeBackEnabled; + + /** + * A flag that indicates whether swipe back is enabled. + * If enabled, the user can swipe from the left edge of the screen to go back to the previous view. + * If disabled, the swipe gesture is ignored. + * The default value is {@code true}. + */ + public final BooleanProperty swipeBackEnabledProperty() { + if (swipeBackEnabled == null) { + swipeBackEnabled = new SimpleBooleanProperty(this, "swipeBackEnabled", DEFAULT_SWIPE_BACK_ENABLED); + } + return swipeBackEnabled; + } + + public final boolean isSwipeBackEnabled() { + return swipeBackEnabled == null ? DEFAULT_SWIPE_BACK_ENABLED : swipeBackEnabled.get(); + } + + public final void setSwipeBackEnabled(boolean swipeBackEnabled) { + swipeBackEnabledProperty().set(swipeBackEnabled); + } + + // swipe forward enabled + + private BooleanProperty swipeForwardEnabled; + + /** + * A flag that indicates whether swipe forward is enabled. + * If enabled, the user can swipe from the right edge of the screen to go forward to the next view. + * If disabled, the swipe gesture is ignored. + * The default value is {@code true}. + */ + public final BooleanProperty swipeForwardEnabledProperty() { + if (swipeForwardEnabled == null) { + swipeForwardEnabled = new SimpleBooleanProperty(this, "swipeForwardEnabled", DEFAULT_SWIPE_FORWARD_ENABLED); + } + return swipeForwardEnabled; + } + + public final boolean isSwipeForwardEnabled() { + return swipeForwardEnabled == null ? DEFAULT_SWIPE_FORWARD_ENABLED : swipeForwardEnabled.get(); + } + + public final void setSwipeForwardEnabled(boolean swipeForwardEnabled) { + swipeForwardEnabledProperty().set(swipeForwardEnabled); + } + + // view will appear + + private ObjectProperty viewWillAppear; + + /** + * A callback that is invoked when the view will appear. + * This is typically called just before the view is added to the scene. + */ + public final ObjectProperty viewWillAppearProperty() { + if (viewWillAppear == null) { + viewWillAppear = new SimpleObjectProperty<>(this, "viewWillAppear"); + } + return viewWillAppear; + } + + public final Runnable getViewWillAppear() { + return viewWillAppear == null ? null : viewWillAppear.get(); + } + + public final void setViewWillAppear(Runnable viewWillAppear) { + viewWillAppearProperty().set(viewWillAppear); + } + + // view did appear + + private ObjectProperty viewDidAppear; + + /** + * A callback that is invoked when the view appears. + * This is typically called after the view has been added to the scene. + */ + public final ObjectProperty viewDidAppearProperty() { + if (viewDidAppear == null) { + viewDidAppear = new SimpleObjectProperty<>(this, "viewDidAppear"); + } + return viewDidAppear; + } + + public final Runnable getViewDidAppear() { + return viewDidAppear == null ? null : viewDidAppear.get(); + } + + public final void setViewDidAppear(Runnable viewDidAppear) { + viewDidAppearProperty().set(viewDidAppear); + } + + // view will disappear + + private ObjectProperty viewWillDisappear; + + /** + * A callback that is invoked when the view will disappear. + * This is typically called just before the view is removed from the scene. + */ + public final ObjectProperty viewWillDisappearProperty() { + if (viewWillDisappear == null) { + viewWillDisappear = new SimpleObjectProperty<>(this, "viewWillDisappear"); + } + return viewWillDisappear; + } + + public final Runnable getViewWillDisappear() { + return viewWillDisappear == null ? null : viewWillDisappear.get(); + } + + public final void setViewWillDisappear(Runnable viewWillDisappear) { + viewWillDisappearProperty().set(viewWillDisappear); + } + + // view did disappear + + private ObjectProperty viewDidDisappear; + + /** + * A callback that is invoked when the view disappears. + * This is typically called after the view has been removed from the scene. + */ + public final ObjectProperty viewDidDisappearProperty() { + if (viewDidDisappear == null) { + viewDidDisappear = new SimpleObjectProperty<>(this, "viewDidDisappear"); + } + return viewDidDisappear; + } + + public final Runnable getViewDidDisappear() { + return viewDidDisappear == null ? null : viewDidDisappear.get(); + } + + public final void setViewDidDisappear(Runnable viewDidDisappear) { + viewDidDisappearProperty().set(viewDidDisappear); + } +} + diff --git a/components/src/main/java/com/dlsc/jfxcentral2/events/MobileResponseEvent.java b/components/src/main/java/com/dlsc/jfxcentral2/events/MobileResponseEvent.java index ccc3bf68..8f5a2adb 100644 --- a/components/src/main/java/com/dlsc/jfxcentral2/events/MobileResponseEvent.java +++ b/components/src/main/java/com/dlsc/jfxcentral2/events/MobileResponseEvent.java @@ -1,6 +1,6 @@ package com.dlsc.jfxcentral2.events; -import javafx.scene.Node; +import com.dlsc.jfxcentral2.utils.MobileResponse; -public record MobileResponseEvent(String url, Node view) { +public record MobileResponseEvent(MobileResponse mobileResponse) { } diff --git a/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileResponse.java b/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileResponse.java new file mode 100644 index 00000000..86543dc6 --- /dev/null +++ b/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileResponse.java @@ -0,0 +1,86 @@ +package com.dlsc.jfxcentral2.utils; + +import javafx.scene.Node; + +/** + * The MobileResponse class represents the response generated by a mobile route. + * It encapsulates the details of the requested URL, the view to be displayed, + * and any potential redirection path. + */ +public class MobileResponse { + + /** + * The view to be displayed. + */ + private final Node view; + + /** + * The original request URL. + */ + private final String requestUrl; + + /** + * The path to redirect to, if any. + */ + private final String redirectPath; + + /** + * The final URL after all redirections have been resolved. + */ + private String finalUrl; + + /** + * Private constructor to initialize a MobileResponse instance. + * + * @param requestUrl the original request URL + * @param view the view to be displayed + * @param redirectPath the path to redirect to, if any + */ + private MobileResponse(String requestUrl, Node view, String redirectPath) { + this.requestUrl = requestUrl; + this.view = view; + this.redirectPath = redirectPath; + } + + /** + * Creates a MobileResponse instance representing a view response. + * + * @param requestUrl the original request URL + * @param view the view to be displayed + * @return a new MobileResponse instance + */ + public static MobileResponse view(String requestUrl, Node view) { + return new MobileResponse(requestUrl, view, null); + } + + /** + * Creates a MobileResponse instance representing a redirect response. + * + * @param requestUrl the original request URL + * @param redirectPath the path to redirect to + * @return a new MobileResponse instance + */ + public static MobileResponse redirect(String requestUrl, String redirectPath) { + return new MobileResponse(requestUrl, null, redirectPath); + } + + public String getRequestUrl() { + return requestUrl; + } + + public Node getView() { + return view; + } + + public String getRedirectPath() { + return redirectPath; + } + + public String getFinalUrl() { + return finalUrl; + } + + public void setFinalUrl(String finalUrl) { + this.finalUrl = finalUrl; + } +} diff --git a/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRoute.java b/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRoute.java index c72e35ef..0884f5cb 100644 --- a/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRoute.java +++ b/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRoute.java @@ -1,8 +1,6 @@ package com.dlsc.jfxcentral2.utils; -import javafx.scene.Node; import javafx.util.Callback; -import org.apache.commons.lang3.StringUtils; import java.util.regex.Pattern; @@ -12,17 +10,18 @@ */ public class MobileRoute { + private final String path; - private final Callback viewCallback; + private final Callback responseCallback; private final String redirectPath; private final Pattern pattern; /** * Constructor for normal routes */ - public MobileRoute(String path, Callback viewCallback) { + public MobileRoute(String path, Callback responseCallback) { this.path = path; - this.viewCallback = viewCallback; + this.responseCallback = responseCallback; this.redirectPath = null; this.pattern = Pattern.compile(Pattern.quote(path) + "(/.*)?"); } @@ -32,7 +31,7 @@ public MobileRoute(String path, Callback viewCallback) { */ public MobileRoute(String path, String redirectPath) { this.path = path; - this.viewCallback = null; + this.responseCallback = null; this.redirectPath = redirectPath; this.pattern = null; } @@ -42,11 +41,14 @@ public boolean matches(String url) { // Strict match for redirect routes return url.equals(path); } - return url.equals(path) || pattern.matcher(StringUtils.trimToEmpty(url)).matches(); + return url.equals(path) || pattern.matcher(url.trim()).matches(); } - public Node createView(String url) { - return viewCallback.call(url); + public MobileResponse createResponse(String url) { + if (redirectPath != null) { + return MobileResponse.redirect(url, redirectPath); + } + return responseCallback.call(url); } public String getRedirectPath() { @@ -57,8 +59,8 @@ public String getPath() { return path; } - public static MobileRoute get(String path, Callback viewCallback) { - return new MobileRoute(path, viewCallback); + public static MobileRoute get(String path, Callback responseCallback) { + return new MobileRoute(path, responseCallback); } public static MobileRoute redirect(String path, String redirectPath) { diff --git a/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRouter.java b/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRouter.java index be610a6c..4dcc8919 100644 --- a/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRouter.java +++ b/components/src/main/java/com/dlsc/jfxcentral2/utils/MobileRouter.java @@ -9,6 +9,7 @@ import javafx.beans.property.ReadOnlyStringWrapper; import javafx.scene.Node; import javafx.scene.control.Label; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -49,7 +50,7 @@ public void navigateTo(String link) { long start = System.currentTimeMillis(); System.out.println(">>> Navigating to: " + link); - if (!Objects.equals(navigator.getCurrent(), link)) { + if (!Objects.equals(navigator.getCurrent(), link) && !StringUtils.equalsIgnoreCase(link, PagePath.REFRESH)) { // Record the navigation in the history manager navigator.visit(link); } @@ -60,20 +61,25 @@ private void updateView(String link, long start) { currentPath.set(link); for (MobileRoute mobileRoute : mobileRoutes) { if (mobileRoute.matches(link)) { - if (mobileRoute.getRedirectPath() != null) { - navigateTo(mobileRoute.getRedirectPath()); + MobileResponse response = mobileRoute.createResponse(link); + + String redirectPath = response.getRedirectPath(); + if (redirectPath != null) { + navigateTo(redirectPath); loadTime.set(System.currentTimeMillis() - start); return; } - Node view = mobileRoute.createView(link); + + Node view = response.getView(); if (view != null) { - EventBusUtil.post(new MobileResponseEvent(link, view)); + response.setFinalUrl(link); + EventBusUtil.post(new MobileResponseEvent(response)); loadTime.set(System.currentTimeMillis() - start); return; } } } - EventBusUtil.post(new MobileResponseEvent(link, new Label("Error: 404 " + link))); + EventBusUtil.post(new MobileResponseEvent(MobileResponse.view(link, new Label("404 - Not Found")))); loadTime.set(System.currentTimeMillis() - start); } diff --git a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileHomePage.java b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileHomePage.java index f59e4d38..452a3f07 100644 --- a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileHomePage.java +++ b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileHomePage.java @@ -11,14 +11,13 @@ import com.dlsc.jfxcentral.data.model.RealWorldApp; import com.dlsc.jfxcentral.data.model.Tip; import com.dlsc.jfxcentral.data.model.Video; +import com.dlsc.jfxcentral2.components.MobilePageBase; import com.dlsc.jfxcentral2.components.PrettyScrollPane; -import com.dlsc.jfxcentral2.components.SizeSupport; import com.dlsc.jfxcentral2.mobile.components.MobileSearchView; import com.dlsc.jfxcentral2.mobile.home.CategoryAdvancedView; import com.dlsc.jfxcentral2.mobile.home.CategoryPreviewView; import com.dlsc.jfxcentral2.mobile.home.HomePageHeader; import com.dlsc.jfxcentral2.mobile.home.WeekLinksView; -import com.dlsc.jfxcentral2.model.Size; import com.dlsc.jfxcentral2.utils.PagePath; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; @@ -33,11 +32,10 @@ import java.util.Collections; import java.util.List; -public class MobileHomePage extends VBox { +public class MobileHomePage extends MobilePageBase { private static MobileHomePage instance; - private final SizeSupport sizeSupport = new SizeSupport(this); private final SearchTextField searchTextField; public enum ContentType { @@ -151,20 +149,6 @@ private Node createNormalView() { return prettyScrollPane; } - // Size - - public final ObjectProperty sizeProperty() { - return sizeSupport.sizeProperty(); - } - - public final void setSize(Size size) { - sizeSupport.setSize(size); - } - - public final Size getSize() { - return sizeSupport.getSize(); - } - private List getRandomSample(List list, int sampleSize) { if (sampleSize > list.size()) { throw new IllegalArgumentException("Sample size must be less than or equal to the size of the list"); diff --git a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileLinksOfTheWeekPage.java b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileLinksOfTheWeekPage.java index dac35f65..ca2179e0 100644 --- a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileLinksOfTheWeekPage.java +++ b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/MobileLinksOfTheWeekPage.java @@ -3,37 +3,29 @@ import com.dlsc.jfxcentral.data.DataRepository2; import com.dlsc.jfxcentral.data.model.LinksOfTheWeek; import com.dlsc.jfxcentral2.components.CustomMarkdownView; -import com.dlsc.jfxcentral2.components.PrettyScrollPane; -import com.dlsc.jfxcentral2.components.SizeSupport; +import com.dlsc.jfxcentral2.components.MobilePageBase; import com.dlsc.jfxcentral2.mobile.components.MobileCategoryHeader; -import com.dlsc.jfxcentral2.mobile.components.PageView; import com.dlsc.jfxcentral2.model.Size; import com.dlsc.jfxcentral2.utils.IkonUtil; -import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; -import org.kordamp.ikonli.javafx.FontIcon; -import org.kordamp.ikonli.material2.Material2RoundAL; -import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Comparator; -public class MobileLinksOfTheWeekPage extends VBox { +public class MobileLinksOfTheWeekPage extends MobilePageBase { private static final String DEFAULT_STYLE_CLASS = "lotw-page-view"; - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); - - private final SizeSupport sizeSupport = new SizeSupport(this); - private final LinksContentView[] cachedContentAry = new LinksContentView[3]; + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG); public MobileLinksOfTheWeekPage(ObjectProperty size) { this(); @@ -52,100 +44,56 @@ public MobileLinksOfTheWeekPage() { ArrayList sortedLinks = new ArrayList<>(DataRepository2.getInstance().getLinksOfTheWeek()); sortedLinks.sort(Comparator.comparing(LinksOfTheWeek::getCreatedOn).reversed()); - PageView pageView = new PageView(); - pageView.setPageCount(sortedLinks.size()); - pageView.setPageFactory(index -> { - LinksOfTheWeek weakLinks = sortedLinks.get(index); - - LinksContentView contentView = getLinksContentView(index); - contentView.setDate(weakLinks.getCreatedOn()); - contentView.setContent(DataRepository2.getInstance().getLinksOfTheWeekReadMe(weakLinks)); - return contentView; - }); - - PrettyScrollPane scrollPane = new PrettyScrollPane(pageView); - scrollPane.getStyleClass().add("mobile"); - VBox.setVgrow(scrollPane, Priority.ALWAYS); + ListView listView = new ListView<>(); + listView.getStyleClass().add("mobile"); + listView.getItems().setAll(sortedLinks); + listView.setCellFactory(param -> new LinksOfTheWeekCell()); - getChildren().addAll(header, scrollPane); - } + StackPane contentWrapper = new StackPane(listView); + contentWrapper.getStyleClass().add("content-wrapper"); + VBox.setVgrow(contentWrapper, Priority.ALWAYS); - private LinksContentView getLinksContentView(int currentPageIndex) { - int aryIndex = currentPageIndex % cachedContentAry.length; - LinksContentView mdView = cachedContentAry[aryIndex]; - if (mdView == null) { - cachedContentAry[aryIndex] = new LinksContentView(); - mdView = cachedContentAry[aryIndex]; - } - return mdView; + getChildren().addAll(header, contentWrapper); } - // size - - public final ObjectProperty sizeProperty() { - return sizeSupport.sizeProperty(); - } + private static class LinksOfTheWeekCell extends ListCell { - public final Size getSize() { - return sizeSupport.getSize(); - } + private final VBox graphic = new VBox(); + private final CustomMarkdownView markdownView; + private final Label dateLabel; - public final void setSize(Size size) { - sizeSupport.setSize(size); - } + public LinksOfTheWeekCell() { + getStyleClass().add("links-cell"); + setPrefWidth(0); - private static class LinksContentView extends VBox { + dateLabel = new Label(); - public LinksContentView() { - getStyleClass().add("links-content-view"); + Region separator = new Region(); + separator.getStyleClass().add("separator"); + HBox.setHgrow(separator, Priority.ALWAYS); - Label dateLabel = new Label(); - dateLabel.setGraphic(new FontIcon(Material2RoundAL.DATE_RANGE)); - StackPane dateLabelContainer = new StackPane(dateLabel); + HBox dateLabelContainer = new HBox(dateLabel, separator); dateLabelContainer.getStyleClass().add("date-label-container"); - dateLabel.textProperty().bind(Bindings.createStringBinding(() -> { - LocalDate date = getDate(); - return date != null ? DATE_FORMATTER.format(date) : "Unknown date"; - }, date)); - - CustomMarkdownView markdownView = new CustomMarkdownView(); - markdownView.mdStringProperty().bind(contentProperty()); - // markdownView.setPrefHeight(markdownView.getWidth()); - - getChildren().addAll(dateLabelContainer, markdownView); - } - - // create on date - - private final ObjectProperty date = new SimpleObjectProperty<>(this, "date"); - - public final LocalDate getDate() { - return date.get(); - } - - public final ObjectProperty dateProperty() { - return date; - } - - public final void setDate(LocalDate date) { - this.date.set(date); - } - - // links of the week content - - private final StringProperty content = new SimpleStringProperty(this, "content"); - - public final String getContent() { - return content.get(); - } + markdownView = new CustomMarkdownView(); + markdownView.setFillWidth(true); - public final StringProperty contentProperty() { - return content; + graphic.getStyleClass().add("cell-content"); + graphic.getChildren().addAll(dateLabelContainer, markdownView); } - public final void setContent(String content) { - this.content.set(content); + @Override + protected void updateItem(LinksOfTheWeek item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + dateLabel.setText(""); + markdownView.setMdString(""); + setGraphic(null); + } else { + dateLabel.setText(DATE_FORMATTER.format(item.getCreatedOn())); + markdownView.setMdString(DataRepository2.getInstance().getLinksOfTheWeekReadMe(item)); + setGraphic(graphic); + } } } diff --git a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/category/MobileCategoryPageBase.java b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/category/MobileCategoryPageBase.java index 0136f3a3..c3691e8f 100644 --- a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/category/MobileCategoryPageBase.java +++ b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/category/MobileCategoryPageBase.java @@ -1,7 +1,7 @@ package com.dlsc.jfxcentral2.mobile.pages.category; import com.dlsc.jfxcentral.data.model.ModelObject; -import com.dlsc.jfxcentral2.components.SizeSupport; +import com.dlsc.jfxcentral2.components.MobilePageBase; import com.dlsc.jfxcentral2.mobile.components.MobileCategoryHeader; import com.dlsc.jfxcentral2.mobile.components.ModelListCell; import com.dlsc.jfxcentral2.mobile.components.ModelListView; @@ -22,9 +22,7 @@ import java.util.List; import java.util.function.Predicate; -public abstract class MobileCategoryPageBase extends VBox { - - private final SizeSupport sizeSupport = new SizeSupport(this); +public abstract class MobileCategoryPageBase extends MobilePageBase { public MobileCategoryPageBase(ObjectProperty size) { sizeProperty().bind(size); @@ -76,20 +74,6 @@ protected String getSearchPromptText() { return "Search..."; } - // size - - public final ObjectProperty sizeProperty() { - return sizeSupport.sizeProperty(); - } - - public final Size getSize() { - return sizeSupport.getSize(); - } - - public final void setSize(Size size) { - sizeSupport.setSize(size); - } - // blocking property to control the glass pane private final BooleanProperty blocking = new SimpleBooleanProperty(this, "blocking"); diff --git a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/details/MobileDetailsPageBase.java b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/details/MobileDetailsPageBase.java index f4b6bcda..42e1f906 100644 --- a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/details/MobileDetailsPageBase.java +++ b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/pages/details/MobileDetailsPageBase.java @@ -17,7 +17,7 @@ import com.dlsc.jfxcentral.data.model.Tool; import com.dlsc.jfxcentral.data.model.Tutorial; import com.dlsc.jfxcentral.data.model.Video; -import com.dlsc.jfxcentral2.components.SizeSupport; +import com.dlsc.jfxcentral2.components.MobilePageBase; import com.dlsc.jfxcentral2.components.detailsbox.AppsDetailsBox; import com.dlsc.jfxcentral2.components.detailsbox.BlogsDetailsBox; import com.dlsc.jfxcentral2.components.detailsbox.BooksDetailsBox; @@ -34,15 +34,12 @@ import com.dlsc.jfxcentral2.model.Size; import javafx.beans.property.ObjectProperty; import javafx.scene.Node; -import javafx.scene.layout.VBox; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; -public abstract class MobileDetailsPageBase extends VBox { - - private final SizeSupport sizeSupport = new SizeSupport(this); +public abstract class MobileDetailsPageBase extends MobilePageBase { private T item; private final Class clazz; @@ -60,18 +57,6 @@ public Class getModelClazz() { return clazz; } - public final ObjectProperty sizeProperty() { - return sizeSupport.sizeProperty(); - } - - public final Size getSize() { - return sizeSupport.getSize(); - } - - public final void setSize(Size size) { - sizeSupport.setSize(size); - } - public T getItem() { return item; } diff --git a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/skin/MainPageSkin.java b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/skin/MainPageSkin.java index 67e1514d..9c09ce83 100644 --- a/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/skin/MainPageSkin.java +++ b/mobile/src/main/java/com/dlsc/jfxcentral2/mobile/skin/MainPageSkin.java @@ -28,9 +28,9 @@ public MainPageSkin(MainPage control) { } @Subscribe - public void onMobileResponseEvent(MobileResponseEvent responseView) { - System.out.println("responseView = " + responseView); - Node view = responseView.view(); + public void onMobileResponseEvent(MobileResponseEvent event) { + Node view = event.mobileResponse().getView(); + borderPane.setCenter(view); // focus the preferred node diff --git a/mobile/src/main/resources/com/dlsc/jfxcentral2/mobile/mobile.css b/mobile/src/main/resources/com/dlsc/jfxcentral2/mobile/mobile.css index 867208a5..d74ef6af 100644 --- a/mobile/src/main/resources/com/dlsc/jfxcentral2/mobile/mobile.css +++ b/mobile/src/main/resources/com/dlsc/jfxcentral2/mobile/mobile.css @@ -563,65 +563,45 @@ -fx-background-color: -white; } -.lotw-page-view:sm .links-of-the-week-header:sm { - -fx-min-height: 170px; - -fx-pref-height: 170px; - -fx-max-height: 170px; -} - -.lotw-page-view:md .links-of-the-week-header:md { - -fx-min-height: 240px; - -fx-pref-height: 240px; - -fx-max-height: 240px; +.lotw-page-view .content-wrapper { + -fx-padding: 10px 10px 0 10px; } -.lotw-page-view:lg .links-of-the-week-header:lg { - -fx-min-height: 260px; - -fx-pref-height: 260px; - -fx-max-height: 260px; +.lotw-page-view .list-view .list-cell:filled:pressed { + -fx-background-color: -white; } -.lotw-page-view:sm .links-of-the-week-header:sm .header-title { - -fx-font-size: 25px; - -fx-graphic-text-gap: 10px; +.lotw-page-view .links-cell { + -fx-padding: 0 0 30px 0; } -.lotw-page-view:sm .links-of-the-week-header:sm .header-title .ikonli-font-icon { - -fx-icon-size: 28px; +.lotw-page-view .links-cell .custom-markdown-view { + -fx-padding: 0 15px; } -.lotw-page-view .date-label-container { - -fx-font-size: 16px; - -fx-background-color: -bright-blue; +.lotw-page-view .links-cell .custom-markdown-view .markdown-code { + -fx-text-fill: -grey-100; } -.lotw-page-view .date-label-container .label { - -fx-text-fill: -white; +.lotw-page-view .links-cell .date-label-container .label { + -fx-text-fill: -grey-100; -fx-graphic-text-gap: 10px; - -fx-padding: 5px 0; -} - -.lotw-page-view .date-label-container .ikonli-font-icon { - -fx-icon-color: -white; -} - -.lotw-page-view .links-content-view { - -fx-spacing: 0px; - -fx-padding: 10px; - -fx-background-color: -white; - -fx-background-insets: 10px; - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.1), 12, 0, 0, 0); + -fx-font-size: 19px; + -fx-font-family: "Spline Sans SemiBold"; } -.lotw-page-view .links-content-view .custom-markdown-view { - -fx-padding: 0 10px; +.lotw-page-view .links-cell .date-label-container { + -fx-alignment: center; + -fx-spacing: 10px; } -.lotw-page-view .links-content-view .date-label-container .ikonli-font-icon { - -fx-icon-size: 20px; +.lotw-page-view .links-cell .date-label-container .separator { + -fx-background-color: -grey-10; + -fx-background-insets: 0; + -fx-max-height: 1px; } -.lotw-page-view .links-content-view .date-label-container .date-label { +.lotw-page-view .links-cell .date-label-container .date-label { -fx-text-fill: -grey-100; }