From b49f249413c429cdb68466ad6ed64b7fa4e4edae Mon Sep 17 00:00:00 2001 From: Songlin Jiang Date: Sun, 30 Jul 2023 23:08:16 +0300 Subject: [PATCH] Reorganize Libraries and add search UI in panels Create a separate location in the UI for each functionality - (Library) Bookmarks/History - Downloads - Add-ons - Web Apps Add launchers in the UI to open each of those locations - (Library) Bookmarks/History, Downloads and Web Apps from the tray - Add-ons from Hamburger Menu Add Search UI in: - Bookmarks - History - Downloads - Web Apps Signed-off-by: Songlin Jiang --- .../com/igalia/wolvic/VRBrowserActivity.java | 2 +- .../wolvic/addons/views/AddonsPanel.java | 149 +++++++++ .../wolvic/addons/views/AddonsView.java | 32 +- .../igalia/wolvic/browser/WebAppsStore.java | 11 + .../wolvic/ui/adapters/BookmarkAdapter.java | 21 +- .../com/igalia/wolvic/ui/adapters/WebApp.java | 14 + .../DownloadsContextMenuCallback.java | 2 +- .../wolvic/ui/viewmodel/WindowViewModel.java | 74 ++++- .../ui/views/downloads/DownloadsPanel.java | 178 +++++++++++ .../{library => downloads}/DownloadsView.java | 37 ++- .../ui/views/library/BookmarksView.java | 7 + .../wolvic/ui/views/library/HistoryView.java | 13 + .../wolvic/ui/views/library/LibraryPanel.java | 90 ++---- .../wolvic/ui/views/library/LibraryView.java | 5 + .../wolvic/ui/views/webapps/WebAppsPanel.java | 176 +++++++++++ .../{library => webapps}/WebAppsView.java | 38 ++- .../ui/widgets/NavigationBarWidget.java | 18 +- .../wolvic/ui/widgets/TrayListener.java | 2 + .../igalia/wolvic/ui/widgets/TrayWidget.java | 128 +++++++- .../wolvic/ui/widgets/WindowWidget.java | 294 +++++++++++++++--- .../com/igalia/wolvic/ui/widgets/Windows.java | 50 ++- .../DownloadsContextMenuWidget.java | 3 +- .../ui/widgets/menus/HamburgerMenuWidget.java | 2 +- .../library/LibraryContextMenuWidget.java | 6 +- .../res/drawable/ic_icon_webapps_clip.xml | 5 + .../drawable/tray_background_left_round.xml | 9 + .../drawable/tray_background_right_round.xml | 9 + app/src/main/res/layout/library.xml | 57 ++-- app/src/main/res/layout/navigation_url.xml | 12 +- app/src/main/res/layout/tray.xml | 134 ++++++-- app/src/main/res/values-en-rGB/strings.xml | 11 + app/src/main/res/values-zh-rCN/strings.xml | 8 + app/src/main/res/values-zh-rTW/strings.xml | 11 + app/src/main/res/values/dimen.xml | 4 +- app/src/main/res/values/strings.xml | 11 + 35 files changed, 1389 insertions(+), 234 deletions(-) create mode 100644 app/src/common/shared/com/igalia/wolvic/addons/views/AddonsPanel.java create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsPanel.java rename app/src/common/shared/com/igalia/wolvic/ui/views/{library => downloads}/DownloadsView.java (93%) create mode 100644 app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsPanel.java rename app/src/common/shared/com/igalia/wolvic/ui/views/{library => webapps}/WebAppsView.java (83%) rename app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/{library => }/DownloadsContextMenuWidget.java (93%) create mode 100644 app/src/main/res/drawable/ic_icon_webapps_clip.xml create mode 100644 app/src/main/res/drawable/tray_background_left_round.xml create mode 100644 app/src/main/res/drawable/tray_background_right_round.xml diff --git a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java index 4fdbfc65da..3a43d99a83 100644 --- a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java +++ b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java @@ -1150,7 +1150,7 @@ void handleMotionEvent(final int aHandle, final int aDevice, final boolean aFocu // We shouldn't divide the scale factor when we pass the motion event to the web engine if (widget instanceof WindowWidget) { WindowWidget windowWidget = (WindowWidget) widget; - if (!windowWidget.isLibraryVisible()) { + if (windowWidget.isInWebPage()) { scale = 1.0f; } } diff --git a/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsPanel.java b/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsPanel.java new file mode 100644 index 0000000000..99b53105a6 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsPanel.java @@ -0,0 +1,149 @@ +package com.igalia.wolvic.addons.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; + +import com.igalia.wolvic.BuildConfig; +import com.igalia.wolvic.R; +import com.igalia.wolvic.VRBrowserActivity; +import com.igalia.wolvic.VRBrowserApplication; +import com.igalia.wolvic.databinding.LibraryBinding; +import com.igalia.wolvic.ui.delegates.LibraryNavigationDelegate; +import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; + +import java.util.concurrent.Executor; + +public class AddonsPanel extends FrameLayout { + + private LibraryBinding mBinding; + protected WidgetManagerDelegate mWidgetManager; + protected Executor mUIThreadExecutor; + private AddonsView mAddonsView; + + public AddonsPanel(@NonNull Context context) { + super(context); + initialize(); + } + + public AddonsPanel(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public AddonsPanel(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + protected void initialize() { + mWidgetManager = ((VRBrowserActivity) getContext()); + mUIThreadExecutor = ((VRBrowserApplication) getContext().getApplicationContext()).getExecutors().mainThread(); + + mAddonsView = new AddonsView(getContext(), this); + + updateUI(); + } + + @SuppressLint("ClickableViewAccessibility") + public void updateUI() { + removeAllViews(); + + LayoutInflater inflater = LayoutInflater.from(getContext()); + + // Inflate this data binding layout + mBinding = DataBindingUtil.inflate(inflater, R.layout.library, this, true); + mBinding.searchBar.setVisibility(View.GONE); + mBinding.buttons.setVisibility(View.GONE); + mBinding.setLifecycleOwner((VRBrowserActivity) getContext()); + mBinding.setSupportsSystemNotifications(BuildConfig.SUPPORTS_SYSTEM_NOTIFICATIONS); + mBinding.setDelegate(new LibraryNavigationDelegate() { + @Override + public void onClose(@NonNull View view) { + requestFocus(); + mWidgetManager.getFocusedWindow().hideAddonsPanel(); + } + + @Override + public void onBack(@NonNull View view) { + requestFocus(); + mAddonsView.onBack(); + mBinding.setCanGoBack(mAddonsView.canGoBack()); + } + + @Override + public void onButtonClick(@NonNull View view) { + requestFocus(); + selectTab(); + } + }); + mBinding.executePendingBindings(); + + selectTab(); + + setOnTouchListener((v, event) -> { + v.requestFocusFromTouch(); + return false; + }); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (mBinding != null) { + mBinding.tabcontent.removeAllViews(); + } + + mAddonsView.updateUI(); + + updateUI(); + } + + public void onShow() { + if (mAddonsView != null) { + mAddonsView.onShow(); + mBinding.searchBar.setQuery("", false); + mBinding.searchBar.clearFocus(); + } + } + + public void onHide() { + if (mAddonsView != null) { + mAddonsView.onHide(); + } + } + + public boolean onBack() { + if (mAddonsView != null) { + return mAddonsView.onBack(); + } + + return false; + } + + public void onDestroy() { + mAddonsView.onDestroy(); + } + + private void selectTab() { + mBinding.setCanGoBack(mAddonsView.canGoBack()); + mBinding.tabcontent.addView(mAddonsView); + mAddonsView.onShow(); + } + + public void onViewUpdated(@NonNull String title) { + if (mBinding != null) { + mBinding.title.setText(title); + mBinding.setCanGoBack(mAddonsView.canGoBack()); + } + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsView.java b/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsView.java index 3a91b9f461..8cb919c23f 100644 --- a/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsView.java +++ b/app/src/common/shared/com/igalia/wolvic/addons/views/AddonsView.java @@ -12,7 +12,6 @@ import com.igalia.wolvic.addons.adapters.AddonsViewAdapter; import com.igalia.wolvic.addons.delegates.AddonsDelegate; import com.igalia.wolvic.databinding.AddonsBinding; -import com.igalia.wolvic.ui.views.library.LibraryPanel; import com.igalia.wolvic.ui.views.library.LibraryView; import com.igalia.wolvic.utils.SystemUtils; @@ -25,6 +24,7 @@ public class AddonsView extends LibraryView implements AddonsDelegate { private AddonsBinding mBinding; private AddonsViewAdapter mAdapter; + private AddonsPanel mAddonsPanel; public AddonsView(@NonNull Context context) { super(context); @@ -32,9 +32,9 @@ public AddonsView(@NonNull Context context) { initialize(); } - public AddonsView(@NonNull Context context, @NonNull LibraryPanel rootPanel) { - super(context, rootPanel); - + public AddonsView(@NonNull Context context, @NonNull AddonsPanel panel) { + super(context); + mAddonsPanel = panel; initialize(); } @@ -79,12 +79,12 @@ public void onShow() { updateLayout(); if (mBinding.pager.getCurrentItem() == AddonsViewAdapter.ADDONS_LEVEL_0) { - if (mRootPanel != null) { - mRootPanel.onViewUpdated(getContext().getString(R.string.addons_title)); + if (mAddonsPanel != null) { + mAddonsPanel.onViewUpdated(getContext().getString(R.string.addons_title)); } } else { - if (mRootPanel != null) { - mRootPanel.onViewUpdated(ExtensionsKt.translateName(mAdapter.getCurrentAddon(), getContext())); + if (mAddonsPanel != null) { + mAddonsPanel.onViewUpdated(ExtensionsKt.translateName(mAdapter.getCurrentAddon(), getContext())); } } } @@ -144,8 +144,8 @@ public void showAddonsList() { mAdapter.setCurrentAddon(null); mAdapter.setCurrentItem(AddonsViewAdapter.ADDONS_LIST); mBinding.pager.setCurrentItem(AddonsViewAdapter.ADDONS_LEVEL_0); - if (mRootPanel != null) { - mRootPanel.onViewUpdated(getContext().getString(R.string.addons_title)); + if (mAddonsPanel != null) { + mAddonsPanel.onViewUpdated(getContext().getString(R.string.addons_title)); } } @@ -154,8 +154,8 @@ public void showAddonOptions(@NonNull Addon addon) { mAdapter.setCurrentAddon(addon); mAdapter.setCurrentItem(AddonsViewAdapter.ADDON_OPTIONS); mBinding.pager.setCurrentItem(AddonsViewAdapter.ADDONS_LEVEL_1); - if (mRootPanel != null) { - mRootPanel.onViewUpdated(ExtensionsKt.translateName(addon, getContext())); + if (mAddonsPanel != null) { + mAddonsPanel.onViewUpdated(ExtensionsKt.translateName(addon, getContext())); } } @@ -164,8 +164,8 @@ public void showAddonOptionsDetails(@NonNull Addon addon, int page) { mAdapter.setCurrentAddon(addon); mAdapter.setCurrentItem(AddonsViewAdapter.ADDON_OPTIONS_DETAILS); mBinding.pager.setCurrentItem(page); - if (mRootPanel != null) { - mRootPanel.onViewUpdated(ExtensionsKt.translateName(addon, getContext())); + if (mAddonsPanel != null) { + mAddonsPanel.onViewUpdated(ExtensionsKt.translateName(addon, getContext())); } } @@ -174,8 +174,8 @@ public void showAddonOptionsPermissions(@NonNull Addon addon) { mAdapter.setCurrentAddon(addon); mAdapter.setCurrentItem(AddonsViewAdapter.ADDON_OPTIONS_PERMISSIONS); mBinding.pager.setCurrentItem(AddonsViewAdapter.ADDONS_LEVEL_2); - if (mRootPanel != null) { - mRootPanel.onViewUpdated(ExtensionsKt.translateName(addon, getContext())); + if (mAddonsPanel != null) { + mAddonsPanel.onViewUpdated(ExtensionsKt.translateName(addon, getContext())); } } diff --git a/app/src/common/shared/com/igalia/wolvic/browser/WebAppsStore.java b/app/src/common/shared/com/igalia/wolvic/browser/WebAppsStore.java index 9470f8190b..4b290ca06f 100644 --- a/app/src/common/shared/com/igalia/wolvic/browser/WebAppsStore.java +++ b/app/src/common/shared/com/igalia/wolvic/browser/WebAppsStore.java @@ -70,6 +70,17 @@ private void saveWebAppsListToStorage() { SettingsStore.getInstance(mContext).setWebAppsData(json); } + public void updateWebAppOpenTime(@NonNull String id) { + WebApp existingWebApp = mWebApps.get(id); + if (existingWebApp == null) { + return; + } + + existingWebApp.setLastOpenTime(); + saveWebAppsListToStorage(); + notifyListeners(); + } + /** * @return {@code true} if the map did not contain the specified Web app (so it was added), * and {@code false} if the Web app was already in the list (so it was updated). diff --git a/app/src/common/shared/com/igalia/wolvic/ui/adapters/BookmarkAdapter.java b/app/src/common/shared/com/igalia/wolvic/ui/adapters/BookmarkAdapter.java index ac271eff20..b72c89bef3 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/adapters/BookmarkAdapter.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/adapters/BookmarkAdapter.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import mozilla.appservices.places.BookmarkRoot; import mozilla.components.browser.icons.IconRequest; @@ -46,6 +47,7 @@ public class BookmarkAdapter extends RecyclerView.Adapter filterBookmarkList(List list) { + return list.stream() + .filter(value -> { + if (value.getTitle() != null && !mSearchFilter.isEmpty()) { + return value.getTitle().toLowerCase().contains(mSearchFilter); + } + return true; + }) + .collect(Collectors.toList()); + } + public void setBookmarkList(final List bookmarkList) { mBookmarksList = bookmarkList; List newDisplayList; if (mDisplayList == null || mDisplayList.isEmpty()) { newDisplayList = Bookmark.getDisplayListTree(mBookmarksList, Collections.singletonList(BookmarkRoot.Mobile.getId())); - mDisplayList = newDisplayList; + mDisplayList = filterBookmarkList(newDisplayList); for (Bookmark node : mDisplayList) { if (node.isExpanded()) { if (mBookmarkItemCallback != null) { @@ -86,7 +103,7 @@ public void setBookmarkList(final List bookmarkList) { } else { List openFoldersGuid = Bookmark.getOpenFoldersGuid(mDisplayList); - newDisplayList = Bookmark.getDisplayListTree(mBookmarksList, openFoldersGuid); + newDisplayList = filterBookmarkList(Bookmark.getDisplayListTree(mBookmarksList, openFoldersGuid)); notifyDiff(newDisplayList); } } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/adapters/WebApp.java b/app/src/common/shared/com/igalia/wolvic/ui/adapters/WebApp.java index 08c3424bcf..d6875e67c5 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/adapters/WebApp.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/adapters/WebApp.java @@ -25,6 +25,7 @@ */ public class WebApp { @NonNull private String mIdentity; + @NonNull private long mLastOpenTime; private WebAppManifest mManifest; private OptionalInt mHashCode = OptionalInt.empty(); @@ -54,6 +55,7 @@ public WebApp(JSONObject manifest) throws IOException { String id = manifest.optString("id"); URL identityUrl = new URL(startUrl.getProtocol(), startUrl.getHost(), startUrl.getPort(), id); mIdentity = identityUrl.toString(); + mLastOpenTime = 0; } else { // Since Identity is used to uniquely identify the Web application, // we treat its absence as an error. @@ -66,6 +68,16 @@ public String getId() { return mIdentity; } + @NonNull + public long getLastOpenTime() { + return mLastOpenTime; + } + + @NonNull + public void setLastOpenTime() { + mLastOpenTime = System.currentTimeMillis(); + } + public String getName() { return mManifest.getName(); } @@ -105,6 +117,7 @@ public List getIconResources() { public void copyFrom(WebApp webApp) { mIdentity = webApp.mIdentity; + mLastOpenTime = webApp.mLastOpenTime; mManifest = webApp.mManifest; mHashCode = OptionalInt.empty(); } @@ -133,6 +146,7 @@ public int hashCode() { public String toString() { return "WebApp{" + "mIdentity='" + mIdentity + '\'' + + ", mLastOpenTime='" + mLastOpenTime + '\'' + ", mName='" + getName() + '\'' + ", mShortName='" + getShortName() + '\'' + ", mScope='" + getScope() + '\'' + diff --git a/app/src/common/shared/com/igalia/wolvic/ui/callbacks/DownloadsContextMenuCallback.java b/app/src/common/shared/com/igalia/wolvic/ui/callbacks/DownloadsContextMenuCallback.java index 6a96cef91c..356c2d4c8a 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/callbacks/DownloadsContextMenuCallback.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/callbacks/DownloadsContextMenuCallback.java @@ -1,6 +1,6 @@ package com.igalia.wolvic.ui.callbacks; -import com.igalia.wolvic.ui.widgets.menus.library.DownloadsContextMenuWidget; +import com.igalia.wolvic.ui.widgets.menus.DownloadsContextMenuWidget; public interface DownloadsContextMenuCallback extends LibraryContextMenuCallback { void onDelete(DownloadsContextMenuWidget.DownloadsContextMenuItem item); diff --git a/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/WindowViewModel.java b/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/WindowViewModel.java index 7e0b3a2487..3bc42a45fe 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/WindowViewModel.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/viewmodel/WindowViewModel.java @@ -49,6 +49,9 @@ public class WindowViewModel extends AndroidViewModel { private MutableLiveData isActiveWindow; private MediatorLiveData isTitleBarVisible; private MutableLiveData isLibraryVisible; + private MutableLiveData isDownloadsVisible; + private MutableLiveData isWebAppsVisible; + private MutableLiveData isAddonsVisible; private MutableLiveData isLoading; private MutableLiveData isMicrophoneEnabled; private MutableLiveData isBookmarked; @@ -129,6 +132,9 @@ public WindowViewModel(Application application) { isTitleBarVisible.setValue(new ObservableBoolean(true)); isLibraryVisible = new MutableLiveData<>(new ObservableBoolean(false)); + isDownloadsVisible = new MutableLiveData<>(new ObservableBoolean(false)); + isWebAppsVisible = new MutableLiveData<>(new ObservableBoolean(false)); + isAddonsVisible = new MutableLiveData<>(new ObservableBoolean(false)); isLoading = new MutableLiveData<>(new ObservableBoolean(false)); isMicrophoneEnabled = new MutableLiveData<>(new ObservableBoolean(true)); @@ -152,6 +158,9 @@ public WindowViewModel(Application application) { isInsecureVisible.addSource(isInsecure, mIsInsecureVisibleObserver); isInsecureVisible.addSource(isPrivateSession, mIsInsecureVisibleObserver); isInsecureVisible.addSource(isLibraryVisible, mIsInsecureVisibleObserver); + isInsecureVisible.addSource(isDownloadsVisible, mIsInsecureVisibleObserver); + isInsecureVisible.addSource(isWebAppsVisible, mIsInsecureVisibleObserver); + isInsecureVisible.addSource(isAddonsVisible, mIsInsecureVisibleObserver); isInsecureVisible.setValue(new ObservableBoolean(false)); isMediaAvailable = new MutableLiveData<>(new ObservableBoolean(false)); @@ -173,6 +182,9 @@ public WindowViewModel(Application application) { isUrlBarButtonsVisible.addSource(isPopUpAvailable, mIsUrlBarButtonsVisibleObserver); isUrlBarButtonsVisible.addSource(isWebXRUsed, mIsUrlBarButtonsVisibleObserver); isUrlBarButtonsVisible.addSource(isLibraryVisible, mIsUrlBarButtonsVisibleObserver); + isUrlBarButtonsVisible.addSource(isDownloadsVisible, mIsUrlBarButtonsVisibleObserver); + isUrlBarButtonsVisible.addSource(isWebAppsVisible, mIsUrlBarButtonsVisibleObserver); + isUrlBarButtonsVisible.addSource(isAddonsVisible, mIsUrlBarButtonsVisibleObserver); isUrlBarButtonsVisible.addSource(isFocused, mIsUrlBarButtonsVisibleObserver); isUrlBarButtonsVisible.setValue(new ObservableBoolean(false)); @@ -231,6 +243,15 @@ public void onChanged(Spannable aUrl) { if (isLibraryVisible.getValue().get()) { url = getApplication().getString(R.string.url_library_title); + } else if (isDownloadsVisible.getValue().get()) { + url = getApplication().getString(R.string.url_downloads_title); + + } else if (isWebAppsVisible.getValue().get()) { + url = getApplication().getString(R.string.url_downloads_title); + + } else if (isAddonsVisible.getValue().get()) { + url = getApplication().getString(R.string.url_addons_title); + } else { if (UrlUtils.isPrivateAboutPage(getApplication(), url) || (UrlUtils.isDataUri(url) && isPrivateSession.getValue().get())) { @@ -261,6 +282,9 @@ public void onChanged(ObservableBoolean o) { UrlUtils.isFileUri(aUrl) || UrlUtils.isHomeUri(getApplication(), aUrl) || isLibraryVisible.getValue().get() || + isDownloadsVisible.getValue().get() || + isWebAppsVisible.getValue().get() || + isAddonsVisible.getValue().get() || UrlUtils.isBlankUri(getApplication(), aUrl)) { isInsecureVisible.postValue(new ObservableBoolean(false)); @@ -282,6 +306,9 @@ public void onChanged(Spannable aUrl) { (UrlUtils.isDataUri(url) && isPrivateSession.getValue().get()) || UrlUtils.isHomeUri(getApplication(), aUrl.toString()) || isLibraryVisible.getValue().get() || + isDownloadsVisible.getValue().get() || + isWebAppsVisible.getValue().get() || + isAddonsVisible.getValue().get() || UrlUtils.isBlankUri(getApplication(), aUrl.toString())) { navigationBarUrl.postValue(""); @@ -298,6 +325,9 @@ public void onChanged(ObservableBoolean o) { isUrlBarButtonsVisible.postValue(new ObservableBoolean( !isFocused.getValue().get() && !isLibraryVisible.getValue().get() && + !isDownloadsVisible.getValue().get() && + !isWebAppsVisible.getValue().get() && + !isAddonsVisible.getValue().get() && !UrlUtils.isContentFeed(getApplication(), aUrl) && !UrlUtils.isPrivateAboutPage(getApplication(), aUrl) && (URLUtil.isHttpUrl(aUrl) || URLUtil.isHttpsUrl(aUrl)) && @@ -317,6 +347,9 @@ public void onChanged(ObservableBoolean o) { public void onChanged(ObservableBoolean o) { isUrlBarIconsVisible.postValue(new ObservableBoolean( !isLibraryVisible.getValue().get() && + !isDownloadsVisible.getValue().get() && + !isWebAppsVisible.getValue().get() && + !isAddonsVisible.getValue().get() && (isLoading.getValue().get() || isInsecureVisible.getValue().get()) )); @@ -430,6 +463,15 @@ private String getHintValue() { if (isLibraryVisible.getValue().get()) { return getApplication().getString(R.string.url_library_title); + } else if (isDownloadsVisible.getValue().get()) { + return getApplication().getString(R.string.url_downloads_title); + + } else if (isWebAppsVisible.getValue().get()) { + return getApplication().getString(R.string.url_webapps_title); + + } else if (isAddonsVisible.getValue().get()) { + return getApplication().getString(R.string.url_addons_title); + } else { return getApplication().getString(R.string.search_placeholder); } @@ -568,6 +610,21 @@ public void setIsLibraryVisible(boolean isLibraryVisible) { this.url.postValue(this.getUrl().getValue()); } + public void setIsDownloadsVisible(boolean isDownloadsVisible) { + this.isDownloadsVisible.postValue(new ObservableBoolean(isDownloadsVisible)); + this.url.postValue(this.getUrl().getValue()); + } + + public void setIsWebAppsVisible(boolean isWebAppsVisible) { + this.isWebAppsVisible.postValue(new ObservableBoolean(isWebAppsVisible)); + this.url.postValue(this.getUrl().getValue()); + } + + public void setIsAddonsVisible(boolean isAddonsVisible) { + this.isAddonsVisible.postValue(new ObservableBoolean(isAddonsVisible)); + this.url.postValue(this.getUrl().getValue()); + } + public void setIsPanelVisible(boolean isVisible) { setIsLibraryVisible(isVisible); } @@ -577,6 +634,21 @@ public MutableLiveData getIsLibraryVisible() { return isLibraryVisible; } + @NonNull + public MutableLiveData getIsDownloadsVisible() { + return isDownloadsVisible; + } + + @NonNull + public MutableLiveData getIsWebAppsVisible() { + return isWebAppsVisible; + } + + @NonNull + public MutableLiveData getIsAddonsVisible() { + return isAddonsVisible; + } + @NonNull public MutableLiveData getIsLoading() { return isLoading; @@ -637,7 +709,7 @@ public MutableLiveData getIsFindInPage() { } public void setIsFindInPage(boolean isFindInPage) { - this.isFindInPage.postValue(new ObservableBoolean(isFindInPage)); + this.isFindInPage.setValue(new ObservableBoolean(isFindInPage)); } @NonNull diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsPanel.java b/app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsPanel.java new file mode 100644 index 0000000000..8dba3165f3 --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsPanel.java @@ -0,0 +1,178 @@ +package com.igalia.wolvic.ui.views.downloads; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.SearchView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; + +import com.igalia.wolvic.BuildConfig; +import com.igalia.wolvic.R; +import com.igalia.wolvic.VRBrowserActivity; +import com.igalia.wolvic.VRBrowserApplication; +import com.igalia.wolvic.addons.views.AddonsView; +import com.igalia.wolvic.databinding.LibraryBinding; +import com.igalia.wolvic.ui.delegates.LibraryNavigationDelegate; +import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; +import com.igalia.wolvic.ui.widgets.Windows; + +import java.util.concurrent.Executor; + +public class DownloadsPanel extends FrameLayout { + + private LibraryBinding mBinding; + protected WidgetManagerDelegate mWidgetManager; + protected Executor mUIThreadExecutor; + private DownloadsView mDownloadsView; + + public DownloadsPanel(@NonNull Context context) { + super(context); + initialize(); + } + + public DownloadsPanel(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public DownloadsPanel(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + protected void initialize() { + mWidgetManager = ((VRBrowserActivity) getContext()); + mUIThreadExecutor = ((VRBrowserApplication) getContext().getApplicationContext()).getExecutors().mainThread(); + + mDownloadsView = new DownloadsView(getContext(), this); + + updateUI(); + } + + @SuppressLint("ClickableViewAccessibility") + public void updateUI() { + removeAllViews(); + + LayoutInflater inflater = LayoutInflater.from(getContext()); + + // Inflate this data binding layout + mBinding = DataBindingUtil.inflate(inflater, R.layout.library, this, true); + mBinding.searchBar.setIconifiedByDefault(false); + mBinding.searchBar.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + mDownloadsView.updateSearchFilter(s.toLowerCase()); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + if (s.isEmpty()) { + mDownloadsView.updateSearchFilter(s.toLowerCase()); + return true; + } + return false; + } + }); + mBinding.searchBar.setOnCloseListener(() -> { + mDownloadsView.updateSearchFilter(""); + return true; + }); + mBinding.buttons.setVisibility(View.GONE); + mBinding.setLifecycleOwner((VRBrowserActivity) getContext()); + mBinding.setSupportsSystemNotifications(BuildConfig.SUPPORTS_SYSTEM_NOTIFICATIONS); + mBinding.setDelegate(new LibraryNavigationDelegate() { + @Override + public void onClose(@NonNull View view) { + requestFocus(); + mWidgetManager.getFocusedWindow().hideDownloadsPanel(); + } + + @Override + public void onBack(@NonNull View view) { + requestFocus(); + mDownloadsView.onBack(); + mBinding.setCanGoBack(mDownloadsView.canGoBack()); + } + + @Override + public void onButtonClick(@NonNull View view) { + requestFocus(); + selectTab(); + } + }); + mBinding.executePendingBindings(); + + selectTab(); + + setOnTouchListener((v, event) -> { + v.requestFocusFromTouch(); + return false; + }); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (mBinding != null) { + mBinding.tabcontent.removeAllViews(); + } + + mDownloadsView.updateUI(); + + updateUI(); + } + + public void onShow() { + if (mDownloadsView == null) { + return; + } + + mDownloadsView.onShow(); + } + + public void onHide() { + if (mDownloadsView == null) { + return; + } + + mDownloadsView.onHide(); + mBinding.searchBar.setQuery("", false); + mBinding.searchBar.clearFocus(); + } + + public boolean onBack() { + if (mDownloadsView == null) { + return false; + } + + return mDownloadsView.onBack(); + } + + public void onDestroy() { + mDownloadsView.onDestroy(); + } + + private void selectTab() { + mBinding.setCanGoBack(mDownloadsView.canGoBack()); + mBinding.tabcontent.addView(mDownloadsView); + mDownloadsView.onShow(); + } + + public void onViewUpdated(@NonNull String title) { + if (mBinding == null) { + return; + } + + mBinding.title.setText(title); + mBinding.setCanGoBack(mDownloadsView.canGoBack()); + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/library/DownloadsView.java b/app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsView.java similarity index 93% rename from app/src/common/shared/com/igalia/wolvic/ui/views/library/DownloadsView.java rename to app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsView.java index 3dd6316a01..beaafd7262 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/library/DownloadsView.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/downloads/DownloadsView.java @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -package com.igalia.wolvic.ui.views.library; +package com.igalia.wolvic.ui.views.downloads; import android.annotation.SuppressLint; import android.content.Context; @@ -33,11 +33,13 @@ import com.igalia.wolvic.ui.callbacks.DownloadsCallback; import com.igalia.wolvic.ui.callbacks.DownloadsContextMenuCallback; import com.igalia.wolvic.ui.viewmodel.DownloadsViewModel; +import com.igalia.wolvic.ui.views.library.LibraryPanel; +import com.igalia.wolvic.ui.views.library.LibraryView; import com.igalia.wolvic.ui.widgets.UIWidget; import com.igalia.wolvic.ui.widgets.WidgetPlacement; import com.igalia.wolvic.ui.widgets.WindowWidget; import com.igalia.wolvic.ui.widgets.dialogs.PromptDialogWidget; -import com.igalia.wolvic.ui.widgets.menus.library.DownloadsContextMenuWidget; +import com.igalia.wolvic.ui.widgets.menus.DownloadsContextMenuWidget; import com.igalia.wolvic.ui.widgets.menus.library.LibraryContextMenuWidget; import com.igalia.wolvic.ui.widgets.menus.library.SortingContextMenuWidget; import com.igalia.wolvic.utils.SystemUtils; @@ -58,9 +60,11 @@ public class DownloadsView extends LibraryView implements DownloadsManager.Downl private DownloadsManager mDownloadsManager; private Comparator mSortingComparator; private DownloadsViewModel mViewModel; + private DownloadsPanel mDownloadsPanel; - public DownloadsView(Context aContext, @NonNull LibraryPanel delegate) { - super(aContext, delegate); + public DownloadsView(Context aContext, @NonNull DownloadsPanel delegate) { + super(aContext); + mDownloadsPanel = delegate; initialize(); } @@ -117,6 +121,12 @@ public void updateUI() { }); } + @Override + public void updateSearchFilter(String s) { + super.updateSearchFilter(s); + onDownloadsUpdate(mDownloadsManager.getDownloads()); + }; + @Override public void onDestroy() { mBinding.downloadsList.removeOnScrollListener(mScrollListener); @@ -127,8 +137,8 @@ public void onShow() { mDownloadsManager.addListener(this); onDownloadsUpdate(mDownloadsManager.getDownloads()); updateLayout(); - if (mRootPanel != null) { - mRootPanel.onViewUpdated(getContext().getString(R.string.downloads_title)); + if (mDownloadsPanel != null) { + mDownloadsPanel.onViewUpdated(getContext().getString(R.string.downloads_title)); } } @@ -174,7 +184,7 @@ public void onClick(@NonNull View view, @NonNull Download item) { SessionStore.get().getActiveSession().loadUri(item.getOutputFileUriAsString()); WindowWidget window = mWidgetManager.getFocusedWindow(); - window.hidePanel(); + window.hideDownloadsPanel(); } } @@ -341,8 +351,8 @@ protected void showSortingContextMenu(@NonNull View view) { float ratio = WidgetPlacement.viewToWidgetRatio(getContext(), window); Rect offsetViewBounds = new Rect(); - mRootPanel.getDrawingRect(offsetViewBounds); - mRootPanel.offsetDescendantRectToMyCoords(view, offsetViewBounds); + mDownloadsPanel.getDrawingRect(offsetViewBounds); + mDownloadsPanel.offsetDescendantRectToMyCoords(view, offsetViewBounds); SortingContextMenuWidget menu = new SortingContextMenuWidget(getContext()); menu.setItemDelegate(item -> { @@ -432,7 +442,14 @@ public void onDownloadsUpdate(@NonNull List downloads) { } else { mViewModel.setIsEmpty(false); mViewModel.setIsLoading(false); - List sorted = downloads.stream().sorted(mSortingComparator).collect(Collectors.toList()); + List sorted = downloads.stream() + .filter(value -> { + if (value.getTitle() != null && !mSearchFilter.isEmpty()) { + return value.getTitle().toLowerCase().contains(mSearchFilter); + } + return true; + }) + .sorted(mSortingComparator).collect(Collectors.toList()); mDownloadsAdapter.setDownloadsList(sorted); } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/library/BookmarksView.java b/app/src/common/shared/com/igalia/wolvic/ui/views/library/BookmarksView.java index 0f2e433c0f..400f68ac6b 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/library/BookmarksView.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/library/BookmarksView.java @@ -145,6 +145,13 @@ public void updateUI() { }); } + @Override + public void updateSearchFilter(String s) { + super.updateSearchFilter(s); + mBookmarkAdapter.setSearchFilter(s); + updateBookmarks(); + }; + @Override public void onShow() { updateLayout(); diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/library/HistoryView.java b/app/src/common/shared/com/igalia/wolvic/ui/views/library/HistoryView.java index 5923dc41e9..e9363d6316 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/library/HistoryView.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/library/HistoryView.java @@ -150,6 +150,12 @@ public void updateUI() { }); } + @Override + public void updateSearchFilter(String s) { + super.updateSearchFilter(s); + updateHistory(); + }; + @Override public void onDestroy() { SessionStore.get().getHistoryStore().removeListener(this); @@ -385,6 +391,13 @@ private void updateHistory() { .sorted(Comparator.comparing(VisitInfo::getVisitTime) .reversed()) .filter(distinctByUrl(VisitInfo::getUrl)) + .filter(value -> { + if (value.getTitle() != null && !mSearchFilter.isEmpty()) { + return value.getTitle().toLowerCase().contains(mSearchFilter) || + value.getUrl().toLowerCase().contains(mSearchFilter); + } + return true; + }) .collect(Collectors.toList()); addSection(orderedItems, getResources().getString(R.string.history_section_today), Long.MAX_VALUE, todayLimit); diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryPanel.java b/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryPanel.java index 373067ad38..cf1832b188 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryPanel.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryPanel.java @@ -7,6 +7,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; +import android.widget.SearchView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,7 +17,6 @@ import com.igalia.wolvic.R; import com.igalia.wolvic.VRBrowserActivity; import com.igalia.wolvic.VRBrowserApplication; -import com.igalia.wolvic.addons.views.AddonsView; import com.igalia.wolvic.databinding.LibraryBinding; import com.igalia.wolvic.ui.delegates.LibraryNavigationDelegate; import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; @@ -29,11 +29,8 @@ public class LibraryPanel extends FrameLayout { private LibraryBinding mBinding; protected WidgetManagerDelegate mWidgetManager; protected Executor mUIThreadExecutor; - private WebAppsView mWebAppsView; private BookmarksView mBookmarksView; private HistoryView mHistoryView; - private DownloadsView mDownloadsView; - private AddonsView mAddonsView; private SystemNotificationsView mSystemNotificationsView; private LibraryView mCurrentView; private @Windows.PanelType int mCurrentPanel; @@ -57,11 +54,8 @@ protected void initialize() { mWidgetManager = ((VRBrowserActivity) getContext()); mUIThreadExecutor = ((VRBrowserApplication) getContext().getApplicationContext()).getExecutors().mainThread(); - mWebAppsView = new WebAppsView(getContext(), this); mBookmarksView = new BookmarksView(getContext(), this); mHistoryView = new HistoryView(getContext(), this); - mDownloadsView = new DownloadsView(getContext(), this); - mAddonsView = new AddonsView(getContext(), this); mSystemNotificationsView = new SystemNotificationsView(getContext(), this); mCurrentPanel = Windows.BOOKMARKS; @@ -76,6 +70,27 @@ public void updateUI() { // Inflate this data binding layout mBinding = DataBindingUtil.inflate(inflater, R.layout.library, this, true); + mBinding.searchBar.setIconifiedByDefault(false); + mBinding.searchBar.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + mCurrentView.updateSearchFilter(s.toLowerCase()); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + if (s.isEmpty()) { + mCurrentView.updateSearchFilter(s.toLowerCase()); + return true; + } + return false; + } + }); + mBinding.searchBar.setOnCloseListener(() -> { + mCurrentView.updateSearchFilter(""); + return true; + }); mBinding.setLifecycleOwner((VRBrowserActivity) getContext()); mBinding.setSupportsSystemNotifications(BuildConfig.SUPPORTS_SYSTEM_NOTIFICATIONS); mBinding.setDelegate(new LibraryNavigationDelegate() { @@ -118,9 +133,6 @@ public void onConfigurationChanged(Configuration newConfig) { mHistoryView.updateUI(); mBookmarksView.updateUI(); - mWebAppsView.updateUI(); - mDownloadsView.updateUI(); - mAddonsView.updateUI(); mSystemNotificationsView.updateUI(); updateUI(); @@ -129,6 +141,8 @@ public void onConfigurationChanged(Configuration newConfig) { public void onShow() { if (mCurrentView != null) { mCurrentView.onShow(); + mBinding.searchBar.setQuery("", false); + mBinding.searchBar.clearFocus(); } } @@ -149,9 +163,6 @@ public boolean onBack() { public void onDestroy() { mBookmarksView.onDestroy(); mHistoryView.onDestroy(); - mWebAppsView.onDestroy(); - mDownloadsView.onDestroy(); - mAddonsView.onDestroy(); mSystemNotificationsView.onDestroy(); } @@ -159,18 +170,9 @@ public void onDestroy() { if (mCurrentView == mBookmarksView) { return Windows.BOOKMARKS; - } else if (mCurrentView == mWebAppsView) { - return Windows.WEB_APPS; - } else if (mCurrentView == mHistoryView) { return Windows.HISTORY; - } else if (mCurrentView == mDownloadsView) { - return Windows.DOWNLOADS; - - } else if (mCurrentView == mAddonsView) { - return Windows.ADDONS; - } else if (mCurrentView == mSystemNotificationsView) { return Windows.NOTIFICATIONS; @@ -180,13 +182,14 @@ public void onDestroy() { } private void selectTab(@NonNull View view) { + if (mCurrentView != null) { + mCurrentView.updateSearchFilter(""); + } + mBinding.tabcontent.removeAllViews(); mBinding.bookmarks.setActiveMode(false); - mBinding.webApps.setActiveMode(false); mBinding.history.setActiveMode(false); - mBinding.downloads.setActiveMode(false); - mBinding.addons.setActiveMode(false); mBinding.notifications.setActiveMode(false); if(view.getId() == R.id.bookmarks){ selectBookmarks(); @@ -194,21 +197,15 @@ private void selectTab(@NonNull View view) { } else if(view.getId() == R.id.history){ selectHistory(); - } else if(view.getId() == R.id.downloads){ - selectDownloads(); - - } else if(view.getId() == R.id.addons){ - selectAddons(); - } else if (view.getId() == R.id.notifications) { selectNotifications(); - } else if (view.getId() == R.id.web_apps) { - selectWebApps(); } mBinding.setCanGoBack(mCurrentView.canGoBack()); mCurrentView.onShow(); + mBinding.searchBar.setQuery("", false); + mBinding.searchBar.clearFocus(); } public void selectPanel(@Windows.PanelType int panelType) { @@ -222,18 +219,9 @@ public void selectPanel(@Windows.PanelType int panelType) { case Windows.BOOKMARKS: selectTab(mBinding.bookmarks); break; - case Windows.WEB_APPS: - selectTab(mBinding.webApps); - break; case Windows.HISTORY: selectTab(mBinding.history); break; - case Windows.DOWNLOADS: - selectTab(mBinding.downloads); - break; - case Windows.ADDONS: - selectTab(mBinding.addons); - break; case Windows.NOTIFICATIONS: selectTab(mBinding.notifications); break; @@ -246,30 +234,12 @@ private void selectBookmarks() { mBinding.tabcontent.addView(mBookmarksView); } - private void selectWebApps() { - mCurrentView = mWebAppsView; - mBinding.webApps.setActiveMode(true); - mBinding.tabcontent.addView(mWebAppsView); - } - private void selectHistory() { mCurrentView = mHistoryView; mBinding.history.setActiveMode(true); mBinding.tabcontent.addView(mHistoryView); } - private void selectDownloads() { - mCurrentView = mDownloadsView; - mBinding.downloads.setActiveMode(true); - mBinding.tabcontent.addView(mDownloadsView); - } - - private void selectAddons() { - mCurrentView = mAddonsView; - mBinding.addons.setActiveMode(true); - mBinding.tabcontent.addView(mAddonsView); - } - private void selectNotifications() { mCurrentView = mSystemNotificationsView; mBinding.notifications.setActiveMode(true); diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryView.java b/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryView.java index 6f123e9bfb..8f1556b95d 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryView.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/library/LibraryView.java @@ -23,6 +23,7 @@ import java.util.concurrent.Executor; public abstract class LibraryView extends FrameLayout { + protected String mSearchFilter = ""; protected WidgetManagerDelegate mWidgetManager; protected LibraryContextMenuWidget mContextMenu; @@ -46,6 +47,10 @@ protected void initialize() { public void updateUI() {}; + public void updateSearchFilter(String s) { + mSearchFilter = s; + }; + public void onDestroy() {}; public void onShow() {} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsPanel.java b/app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsPanel.java new file mode 100644 index 0000000000..b32b9098bb --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsPanel.java @@ -0,0 +1,176 @@ +package com.igalia.wolvic.ui.views.webapps; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.SearchView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.DataBindingUtil; + +import com.igalia.wolvic.BuildConfig; +import com.igalia.wolvic.R; +import com.igalia.wolvic.VRBrowserActivity; +import com.igalia.wolvic.VRBrowserApplication; +import com.igalia.wolvic.databinding.LibraryBinding; +import com.igalia.wolvic.ui.delegates.LibraryNavigationDelegate; +import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; + +import java.util.concurrent.Executor; + +public class WebAppsPanel extends FrameLayout { + + private LibraryBinding mBinding; + protected WidgetManagerDelegate mWidgetManager; + protected Executor mUIThreadExecutor; + private WebAppsView mWebAppsView; + + public WebAppsPanel(@NonNull Context context) { + super(context); + initialize(); + } + + public WebAppsPanel(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public WebAppsPanel(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + protected void initialize() { + mWidgetManager = ((VRBrowserActivity) getContext()); + mUIThreadExecutor = ((VRBrowserApplication) getContext().getApplicationContext()).getExecutors().mainThread(); + + mWebAppsView = new WebAppsView(getContext(), this); + + updateUI(); + } + + @SuppressLint("ClickableViewAccessibility") + public void updateUI() { + removeAllViews(); + + LayoutInflater inflater = LayoutInflater.from(getContext()); + + // Inflate this data binding layout + mBinding = DataBindingUtil.inflate(inflater, R.layout.library, this, true); + mBinding.searchBar.setIconifiedByDefault(false); + mBinding.searchBar.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + mWebAppsView.updateSearchFilter(s.toLowerCase()); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + if (s.isEmpty()) { + mWebAppsView.updateSearchFilter(s.toLowerCase()); + return true; + } + return false; + } + }); + mBinding.searchBar.setOnCloseListener(() -> { + mWebAppsView.updateSearchFilter(""); + return true; + }); + mBinding.buttons.setVisibility(View.GONE); + mBinding.setLifecycleOwner((VRBrowserActivity) getContext()); + mBinding.setSupportsSystemNotifications(BuildConfig.SUPPORTS_SYSTEM_NOTIFICATIONS); + mBinding.setDelegate(new LibraryNavigationDelegate() { + @Override + public void onClose(@NonNull View view) { + requestFocus(); + mWidgetManager.getFocusedWindow().hideWebAppsPanel(); + } + + @Override + public void onBack(@NonNull View view) { + requestFocus(); + mWebAppsView.onBack(); + mBinding.setCanGoBack(mWebAppsView.canGoBack()); + } + + @Override + public void onButtonClick(@NonNull View view) { + requestFocus(); + selectTab(); + } + }); + mBinding.executePendingBindings(); + + selectTab(); + + setOnTouchListener((v, event) -> { + v.requestFocusFromTouch(); + return false; + }); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (mBinding != null) { + mBinding.tabcontent.removeAllViews(); + } + + mWebAppsView.updateUI(); + + updateUI(); + } + + public void onShow() { + if (mWebAppsView == null) { + return; + } + + mWebAppsView.onShow(); + mBinding.searchBar.setQuery("", false); + mBinding.searchBar.clearFocus(); + } + + public void onHide() { + if (mWebAppsView == null) { + return; + } + + mWebAppsView.onHide(); + } + + public boolean onBack() { + if (mWebAppsView == null) { + return false; + } + + return mWebAppsView.onBack(); + } + + public void onDestroy() { + mWebAppsView.onDestroy(); + } + + private void selectTab() { + mBinding.setCanGoBack(mWebAppsView.canGoBack()); + mBinding.tabcontent.addView(mWebAppsView); + mWebAppsView.onShow(); + } + + public void onViewUpdated(@NonNull String title) { + if (mBinding == null) { + return; + } + + mBinding.title.setText(title); + mBinding.setCanGoBack(mWebAppsView.canGoBack()); + } +} diff --git a/app/src/common/shared/com/igalia/wolvic/ui/views/library/WebAppsView.java b/app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsView.java similarity index 83% rename from app/src/common/shared/com/igalia/wolvic/ui/views/library/WebAppsView.java rename to app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsView.java index 1eeca3cb67..a6b117a3c0 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/views/library/WebAppsView.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/views/webapps/WebAppsView.java @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -package com.igalia.wolvic.ui.views.library; +package com.igalia.wolvic.ui.views.webapps; import android.annotation.SuppressLint; import android.content.Context; @@ -27,11 +27,13 @@ import com.igalia.wolvic.ui.adapters.WebApp; import com.igalia.wolvic.ui.adapters.WebAppsAdapter; import com.igalia.wolvic.ui.callbacks.WebAppItemCallback; +import com.igalia.wolvic.ui.views.library.LibraryView; import com.igalia.wolvic.ui.viewmodel.WebAppsViewModel; import com.igalia.wolvic.ui.widgets.WindowWidget; import com.igalia.wolvic.utils.SystemUtils; import java.util.List; +import java.util.stream.Collectors; public class WebAppsView extends LibraryView implements WebAppsStore.WebAppsListener { @@ -41,9 +43,11 @@ public class WebAppsView extends LibraryView implements WebAppsStore.WebAppsList private WebAppsAdapter mWebAppsAdapter; private WebAppsViewModel mViewModel; private Handler mHandler; + private WebAppsPanel mWebAppsPanel; - public WebAppsView(Context aContext, @NonNull LibraryPanel delegate) { - super(aContext, delegate); + public WebAppsView(Context aContext, @NonNull WebAppsPanel delegate) { + super(aContext); + mWebAppsPanel = delegate; initialize(); } @@ -90,8 +94,7 @@ public void updateUI() { mViewModel.setIsNarrow(false); mViewModel.setIsLoading(true); - List webApps = SessionStore.get().getWebAppsStore().getWebApps(); - setWebApps(webApps); + updateWebApps(); setOnTouchListener((v, event) -> { v.requestFocusFromTouch(); @@ -99,6 +102,25 @@ public void updateUI() { }); } + @Override + public void updateSearchFilter(String s) { + super.updateSearchFilter(s); + updateWebApps(); + }; + + private void updateWebApps() { + List webApps = SessionStore.get().getWebAppsStore() + .getWebApps().stream() + .filter(value -> { + if (value.getName() != null && !mSearchFilter.isEmpty()) { + return value.getName().toLowerCase().contains(mSearchFilter) || + value.getStartUrl().toLowerCase().contains(mSearchFilter); + } + return true; + }) + .collect(Collectors.toList()); + setWebApps(webApps); + } // WebAppsStore.WebAppsListener @Override @@ -120,8 +142,8 @@ private void setWebApps(@NonNull List webApps) { public void onShow() { updateLayout(); mBinding.webAppsList.smoothScrollToPosition(0); - if (mRootPanel != null) { - mRootPanel.onViewUpdated(getContext().getString(R.string.web_apps_title)); + if (mWebAppsPanel != null) { + mWebAppsPanel.onViewUpdated(getContext().getString(R.string.web_apps_title)); } } @@ -140,6 +162,8 @@ public void onClick(@NonNull View view, @NonNull WebApp item) { Session session = SessionStore.get().getActiveSession(); session.loadUri(item.getStartUrl()); + SessionStore.get().getWebAppsStore().updateWebAppOpenTime(item.getId()); + WindowWidget window = mWidgetManager.getFocusedWindow(); window.hidePanel(); } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/NavigationBarWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/NavigationBarWidget.java index 72a9c2b5b5..b729024058 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/NavigationBarWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/NavigationBarWidget.java @@ -1301,6 +1301,16 @@ public void onLibraryClicked() { } } + @Override + public void onDownloadsClicked() { + onLibraryClicked(); + } + + @Override + public void onWebAppsClicked() { + onLibraryClicked(); + } + private void finishWidgetResize() { mWidgetManager.finishWidgetResize(mAttachedWindow); } @@ -1335,7 +1345,7 @@ public void onSendTab() { @Override public void onFindInPage() { hideMenu(); - mAttachedWindow.hidePanel(); + mAttachedWindow.hideAllPanel(); mViewModel.setIsFindInPage(true); } @@ -1379,11 +1389,11 @@ public void onSwitchMode() { public void onAddons() { hideMenu(); - if (!mAttachedWindow.isLibraryVisible()) { - mAttachedWindow.switchPanel(Windows.ADDONS); + if (!mAttachedWindow.isAddonsVisible()) { + mAttachedWindow.switchAddonsPanel(); } else if (mAttachedWindow.getSelectedPanel() != Windows.ADDONS) { - mAttachedWindow.showPanel(Windows.ADDONS); + mAttachedWindow.showAddonsPanel(); } } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayListener.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayListener.java index ea7d96861a..8416e390c6 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayListener.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/TrayListener.java @@ -5,4 +5,6 @@ default void onPrivateBrowsingClicked() {} default void onAddWindowClicked() {} default void onTabsClicked() {} default void onLibraryClicked() {} + default void onDownloadsClicked() {} + default void onWebAppsClicked() {} } 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 6630e46e5c..856bd4f650 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 @@ -42,11 +42,13 @@ import com.igalia.wolvic.audio.AudioEngine; import com.igalia.wolvic.browser.BookmarksStore; import com.igalia.wolvic.browser.SettingsStore; +import com.igalia.wolvic.browser.WebAppsStore; import com.igalia.wolvic.browser.engine.Session; import com.igalia.wolvic.browser.engine.SessionStore; import com.igalia.wolvic.databinding.TrayBinding; import com.igalia.wolvic.downloads.Download; import com.igalia.wolvic.downloads.DownloadsManager; +import com.igalia.wolvic.ui.adapters.WebApp; import com.igalia.wolvic.ui.viewmodel.TrayViewModel; import com.igalia.wolvic.ui.viewmodel.WindowViewModel; import com.igalia.wolvic.ui.views.UIButton; @@ -63,6 +65,9 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; + +import mozilla.components.browser.icons.IconRequest; public class TrayWidget extends UIWidget implements WidgetManagerDelegate.UpdateListener, DownloadsManager.DownloadsListener, ConnectivityReceiver.Delegate { @@ -97,6 +102,7 @@ public class TrayWidget extends UIWidget implements WidgetManagerDelegate.Update private int mLeftControllerBatteryLevel; private int mRightControllerBatteryLevel; private ConnectivityReceiver mConnectivityReceived; + private WebAppsStore.WebAppsListener mWebAppsListener; public TrayWidget(Context aContext) { super(aContext); @@ -228,6 +234,26 @@ public void updateUI() { view.requestFocusFromTouch(); }); + mBinding.downloadsButton.setOnHoverListener(mButtonScaleHoverListener); + mBinding.downloadsButton.setOnClickListener(view -> { + if (mAudio != null) { + mAudio.playSound(AudioEngine.Sound.CLICK); + } + + notifyDownloadsClicked(); + view.requestFocusFromTouch(); + }); + + mBinding.webAppsButton.setOnHoverListener(mButtonScaleHoverListener); + mBinding.webAppsButton.setOnClickListener(view -> { + if (mAudio != null) { + mAudio.playSound(AudioEngine.Sound.CLICK); + } + + notifyWebAppsClicked(); + view.requestFocusFromTouch(); + }); + mBinding.wifi.setOnHoverListener((view, motionEvent) -> { if (motionEvent.getAction() == MotionEvent.ACTION_HOVER_ENTER) { NotificationManager.Notification notification = new NotificationManager.Builder(TrayWidget.this) @@ -352,6 +378,7 @@ public void updateUI() { updateTime(); updateWifi(); + updateWebApps(); } public void start(Context context) { @@ -419,6 +446,55 @@ public void onConfigurationChanged(Configuration newConfig) { return false; }; + public void updateWebApps() { + List webApps = SessionStore.get().getWebAppsStore() + .getWebApps().stream().sorted((a, b) -> { + if (a.getLastOpenTime() == b.getLastOpenTime()) { + return 0; + } + + return a.getLastOpenTime() > b.getLastOpenTime() ? -1 : 1; + }) + .limit(3).collect(Collectors.toList()); + setWebApps(webApps); + } + + private void setWebApp(@NonNull UIButton view, @NonNull WebApp webApp) { + view.setVisibility(View.VISIBLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + view.setTooltipText(webApp.getName()); + } + view.setOnHoverListener(mButtonScaleHoverListener); + SessionStore sessionStore = SessionStore.get(); + sessionStore.getBrowserIcons().loadIntoView(view, + webApp.getStartUrl(), webApp.getIconResources(), IconRequest.Size.LAUNCHER); + view.setOnClickListener(v -> { + Session session = sessionStore.getActiveSession(); + session.loadUri(webApp.getStartUrl()); + + SessionStore.get().getWebAppsStore().updateWebAppOpenTime(webApp.getId()); + + WindowWidget window = mWidgetManager.getFocusedWindow(); + window.hidePanel(); + }); + } + + private void setWebApps(@NonNull List webApps) { + mBinding.webApp1.setVisibility(View.GONE); + mBinding.webApp2.setVisibility(View.GONE); + mBinding.webApp3.setVisibility(View.GONE); + switch (webApps.size()) { + default: + setWebApp(mBinding.webApp3, webApps.get(2)); + case 2: + setWebApp(mBinding.webApp2, webApps.get(1)); + case 1: + setWebApp(mBinding.webApp1, webApps.get(0)); + case 0: + break; + } + } + private void animateViewPadding(View view, int paddingStart, int paddingEnd, int duration) { if (view.isPressed() || !mIsWindowAttached) { view.setPadding(paddingEnd, paddingEnd, paddingEnd, paddingEnd); @@ -492,6 +568,16 @@ private void notifyLibraryClicked() { mTrayListeners.forEach(TrayListener::onLibraryClicked); } + private void notifyDownloadsClicked() { + hideNotifications(); + mTrayListeners.forEach(TrayListener::onDownloadsClicked); + } + + private void notifyWebAppsClicked() { + hideNotifications(); + mTrayListeners.forEach(TrayListener::onWebAppsClicked); + } + @Override protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { Context context = getContext(); @@ -575,10 +661,17 @@ public void detachFromWindow() { if (mViewModel != null) { mViewModel.getIsLibraryVisible().removeObserver(mIsLibraryVisible); + mViewModel.getIsDownloadsVisible().removeObserver(mIsDownloadsVisible); + mViewModel.getIsWebAppsVisible().removeObserver(mIsWebAppsVisible); mViewModel.getIsPrivateSession().removeObserver(mIsPrivateSession); mViewModel = null; } + if (mWebAppsListener != null) { + SessionStore.get().getWebAppsStore().removeListener(mWebAppsListener); + mWebAppsListener = null; + } + mIsWindowAttached = false; } @@ -598,12 +691,17 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { ViewModelProvider.AndroidViewModelFactory.getInstance(((VRBrowserActivity) getContext()).getApplication())) .get(String.valueOf(mAttachedWindow.hashCode()), WindowViewModel.class); mViewModel.getIsLibraryVisible().observe((VRBrowserActivity)getContext(), mIsLibraryVisible); + mViewModel.getIsDownloadsVisible().observe((VRBrowserActivity)getContext(), mIsDownloadsVisible); + mViewModel.getIsWebAppsVisible().observe((VRBrowserActivity)getContext(), mIsWebAppsVisible); mViewModel.getIsPrivateSession().observe((VRBrowserActivity)getContext(), mIsPrivateSession); mBinding.setViewmodel(mViewModel); SessionStore.get().getBookmarkStore().addListener(mBookmarksListener); + mWebAppsListener = webApps -> updateWebApps(); + SessionStore.get().getWebAppsStore().addListener(mWebAppsListener); + mIsWindowAttached = true; } @@ -618,6 +716,28 @@ public void attachToWindow(@NonNull WindowWidget aWindow) { } }; + private Observer mIsDownloadsVisible = aBoolean -> { + if (mBinding.downloadsButton.isHovered()) { + return; + } + if (aBoolean.get()) { + animateViewPadding(mBinding.downloadsButton, mMaxPadding, mMinPadding, ICON_ANIMATION_DURATION); + } else { + animateViewPadding(mBinding.downloadsButton, mMinPadding, mMaxPadding, ICON_ANIMATION_DURATION); + } + }; + + private Observer mIsWebAppsVisible = aBoolean -> { + if (mBinding.webAppsButton.isHovered()) { + return; + } + if (aBoolean.get()) { + animateViewPadding(mBinding.webAppsButton, mMaxPadding, mMinPadding, ICON_ANIMATION_DURATION); + } else { + animateViewPadding(mBinding.webAppsButton, mMinPadding, mMaxPadding, ICON_ANIMATION_DURATION); + } + }; + private Observer mIsPrivateSession = aBoolean -> { if (mBinding.privateButton.isHovered() || mViewModel.getIsPrivateSession().getValue().get() == aBoolean.get()) { return; @@ -684,12 +804,12 @@ public void showBookmarkAddedNotification() { } public void showWebAppAddedNotification() { - showNotification(WEB_APP_ADDED_NOTIFICATION_ID, mBinding.libraryButton, R.string.web_apps_saved_notification); + showNotification(WEB_APP_ADDED_NOTIFICATION_ID, mBinding.webAppsButton, R.string.web_apps_saved_notification); } public void showDownloadCompletedNotification(String filename) { showNotification(DOWNLOAD_COMPLETED_NOTIFICATION_ID, - mBinding.libraryButton, + mBinding.downloadsButton, getContext().getString(R.string.download_completed_notification, filename)); } @@ -744,7 +864,7 @@ public void onDownloadsUpdate(@NonNull List downloads) { long inProgressNum = downloads.stream().filter(item -> item.inProgress()).count(); mTrayViewModel.setDownloadsNumber((int)inProgressNum); if (inProgressNum == 0) { - mBinding.libraryButton.setLevel(0); + mBinding.downloadsButton.setLevel(0); } else { long size = downloads.stream() @@ -756,7 +876,7 @@ public void onDownloadsUpdate(@NonNull List downloads) { .sum(); if (size > 0) { long percent = downloaded*100/size; - mBinding.libraryButton.setLevel((int)percent*100); + mBinding.downloadsButton.setLevel((int)percent*100); } } } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/WindowWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/WindowWidget.java index e816af834b..bbfa8684d4 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/WindowWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/WindowWidget.java @@ -41,6 +41,8 @@ import com.igalia.wolvic.R; import com.igalia.wolvic.VRBrowserActivity; import com.igalia.wolvic.VRBrowserApplication; +import com.igalia.wolvic.addons.views.AddonsPanel; +import com.igalia.wolvic.browser.Addons; import com.igalia.wolvic.browser.BookmarksStore; import com.igalia.wolvic.browser.Media; import com.igalia.wolvic.browser.PromptDelegate; @@ -62,7 +64,9 @@ import com.igalia.wolvic.telemetry.TelemetryService; import com.igalia.wolvic.ui.adapters.WebApp; import com.igalia.wolvic.ui.viewmodel.WindowViewModel; +import com.igalia.wolvic.ui.views.downloads.DownloadsPanel; import com.igalia.wolvic.ui.views.library.LibraryPanel; +import com.igalia.wolvic.ui.views.webapps.WebAppsPanel; import com.igalia.wolvic.ui.widgets.dialogs.PromptDialogWidget; import com.igalia.wolvic.ui.widgets.dialogs.SelectionActionWidget; import com.igalia.wolvic.ui.widgets.menus.ContextMenuWidget; @@ -134,6 +138,9 @@ public class WindowWidget extends UIWidget implements SessionChangeListener, private Session mSession; private int mWindowId; private LibraryPanel mLibrary; + private DownloadsPanel mDownloads; + private WebAppsPanel mWebApps; + private AddonsPanel mAddons; private Windows.WindowPlacement mWindowPlacement = Windows.WindowPlacement.FRONT; private Windows.WindowPlacement mWindowPlacementBeforeFullscreen = Windows.WindowPlacement.FRONT; private float mMaxWindowScale = 3; @@ -217,6 +224,9 @@ private void initialize(Context aContext) { setupListeners(mSession); mLibrary = new LibraryPanel(aContext); + mDownloads = new DownloadsPanel(aContext); + mWebApps = new WebAppsPanel(aContext); + mAddons = new AddonsPanel(aContext); SessionStore.get().getBookmarkStore().addListener(mBookmarksListener); @@ -335,6 +345,21 @@ protected void onDismiss() { hidePanel(); } + } else if (mViewModel.getIsDownloadsVisible().getValue().get()) { + if (!mDownloads.onBack()) { + hideDownloadsPanel(); + } + + } else if (mViewModel.getIsWebAppsVisible().getValue().get()) { + if (!mWebApps.onBack()) { + hideWebAppsPanel(); + } + + } else if (mViewModel.getIsAddonsVisible().getValue().get()) { + if (!mAddons.onBack()) { + hideAddonsPanel(); + } + } else { if (mSession.canGoBack()) { mSession.goBack(); @@ -366,6 +391,9 @@ public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mLibrary.onConfigurationChanged(newConfig); + mDownloads.onConfigurationChanged(newConfig); + mWebApps.onConfigurationChanged(newConfig); + mAddons.onConfigurationChanged(newConfig); mViewModel.refresh(); } @@ -375,6 +403,9 @@ public void close() { hideContextMenus(); releaseWidget(); mLibrary.onDestroy(); + mDownloads.onDestroy(); + mWebApps.onDestroy(); + mAddons.onDestroy(); mViewModel.setIsTopBarVisible(false); mViewModel.setIsTitleBarVisible(false); SessionStore.get().destroySession(mSession); @@ -461,6 +492,7 @@ private void unsetView(View view, boolean switchSurface) { removeView(view); view.setVisibility(GONE); + float prevDensity = mWidgetPlacement.density; if (switchSurface) { setWillNotDraw(true); if (mTexture != null) { @@ -472,11 +504,12 @@ private void unsetView(View view, boolean switchSurface) { } mSurface = new Surface(mTexture); } - float prevDensity = mWidgetPlacement.density; mWidgetPlacement.density = getBrowserDensity(); - mWidgetManager.updateWidget(WindowWidget.this); - mWidgetManager.popWorldBrightness(WindowWidget.this); - mWidgetManager.popBackHandler(mBackHandler); + } + mWidgetManager.updateWidget(WindowWidget.this); + mWidgetManager.popWorldBrightness(WindowWidget.this); + mWidgetManager.popBackHandler(mBackHandler); + if (switchSurface) { if (mTexture != null) { resumeCompositor(); } @@ -489,6 +522,22 @@ public boolean isLibraryVisible() { return mViewModel.getIsLibraryVisible().getValue().get(); } + public boolean isDownloadsVisible() { + return mViewModel.getIsDownloadsVisible().getValue().get(); + } + + public boolean isWebAppsVisible() { + return mViewModel.getIsWebAppsVisible().getValue().get(); + } + + public boolean isAddonsVisible() { + return mViewModel.getIsAddonsVisible().getValue().get(); + } + + public boolean isInWebPage() { + return !(isLibraryVisible() || isDownloadsVisible() || isWebAppsVisible() || isAddonsVisible() || isCurrentUriBlank()); + } + public int getWindowWidth() { return mWidgetPlacement.width; } @@ -502,9 +551,22 @@ int getSelectedPanel() { return mLibrary.getSelectedPanelType(); } - private void hideLibraryPanel() { + public void hideAllPanel() { + hideAllPanel(true); + } + + private void hideAllPanel(boolean switchSurface) { if (mViewModel.getIsLibraryVisible().getValue().get()) { - hidePanel(true); + hidePanel(switchSurface); + } + if (mViewModel.getIsDownloadsVisible().getValue().get()) { + hideDownloadsPanel(switchSurface); + } + if (mViewModel.getIsWebAppsVisible().getValue().get()) { + hideWebAppsPanel(switchSurface); + } + if (mViewModel.getIsAddonsVisible().getValue().get()) { + hideAddonsPanel(switchSurface); } } @@ -513,39 +575,90 @@ public void switchPanel(@Windows.PanelType int panelType) { hidePanel(true); } else { + hideAllPanel(false); showPanel(panelType, true); + mViewModel.refresh(); + } + } + + public void switchDownloadsPanel() { + if (mViewModel.getIsDownloadsVisible().getValue().get()) { + hideDownloadsPanel(true); + + } else { + hideAllPanel(false); + showDownloadsPanel(true); + mViewModel.refresh(); + } + } + + public void switchWebAppsPanel() { + if (mViewModel.getIsWebAppsVisible().getValue().get()) { + hideWebAppsPanel(true); + + } else { + hideAllPanel(false); + showWebAppsPanel(true); + mViewModel.refresh(); + } + } + + public void switchAddonsPanel() { + if (mViewModel.getIsAddonsVisible().getValue().get()) { + hideAddonsPanel(true); + + } else { + hideAllPanel(false); + showAddonsPanel(true); + mViewModel.refresh(); } } Runnable mRestoreFirstPaint; + private void showPanelCommonAction() { + if (mRestoreFirstPaint != null || isFirstPaintReady() || + mFirstDrawCallback == null || mSurface == null) { + return; + } + + final Runnable firstDrawCallback = mFirstDrawCallback; + onFirstContentfulPaint(mSession.getWSession()); + mRestoreFirstPaint = () -> { + setFirstPaintReady(false); + setFirstDrawCallback(firstDrawCallback); + if (mWidgetManager != null) { + mWidgetManager.updateWidget(WindowWidget.this); + } + }; + } + + private void hidePanelCommonAction() { + if (mRestoreFirstPaint != null) { + mRestoreFirstPaint.run(); + mRestoreFirstPaint = null; + } + } + public void showPanel(@Windows.PanelType int panelType) { showPanel(panelType, true); } private void showPanel(@Windows.PanelType int panelType, boolean switchSurface) { - if (mLibrary != null) { - if (mView == null) { - setView(mLibrary, switchSurface); - mLibrary.selectPanel(panelType); - mLibrary.onShow(); - mViewModel.setIsFindInPage(false); - mViewModel.setIsPanelVisible(true); - if (mRestoreFirstPaint == null && !isFirstPaintReady() && (mFirstDrawCallback != null) && (mSurface != null)) { - final Runnable firstDrawCallback = mFirstDrawCallback; - onFirstContentfulPaint(mSession.getWSession()); - mRestoreFirstPaint = () -> { - setFirstPaintReady(false); - setFirstDrawCallback(firstDrawCallback); - if (mWidgetManager != null) { - mWidgetManager.updateWidget(WindowWidget.this); - } - }; - } + if (mLibrary == null) { + return; + } - } else if (mView == mLibrary) { - mLibrary.selectPanel(panelType); - } + if (mView == null) { + setView(mLibrary, switchSurface); + mLibrary.selectPanel(panelType); + mLibrary.onShow(); + mViewModel.setIsFindInPage(false); + mViewModel.setIsPanelVisible(true); + showPanelCommonAction(); + + } else if (mView == mLibrary) { + mLibrary.selectPanel(panelType); } } @@ -559,9 +672,101 @@ private void hidePanel(boolean switchSurface) { mLibrary.onHide(); mViewModel.setIsPanelVisible(false); } - if (switchSurface && mRestoreFirstPaint != null) { - mRestoreFirstPaint.run(); - mRestoreFirstPaint = null; + if (switchSurface) { + hidePanelCommonAction(); + } + } + + public void showDownloadsPanel() { + showDownloadsPanel(true); + } + + private void showDownloadsPanel(boolean switchSurface) { + if (mDownloads == null || mView != null) { + return; + } + + setView(mDownloads, switchSurface); + mDownloads.onShow(); + mViewModel.setIsFindInPage(false); + mViewModel.setIsDownloadsVisible(true); + showPanelCommonAction(); + } + + public void hideDownloadsPanel() { + hideDownloadsPanel(true); + } + + private void hideDownloadsPanel(boolean switchSurface) { + if (mView != null && mDownloads != null) { + unsetView(mDownloads, switchSurface); + mDownloads.onHide(); + mViewModel.setIsDownloadsVisible(false); + } + if (switchSurface) { + hidePanelCommonAction(); + } + } + + public void showWebAppsPanel() { + showWebAppsPanel(true); + } + + private void showWebAppsPanel(boolean switchSurface) { + if (mWebApps == null || mView != null) { + return; + } + + setView(mWebApps, switchSurface); + mWebApps.onShow(); + mViewModel.setIsFindInPage(false); + mViewModel.setIsWebAppsVisible(true); + showPanelCommonAction(); + } + + public void hideWebAppsPanel() { + hideWebAppsPanel(true); + } + + private void hideWebAppsPanel(boolean switchSurface) { + if (mView != null && mWebApps != null) { + unsetView(mWebApps, switchSurface); + mWebApps.onHide(); + mViewModel.setIsWebAppsVisible(false); + } + if (switchSurface) { + hidePanelCommonAction(); + } + } + + public void showAddonsPanel() { + showAddonsPanel(true); + } + + private void showAddonsPanel(boolean switchSurface) { + if (mAddons == null || mView != null) { + return; + } + + setView(mAddons, switchSurface); + mAddons.onShow(); + mViewModel.setIsFindInPage(false); + mViewModel.setIsAddonsVisible(true); + showPanelCommonAction(); + } + + public void hideAddonsPanel() { + hideAddonsPanel(true); + } + + private void hideAddonsPanel(boolean switchSurface) { + if (mView != null && mAddons != null) { + unsetView(mAddons, switchSurface); + mAddons.onHide(); + mViewModel.setIsAddonsVisible(false); + } + if (switchSurface) { + hidePanelCommonAction(); } } @@ -1105,16 +1310,14 @@ public void setVisible(boolean aVisible) { } } mWidgetPlacement.visible = aVisible; - if (!aVisible) { - if (mViewModel.getIsLibraryVisible().getValue().get()) { + if (!isInWebPage()) { + if (!aVisible) { mWidgetManager.popWorldBrightness(this); - } - - } else { - if (mViewModel.getIsLibraryVisible().getValue().get()) { + } else { mWidgetManager.pushWorldBrightness(this, WidgetManagerDelegate.DEFAULT_DIM_BRIGHTNESS); } } + mWidgetManager.updateWidget(this); if (!aVisible) { clearFocus(); @@ -1178,7 +1381,7 @@ public void setSession(@NonNull Session aSession, @OldSessionDisplayAction int a mCaptureOnPageStop = false; if (hidePanel) { - hideLibraryPanel(); + hideAllPanel(true); } } @@ -1609,17 +1812,17 @@ private int getWindowWidth(float aWorldWidth) { private NavigationBarWidget.NavigationListener mNavigationBarListener = new NavigationBarWidget.NavigationListener() { @Override public void onBack() { - hideLibraryPanel(); + hideAllPanel(true); } @Override public void onForward() { - hideLibraryPanel(); + hideAllPanel(true); } @Override public void onReload() { - hideLibraryPanel(); + hideAllPanel(true); } @Override @@ -1629,7 +1832,7 @@ public void onStop() { @Override public void onHome() { - hideLibraryPanel(); + hideAllPanel(true); } }; @@ -1981,6 +2184,7 @@ WResult onLoadRequest(WSession aSession, @NonNull LoadRequest aReq Uri uri = Uri.parse(aRequest.uri); if (UrlUtils.isAboutPage(uri.toString())) { + hideAllPanel(false); if(UrlUtils.isBookmarksUrl(uri.toString())) { showPanel(Windows.BOOKMARKS); @@ -1988,17 +2192,17 @@ WResult onLoadRequest(WSession aSession, @NonNull LoadRequest aReq showPanel(Windows.HISTORY); } else if (UrlUtils.isDownloadsUrl(uri.toString())) { - showPanel(Windows.DOWNLOADS); + showDownloadsPanel(); } else if (UrlUtils.isAddonsUrl(uri.toString())) { - showPanel(Windows.ADDONS); + showAddonsPanel(); } else { - hideLibraryPanel(); + hidePanelCommonAction(); } } else { - hideLibraryPanel(); + hideAllPanel(true); } if ("file".equalsIgnoreCase(uri.getScheme())) { 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 2e258db486..bd73b2a3f7 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 @@ -376,6 +376,8 @@ public void closeWindow(@NonNull WindowWidget aWindow) { WindowWidget rightWindow = getRightWindow(); aWindow.hidePanel(); + aWindow.hideDownloadsPanel(); + aWindow.hideWebAppsPanel(); if (leftWindow == aWindow) { removeWindow(leftWindow); @@ -601,12 +603,37 @@ public void exitImmersiveMode() { } } + private void closeAllPanelsInFocusedWindowIfNeeded() { + closeLibraryPanelInFocusedWindowIfNeeded(); + closeDownloadsPanelInFocusedWindowIfNeeded(); + closeWebAppsPanelInFocusedWindowIfNeeded(); + closeAddonsPanelInFocusedWindowIfNeeded(); + } + private void closeLibraryPanelInFocusedWindowIfNeeded() { if (!mFocusedWindow.isLibraryVisible()) return; mFocusedWindow.switchPanel(NONE); } + private void closeDownloadsPanelInFocusedWindowIfNeeded() { + if (!mFocusedWindow.isDownloadsVisible()) + return; + mFocusedWindow.switchDownloadsPanel(); + } + + private void closeWebAppsPanelInFocusedWindowIfNeeded() { + if (!mFocusedWindow.isWebAppsVisible()) + return; + mFocusedWindow.switchWebAppsPanel(); + } + + private void closeAddonsPanelInFocusedWindowIfNeeded() { + if (!mFocusedWindow.isAddonsVisible()) + return; + mFocusedWindow.switchAddonsPanel(); + } + public void enterPrivateMode() { if (mPrivateMode) { return; @@ -617,7 +644,7 @@ public void enterPrivateMode() { mRegularWindowPlacement = mFocusedWindow.getWindowPlacement(); // Make sure we close the library before entering private mode. Otherwise we would // get a EGL crash in Gecko. - closeLibraryPanelInFocusedWindowIfNeeded(); + closeAllPanelsInFocusedWindowIfNeeded(); } else { mRegularWindowPlacement = WindowPlacement.FRONT; } @@ -662,7 +689,7 @@ public void exitPrivateMode() { mPrivateWindowPlacement = mFocusedWindow.getWindowPlacement(); // Make sure we close the library before exiting private mode. Otherwise we would // get a EGL crash in Gecko. - closeLibraryPanelInFocusedWindowIfNeeded(); + closeAllPanelsInFocusedWindowIfNeeded(); } else { mPrivateWindowPlacement = WindowPlacement.FRONT; } @@ -1168,12 +1195,17 @@ public void onAddWindowClicked() { @Override public void onLibraryClicked() { - if (mDownloadsManager.isDownloading()) { - mFocusedWindow.switchPanel(Windows.DOWNLOADS); + mFocusedWindow.switchPanel(Windows.NONE); + } - } else { - mFocusedWindow.switchPanel(Windows.NONE); - } + @Override + public void onDownloadsClicked() { + mFocusedWindow.switchDownloadsPanel(); + } + + @Override + public void onWebAppsClicked() { + mFocusedWindow.switchWebAppsPanel(); } @Override @@ -1694,7 +1726,7 @@ public void showWebAppAddedNotification() { } public boolean isSessionFocused(@NonNull Session session) { - return mRegularWindows.stream().anyMatch(window -> window.getSession() == session && !window.isLibraryVisible() && session.isPrivateMode() == mPrivateMode) || - mPrivateWindows.stream().anyMatch(window -> window.getSession() == session && !window.isLibraryVisible() && session.isPrivateMode() == mPrivateMode ); + return mRegularWindows.stream().anyMatch(window -> window.getSession() == session && !window.isLibraryVisible() && !window.isDownloadsVisible() && !window.isWebAppsVisible() && session.isPrivateMode() == mPrivateMode) || + mPrivateWindows.stream().anyMatch(window -> window.getSession() == session && !window.isLibraryVisible() && !window.isDownloadsVisible() && !window.isWebAppsVisible() && session.isPrivateMode() == mPrivateMode ); } } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/DownloadsContextMenuWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/DownloadsContextMenuWidget.java similarity index 93% rename from app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/DownloadsContextMenuWidget.java rename to app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/DownloadsContextMenuWidget.java index 6700523c7a..1ed1d9bf55 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/DownloadsContextMenuWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/DownloadsContextMenuWidget.java @@ -1,4 +1,4 @@ -package com.igalia.wolvic.ui.widgets.menus.library; +package com.igalia.wolvic.ui.widgets.menus; import android.content.Context; @@ -6,6 +6,7 @@ import com.igalia.wolvic.R; import com.igalia.wolvic.ui.callbacks.DownloadsContextMenuCallback; +import com.igalia.wolvic.ui.widgets.menus.library.LibraryContextMenuWidget; import java.util.EnumSet; diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/HamburgerMenuWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/HamburgerMenuWidget.java index 1c5dd19b5c..4c9722ab9d 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/HamburgerMenuWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/HamburgerMenuWidget.java @@ -199,7 +199,7 @@ private void updateItems() { final Session activeSession = SessionStore.get().getActiveSession(); String url = activeSession.getCurrentUri(); - boolean showAddons = (URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url)) && !mWidgetManager.getFocusedWindow().isLibraryVisible(); + boolean showAddons = (URLUtil.isHttpsUrl(url) || URLUtil.isHttpUrl(url)) && !mWidgetManager.getFocusedWindow().isAddonsVisible(); final SessionState tab = ComponentsAdapter.get().getSessionStateForSession(activeSession); if (tab != null && showAddons) { final List extensions = ComponentsAdapter.get().getSortedEnabledExtensions(); diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/LibraryContextMenuWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/LibraryContextMenuWidget.java index a55e319e5b..1375980e9c 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/LibraryContextMenuWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/menus/library/LibraryContextMenuWidget.java @@ -40,10 +40,10 @@ public String getTitle() { } - ArrayList mItems; + protected ArrayList mItems; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - Optional mItemDelegate; - LibraryContextMenuItem mItem; + protected Optional mItemDelegate; + protected LibraryContextMenuItem mItem; protected LibraryContextMenuWidget(Context aContext, LibraryContextMenuItem item, EnumSet additionalActions) { super(aContext, R.layout.library_menu); diff --git a/app/src/main/res/drawable/ic_icon_webapps_clip.xml b/app/src/main/res/drawable/ic_icon_webapps_clip.xml new file mode 100644 index 0000000000..1887ad5681 --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_webapps_clip.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/drawable/tray_background_left_round.xml b/app/src/main/res/drawable/tray_background_left_round.xml new file mode 100644 index 0000000000..cfe61b72a4 --- /dev/null +++ b/app/src/main/res/drawable/tray_background_left_round.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/tray_background_right_round.xml b/app/src/main/res/drawable/tray_background_right_round.xml new file mode 100644 index 0000000000..3562f73083 --- /dev/null +++ b/app/src/main/res/drawable/tray_background_right_round.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/library.xml b/app/src/main/res/layout/library.xml index 078fbb9c66..ac7546c855 100644 --- a/app/src/main/res/layout/library.xml +++ b/app/src/main/res/layout/library.xml @@ -11,9 +11,6 @@ name="supportsSystemNotifications" type="boolean" /> - @@ -32,26 +29,11 @@ android:src="@drawable/ic_icon_bookmark" android:id="@+id/bookmarks" android:onClick="@{(view) -> delegate != null ? delegate.onButtonClick(view) : void}"/> - - - - + android:layout_alignParentTop="true" + android:orientation="horizontal"> - - + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/layout/navigation_url.xml b/app/src/main/res/layout/navigation_url.xml index a4821aff38..a91161e8ae 100644 --- a/app/src/main/res/layout/navigation_url.xml +++ b/app/src/main/res/layout/navigation_url.xml @@ -228,8 +228,8 @@ + app:visibleGone="@{viewmodel.isWebApp && !(viewmodel.isLibraryVisible || viewmodel.isDownloadsVisible || viewmodel.isWebAppsVisible || viewmodel.isUrlEmpty) && !viewmodel.isFocused}" /> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="top|center_horizontal" + android:minWidth="@dimen/tray_width" + android:minHeight="@dimen/tray_height"> + android:scaleType="fitCenter" + app:tint="@color/iron" /> + app:tint="@color/concrete" + app:visibleGone="@{traymodel.wifiConnected}" /> + android:scaleType="fitCenter" + app:tint="@color/concrete" /> + android:scaleType="fitCenter" + app:tint="@color/concrete" /> + android:scaleType="fitCenter" + app:tint="@color/concrete" /> - + android:src="@drawable/ic_icon_library" + app:clipDrawable="@drawable/ic_icon_library_clip" + app:activeMode="@{viewmodel.isLibraryVisible}"/> + android:src="@drawable/ic_icon_downloads" + app:clipDrawable="@drawable/ic_icon_downloads_clip" + app:activeMode="@{viewmodel.isDownloadsVisible}"/> + + + + android:src="@drawable/ic_icon_tray_settings_v3" /> + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 51cecd3d6d..a66637082b 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -1695,6 +1695,14 @@ tray and the Downloads view is closed. The button it labels, when pressed, closes the Downloads view. --> Close Downloads + + Open Web Apps + + + Close Web Apps + Open Library @@ -1822,6 +1830,9 @@ Downloads + + Web Apps + Addons diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 50bc2185d1..c6c70df538 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1261,6 +1261,12 @@ 关闭下载项 + + 打开网页应用 + + 关闭网页应用 打开“我的足迹” @@ -1362,6 +1368,8 @@ 历史记录 下载项 + + 网页应用 附加组件 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 08f6523e3a..7f025bec86 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1696,6 +1696,14 @@ tray and the Downloads view is closed. The button it labels, when pressed, closes the Downloads view. --> 關閉下載項目 + + 開啟網頁應用 + + + 關閉網頁應用 + 開啟收藏庫 @@ -1823,6 +1831,9 @@ 下載項目 + + 網頁應用 + 附加元件 diff --git a/app/src/main/res/values/dimen.xml b/app/src/main/res/values/dimen.xml index 0282b7be26..faf81d80c1 100644 --- a/app/src/main/res/values/dimen.xml +++ b/app/src/main/res/values/dimen.xml @@ -129,8 +129,8 @@ 0.25 -2.5 - 1.2 - 204dp + 2 + 408dp 66dp 1.0 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0fead1c43d..0aa80d880d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1821,6 +1821,14 @@ tray and the Downloads view is closed. The button it labels, when pressed, closes the Downloads view. --> Close Downloads + + Open Web Apps + + + Close Web Apps + Open Library @@ -1950,6 +1958,9 @@ Downloads + + Web Apps + Addons