Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Original file line number Diff line number Diff line change
Expand Up @@ -4502,6 +4502,95 @@ public void actionPerformed(ActionEvent ae) {
});
simulateMenu.add(pushSim);

// Mirrors scrollViewShouldScrollToTop: in CodenameOne_GLViewController.m
// which dispatches a synthetic tap at (displayWidth/2, 0). The
// simulator otherwise can't reproduce that path because clicking the
// visible status bar just hits the bar directly.
JMenuItem statusBarTapDiag = new JMenuItem("iOS Status Bar Tap");
statusBarTapDiag.setToolTipText("Synthesizes the (displayWidth/2, 0) tap that iOS dispatches when the status bar is tapped, and reports which component would receive it.");
statusBarTapDiag.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent ae) {
Form f = Display.getInstance().getCurrent();
if (f == null) {
JOptionPane.showMessageDialog(canvas, "No current form to tap.");
return;
}
int tapX = getDisplayWidthImpl() / 2;
int tapY = 0;
StringBuilder report = new StringBuilder();
report.append("Simulating the iOS status-bar tap path.\n");
report.append("iOS native code synthesizes a tap at (displayWidth/2, 0)\n");
report.append("when scrollViewShouldScrollToTop: fires.\n\n");
report.append("Tap coordinates: (").append(tapX).append(", ").append(tapY).append(")\n\n");

UIManager um = f.getUIManager();
boolean paintsTitleBar = um.isThemeConstant("paintsTitleBarBool", false);
boolean scrollsUp = um.isThemeConstant("statusBarScrollsUpBool", true);
report.append("paintsTitleBarBool = ").append(paintsTitleBar).append("\n");
report.append("statusBarScrollsUpBool = ").append(scrollsUp).append("\n\n");

if (!paintsTitleBar) {
report.append("WARNING: paintsTitleBarBool is false. Form.createStatusBar()\n");
report.append("is not invoked, so no StatusBar component exists. Add\n");
report.append(" paintsTitleBarBool: true;\n");
report.append(" includeNativeBool: true;\n");
report.append("to your CSS #Constants block to enable the iOS behavior.\n\n");
}
if (paintsTitleBar && !scrollsUp) {
report.append("WARNING: statusBarScrollsUpBool is false. The StatusBar is\n");
report.append("created as a non-tappable Container, so no tap-to-scroll\n");
report.append("listener is wired up.\n\n");
}

Component responder = f.getResponderAt(tapX, tapY);
if (responder == null) {
report.append("No responder at (").append(tapX).append(", ").append(tapY).append(").\n");
report.append("On a device the iOS-synthesized tap will silently no-op.\n");
} else {
String uiid = responder.getUIID();
report.append("Responder at top-center:\n");
report.append(" class = ").append(responder.getClass().getName()).append("\n");
report.append(" UIID = ").append(uiid).append("\n");
com.codename1.ui.geom.Rectangle bounds = new com.codename1.ui.geom.Rectangle(
responder.getAbsoluteX(), responder.getAbsoluteY(),
responder.getWidth(), responder.getHeight());
report.append(" bounds = ").append(bounds.getX()).append(",").append(bounds.getY())
.append(" ").append(bounds.getSize().getWidth()).append("x").append(bounds.getSize().getHeight()).append("\n\n");
if ("StatusBar".equals(uiid) || "StatusBarLandscape".equals(uiid)) {
report.append("OK: this is the built-in StatusBar component. The iOS\n");
report.append("tap-to-scroll-to-top should work on a device, provided\n");
report.append("the native iOS theme is included (includeNativeBool).\n");
} else {
report.append("PROBLEM: the responder is NOT the built-in StatusBar.\n");
report.append("On an iOS device the synthesized tap will be delivered\n");
report.append("to this component instead of the scroll-to-top button,\n");
report.append("so the standard iOS gesture appears broken.\n\n");
report.append("Common causes:\n");
report.append(" - A custom component overlaps the top-center pixel\n");
report.append(" (e.g. a Toolbar side menu icon, an absolute-laid-out\n");
report.append(" PeerComponent, or a translucent overlay).\n");
report.append(" - The StatusBar UIID was zeroed out so the button has\n");
report.append(" no height, letting another component sit on top.\n");
report.append(" - Form.createStatusBar() was overridden without wiring\n");
report.append(" a tap-to-scroll listener.\n");
}
}

Log.p(report.toString());
JavaSEPort.this.pointerPressed(tapX, tapY);
JavaSEPort.this.pointerReleased(tapX, tapY);

JTextArea ta = new JTextArea(report.toString());
ta.setEditable(false);
ta.setFont(new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, 12));
JScrollPane sp = new JScrollPane(ta);
sp.setPreferredSize(new java.awt.Dimension(560, 360));
JOptionPane.showMessageDialog(canvas, sp, "iOS Status Bar Tap Diagnostic", JOptionPane.INFORMATION_MESSAGE);
}
});
simulateMenu.add(statusBarTapDiag);

