Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
45289ab
refactor(browser): keep a list of devices in the emulator device list
heyitsaadin Oct 10, 2026
64b915d
refactor(browser): register each device when added to the list
heyitsaadin Oct 10, 2026
fc7e8d9
feat(browser): apply the saved default preview size once layout is ready
heyitsaadin Oct 10, 2026
fced123
feat(browser): save and restore a default device preview size
heyitsaadin Oct 10, 2026
91f3ca2
feat(browser): add "Set as Default" and "Clear Default" menu items
heyitsaadin Oct 10, 2026
615296b
feat(browser): handle "Set as Default" and "Clear Default" actions
heyitsaadin Oct 10, 2026
8fd2c31
feat(browser): show default menu items only while Devices mode is on
heyitsaadin Oct 10, 2026
4791cba
feat(browser): open the preview in Devices mode when a default is set
heyitsaadin Oct 10, 2026
bbf48d8
feat(browser): restore the default preview mode on startup
heyitsaadin Oct 10, 2026
28d28bc
refactor(browser): allow applying desktop mode without reloading
heyitsaadin Oct 11, 2026
752a64c
refactor(browser): make the reload in setDesktopMode optional
heyitsaadin Oct 11, 2026
477aedc
fix(browser): apply saved desktop mode before the first load to avoid…
heyitsaadin Oct 11, 2026
e0a18db
fix(browser): only apply dimensions after layout, not desktop mode
heyitsaadin Oct 11, 2026
5a53452
fix(browser): re-fit saved presets to the current screen on restore
heyitsaadin Oct 11, 2026
b43da8f
fix(browser): reload once after the saved size is applied
heyitsaadin Oct 11, 2026
c7fcd73
fix(browser): don't resize the webview before the emulator is set up
heyitsaadin Oct 11, 2026
4c629f7
fix(browser): add reloadAfterLayout helper, only when a page was requ…
heyitsaadin Oct 11, 2026
1d23d80
fix(browser): show "Clear Default" only when a default is saved
heyitsaadin Oct 11, 2026
bf0ec37
fix(browser): refresh default menu items after Set as Default
heyitsaadin Oct 11, 2026
7f4afd8
fix(browser): refresh default menu items after Clear Default
heyitsaadin Oct 11, 2026
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
103 changes: 102 additions & 1 deletion src/plugins/browser/android/com/foxdebug/browser/Browser.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public void onDownloadStart(String url, String userAgent,
createMenu();
addView(titleLayout);
addView(webViewContainer);
restoreEmulator();
}

private void createMenu() {
Expand All @@ -236,6 +237,10 @@ private void createMenu() {
menu.addItem(Ui.Icons.DEVICES, "Devices", false);
menu.addItem(Ui.Icons.NO_CACHE, "Disable Cache", false);
menu.addItem(Ui.Icons.TERMINAL, "Console", false);
menu.addItem(Ui.Icons.TUNE, "Set as Default");
menu.addItem(Ui.Icons.REFRESH, "Clear Default");
menu.setVisible("Set as Default", false);
menu.setVisible("Clear Default", false);
menu.addItem(Ui.Icons.OPEN_IN_BROWSER, "Open in Browser");
menu.addItem(Ui.Icons.EXIT, "Exit");

Expand All @@ -250,6 +255,7 @@ public void onSelect(String action, Boolean checked) {
}

emulator = checked;
updateDefaultItems(checked);
if (checked) {
setDesktopMode(true);
setConsoleVisible(false);
Expand Down Expand Up @@ -284,6 +290,26 @@ public void onGlobalLayout() {
case "Console":
setConsoleVisible(checked);
break;
case "Set as Default":
if (deviceEmulator != null) {
deviceEmulator.saveAsDefault();
updateDefaultItems(emulator);
Toast
.makeText(
context,
"Saved as default preview",
Toast.LENGTH_SHORT
)
.show();
}
break;
case "Clear Default":
Emulator.clearDefault(context);
updateDefaultItems(emulator);
Toast
.makeText(context, "Default cleared", Toast.LENGTH_SHORT)
.show();
break;
case "Disable Cache":
webView
.getSettings()
Expand All @@ -308,6 +334,75 @@ public void onGlobalLayout() {
);
}

private void updateDefaultItems(boolean emulatorOn) {
menu.setVisible("Set as Default", emulatorOn);
menu.setVisible(
"Clear Default",
emulatorOn && Emulator.hasDefault(context)
);
}

private void reloadAfterLayout() {
// If no page was requested yet, the first load will already happen at
// the saved size, so there is nothing to reload.
if (url.isEmpty()) return;

webView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
webView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
webView.reload();
}
}
);
}

private void restoreEmulator() {
if (onlyConsole) return;
if (!Emulator.hasDefault(context)) return;

// Apply the saved mode before the first navigation so the page is not
// loaded with the wrong user agent and then reloaded.
setDesktopMode(true, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The desktop user agent is set before the first load (good), but the first page load still happens at full size, not at the saved size.

BrowserActivity.onCreate calls browser.setUrl(url) right after the constructor, before any layout happens. The saved size only arrives two layout passes later: first the webview's layout listener below, then the emulator's own listener in setReference, which calls restoreDefaultDevice. So the page starts loading at the full webview width and is resized partway through.

CSS media queries will adjust after the resize, but scripts that read innerWidth or measure the layout once on load will keep the full-size values. A fast localhost server may even finish the page before the resize. Switching Devices on by hand doesn't have this problem, because it reloads after the size is applied.

This depends on timing, so could you check it on a device with a page that logs innerWidth on load? If it reproduces, reload once after the saved size is applied, but only if the page has already started loading.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The page now reloads once after the saved size is applied, and only if a page was already requested.


webView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (webView.getMeasuredWidth() == 0) return;
webView.getViewTreeObserver().removeOnGlobalLayoutListener(this);

if (deviceEmulator == null) {
createDeviceEmulatorLayout();
deviceEmulator.setRestoredListener(
new Runnable() {
@Override
public void run() {
reloadAfterLayout();
}
}
);
}

emulator = true;
menu.setChecked("Devices", true);
updateDefaultItems(true);
setConsoleVisible(false);
menu.setChecked("Console", false);
menu.setVisible("Console", false);
addView(deviceEmulator);
// The emulator sets up its sliders on a later layout pass and then
// applies the saved size itself, so the webview is not resized here.
}
}
);
}

