Skip to content

Commit

Permalink
Initial implementation of browser tabs
Browse files Browse the repository at this point in the history
The Tabs Bar is a small window that sits next to the current window
and which lists the open tabs.

There are two implementations:
  - HorizontalTabsBar on the top of the window
  - VerticalTabsBar on the side of the window

The Tabs Bar is managed by VRBrowserActivity.

SessionStore now keeps a list of session change listeners.

TabDelegate is moved to a separate interface.

The current tabs style can be changed in Settings -> Display.

Note that we don't (yet) link windows and tabs, so the interface
gets a bit confusing when we have more than one window open.
  • Loading branch information
felipeerias committed Oct 1, 2024
1 parent 22da6fa commit 543e9bd
Show file tree
Hide file tree
Showing 28 changed files with 978 additions and 21 deletions.
65 changes: 57 additions & 8 deletions app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,16 @@
import com.igalia.wolvic.telemetry.TelemetryService;
import com.igalia.wolvic.ui.OffscreenDisplay;
import com.igalia.wolvic.ui.adapters.Language;
import com.igalia.wolvic.ui.widgets.AbstractTabsBar;
import com.igalia.wolvic.ui.widgets.AppServicesProvider;
import com.igalia.wolvic.ui.widgets.HorizontalTabsBar;
import com.igalia.wolvic.ui.widgets.KeyboardWidget;
import com.igalia.wolvic.ui.widgets.NavigationBarWidget;
import com.igalia.wolvic.ui.widgets.RootWidget;
import com.igalia.wolvic.ui.widgets.TrayWidget;
import com.igalia.wolvic.ui.widgets.UISurfaceTextureRenderer;
import com.igalia.wolvic.ui.widgets.UIWidget;
import com.igalia.wolvic.ui.widgets.VerticalTabsBar;
import com.igalia.wolvic.ui.widgets.WebXRInterstitialWidget;
import com.igalia.wolvic.ui.widgets.Widget;
import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate;
Expand Down Expand Up @@ -105,6 +108,7 @@
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
Expand Down Expand Up @@ -213,6 +217,7 @@ public void run() {
RootWidget mRootWidget;
KeyboardWidget mKeyboard;
NavigationBarWidget mNavigationBar;
AbstractTabsBar mTabsBar;
CrashDialogWidget mCrashDialog;
TrayWidget mTray;
WhatsNewWidget mWhatsNewWidget = null;
Expand Down Expand Up @@ -465,9 +470,19 @@ public void onWindowVideoAvailabilityChanged(@NonNull WindowWidget aWindow) {
mTray = new TrayWidget(this);
mTray.addListeners(mWindows);
mTray.setAddWindowVisible(mWindows.canOpenNewWindow());

// Create Tabs bar widget
if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_HORIZONTAL) {
mTabsBar = new HorizontalTabsBar(this, mWindows);
} else if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_VERTICAL) {
mTabsBar = new VerticalTabsBar(this, mWindows);
} else {
mTabsBar = null;
}

attachToWindow(mWindows.getFocusedWindow(), null);

addWidgets(Arrays.asList(mRootWidget, mNavigationBar, mKeyboard, mTray, mWebXRInterstitial));
addWidgets(Arrays.asList(mRootWidget, mNavigationBar, mKeyboard, mTray, mTabsBar, mWebXRInterstitial));

// Create the platform plugin after widgets are created to be extra safe.
mPlatformPlugin = createPlatformPlugin(this);
Expand All @@ -483,10 +498,18 @@ private void attachToWindow(@NonNull WindowWidget aWindow, @Nullable WindowWidge
mKeyboard.attachToWindow(aWindow);
mTray.attachToWindow(aWindow);

if (mTabsBar != null) {
mTabsBar.attachToWindow(aWindow);
}

if (aPrevWindow != null) {
updateWidget(mNavigationBar);
updateWidget(mTabsBar);
updateWidget(mKeyboard);
updateWidget(mTray);
if (mTabsBar != null) {
updateWidget(mTabsBar);
}
}
}