if (appFrame == null) {
toolsMenu.add(componentTreeInspector);
}
Expand Down
53 changes: 46 additions & 7 deletions Ports/iOSPort/nativeSources/CodenameOne_GLViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,51 @@ - (void)layoutSubviews {

static CN1StatusBarTapProxyView *cn1StatusBarTapProxy = nil;

// Diagnostic counters for the status-bar tap-to-scroll-to-top path. Exposed
// to Java via Display.getProperty("cn1.iosStatusBarTap.*") so users can
// confirm whether iOS actually delivered the scroll-to-top message and
// what coordinates were synthesized into CodenameOne. Useful when the
// gesture appears to do nothing on a device but works in the simulator.
static int cn1StatusBarTapCount = 0;
static double cn1StatusBarTapLastEpochMillis = 0;
static int cn1StatusBarTapLastX = -1;
static int cn1StatusBarTapLastY = -1;

int cn1GetStatusBarTapCount() { return cn1StatusBarTapCount; }
double cn1GetStatusBarTapLastEpochMillis() { return cn1StatusBarTapLastEpochMillis; }
int cn1GetStatusBarTapLastX() { return cn1StatusBarTapLastX; }
int cn1GetStatusBarTapLastY() { return cn1StatusBarTapLastY; }
BOOL cn1IsStatusBarTapProxyInstalled() {
return cn1StatusBarTapProxy != nil && cn1StatusBarTapProxy.superview != nil;
}

// Forward declarations -- the actual definitions of pointerPressedC and
// pointerReleasedC live further down in this file, but cn1FireStatusBarTap
// (defined immediately below so it sits next to the static counter state it
// drives) needs to call them.
extern void pointerPressedC(int* x, int* y, int length);
extern void pointerReleasedC(int* x, int* y, int length);

// Fires the same diagnostic-counter bump and synthesized pointer event the
// scrollViewShouldScrollToTop: delegate dispatches. Exposed so an
// instrumented native interface can drive the path from a screenshot test
// without waiting for a real status-bar tap.
void cn1FireStatusBarTap() {
int xArray[1];
int yArray[1];
xArray[0] = displayWidth / 2;
yArray[0] = 0;
cn1StatusBarTapCount++;
cn1StatusBarTapLastEpochMillis = [[NSDate date] timeIntervalSince1970] * 1000.0;
cn1StatusBarTapLastX = xArray[0];
cn1StatusBarTapLastY = yArray[0];
pointerPressedC(xArray, yArray, 1);
pointerReleasedC(xArray, yArray, 1);
if (cn1StatusBarTapProxy != nil) {
cn1StatusBarTapProxy.contentOffset = CGPointMake(0, 1);
}
}


// 1 for portrait lock, and 2 for landscape lock
int orientationLock = 0;
Expand Down Expand Up @@ -2023,13 +2068,7 @@ - (void)cn1InstallStatusBarTapProxy {

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
if (scrollView == cn1StatusBarTapProxy) {
int xArray[1];
int yArray[1];
xArray[0] = displayWidth / 2;
yArray[0] = 0;
pointerPressedC(xArray, yArray, 1);
pointerReleasedC(xArray, yArray, 1);
cn1StatusBarTapProxy.contentOffset = CGPointMake(0, 1);
cn1FireStatusBarTap();
return NO;
}
return YES;
Expand Down
46 changes: 46 additions & 0 deletions Ports/iOSPort/nativeSources/IOSNative.m
Original file line number Diff line number Diff line change
Expand Up @@ -4692,6 +4692,32 @@ JAVA_OBJECT com_codename1_impl_ios_IOSNative_getDeviceName__(CN1_THREAD_STATE_MU
return fromNSString(CN1_THREAD_STATE_PASS_ARG [[UIDevice currentDevice] name]);
}

extern int cn1GetStatusBarTapCount();
extern double cn1GetStatusBarTapLastEpochMillis();
extern int cn1GetStatusBarTapLastX();
extern int cn1GetStatusBarTapLastY();
extern BOOL cn1IsStatusBarTapProxyInstalled();

JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapCount__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return (JAVA_INT)cn1GetStatusBarTapCount();
}

JAVA_LONG com_codename1_impl_ios_IOSNative_getStatusBarTapLastEpochMillis__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return (JAVA_LONG)cn1GetStatusBarTapLastEpochMillis();
}

JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastX__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return (JAVA_INT)cn1GetStatusBarTapLastX();
}

JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastY__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return (JAVA_INT)cn1GetStatusBarTapLastY();
}

JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isStatusBarTapProxyInstalled__(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return cn1IsStatusBarTapProxyInstalled() ? JAVA_TRUE : JAVA_FALSE;
}

JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isGoodLocation___long(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_LONG peer) {
POOL_BEGIN();
CLLocationManager* l = (BRIDGE_CAST CLLocationManager*)((void *)peer);
Expand Down Expand Up @@ -9250,6 +9276,26 @@ JAVA_OBJECT com_codename1_impl_ios_IOSNative_getDeviceName___R_java_lang_String(
return com_codename1_impl_ios_IOSNative_getDeviceName__(CN1_THREAD_STATE_PASS_ARG instanceObject);
}

JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapCount___R_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return com_codename1_impl_ios_IOSNative_getStatusBarTapCount__(CN1_THREAD_STATE_PASS_ARG instanceObject);
}

JAVA_LONG com_codename1_impl_ios_IOSNative_getStatusBarTapLastEpochMillis___R_long(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return com_codename1_impl_ios_IOSNative_getStatusBarTapLastEpochMillis__(CN1_THREAD_STATE_PASS_ARG instanceObject);
}

JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastX___R_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return com_codename1_impl_ios_IOSNative_getStatusBarTapLastX__(CN1_THREAD_STATE_PASS_ARG instanceObject);
}

JAVA_INT com_codename1_impl_ios_IOSNative_getStatusBarTapLastY___R_int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return com_codename1_impl_ios_IOSNative_getStatusBarTapLastY__(CN1_THREAD_STATE_PASS_ARG instanceObject);
}

JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isStatusBarTapProxyInstalled___R_boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject) {
return com_codename1_impl_ios_IOSNative_isStatusBarTapProxyInstalled__(CN1_THREAD_STATE_PASS_ARG instanceObject);
}