private void createDeviceEmulatorLayout() {
Browser browser = this;
deviceEmulator = new Emulator(context, theme);
Expand Down Expand Up @@ -339,6 +434,10 @@ private void setTextViewProperties(TextView textView, int height) {
}

private void setDesktopMode(boolean enabled) {
setDesktopMode(enabled, true);
}

private void setDesktopMode(boolean enabled, boolean reload) {
int width = 0;
int height = 0;
WebSettings webSettings = webView.getSettings();
Expand All @@ -360,7 +459,9 @@ private void setDesktopMode(boolean enabled) {
webSettings.setLoadWithOverviewMode(enabled);
webSettings.setSupportZoom(enabled);
webSettings.setBuiltInZoomControls(enabled);
webView.reload();
if (reload) {
webView.reload();
}
}

public void setDesktopMode() {
Expand Down
106 changes: 106 additions & 0 deletions src/plugins/browser/android/com/foxdebug/browser/Emulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ public void onGlobalLayout() {
if (listener != null) {
listener.onChange(iWidth, correctedHeight, 1);
}

restoreDefaultDevice();
}
}
);
Expand Down Expand Up @@ -302,6 +304,108 @@ private void selectDevice(Device device) {
listener.onChange(width, height, maxScale / 100f);
}

public static boolean hasDefault(Context context) {
return context
.getSharedPreferences("acode_browser", Context.MODE_PRIVATE)
.getBoolean("default_set", false);
}

public static void clearDefault(Context context) {
context
.getSharedPreferences("acode_browser", Context.MODE_PRIVATE)
.edit()
.putBoolean("default_set", false)
.apply();
}

public void saveAsDefault() {
context
.getSharedPreferences("acode_browser", Context.MODE_PRIVATE)
.edit()
.putBoolean("default_set", true)
.putInt("default_width", getWidthProgress())
.putInt("default_height", getHeightProgress())
.putInt("default_scale", seekBars.get("scale").getProgress())
.putString(
"default_device",
selectedDevice != null ? selectedDevice.name : "Custom"
)
.apply();
}

private Runnable restoredListener;

public void setRestoredListener(Runnable listener) {
restoredListener = listener;
}

private void restoreDefaultDevice() {
if (!hasDefault(context)) return;

android.content.SharedPreferences prefs = context.getSharedPreferences(
"acode_browser",
Context.MODE_PRIVATE
);
SeekBar widthSeekBar = seekBars.get("width");
SeekBar heightSeekBar = seekBars.get("height");
SeekBar scaleSeekBar = seekBars.get("scale");
String name = prefs.getString("default_device", "Custom");

Device preset = null;
for (Device device : deviceListView.devices) {
if (device.id != customDevice.id && device.name.equals(name)) {
preset = device;
break;
}
}

if (preset != null && listener != null) {
// A preset is re-fitted to the current screen, so it keeps its shape if
// the screen size changed since it was saved (rotation, split-screen,
// foldables). Only the scale is restored from the saved value.
if (deviceListView.selectedDeviceView != null) {
deviceListView.selectedDeviceView.deselect();
}
deviceListView.select(preset);
selectDevice(preset);

int scale = Math.min(
prefs.getInt("default_scale", scaleSeekBar.getMax()),
scaleSeekBar.getMax()
);
scaleSeekBar.setProgress(scale);
listener.onChange(getWidthProgress(), getHeightProgress(), scale / 100f);
} else {
// Custom sizes are restored from the saved width/height.
int width = Math.min(
prefs.getInt("default_width", widthSeekBar.getMax()),
widthSeekBar.getMax()
);
int height = Math.min(
prefs.getInt("default_height", heightSeekBar.getMax()),
heightSeekBar.getMax()
);

widthSeekBar.setProgress(width);
heightSeekBar.setProgress(height);
setMaxScale(width, height);
int scale = Math.min(
prefs.getInt("default_scale", 100),
scaleSeekBar.getMax()
);
scaleSeekBar.setProgress(scale);
selectedDevice = customDevice;

if (listener != null) {
listener.onChange(width, height, scale / 100f);
}
}

if (restoredListener != null) {
restoredListener.run();
}
}

private void setMaxScale(int width, int height) {
SeekBar scaleSeekBar = seekBars.get("scale");
SeekBar widthSeekBar = seekBars.get("width");
Expand Down Expand Up @@ -367,6 +471,7 @@ public void deselect() {
class DeviceListView extends ScrollView {

DeviceView selectedDeviceView;
ArrayList<Device> devices = new ArrayList<Device>();
LinearLayout deviceListLayout;
Callback callback;
Context context;
Expand Down Expand Up @@ -407,6 +512,7 @@ public void select(Device device) {
}

public void add(Device device) {
devices.add(device);
DeviceView deviceView = new DeviceView(context, device, theme);
deviceListLayout.addView(deviceView);

Expand Down
Loading