JPro-one / JPro-Tickets

The right place to report about bugs or suggest improvements for JPro.
https://www.jpro.one
9 stars 4 forks source link

JPro stage width cange listener is not working fine in a case of screen orientation change on Android! #145

Open ctoabidmaqbool opened 1 year ago

ctoabidmaqbool commented 1 year ago

Hi! I have an JPro App running on my local pc. I am trying to connect my App on Android device using local network, e.g. through IP address.

When orientation of device changes sometime codding not works correctly!

Basically we are using listener on stage which compare width and height to calculate orientation of device e.g. Portrait or Landscape.

We are using latest release of JPro plugin (except routing).

We have implemented something like this method for detection of screen orientation.

public void runOnWidthChanged(Stage stage, Runnable runOnG800, Runnable runOnL800, Runnable runOnL380) {
        BooleanProperty widthG800 = new SimpleBooleanProperty();
        BooleanProperty widthL380 = new SimpleBooleanProperty();

        stage.widthProperty().addListener((observable, oldValue, newValue) -> {
            widthG800.set((newValue.intValue() > 800));
            widthL380.set((newValue.intValue() < 380));
        });

        widthG800.addListener((observable, oldValue, newValue) -> {
            if (newValue) { // > 800
//                System.out.println("Laptop Display");
                runOnG800.run();
            } else { // <= 800
//                System.out.println("Tablet Display");
                runOnL800.run();
            }
        });
        widthL380.addListener((observable, oldValue, newValue) -> {
            if (newValue) { // < 380
//                System.out.println("Android Display");
                runOnL380.run();
            } else { // >= 380
//                System.out.println("Tablet Display");
                runOnL800.run();
            }
        });

        // First run even if Stage width not changed
//        javafx.application.Platform.runLater(() -> {
            if (stage.getWidth() > 800) {
//                System.out.println("Laptop Display");
                runOnG800.run();
            } else if (stage.getWidth() >= 380) {
//                System.out.println("Tablet Display");
                runOnL800.run();
            } else {
//                System.out.println("Android Display");
                runOnL380.run();
            }
//        });

    }
FlorianKirmaier commented 1 year ago

This also depends on the index.html file, and how the Stage/Scene/Root is set up. So I guess I would need a sample application. And a better description of what's actually happening. Maybe then it will be immediately clear whats happening.

ctoabidmaqbool commented 1 year ago

Hi @FlorianKirmaier ! I want to design different front-end GUI depending upon the orientation of devices, e.g. landscape or portrait. Also w.r.t different width like CSS media queries.

I am not sure, how can I achieve them in JPro?

The main thing is somehow I can add listener to main viewport area's size of web! But how to achieve this in JPro Web?

image

FlorianKirmaier commented 1 year ago

The Node of your Page, should, by default be layout to the size (width/height) of the WebSite. (You might want to set it to fullscreen: .filter(Filters.FullscreenFilter(true)))

If this is not the case - for some reason, then I need a minimal sample to take a look into it.

Then, you can either have you Page handle the layout-logic, to decide based on it's width/height how it's layouting. Or probably simpler: You can refresh the page, when the width/height changes, and return a different Node based on the width/height .

ctoabidmaqbool commented 1 year ago

@FlorianKirmaier Hi! there is a technical problem with .filter(Filters.FullscreenFilter(true)).

Initial app when load first time in portrait view!

When switch to Landscape view (by rotating Android device), all is working well:

But when again switch to portrait view, something wrong happens, like zoom out of app:

In the same why when again switch to landscape view, vertical scroll happens:

As see in screen shots something unexpected happens.

Sample code to demonstrate the issue:

package com.jpro.hellojpro;

import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;
import javafx.stage.Stage;
import one.jpro.routing.*;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Map;

import static one.jpro.routing.RouteUtils.*;

public class JProScreenOrientationTest extends RouteApp {
    private Stage pStage;

    @Override
    public void start(Stage pStage) {
        this.pStage = pStage;
        super.start(pStage);
    }

    @Override
    public Route createRoute() {
        return Route.empty()
                .and(redirect("/", "/info"))
                .and(get("/info", (r) -> new InfoPage(pStage)))
                .filter(Filters.FullscreenFilter(true));
    }
}

class InfoPage extends View {
    private Stage pStage;

    public InfoPage(Stage pStage) {
        this.pStage = pStage;
    }

    @Override
    public String title() {
        return "Sample JPro Page";
    }

    @Override
    public String description() {
        return "This is a simple page where JPro testing done.";
    }

    @Override
    public Node content() {
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);

        Label lblText = new Label("+92 (302) 050-6910");
        LinkUtil.setLink(lblText, "tel:+923020506910");

        Label lblText1 = new Label("cto.ms@outlook.com");
        LinkUtil.setLink(lblText1, "mailto:cto.ms@outlook.com");

        Label lblText2 = new Label("Info with Parm");
        LinkUtil.setLink(lblText2, "/info?q=About");

