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 adyacent to the current window and which lists the open tabs.

        This is a very simple implementation for now, as we test and iterate.

        Other minor changes:
         - TabsBar is managed by VRBrowserActivity
         - Add session change listeners to SessionStore
         - Move TabDelegate to a separate interface
  • Loading branch information
felipeerias committed Oct 1, 2024
1 parent 22da6fa commit e791cfd
Show file tree
Hide file tree
Showing 29 changed files with 1,036 additions and 38 deletions.
50 changes: 42 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 All @@ -101,10 +104,12 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
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 +218,7 @@ public void run() {
RootWidget mRootWidget;
KeyboardWidget mKeyboard;
NavigationBarWidget mNavigationBar;
AbstractTabsBar mTabsBar;
CrashDialogWidget mCrashDialog;
TrayWidget mTray;
WhatsNewWidget mWhatsNewWidget = null;
Expand Down Expand Up @@ -467,7 +473,16 @@ public void onWindowVideoAvailabilityChanged(@NonNull WindowWidget aWindow) {
mTray.setAddWindowVisible(mWindows.canOpenNewWindow());
attachToWindow(mWindows.getFocusedWindow(), null);

addWidgets(Arrays.asList(mRootWidget, mNavigationBar, mKeyboard, mTray, mWebXRInterstitial));
// 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;
}

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 @@ -480,13 +495,18 @@ public void onWindowVideoAvailabilityChanged(@NonNull WindowWidget aWindow) {
private void attachToWindow(@NonNull WindowWidget aWindow, @Nullable WindowWidget aPrevWindow) {
mPermissionDelegate.setParentWidgetHandle(aWindow.getHandle());
mNavigationBar.attachToWindow(aWindow);
mTabsBar.attachToWindow(aWindow);
mKeyboard.attachToWindow(aWindow);
mTray.attachToWindow(aWindow);

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

Expand Down Expand Up @@ -772,14 +792,28 @@ 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.display_options_tabs_location))) {
// remove the previous widget
if (mTabsBar != null) {
removeWidget(mTabsBar);
mTabsBar = null;
}

if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_HORIZONTAL) {
mTabsBar = new HorizontalTabsBar(this, mWindows);
addWidgets(Collections.singleton(mTabsBar));
} else if (mSettings.getTabsLocation() == SettingsStore.TABS_LOCATION_VERTICAL) {
mTabsBar = new VerticalTabsBar(this, mWindows);
addWidgets(Collections.singleton(mTabsBar));
}
}
}

void loadFromIntent(final Intent intent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
public interface SessionChangeListener {
default void onSessionAdded(Session aSession) {}
default void onSessionOpened(Session aSession) {}
default void onSessionUpdated(Session aSession) {}
default void onSessionClosed(Session aSession) {}
default void onSessionRemoved(String aId) {}
default void onSessionStateChanged(Session aSession, boolean aActive) {}
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 @@ -1115,6 +1115,10 @@ public void onLocationChange(@NonNull WSession aSession, String aUri) {
aSession.loadUri(overrideUri);
}
}

for (SessionChangeListener listener: mSessionChangeListeners) {
listener.onSessionUpdated(this);
}
}

@Override
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 @@ -513,33 +528,56 @@ public void removePermissionException(@NonNull String uri, @SitePermission.Categ

@Override
public void onSessionAdded(Session aSession) {
Log.e(LOGTAG, "onSessionAdded " + aSession);
ComponentsAdapter.get().addSession(aSession);

// ok, this is a bit confusing
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionAdded(aSession);
}
}

@Override
public void onSessionOpened(Session aSession) {
Log.e(LOGTAG, "onSessionOpened " + aSession);
ComponentsAdapter.get().link(aSession);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionOpened(aSession);
}
}

@Override
public void onSessionClosed(Session aSession) {
Log.e(LOGTAG, "onSessionClosed " + aSession);
ComponentsAdapter.get().unlink(aSession);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionClosed(aSession);
}
}

@Override
public void onSessionRemoved(String aId) {
Log.e(LOGTAG, "onSessionRemoved " + aId);
ComponentsAdapter.get().removeSession(aId);
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionRemoved(aId);
}
}

@Override
public void onSessionStateChanged(Session aSession, boolean aActive) {
Log.e(LOGTAG, "onSessionStateChanged " + aSession + " active=" + aActive);
if (aActive) {
ComponentsAdapter.get().selectSession(aSession);
}
for (SessionChangeListener listener : mSessionChangeListeners) {
listener.onSessionStateChanged(aSession, aActive);
}
}

@Override
public void onCurrentSessionChange(WSession aOldSession, WSession aSession) {
Log.e(LOGTAG, "onCurrentSessionChange old: " + aOldSession + " new: " + aSession);
Session oldSession = getSession(aOldSession);
Session newSession = getSession(aSession);
if (oldSession != null) {
Expand All @@ -549,6 +587,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,109 @@
package com.igalia.wolvic.ui.adapters;

import android.util.Log;
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 com.igalia.wolvic.utils.SystemUtils;

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

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

private static final String LOGTAG = SystemUtils.createLogtag(TabsBarAdapter.class);

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);

Log.e(LOGTAG, "updateTabs: " + aTabs.size());
for (Session session : aTabs) {
Log.e(LOGTAG, " " + session.getCurrentUri() + " " + session.getLastUse() + (session.isActive() ? " ACTIVE" : ""));
}

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) {
Log.e(LOGTAG, "onClick");

mTabDelegate.onTabSelect(item.getSession());
}

@Override
public void onClose(TabsBarItem item) {
Log.e(LOGTAG, "onClose");

mTabDelegate.onTabsClose(Collections.singletonList(item.getSession()));

// this should eventually trigger a refresh of the tabs
}
};
}
Loading

0 comments on commit e791cfd

Please sign in to comment.