Expand Down Expand Up @@ -772,14 +795,40 @@ public void onConfigurationChanged(Configuration newConfig) {

@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getString(R.string.settings_key_voice_search_service))) {
initializeSpeechRecognizer();
} else if (key.equals(getString(R.string.settings_key_head_lock))) {
boolean isHeadLockEnabled = SettingsStore.getInstance(this).isHeadLockEnabled();
setHeadLockEnabled(isHeadLockEnabled);
if (!isHeadLockEnabled)
recenterUIYaw(WidgetManagerDelegate.YAW_TARGET_ALL);
if (Objects.equals(key, getString(R.string.settings_key_voice_search_service))) {
initializeSpeechRecognizer();
} else if (Objects.equals(key, getString(R.string.settings_key_head_lock))) {
boolean isHeadLockEnabled = mSettings.isHeadLockEnabled();
setHeadLockEnabled(isHeadLockEnabled);
if (!isHeadLockEnabled)
recenterUIYaw(WidgetManagerDelegate.YAW_TARGET_ALL);
} else if (Objects.equals(key, getString(R.string.settings_key_tabs_location))) {

Log.e(LOGTAG, "update tabs location");

// remove the previous widget
if (mTabsBar != null) {
Log.e(LOGTAG, "remove previous widget");
removeWidget(mTabsBar);
}

if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_TRAY) {
mTabsBar = null;
} else {
if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_HORIZONTAL) {
mTabsBar = new HorizontalTabsBar(this, mWindows);
} else if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_VERTICAL) {
mTabsBar = new VerticalTabsBar(this, mWindows);
} else {
Log.e(LOGTAG, "Invalid value for tabs location");
mTabsBar = null;
return;
}
addWidget(mTabsBar);
mTabsBar.attachToWindow(mWindows.getFocusedWindow());
updateWidget(mTabsBar);
}
}
}

