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.

WindowViewModel gets three new fields:
- isTabsBarVisible
- usesHorizontalTabsBar
- usesVerticalTabsBar

TopBarWidget uses two of these fields to update its placement
so as to not overlap with the tabs bar.

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 15, 2024
1 parent 6b65026 commit 3fccc3e
Show file tree
Hide file tree
Showing 31 changed files with 1,142 additions and 24 deletions.
66 changes: 58 additions & 8 deletions app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,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 @@ -104,6 +107,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 @@ -209,6 +213,7 @@ public void run() {
RootWidget mRootWidget;
KeyboardWidget mKeyboard;
NavigationBarWidget mNavigationBar;
AbstractTabsBar mTabsBar;
CrashDialogWidget mCrashDialog;
TrayWidget mTray;
WhatsNewWidget mWhatsNewWidget = null;
Expand Down Expand Up @@ -445,9 +450,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 @@ -463,10 +478,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 @@ -719,14 +742,41 @@ 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);
mTabsBar.releaseWidget();
}

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 3fccc3e

Please sign in to comment.