        root.getChildren().addAll(lblText, lblText1, lblText2);

        return root;
    }

}

defaultpage if it matter somehow:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>JPro Testing</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta charset="UTF-8">
    <meta name="jpro Application: Hellodescription" content="description"/>

    <meta name="description" content="temp">

    <link rel="stylesheet" type="text/css" href="/jpro/css/jpro.css">
    <script src="/jpro/js/jpro.js" type="text/javascript"></script>

    <style>
        body {
            overflow-x: hidden; /* Hide horizontal scrollbar */
        }
    </style>
</head>

<div>
    <jpro-app href="/app/default" fxContextMenu="false" fullscreen="true" nativeScrolling="true"  snapshot="auto" userSelect="true"></jpro-app>
</div>

</body>
</html>
FlorianKirmaier commented 1 year ago

A small tip before looking into the topic in detail: There is now the method RouteUtils.viewFromNode, which creates a simple View from a Node. If you don't want to set the description/title/fullscreen, using this method makes the code much more concise.

besidev commented 1 year ago

Hello @ctoabidmaqbool,

The following website shpresa.shop is also running with JPro server. Can you please open it and see if you experience the same issue for some reason? You feedback will help us better understand the issue. Thank You.

ctoabidmaqbool commented 1 year ago

@besidev I have tried your provided website. It looks like it's working fine on Desktop Chrome and Android Chrome. e.g. Screen Orientation is working fine.

In both cases Landscape and Portrait responsive forms are fine.

More over this website opensources project or some closed one. It's looks like are forms are made up of using simple html,css,js running on jpro.one server?

ctoabidmaqbool commented 1 year ago

I think, WebAPI.getWebAPI(stage).getBrowserSize().getWidth() or WebAPI.getWebAPI(stage).getBrowserSize().getHeight() return the correct page width and height. But change listener can't be applied to it, as this not a property rather it's simple values.

System.out.println("browser width: " + WebAPI.getWebAPI(model.getStage()).getBrowserSize().getWidth());
System.out.println("stage width" + model.getStage().getWidth());
System.out.println("browser height: " + WebAPI.getWebAPI(model.getStage()).getBrowserSize().getHeight());
System.out.println("stage height" + model.getStage().getHeight());

stage.getWidth() or stage.getHeight() not return correct page height / width (visible viewport size).

besidev commented 1 year ago

@besidev I have tried your provided website. It looks like it's working fine on Desktop Chrome and Android Chrome. e.g. Screen Orientation is working fine.

In both cases Landscape and Portrait responsive forms are fine.

More over this website opensources project or some closed one. It's looks like are forms are made up of using simple html,css,js running on jpro.one server?

The shpresa.shop website is not open source, but everything is done in JavaFX code, running via JPro server.

besidev commented 1 year ago

I think, WebAPI.getWebAPI(stage).getBrowserSize().getWidth() or WebAPI.getWebAPI(stage).getBrowserSize().getHeight() return the correct page width and height. But change listener can't be applied to it, as this not a property rather it's simple values.

System.out.println("browser width: " + WebAPI.getWebAPI(model.getStage()).getBrowserSize().getWidth());
System.out.println("stage width" + model.getStage().getWidth());
System.out.println("browser height: " + WebAPI.getWebAPI(model.getStage()).getBrowserSize().getHeight());
System.out.println("stage height" + model.getStage().getHeight());

stage.getWidth() or stage.getHeight() not return correct page height / width (visible viewport size).

WebAPI.layoutRoot(stage) is used internally to match the browser's size (width and height) with the root node (container), so basically you should not need to use WebAPI.getWebAPI(stage).getBrowserSize().getWidth() and WebAPI.getWebAPI(stage).getBrowserSize().getHeight() or even the property if it was in the WebAPI, the width/height property of the root container should hold the correct values you need. We need to investigate this part.

ctoabidmaqbool commented 1 year ago

Yes! As Javafx -> CSS doesn't have media queries yet. It's must be possible to get correct width and height of page to emulate media queries using javafx! image

ctoabidmaqbool commented 1 year ago

Okey! Still we can't get height of root element by using this method e.g. root.getHeight() however root.getWidth() somehow works.

ctoabidmaqbool commented 1 year ago

Hi! After a couple of try, researching and tying provided fixes, I have got expected behavior!

image

ResponsiveApp.java

package com.jpro.hellojpro;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import one.jpro.routing.Filters;
import one.jpro.routing.Route;
import one.jpro.routing.RouteApp;
import one.jpro.routing.RouteUtils;

public class ResponsiveApp extends RouteApp {

    Label infoWidth;
    Label infoHeight;

    public static void main(String[] args) {
        launch(args);
    }

    public Route createRoute() {
        return Route.empty()
                .and(RouteUtils.getNode("/", (r) -> getView()))
                .filter(Filters.FullscreenFilter(true));
        // .filter(DevFilter.create());
    }