void loadFromIntent(final Intent intent) {
Expand Down
19 changes: 19 additions & 0 deletions app/src/common/shared/com/igalia/wolvic/browser/SettingsStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ SettingsStore getInstance(final @NonNull Context aContext) {
public static final int INTERNAL = 0;
public static final int EXTERNAL = 1;

@IntDef(value = { TABS_LOCATION_TRAY, TABS_LOCATION_HORIZONTAL, TABS_LOCATION_VERTICAL})
public @interface TabsLocation {}
public static final int TABS_LOCATION_TRAY = 0;
public static final int TABS_LOCATION_HORIZONTAL = 1;
public static final int TABS_LOCATION_VERTICAL = 2;

private Context mContext;
private SharedPreferences mPrefs;
private SettingsViewModel mSettingsViewModel;
Expand Down Expand Up @@ -113,6 +119,7 @@ SettingsStore getInstance(final @NonNull Context aContext) {
public final static boolean AUDIO_ENABLED = BuildConfig.FLAVOR_backend == "chromium";
public final static boolean LATIN_AUTO_COMPLETE_ENABLED = false;
public final static boolean WINDOW_MOVEMENT_DEFAULT = false;
public final static @TabsLocation int TABS_LOCATION_DEFAULT = TABS_LOCATION_TRAY;
public final static float CYLINDER_DENSITY_ENABLED_DEFAULT = 4680.0f;
public final static float HAPTIC_PULSE_DURATION_DEFAULT = 10.0f;
public final static float HAPTIC_PULSE_INTENSITY_DEFAULT = 1.0f;
Expand Down Expand Up @@ -374,6 +381,18 @@ public void setWindowMovementEnabled(boolean isEnabled) {
mSettingsViewModel.setWindowMovementEnabled(isEnabled);
}

@TabsLocation
public int getTabsLocation() {
return mPrefs.getInt(
mContext.getString(R.string.settings_key_tabs_location), TABS_LOCATION_DEFAULT);
}

public void setTabsLocation(@TabsLocation int tabsLocation) {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putInt(mContext.getString(R.string.settings_key_tabs_location), tabsLocation);
editor.commit();
}

public boolean isEnvironmentOverrideEnabled() {
return mPrefs.getBoolean(
mContext.getString(R.string.settings_key_environment_override), ENV_OVERRIDE_DEFAULT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -84,9 +85,11 @@ public static SessionStore get() {
private FxaWebChannelFeature mWebChannelsFeature;
private Store.Subscription mStoreSubscription;
private BrowserIconsHelper mBrowserIconsHelper;
private final LinkedHashSet<SessionChangeListener> mSessionChangeListeners;

private SessionStore() {
mSessions = new ArrayList<>();
mSessionChangeListeners = new LinkedHashSet<>();
}

public void initialize(Context context) {
Expand Down Expand Up @@ -358,6 +361,10 @@ public Session getActiveSession() {
return mActiveSession;
}

public List<Session> getSessions(boolean aPrivateMode) {
return mSessions.stream().filter(session -> session.isPrivateMode() == aPrivateMode).collect(Collectors.toList());
}

public ArrayList<Session> getSortedSessions(boolean aPrivateMode) {
ArrayList<Session> result = new ArrayList<>(mSessions);
result.removeIf(session -> session.isPrivateMode() != aPrivateMode);
Expand All @@ -374,6 +381,14 @@ public void setPermissionDelegate(PermissionDelegate delegate) {
mPermissionDelegate = delegate;
}

public void addSessionChangeListener(SessionChangeListener listener) {
mSessionChangeListeners.add(listener);
}

public void removeSessionChangeListener(SessionChangeListener listener) {
mSessionChangeListeners.remove(listener);
}

public BookmarksStore getBookmarkStore() {
return mBookmarksStore;
}
Expand Down Expand Up @@ -514,28 +529,43 @@ public void removePermissionException(@NonNull String uri, @SitePermission.Categ
@Override
public void onSessionAdded(Session aSession) {
ComponentsAdapter.get().addSession(aSession);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionAdded(aSession);
}
}

@Override
public void onSessionOpened(Session aSession) {
ComponentsAdapter.get().link(aSession);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionOpened(aSession);
}
}

@Override
public void onSessionClosed(Session aSession) {
ComponentsAdapter.get().unlink(aSession);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionClosed(aSession);
}
}

@Override
public void onSessionRemoved(String aId) {
ComponentsAdapter.get().removeSession(aId);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionRemoved(aId);
}
}

@Override
public void onSessionStateChanged(Session aSession, boolean aActive) {
if (aActive) {
ComponentsAdapter.get().selectSession(aSession);
}
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionStateChanged(aSession, aActive);
}
}

@Override
Expand All @@ -549,6 +579,9 @@ public void onCurrentSessionChange(WSession aOldSession, WSession aSession) {
ComponentsAdapter.get().link(newSession);
}

for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onCurrentSessionChange(aOldSession, aSession);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.igalia.wolvic.ui.adapters;

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.igalia.wolvic.R;
import com.igalia.wolvic.browser.engine.Session;
import com.igalia.wolvic.ui.views.TabsBarItem;
import com.igalia.wolvic.ui.widgets.TabDelegate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class TabsBarAdapter extends RecyclerView.Adapter<TabsBarAdapter.ViewHolder> {

public enum Orientation {HORIZONTAL, VERTICAL}

private final TabDelegate mTabDelegate;
private final Orientation mOrientation;
private final List<Session> mTabs = new ArrayList<>();

static class ViewHolder extends RecyclerView.ViewHolder {
TabsBarItem mTabBarItem;

ViewHolder(TabsBarItem v) {
super(v);
mTabBarItem = v;
}
}

public TabsBarAdapter(@NonNull TabDelegate tabDelegate, Orientation orientation) {
mTabDelegate = tabDelegate;
mOrientation = orientation;
}

@Override
public long getItemId(int position) {
if (position == 0) {
return 0;
} else {
return mTabs.get(position - 1).getId().hashCode();
}
}

public void updateTabs(List<Session> aTabs) {
mTabs.clear();
mTabs.addAll(aTabs);

notifyDataSetChanged();
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@LayoutRes int layout;
if (mOrientation == Orientation.HORIZONTAL) {
layout = R.layout.tabs_bar_item_horizontal;
} else {
layout = R.layout.tabs_bar_item_vertical;
}
TabsBarItem view = (TabsBarItem) LayoutInflater.from(parent.getContext()).inflate(layout, parent, false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.mTabBarItem.setDelegate(mItemDelegate);

Session session = mTabs.get(position);
holder.mTabBarItem.attachToSession(session);
}

@Override
public int getItemCount() {
return mTabs.size();
}

private final TabsBarItem.Delegate mItemDelegate = new TabsBarItem.Delegate() {
@Override
public void onClick(TabsBarItem item) {
mTabDelegate.onTabSelect(item.getSession());
}

@Override
public void onClose(TabsBarItem item) {
mTabDelegate.onTabsClose(Collections.singletonList(item.getSession()));
}
};
}
Loading

0 comments on commit 543e9bd

Please sign in to comment.