From e791cfd6d0d27967616f40194cb1201b17e6d0a2 Mon Sep 17 00:00:00 2001 From: Felipe Erias Date: Fri, 20 Sep 2024 23:31:49 +0900 Subject: [PATCH] Initial implementation of browser tabs 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 --- .../com/igalia/wolvic/VRBrowserActivity.java | 50 ++++- .../wolvic/browser/SessionChangeListener.java | 1 + .../igalia/wolvic/browser/SettingsStore.java | 19 ++ .../igalia/wolvic/browser/engine/Session.java | 4 + .../wolvic/browser/engine/SessionStore.java | 41 +++++ .../wolvic/ui/adapters/TabsBarAdapter.java | 109 +++++++++++ .../wolvic/ui/viewmodel/TrayViewModel.java | 11 ++ .../wolvic/ui/views/CustomFastScroller.java | 2 +- .../igalia/wolvic/ui/views/TabsBarItem.java | 172 ++++++++++++++++++ .../wolvic/ui/widgets/AbstractTabsBar.java | 95 ++++++++++ .../wolvic/ui/widgets/HorizontalTabsBar.java | 81 +++++++++ .../igalia/wolvic/ui/widgets/TabDelegate.java | 11 ++ .../igalia/wolvic/ui/widgets/TabsWidget.java | 6 - .../igalia/wolvic/ui/widgets/TrayWidget.java | 59 +++--- .../wolvic/ui/widgets/VerticalTabsBar.java | 82 +++++++++ .../com/igalia/wolvic/ui/widgets/Windows.java | 3 +- .../widgets/settings/DisplayOptionsView.java | 20 ++ app/src/main/res/drawable/tabs_bar_bg.xml | 13 ++ .../main/res/drawable/tabs_bar_item_bg.xml | 39 ++++ app/src/main/res/layout/options_display.xml | 8 + .../main/res/layout/tabs_bar_horizontal.xml | 37 ++++ .../res/layout/tabs_bar_item_horizontal.xml | 71 ++++++++ .../res/layout/tabs_bar_item_vertical.xml | 76 ++++++++ app/src/main/res/layout/tabs_bar_vertical.xml | 37 ++++ app/src/main/res/layout/tray.xml | 1 + app/src/main/res/values/dimen.xml | 4 + app/src/main/res/values/non_L10n.xml | 1 + app/src/main/res/values/options_values.xml | 17 ++ app/src/main/res/values/strings.xml | 4 + 29 files changed, 1036 insertions(+), 38 deletions(-) create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/adapters/TabsBarAdapter.java create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/views/TabsBarItem.java create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/widgets/AbstractTabsBar.java create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/widgets/HorizontalTabsBar.java create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/widgets/TabDelegate.java create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/widgets/VerticalTabsBar.java create mode 100644 app/src/main/res/drawable/tabs_bar_bg.xml create mode 100644 app/src/main/res/drawable/tabs_bar_item_bg.xml create mode 100644 app/src/main/res/layout/tabs_bar_horizontal.xml create mode 100644 app/src/main/res/layout/tabs_bar_item_horizontal.xml create mode 100644 app/src/main/res/layout/tabs_bar_item_vertical.xml create mode 100644 app/src/main/res/layout/tabs_bar_vertical.xml diff --git a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java index a7ac337d48..4db35cb18e 100644 --- a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java +++ b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java @@ -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; @@ -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; @@ -213,6 +218,7 @@ public void run() { RootWidget mRootWidget; KeyboardWidget mKeyboard; NavigationBarWidget mNavigationBar; + AbstractTabsBar mTabsBar; CrashDialogWidget mCrashDialog; TrayWidget mTray; WhatsNewWidget mWhatsNewWidget = null; @@ -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); @@ -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); + } } } @@ -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) { diff --git a/app/src/common/shared/com/igalia/wolvic/browser/SessionChangeListener.java b/app/src/common/shared/com/igalia/wolvic/browser/SessionChangeListener.java index 510d02cc77..cf530c199e 100644 --- a/app/src/common/shared/com/igalia/wolvic/browser/SessionChangeListener.java +++ b/app/src/common/shared/com/igalia/wolvic/browser/SessionChangeListener.java @@ -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) {} diff --git a/app/src/common/shared/com/igalia/wolvic/browser/SettingsStore.java b/app/src/common/shared/com/igalia/wolvic/browser/SettingsStore.java index cb609ba28d..b84a9faf69 100644 --- a/app/src/common/shared/com/igalia/wolvic/browser/SettingsStore.java +++ b/app/src/common/shared/com/igalia/wolvic/browser/SettingsStore.java @@ -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; @@ -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; @@ -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); diff --git a/app/src/common/shared/com/igalia/wolvic/browser/engine/Session.java b/app/src/common/shared/com/igalia/wolvic/browser/engine/Session.java index 6a21ea7f74..03a1f8da15 100644 --- a/app/src/common/shared/com/igalia/wolvic/browser/engine/Session.java +++ b/app/src/common/shared/com/igalia/wolvic/browser/engine/Session.java @@ -1115,6 +1115,10 @@ public void onLocationChange(@NonNull WSession aSession, String aUri) { aSession.loadUri(overrideUri); } } + + for (SessionChangeListener listener: mSessionChangeListeners) { + listener.onSessionUpdated(this); + } } @Override diff --git a/app/src/common/shared/com/igalia/wolvic/browser/engine/SessionStore.java b/app/src/common/shared/com/igalia/wolvic/browser/engine/SessionStore.java index 282dc72a59..5715c89e15 100644 --- a/app/src/common/shared/com/igalia/wolvic/browser/engine/SessionStore.java +++ b/app/src/common/shared/com/igalia/wolvic/browser/engine/SessionStore.java @@ -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; @@ -84,9 +85,11 @@ public static SessionStore get() { private FxaWebChannelFeature mWebChannelsFeature; private Store.Subscription mStoreSubscription; private BrowserIconsHelper mBrowserIconsHelper; + private final LinkedHashSet mSessionChangeListeners; private SessionStore() { mSessions = new ArrayList<>(); + mSessionChangeListeners = new LinkedHashSet<>(); } public void initialize(Context context) { @@ -358,6 +361,10 @@ public Session getActiveSession() { return mActiveSession; } + public List getSessions(boolean aPrivateMode) { + return mSessions.stream().filter(session -> session.isPrivateMode() == aPrivateMode).collect(Collectors.toList()); + } + public ArrayList getSortedSessions(boolean aPrivateMode) { ArrayList result = new ArrayList<>(mSessions); result.removeIf(session -> session.isPrivateMode() != aPrivateMode); @@ -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; } @@ -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) { @@ -549,6 +587,9 @@ public void onCurrentSessionChange(WSession aOldSession, WSession aSession) { ComponentsAdapter.get().link(newSession); } + for (SessionChangeListener listener : mSessionChangeListeners) { + listener.onCurrentSessionChange(aOldSession, aSession); + } } @Override diff --git a/app/src/common/shared/com/igalia/wolvic/ui/adapters/TabsBarAdapter.java b/app/src/common/shared/com/igalia/wolvic/ui/adapters/TabsBarAdapter.java new file mode 100644 index 0000000000..1eca894159 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/adapters/TabsBarAdapter.java @@ -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 { + + 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 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 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 + } + }; +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/TrayViewModel.java b/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/TrayViewModel.java index 12b0934038..2aec013e81 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/TrayViewModel.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/TrayViewModel.java @@ -21,6 +21,7 @@ public class TrayViewModel extends AndroidViewModel { private MediatorLiveData isVisible; private MutableLiveData time; private MutableLiveData pm; + private MutableLiveData needsTabsButton; private MutableLiveData wifiConnected; private MutableLiveData headsetIcon; private MutableLiveData headsetBatteryLevel; @@ -44,6 +45,7 @@ public TrayViewModel(@NonNull Application application) { time = new MutableLiveData<>(); pm = new MutableLiveData<>(); pm = new MutableLiveData<>(); + needsTabsButton = new MutableLiveData<>(new ObservableBoolean(true)); wifiConnected = new MutableLiveData<>(new ObservableBoolean(true)); headsetIcon = new MutableLiveData<>(new ObservableInt(R.drawable.ic_icon_statusbar_headset_normal)); headsetBatteryLevel = new MutableLiveData<>(new ObservableInt(R.drawable.ic_icon_statusbar_indicator)); @@ -69,6 +71,7 @@ public void refresh() { isKeyboardVisible.setValue(isKeyboardVisible.getValue()); time.postValue(time.getValue()); pm.postValue(pm.getValue()); + needsTabsButton.postValue(needsTabsButton.getValue()); wifiConnected.postValue(wifiConnected.getValue()); headsetIcon.setValue(headsetIcon.getValue()); headsetBatteryLevel.setValue(headsetBatteryLevel.getValue()); @@ -127,6 +130,14 @@ public MutableLiveData getPm() { return pm; } + public void setNeedsTabsButton(boolean needsTabs) { + this.needsTabsButton.setValue(new ObservableBoolean(needsTabs)); + } + + public MutableLiveData getNeedsTabsButton() { + return needsTabsButton; + } + public void setWifiConnected(boolean connected) { this.wifiConnected.setValue(new ObservableBoolean(connected)); } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/CustomFastScroller.java b/app/src/common/shared/com/igalia/wolvic/ui/views/CustomFastScroller.java index f4682ccf40..a3dd1cbd9e 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/CustomFastScroller.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/CustomFastScroller.java @@ -361,7 +361,7 @@ void updateScrollPosition(int offsetX, int offsetY) { int verticalContentLength = mRecyclerView.computeVerticalScrollRange(); int verticalVisibleLength = mRecyclerViewHeight; mNeedVerticalScrollbar = verticalContentLength - verticalVisibleLength > 0 - && mRecyclerViewHeight >= mScrollbarMinimumRange || mNeedHorizontalScrollbar; + && mRecyclerViewHeight >= mScrollbarMinimumRange || mNeedVerticalScrollbar; int horizontalContentLength = mRecyclerView.computeHorizontalScrollRange(); int horizontalVisibleLength = mRecyclerViewWidth; diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/TabsBarItem.java b/app/src/common/shared/com/igalia/wolvic/ui/views/TabsBarItem.java new file mode 100644 index 0000000000..3b5f0d56d9 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/TabsBarItem.java @@ -0,0 +1,172 @@ +package com.igalia.wolvic.ui.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModel; + +import com.igalia.wolvic.R; +import com.igalia.wolvic.browser.SessionChangeListener; +import com.igalia.wolvic.browser.api.WSession; +import com.igalia.wolvic.browser.engine.Session; +import com.igalia.wolvic.browser.engine.SessionStore; +import com.igalia.wolvic.utils.SystemUtils; +import com.igalia.wolvic.utils.UrlUtils; + +import java.util.Objects; + +import mozilla.components.browser.icons.IconRequest; + +public class TabsBarItem extends RelativeLayout implements WSession.ContentDelegate, WSession.NavigationDelegate, + SessionChangeListener { + + private static final String LOGTAG = SystemUtils.createLogtag(TabsBarItem.class); + + protected ViewGroup mTabDetailsView; + protected ImageView mFavicon; + protected TextView mSubtitle; + protected TextView mTitle; + protected UIButton mCloseButton; + protected Delegate mDelegate; + protected Session mSession; + protected ViewModel mViewModel; + + public interface Delegate { + void onClick(TabsBarItem aSender); + + void onClose(TabsBarItem aSender); + } + + public TabsBarItem(Context context) { + super(context); + } + + public TabsBarItem(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public TabsBarItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mTabDetailsView = findViewById(R.id.tab_details); + + mCloseButton = findViewById(R.id.tab_close_button); + mCloseButton.setOnClickListener(v -> { + v.requestFocusFromTouch(); + if (mDelegate != null) { + mDelegate.onClose(this); + } + }); + + mFavicon = findViewById(R.id.tab_favicon); + mTitle = findViewById(R.id.tab_title); + mSubtitle = findViewById(R.id.tab_subtitle); + + this.setOnClickListener(mClickListener); + } + + private final OnClickListener mClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + + Log.e(LOGTAG, "OnClickListener.onClick " + mDelegate); + + if (mDelegate != null) { + mDelegate.onClick(TabsBarItem.this); + } + } + }; + + public void attachToSession(@Nullable Session aSession) { + if (mSession != null) { + mSession.removeContentListener(this); + mSession.removeNavigationListener(this); + mSession.removeSessionChangeListener(this); + } + + mSession = aSession; + if (mSession != null) { + mSession.addContentListener(this); + mSession.addNavigationListener(this); + mSession.addSessionChangeListener(this); + + Log.e(LOGTAG, "attachToSession: " + mSession.getCurrentTitle() + " " + mSession.getCurrentUri()); + + mTitle.setText(mSession.getCurrentTitle()); + mSubtitle.setText(UrlUtils.stripProtocol(mSession.getCurrentUri())); + SessionStore.get().getBrowserIcons().loadIntoView( + mFavicon, mSession.getCurrentUri(), IconRequest.Size.DEFAULT); + + setActive(mSession.isActive()); + } else { + // Null session + mTitle.setText(null); + mSubtitle.setText(null); + mFavicon.setImageDrawable(null); + } + } + + public Session getSession() { + return mSession; + } + + public void setDelegate(Delegate aDelegate) { + mDelegate = aDelegate; + } + + @Override + public void onTitleChange(@NonNull WSession session, @Nullable String title) { + if (mSession == null || mSession.getWSession() != session) { + return; + } + mTitle.setText(title); + } + + @Override + public void onLocationChange(@NonNull WSession session, @Nullable String url) { + if (mSession == null || mSession.getWSession() != session) { + return; + } + + if (url == null) { + mSubtitle.setText(null); + mFavicon.setImageDrawable(null); + } else { + mSubtitle.setText(UrlUtils.stripProtocol(mSession.getCurrentUri())); + SessionStore.get().getBrowserIcons().loadIntoView( + mFavicon, mSession.getCurrentUri(), IconRequest.Size.DEFAULT); + } + } + + @Override + public void onSessionStateChanged(Session aSession, boolean aActive) { + // this should take into account the currently focused session in the window + if (Objects.equals(mSession, aSession)) { + setActive(aActive); + } + } + + @Override + public void onCloseRequest(@NonNull WSession aSession) { + if (mSession.getWSession() == aSession) { + mDelegate.onClose(this); + } + } + + public void setActive(boolean isActive) { + setSelected(isActive); + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/AbstractTabsBar.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/AbstractTabsBar.java new file mode 100644 index 0000000000..c6bf5ca6f4 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/AbstractTabsBar.java @@ -0,0 +1,95 @@ +package com.igalia.wolvic.ui.widgets; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.igalia.wolvic.browser.SessionChangeListener; +import com.igalia.wolvic.browser.api.WSession; +import com.igalia.wolvic.browser.engine.Session; +import com.igalia.wolvic.ui.adapters.TabsBarAdapter; +import com.igalia.wolvic.ui.views.TabsBarItem; + +public abstract class AbstractTabsBar extends UIWidget implements SessionChangeListener { + + protected TabDelegate mTabDelegate; + + public AbstractTabsBar(Context aContext) { + super(aContext); + } + + public AbstractTabsBar(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + } + + public AbstractTabsBar(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + } + + public void setTabDelegate(TabDelegate aDelegate) { + mTabDelegate = aDelegate; + } + + public abstract void refreshTabs(); + + @Override + public void onSessionAdded(Session aSession) { + // who calls this??? + Log.e(LOGTAG, "onSessionAdded: " + aSession); + refreshTabs(); + } + + @Override + public void onSessionOpened(Session aSession) { + Log.e(LOGTAG, "onSessionOpened: " + aSession); + refreshTabs(); + } + + @Override + public void onSessionUpdated(Session aSession) { + Log.e(LOGTAG, "onSessionUpdated: " + aSession); + refreshTabs(); + } + + @Override + public void onSessionClosed(Session aSession) { + Log.e(LOGTAG, "onSessionClosed: " + aSession); + refreshTabs(); + } + + @Override + public void onSessionRemoved(String aId) { + Log.e(LOGTAG, "onSessionRemoved: " + aId); + refreshTabs(); + } + + @Override + public void onSessionStateChanged(Session aSession, boolean aActive) { + Log.e(LOGTAG, "onSessionStateChanged: " + aSession + ", " + aActive); + refreshTabs(); + } + + @Override + public void onCurrentSessionChange(WSession aOldSession, WSession aSession) { + Log.e(LOGTAG, "onCurrentSessionChange: old " + aSession + ", new " + aSession); + refreshTabs(); + } + + @Override + public void onStackSession(Session aSession) { + Log.e(LOGTAG, "onStackSession: " + aSession); + refreshTabs(); + } + + @Override + public void onUnstackSession(Session aSession, Session aParent) { + Log.e(LOGTAG, "onUnstackSession: " + aSession); + refreshTabs(); + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/HorizontalTabsBar.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/HorizontalTabsBar.java new file mode 100644 index 0000000000..4077ffddb4 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/HorizontalTabsBar.java @@ -0,0 +1,81 @@ +package com.igalia.wolvic.ui.widgets; + +import android.content.Context; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.igalia.wolvic.R; +import com.igalia.wolvic.browser.SessionChangeListener; +import com.igalia.wolvic.browser.SettingsStore; +import com.igalia.wolvic.browser.engine.SessionStore; +import com.igalia.wolvic.ui.adapters.TabsBarAdapter; + +public class HorizontalTabsBar extends AbstractTabsBar implements SessionChangeListener { + + protected boolean mPrivateMode; + protected Button mAddTabButton; + protected RecyclerView mTabsList; + protected LinearLayoutManager mLayoutManager; + protected TabsBarAdapter mAdapter; + protected final TabDelegate mTabDelegate; + + public HorizontalTabsBar(Context aContext, TabDelegate aDelegate) { + super(aContext); + mTabDelegate = aDelegate; + updateUI(); + } + + private void updateUI() { + removeAllViews(); + + inflate(getContext(), R.layout.tabs_bar_horizontal, this); + + mAddTabButton = findViewById(R.id.add_tab); + mAddTabButton.setOnClickListener(v -> mTabDelegate.onTabAdd()); + + mTabsList = findViewById(R.id.tabsRecyclerView); + mLayoutManager = new LinearLayoutManager(getContext()); + mLayoutManager.setOrientation(RecyclerView.HORIZONTAL); + mTabsList.setLayoutManager(mLayoutManager); + mAdapter = new TabsBarAdapter(mTabDelegate, TabsBarAdapter.Orientation.HORIZONTAL); + mTabsList.setAdapter(mAdapter); + + SessionStore.get().addSessionChangeListener(this); + } + + @Override + protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { + Context context = getContext(); + aPlacement.width = SettingsStore.getInstance(getContext()).getWindowWidth(); + aPlacement.height = WidgetPlacement.dpDimension(context, R.dimen.horizontal_tabs_bar_height); + aPlacement.worldWidth = aPlacement.width * WidgetPlacement.worldToDpRatio(context); + aPlacement.translationX = WidgetPlacement.dpDimension(context, R.dimen.top_bar_window_margin); + aPlacement.anchorX = 0.0f; + aPlacement.anchorY = 0.0f; + aPlacement.parentAnchorX = 0.0f; + aPlacement.parentAnchorY = 1.0f; + aPlacement.parentAnchorGravity = WidgetPlacement.GRAVITY_DEFAULT; + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.context_menu_z_distance); + } + + @Override + public void attachToWindow(@NonNull WindowWidget window) { + super.attachToWindow(window); + mPrivateMode = window.getSession().isPrivateMode(); + mWidgetPlacement.parentHandle = window.getHandle(); + mWidgetPlacement.width = window.getPlacement().width; + refreshTabs(); + } + + @Override + public void detachFromWindow() { + super.detachFromWindow(); + } + + public void refreshTabs() { + mAdapter.updateTabs(SessionStore.get().getSessions(mPrivateMode)); + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabDelegate.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabDelegate.java new file mode 100644 index 0000000000..b797a9d062 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabDelegate.java @@ -0,0 +1,11 @@ +package com.igalia.wolvic.ui.widgets; + +import com.igalia.wolvic.browser.engine.Session; + +import java.util.List; + +public interface TabDelegate { + void onTabAdd(); + void onTabSelect(Session aTab); + void onTabsClose(List aTabs); +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabsWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabsWidget.java index 24f8a19fe8..33fced85d8 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabsWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TabsWidget.java @@ -47,12 +47,6 @@ public class TabsWidget extends UIDialog { protected boolean mSelecting; protected ArrayList mSelectedTabs = new ArrayList<>(); - public interface TabDelegate { - void onTabSelect(Session aTab); - void onTabAdd(); - void onTabsClose(List aTabs); - } - public TabsWidget(Context aContext) { super(aContext); mBitmapCache = BitmapCache.getInstance(aContext); diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayWidget.java index a8c6773d1a..52231810e6 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayWidget.java @@ -11,6 +11,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; @@ -30,12 +31,13 @@ import android.view.animation.AccelerateDecelerateInterpolator; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.databinding.DataBindingUtil; import androidx.databinding.ObservableBoolean; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import androidx.preference.PreferenceManager; -import com.igalia.wolvic.BuildConfig; import com.igalia.wolvic.R; import com.igalia.wolvic.VRBrowserActivity; import com.igalia.wolvic.VRBrowserApplication; @@ -63,8 +65,11 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.Objects; -public class TrayWidget extends UIWidget implements WidgetManagerDelegate.UpdateListener, DownloadsManager.DownloadsListener, ConnectivityReceiver.Delegate { +public class TrayWidget extends UIWidget implements WidgetManagerDelegate.UpdateListener, + DownloadsManager.DownloadsListener, ConnectivityReceiver.Delegate, + SharedPreferences.OnSharedPreferenceChangeListener { private static final int ICON_ANIMATION_DURATION = 200; @@ -118,7 +123,7 @@ private void initialize(Context aContext) { setIsHardwareAccelerationEnabled(false); mTrayViewModel = new ViewModelProvider( - (VRBrowserActivity)getContext(), + (VRBrowserActivity) getContext(), ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) .get(TrayViewModel.class); mTrayViewModel.getIsVisible().observe((VRBrowserActivity) getContext(), mIsVisibleObserver); @@ -138,9 +143,11 @@ private void initialize(Context aContext) { mWidgetManager.addUpdateListener(this); mWidgetManager.getServicesProvider().getDownloadsManager().addListener(this); - mConnectivityReceived = ((VRBrowserApplication)getContext().getApplicationContext()).getConnectivityReceiver(); + mConnectivityReceived = ((VRBrowserApplication) getContext().getApplicationContext()).getConnectivityReceiver(); mConnectivityReceived.addListener(this); + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); + mWifiSSID = getContext().getString(R.string.tray_wifi_no_connection); updateTime(); @@ -159,7 +166,7 @@ public void updateUI() { // Inflate this data binding layout mBinding = DataBindingUtil.inflate(inflater, R.layout.tray, this, true); - mBinding.setLifecycleOwner((VRBrowserActivity)getContext()); + mBinding.setLifecycleOwner((VRBrowserActivity) getContext()); mBinding.setTraymodel(mTrayViewModel); mBinding.setViewmodel(mViewModel); @@ -400,7 +407,7 @@ public void onConfigurationChanged(Configuration newConfig) { } private OnHoverListener mButtonScaleHoverListener = (view, motionEvent) -> { - UIButton button = (UIButton)view; + UIButton button = (UIButton) view; if (button.isActive() || button.isPrivate()) { return false; } @@ -408,13 +415,13 @@ public void onConfigurationChanged(Configuration newConfig) { int ev = motionEvent.getActionMasked(); switch (ev) { case MotionEvent.ACTION_HOVER_ENTER: - if (!view.isPressed() && ViewUtils.isInsideView(view, (int)motionEvent.getRawX(), (int)motionEvent.getRawY())) { + if (!view.isPressed() && ViewUtils.isInsideView(view, (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { animateViewPadding(view, mMaxPadding, mMinPadding, ICON_ANIMATION_DURATION); } return false; case MotionEvent.ACTION_HOVER_EXIT: - if (!ViewUtils.isInsideView(view, (int)motionEvent.getRawX(), (int)motionEvent.getRawY())) { + if (!ViewUtils.isInsideView(view, (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { animateViewPadding(view, mMinPadding, mMaxPadding, ICON_ANIMATION_DURATION); } return false; @@ -436,8 +443,7 @@ private void animateViewPadding(View view, int paddingStart, int paddingEnd, int try { int newPadding = Integer.parseInt(valueAnimator.getAnimatedValue().toString()); view.setPadding(newPadding, newPadding, newPadding, newPadding); - } - catch (NumberFormatException ex) { + } catch (NumberFormatException ex) { Log.e(LOGTAG, "Error parsing tray animation value: " + valueAnimator.getAnimatedValue().toString()); } }); @@ -449,8 +455,8 @@ public void onAnimationStart(Animator animator) { @Override public void onAnimationEnd(Animator animator) { - UIButton button = (UIButton)view; - if(button.isActive() || button.isPrivate()) { + UIButton button = (UIButton) view; + if (button.isActive() || button.isPrivate()) { view.setPadding(mMinPadding, mMinPadding, mMinPadding, mMinPadding); } } @@ -509,7 +515,7 @@ protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { aPlacement.parentAnchorX = 0.5f; aPlacement.parentAnchorY = 0.0f; aPlacement.rotationAxisX = 1.0f; - aPlacement.rotation = (float)Math.toRadians(-45); + aPlacement.rotation = (float) Math.toRadians(-45); aPlacement.cylinder = false; aPlacement.textureScale *= aPlacement.worldWidth; updatePlacementTranslationZ(); @@ -568,7 +574,7 @@ public void hide(@HideFlags int aHideFlags) { @Override public void detachFromWindow() { hideNotifications(); - + if (mSession != null) { mSession = null; } @@ -598,11 +604,11 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { // ModelView creation and observers setup mViewModel = new ViewModelProvider( - (VRBrowserActivity)getContext(), + (VRBrowserActivity) getContext(), ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) .get(String.valueOf(mAttachedWindow.hashCode()), WindowViewModel.class); - mViewModel.getIsLibraryVisible().observe((VRBrowserActivity)getContext(), mIsLibraryVisible); - mViewModel.getIsPrivateSession().observe((VRBrowserActivity)getContext(), mIsPrivateSession); + mViewModel.getIsLibraryVisible().observe((VRBrowserActivity) getContext(), mIsLibraryVisible); + mViewModel.getIsPrivateSession().observe((VRBrowserActivity) getContext(), mIsPrivateSession); mBinding.setViewmodel(mViewModel); @@ -746,7 +752,7 @@ public void onBookmarkAdded() { @Override public void onDownloadsUpdate(@NonNull List downloads) { long inProgressNum = downloads.stream().filter(item -> item.inProgress()).count(); - mTrayViewModel.setDownloadsNumber((int)inProgressNum); + mTrayViewModel.setDownloadsNumber((int) inProgressNum); if (inProgressNum == 0) { mBinding.libraryButton.setLevel(0); @@ -759,8 +765,8 @@ public void onDownloadsUpdate(@NonNull List downloads) { .mapToLong(Download::getDownloadedBytes) .sum(); if (size > 0) { - long percent = downloaded*100/size; - mBinding.libraryButton.setLevel((int)percent*100); + long percent = downloaded * 100 / size; + mBinding.libraryButton.setLevel((int) percent * 100); } } } @@ -803,7 +809,7 @@ private boolean updateWifiIcon(final int level) { if (icon == null) { return false; } - LayerDrawable layerDrawable = (LayerDrawable)icon; + LayerDrawable layerDrawable = (LayerDrawable) icon; VectorDrawable drawable = (VectorDrawable) layerDrawable.findDrawableByLayerId(R.id.wifi_layer1); if (drawable != null) { @@ -840,7 +846,7 @@ private int getWifiSignalStrength(@NonNull WifiManager wifiManager) { return wifiManager.getConnectionInfo().getRssi(); } } - + private void updateWifi() { // We are collecting sensitive data here, so we should ensure the user granted permissions. if (!(SettingsStore.getInstance(getContext()).isTermsServiceAccepted() && @@ -924,4 +930,13 @@ private String getFormattedDate() { SimpleDateFormat.FULL, LocaleUtils.getDisplayLanguage(getContext()).getLocale()); return format.format(new Date()); } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) { + if (Objects.equals(key, getContext().getString(R.string.settings_key_tabs_location))) { + int value = sharedPreferences.getInt(key, SettingsStore.TABS_LOCATION_TRAY); + mTrayViewModel.setNeedsTabsButton(value == SettingsStore.TABS_LOCATION_TRAY); + updateUI(); + } + } } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/VerticalTabsBar.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/VerticalTabsBar.java new file mode 100644 index 0000000000..1c6e130fd0 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/VerticalTabsBar.java @@ -0,0 +1,82 @@ +package com.igalia.wolvic.ui.widgets; + +import android.content.Context; +import android.widget.Button; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.igalia.wolvic.R; +import com.igalia.wolvic.browser.SessionChangeListener; +import com.igalia.wolvic.browser.SettingsStore; +import com.igalia.wolvic.browser.engine.SessionStore; +import com.igalia.wolvic.ui.adapters.TabsBarAdapter; + +public class VerticalTabsBar extends AbstractTabsBar implements SessionChangeListener { + + protected boolean mPrivateMode; + protected Button mAddTabButton; + protected RecyclerView mTabsList; + protected LinearLayoutManager mLayoutManager; + protected TabsBarAdapter mAdapter; + protected TabDelegate mTabDelegate; + + public VerticalTabsBar(Context aContext, TabDelegate aDelegate) { + super(aContext); + mTabDelegate = aDelegate; + updateUI(); + } + + private void updateUI() { + removeAllViews(); + + inflate(getContext(), R.layout.tabs_bar_vertical, this); + + mAddTabButton = findViewById(R.id.add_tab); + mAddTabButton.setOnClickListener(v -> mTabDelegate.onTabAdd()); + + mTabsList = findViewById(R.id.tabsRecyclerView); + mLayoutManager = new LinearLayoutManager(getContext()); + mLayoutManager.setOrientation(RecyclerView.VERTICAL); + mTabsList.setLayoutManager(mLayoutManager); + mAdapter = new TabsBarAdapter(mTabDelegate, TabsBarAdapter.Orientation.VERTICAL); + mTabsList.setAdapter(mAdapter); + + SessionStore.get().addSessionChangeListener(this); + } + + @Override + protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { + Context context = getContext(); + aPlacement.width = WidgetPlacement.dpDimension(context, R.dimen.vertical_tabs_bar_width); + aPlacement.height = SettingsStore.getInstance(getContext()).getWindowHeight(); + aPlacement.worldWidth = aPlacement.width * WidgetPlacement.worldToDpRatio(context); + aPlacement.translationY = WidgetPlacement.dpDimension(context, R.dimen.top_bar_window_margin); + aPlacement.anchorX = 1.0f; + aPlacement.anchorY = 0.0f; + aPlacement.parentAnchorX = 0.0f; + aPlacement.parentAnchorY = 0.0f; + aPlacement.parentAnchorGravity = WidgetPlacement.GRAVITY_CENTER_Y; + aPlacement.layer = true; + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.context_menu_z_distance); + } + + @Override + public void attachToWindow(@NonNull WindowWidget window) { + super.attachToWindow(window); + mPrivateMode = window.getSession().isPrivateMode(); + mWidgetPlacement.parentHandle = window.getHandle(); + mWidgetPlacement.height = window.getPlacement().height; + refreshTabs(); + } + + @Override + public void detachFromWindow() { + super.detachFromWindow(); + } + + public void refreshTabs() { + mAdapter.updateTabs(SessionStore.get().getSessions(mPrivateMode)); + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java index f48ee4df19..981066bd78 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/Windows.java @@ -62,7 +62,7 @@ import mozilla.components.concept.sync.TabData; public class Windows implements TrayListener, TopBarWidget.Delegate, TitleBarWidget.Delegate, - WindowWidget.WindowListener, TabsWidget.TabDelegate, Services.TabReceivedDelegate { + WindowWidget.WindowListener, TabDelegate, Services.TabReceivedDelegate { private static final String LOGTAG = SystemUtils.createLogtag(Windows.class); @@ -1387,6 +1387,7 @@ public void selectTab(@NonNull Session aTab) { public void onTabSelect(Session aTab) { if (mFocusedWindow.getSession() != aTab) { TelemetryService.Tabs.activatedEvent(); + aTab.updateLastUse(); } WindowWidget targetWindow = mFocusedWindow; diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/settings/DisplayOptionsView.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/settings/DisplayOptionsView.java index 68e8f63af3..424807988d 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/settings/DisplayOptionsView.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/settings/DisplayOptionsView.java @@ -96,6 +96,10 @@ protected void updateUI() { mBinding.windowMovementSwitch.setOnCheckedChangeListener(mWindowMovementListener); setWindowMovement(SettingsStore.getInstance(getContext()).isWindowMovementEnabled(), false); + @SettingsStore.TabsLocation int tabsLocation = SettingsStore.getInstance(getContext()).getTabsLocation(); + mBinding.tabsLocationRadio.setOnCheckedChangeListener(mTabsLocationChangeListener); + setTabsLocation(mBinding.tabsLocationRadio.getIdForValue(tabsLocation), false); + mDefaultHomepageUrl = getContext().getString(R.string.HOMEPAGE_URL); mBinding.homepageEdit.setHint1(getContext().getString(R.string.homepage_hint, getContext().getString(R.string.app_name))); @@ -189,6 +193,10 @@ public boolean isEditing() { setWindowMovement(enabled, true); }; + private RadioGroupSetting.OnCheckedChangeListener mTabsLocationChangeListener = (radioGroup, checkedId, doApply) -> { + setTabsLocation(checkedId, true); + }; + private OnClickListener mHomepageListener = (view) -> { if (!mBinding.homepageEdit.getFirstText().isEmpty()) { setHomepage(mBinding.homepageEdit.getFirstText()); @@ -361,6 +369,18 @@ private void setWindowMovement(boolean value, boolean doApply) { } } + private void setTabsLocation(int checkedId, boolean doApply) { + mBinding.tabsLocationRadio.setOnCheckedChangeListener(null); + mBinding.tabsLocationRadio.setChecked(checkedId, doApply); + mBinding.tabsLocationRadio.setOnCheckedChangeListener(mMSSAChangeListener); + + if (doApply) { + int tabsLocationValue = (Integer) mBinding.tabsLocationRadio.getValueForId(checkedId); + SettingsStore.getInstance(getContext()).setTabsLocation(tabsLocationValue); + showRestartDialog(); + } + } + private void setHomepage(String newHomepage) { mBinding.homepageEdit.setOnClickListener(null); mBinding.homepageEdit.setFirstText(newHomepage); diff --git a/app/src/main/res/drawable/tabs_bar_bg.xml b/app/src/main/res/drawable/tabs_bar_bg.xml new file mode 100644 index 0000000000..7172380903 --- /dev/null +++ b/app/src/main/res/drawable/tabs_bar_bg.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tabs_bar_item_bg.xml b/app/src/main/res/drawable/tabs_bar_item_bg.xml new file mode 100644 index 0000000000..3c400a2a03 --- /dev/null +++ b/app/src/main/res/drawable/tabs_bar_item_bg.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/options_display.xml b/app/src/main/res/layout/options_display.xml index d6e8de6dbd..cf15677c9c 100644 --- a/app/src/main/res/layout/options_display.xml +++ b/app/src/main/res/layout/options_display.xml @@ -149,6 +149,14 @@ android:layout_height="wrap_content" app:description="@string/display_options_windows_can_move" /> + + diff --git a/app/src/main/res/layout/tabs_bar_horizontal.xml b/app/src/main/res/layout/tabs_bar_horizontal.xml new file mode 100644 index 0000000000..7522387242 --- /dev/null +++ b/app/src/main/res/layout/tabs_bar_horizontal.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tabs_bar_item_horizontal.xml b/app/src/main/res/layout/tabs_bar_item_horizontal.xml new file mode 100644 index 0000000000..a965dfb891 --- /dev/null +++ b/app/src/main/res/layout/tabs_bar_item_horizontal.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tabs_bar_item_vertical.xml b/app/src/main/res/layout/tabs_bar_item_vertical.xml new file mode 100644 index 0000000000..9ba6026812 --- /dev/null +++ b/app/src/main/res/layout/tabs_bar_item_vertical.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tabs_bar_vertical.xml b/app/src/main/res/layout/tabs_bar_vertical.xml new file mode 100644 index 0000000000..713aae2267 --- /dev/null +++ b/app/src/main/res/layout/tabs_bar_vertical.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tray.xml b/app/src/main/res/layout/tray.xml index 21ac2d6c6b..daa4e7f5a9 100644 --- a/app/src/main/res/layout/tray.xml +++ b/app/src/main/res/layout/tray.xml @@ -168,6 +168,7 @@ app:tooltipDensity="@dimen/tray_tooltip_density" app:tooltipPosition="bottom" app:tooltipLayout="@layout/tooltip_tray" + visibleGone="@{traymodel.needTabsButton}" android:src="@drawable/ic_icon_tray_tabs" app:regularModeBackground="@{traymodel.isMaxWindows ? @drawable/tray_background_unchecked_start : @drawable/tray_background_unchecked_middle}" app:privateModeBackground="@{traymodel.isMaxWindows ? @drawable/tray_background_start_private : @drawable/tray_background_middle_private}" diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 0282b7be26..72c7af0791 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -115,6 +115,10 @@ 7dp 3dp + + 200dp + 52dp + 400dp 200dp diff --git a/app/src/main/res/values/non_L10n.xml b/app/src/main/res/values/non_L10n.xml index 583beb6c3f..0c22302266 100644 --- a/app/src/main/res/values/non_L10n.xml +++ b/app/src/main/res/values/non_L10n.xml @@ -83,6 +83,7 @@ settings_key_privacy_policy_accepted settings_key_search_engine_id settings_key_eye_tracking_supported + settings_key_tabs_location https://github.com/igalia/wolvic/wiki/Environments https://wolvic.com/legal/privacy/ https://www.igalia.com/privacy/&url=%1$s diff --git a/app/src/main/res/values/options_values.xml b/app/src/main/res/values/options_values.xml index 9619e59488..cd12909d16 100644 --- a/app/src/main/res/values/options_values.xml +++ b/app/src/main/res/values/options_values.xml @@ -85,6 +85,23 @@ 2 + + + Tray + Horizontal + Vertical + + + + + 0 + + 1 + + 2 + + + @string/privacy_options_tracking_etp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a9a25fecc..97ccfe7a2a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -503,6 +503,10 @@ customize if windows can be moved. Enabling it adds a Move button to the navigation bar. --> Drag to move windows + + Tabs location + Use Sound Effects