JAVA_BOOLEAN com_codename1_impl_ios_IOSNative_isGoodLocation___long_R_boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_LONG peer) {
return com_codename1_impl_ios_IOSNative_isGoodLocation___long(CN1_THREAD_STATE_PASS_ARG instanceObject, peer);
}
Expand Down
31 changes: 30 additions & 1 deletion Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -6488,7 +6488,36 @@ public void error(Throwable t) {
if(key.equalsIgnoreCase("UDID")) {
return nativeInstance.getUDID();
}

if("cn1.iosStatusBarTap.count".equals(key)) {
return String.valueOf(nativeInstance.getStatusBarTapCount());
}
if("cn1.iosStatusBarTap.lastEpochMillis".equals(key)) {
return String.valueOf(nativeInstance.getStatusBarTapLastEpochMillis());
}
if("cn1.iosStatusBarTap.lastX".equals(key)) {
return String.valueOf(nativeInstance.getStatusBarTapLastX());
}
if("cn1.iosStatusBarTap.lastY".equals(key)) {
return String.valueOf(nativeInstance.getStatusBarTapLastY());
}
if("cn1.iosStatusBarTap.proxyInstalled".equals(key)) {
return String.valueOf(nativeInstance.isStatusBarTapProxyInstalled());
}
if("cn1.iosStatusBarTap.diagnostics".equals(key)) {
int count = nativeInstance.getStatusBarTapCount();
long lastTime = nativeInstance.getStatusBarTapLastEpochMillis();
int lastX = nativeInstance.getStatusBarTapLastX();
int lastY = nativeInstance.getStatusBarTapLastY();
boolean installed = nativeInstance.isStatusBarTapProxyInstalled();
StringBuilder sb = new StringBuilder();
sb.append("count=").append(count);
sb.append(", lastEpochMillis=").append(lastTime);
sb.append(", lastX=").append(lastX);
sb.append(", lastY=").append(lastY);
sb.append(", proxyInstalled=").append(installed);
return sb.toString();
}

return super.getProperty(key, defaultValue);
}

Expand Down
11 changes: 11 additions & 0 deletions Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,17 @@ byte[] loadResource(String name, String type) {
native String getUDID();
native String getOSVersion();
native String getDeviceName();

// Diagnostics for the status-bar tap-to-scroll-to-top path. Surfaced to
// user code via Display.getProperty("cn1.iosStatusBarTap.*") in
// IOSImplementation. Lets developers detect on-device whether iOS is
// delivering the scroll-to-top message at all when the gesture does
// nothing visibly.
native int getStatusBarTapCount();
native long getStatusBarTapLastEpochMillis();
native int getStatusBarTapLastX();
native int getStatusBarTapLastY();
native boolean isStatusBarTapProxyInstalled();

// location manager
native boolean isGPSEnabled();
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codenameone.examples.hellocodenameone;

public class StatusBarTapDiagnosticNativeImpl {
public boolean simulateStatusBarTap() {
return false;
}

public int getTapCount() {
return 0;
}

public boolean isSupported() {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.codenameone.examples.hellocodenameone;

import com.codename1.system.NativeInterface;

public interface StatusBarTapDiagnosticNative extends NativeInterface {
/// Fires the same path the iOS scrollViewShouldScrollToTop: delegate runs:
/// bump the native counter and synthesize a pointer event at
/// (displayWidth/2, 0). On platforms that don't have the iOS proxy view
/// the impl falls back to dispatching the pointer event to the current
/// Form so the screenshot test produces the same visual progression.
boolean simulateStatusBarTap();

int getTapCount();
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ private static int testTimeoutMs() {
new StickyHeaderSlideTransitionScreenshotTest(),
new StickyHeaderFadeTransitionScreenshotTest(),
new TensileBounceScreenshotTest(),
new StatusBarTapDiagnosticScreenshotTest(),
new ComponentReplaceFadeScreenshotTest(),
new ComponentReplaceSlideScreenshotTest(),
new ComponentReplaceFlipScreenshotTest(),
Expand Down Expand Up @@ -292,6 +293,7 @@ private static boolean isJsSkippedScreenshotTest(String testName) {
return "KotlinUiTest".equals(testName)
|| "MainScreenScreenshotTest".equals(testName)
|| "SheetScreenshotTest".equals(testName)
|| "StatusBarTapDiagnosticScreenshotTest".equals(testName)
|| "ImageViewerNavigationScreenshotTest".equals(testName)
|| "TabsScreenshotTest".equals(testName)
|| "TextAreaAlignmentScreenshotTest".equals(testName)
Expand Down
Loading
Loading