    private Node getView() {
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);

        infoWidth = new Label("Width");
        infoHeight = new Label("Height");
        Label infoOrientation = new Label("Orientation");
        Label infoDisplay = new Label("Display");

        root.getChildren().addAll(infoWidth, infoHeight, infoOrientation, infoDisplay);

        Scene scene = getScene();

        runOnOrientationChanged(scene, () -> {
            infoOrientation.setText("Orientaion: Landscape");
        }, () -> {
            infoOrientation.setText("Orientaion: Portrait");
        });

        runOnWidthChanged(scene, () -> {
            infoDisplay.setText("Display: Laptop");
        }, () -> {
            infoDisplay.setText("Display: Tablet");
        }, () -> {
            infoDisplay.setText("Display: Android");
        });

        return root;
    }

    public void runOnOrientationChanged(Scene scene, Runnable runOnLandscape, Runnable runOnPortrait) {
        BooleanProperty orientationProperty = new SimpleBooleanProperty();

        scene.widthProperty().addListener((observable, oldValue, newValue) -> {
            infoWidth.setText("Width: " + newValue);

            orientationProperty.set(newValue.doubleValue() > scene.getHeight());
        });

        scene.heightProperty().addListener((observable, oldValue, newValue) -> {
            infoHeight.setText("Height: " + newValue);

            orientationProperty.set(newValue.doubleValue() < scene.getWidth());
        });

        orientationProperty.addListener((observable, oldValue, newValue) -> {
            if (newValue) {
                runOnLandscape.run();
            } else {
                runOnPortrait.run();
            }
        });

        // First run even if Stage width not changed
        if (scene.getWidth() > scene.getHeight()) {
            // System.out.println("Landscape Display");
            runOnLandscape.run();
        } else {
            // System.out.println("Portrait Display");
            runOnPortrait.run();
        }
    }

    public void runOnWidthChanged(Scene scene, Runnable runOnG800, Runnable runOnL800, Runnable runOnL380) {
        BooleanProperty widthG800 = new SimpleBooleanProperty();
        BooleanProperty widthL380 = new SimpleBooleanProperty();

        scene.widthProperty().addListener((observable, oldValue, newValue) -> {
            widthG800.set((newValue.intValue() > 800));
            widthL380.set((newValue.intValue() < 380));
        });

        widthG800.addListener((observable, oldValue, newValue) -> {
            if (newValue) { // > 800
                // System.out.println("Laptop Display");
                runOnG800.run();
            } else { // <= 800
                // System.out.println("Tablet Display");
                runOnL800.run();
            }
        });
        widthL380.addListener((observable, oldValue, newValue) -> {
            if (newValue) { // < 380
                // System.out.println("Android Display");
                runOnL380.run();
            } else { // >= 380
                // System.out.println("Tablet Display");
                runOnL800.run();
            }
        });

        // First run even if Stage width not changed
        if (scene.getWidth() > 800) {
            // System.out.println("Laptop Display");
            runOnG800.run();
        } else if (scene.getWidth() >= 380) {
            // System.out.println("Tablet Display");
            runOnL800.run();
        } else {
            // System.out.println("Android Display");
            runOnL380.run();
        }
    }
}

defaultpage

<!DOCTYPE html>
<html lang="en">

<head>
    <title>JPro Responsive Testing</title>
    <meta name="description" content="An application for testing of Responsive in jPro">
    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" type="text/css" href="/jpro/css/jpro.css">
    <script src="/jpro/js/jpro.js" type="text/javascript"></script>

    <style>
        body {
            overflow-x: hidden;
        }
    </style>
</head>

<body>
    <div>
        <jpro-app href="/app/default" fxContextMenu="false" fullscreen="true" loader="none" nativeScrolling="true"
            snapshot="auto" userSelect="true" fxHeight="false" />
    </div>
</body>

</html>

@FlorianKirmaier @besidev ! Please see my above code, it's almost working fine. But there is some problem. It's looks like <meta name="viewport" content="width=device-width, initial-scale=1.0"> not working. e.g. when you resize page in chrome-inspect view page scales too & fro.

image

How to fix it, in a case all other codding should works too?

FlorianKirmaier commented 1 year ago

But there is some problem. It's looks like not working. e.g. when you resize page in chrome-inspect view page scales too & fro.

Can you explain more about what you mean? When I use the dev console and select a predefined screen size, and zoom, nothing happens. But that's the case for all web pages I've tested, independent of JPro. What is the behavior you get, and what would you expect?

ctoabidmaqbool commented 1 year ago

@FlorianKirmaier I am talking about simple.

Let open https://google.com website in chrome, and resize the page (the scaling of the page will not be changed, or zoom in/out remains same):

image

Now open jpro app (above code), and resize the page:

image

On resizing the page the scaling of page will be changed, or zoom in/out will be changed on resizing, e.g.:

image

Let me know, if still you not get-up the error or issue?