diff --git a/app/build.gradle b/app/build.gradle index af8a88096..aa3a0b0cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -353,6 +353,7 @@ dependencies { implementation Deps.mozilla_feature_media implementation Deps.mozilla_feature_prompts implementation Deps.mozilla_feature_push + implementation Deps.mozilla_feature_pwa implementation Deps.mozilla_feature_qr implementation Deps.mozilla_feature_search implementation Deps.mozilla_feature_session diff --git a/app/src/main/java/org/mozilla/fenix/components/Components.kt b/app/src/main/java/org/mozilla/fenix/components/Components.kt index 013f52225..585f4fc8d 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Components.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Components.kt @@ -19,7 +19,9 @@ class Components(private val context: Context) { val services by lazy { Services(backgroundServices.accountManager) } val core by lazy { Core(context) } val search by lazy { Search(context) } - val useCases by lazy { UseCases(context, core.sessionManager, core.engine.settings, search.searchEngineManager) } + val useCases by lazy { + UseCases(context, core.sessionManager, core.engine.settings, search.searchEngineManager, core.client) + } val utils by lazy { Utilities(context, core.sessionManager, useCases.sessionUseCases, useCases.searchUseCases) } val analytics by lazy { Analytics(context) } val publicSuffixList by lazy { PublicSuffixList(context) } diff --git a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt index 2f018465b..8baeaf6d0 100644 --- a/app/src/main/java/org/mozilla/fenix/components/UseCases.kt +++ b/app/src/main/java/org/mozilla/fenix/components/UseCases.kt @@ -8,7 +8,9 @@ import android.content.Context import mozilla.components.browser.search.SearchEngineManager import mozilla.components.browser.session.SessionManager import mozilla.components.concept.engine.Settings +import mozilla.components.concept.fetch.Client import mozilla.components.feature.app.links.AppLinksUseCases +import mozilla.components.feature.pwa.WebAppUseCases import mozilla.components.feature.search.SearchUseCases import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SettingsUseCases @@ -24,7 +26,8 @@ class UseCases( private val context: Context, private val sessionManager: SessionManager, private val engineSettings: Settings, - private val searchEngineManager: SearchEngineManager + private val searchEngineManager: SearchEngineManager, + private val httpClient: Client ) { /** * Use cases that provide engine interactions for a given browser session. @@ -47,4 +50,6 @@ class UseCases( val settingsUseCases by lazy { SettingsUseCases(engineSettings, sessionManager) } val appLinksUseCases by lazy { AppLinksUseCases(context.applicationContext) } + + val webAppUseCases by lazy { WebAppUseCases(context, sessionManager, httpClient, supportWebApps = false) } } diff --git a/app/src/main/java/org/mozilla/fenix/components/Utilities.kt b/app/src/main/java/org/mozilla/fenix/components/Utilities.kt index 4f28afbeb..ca3d61c9c 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Utilities.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Utilities.kt @@ -23,13 +23,15 @@ class Utilities( private val searchUseCases: SearchUseCases ) { /** - * Provides intent processing functionality for CustomTab, ACTION_VIEW - * and ACTION_SEND intents. + * Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents. */ val intentProcessor by lazy { TabIntentProcessor(sessionManager, sessionUseCases.loadUrl, searchUseCases.newTabSearch, isPrivate = false) } + /** + * Provides intent processing functionality for ACTION_VIEW and ACTION_SEND intents in private tabs. + */ val privateIntentProcessor by lazy { TabIntentProcessor(sessionManager, sessionUseCases.loadUrl, searchUseCases.newTabSearch, isPrivate = true) } diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt index c5d673d7f..4aa09d800 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt @@ -253,7 +253,7 @@ sealed class Event { enum class Item { SETTINGS, LIBRARY, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB, NEW_PRIVATE_TAB, SHARE, REPORT_SITE_ISSUE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX, - SAVE_TO_COLLECTION + SAVE_TO_COLLECTION, ADD_TO_HOMESCREEN } override val extras: Map? diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 086fc40bb..62299b19e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -11,14 +11,16 @@ import androidx.core.widget.NestedScrollView import androidx.navigation.NavController import com.google.android.material.bottomsheet.BottomSheetBehavior import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.MainScope import kotlinx.coroutines.ObsoleteCoroutinesApi +import kotlinx.coroutines.launch import mozilla.components.browser.session.Session import mozilla.components.concept.engine.EngineView -import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserFragment import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.collections.CreateCollectionViewModel import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components @@ -86,6 +88,11 @@ class DefaultBrowserToolbarController( item.isChecked, currentSession ) + ToolbarMenu.Item.AddToHomeScreen -> { + MainScope().launch { + context.components.useCases.webAppUseCases.addToHomescreen() + } + } ToolbarMenu.Item.Share -> { val currentUrl = currentSession?.url currentUrl?.apply { @@ -175,6 +182,7 @@ class DefaultBrowserToolbarController( ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION + ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN } context.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem)) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index 3228e5cc0..f43a4cd26 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -11,12 +11,12 @@ import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem import mozilla.components.browser.menu.item.BrowserMenuImageText import mozilla.components.browser.menu.item.BrowserMenuItemToolbar import mozilla.components.browser.menu.item.BrowserMenuSwitch -import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R -import org.mozilla.fenix.theme.ThemeManager +import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.ext.components +import org.mozilla.fenix.theme.ThemeManager class DefaultToolbarMenu( private val context: Context, @@ -133,10 +133,22 @@ class DefaultToolbarMenu( onItemTapped.invoke(ToolbarMenu.Item.Library) }, - BrowserMenuSwitch(context.getString(R.string.browser_menu_desktop_site), - requestDesktopStateProvider, { checked -> - onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked)) - }), + BrowserMenuSwitch( + context.getString(R.string.browser_menu_desktop_site), + requestDesktopStateProvider + ) { checked -> + onItemTapped.invoke(ToolbarMenu.Item.RequestDesktop(checked)) + }, + + BrowserMenuImageText( + context.getString(R.string.browser_menu_add_to_homescreen), + R.drawable.ic_add_to_homescreen, + ThemeManager.resolveAttribute(R.attr.primaryText, context) + ) { + onItemTapped.invoke(ToolbarMenu.Item.AddToHomeScreen) + }.apply { + visible = ::shouldShowAddToHomescreen + }, BrowserMenuImageText( context.getString(R.string.browser_menu_find_in_page), @@ -201,4 +213,9 @@ class DefaultToolbarMenu( items } + + private fun shouldShowAddToHomescreen(): Boolean { + return context.components.useCases.webAppUseCases.isPinningSupported() && + context.components.core.sessionManager.selectedSession != null + } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index 4db34ea05..6d443772f 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -24,6 +24,7 @@ interface ToolbarMenu { object ReportIssue : Item() object OpenInFenix : Item() object SaveToCollection : Item() + object AddToHomeScreen : Item() } val menuBuilder: BrowserMenuBuilder diff --git a/app/src/main/res/drawable/ic_add_to_homescreen.xml b/app/src/main/res/drawable/ic_add_to_homescreen.xml new file mode 100644 index 000000000..282453f17 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_to_homescreen.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52f2f736d..2628bc309 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,8 @@ Your Library Desktop site + + Add to Home screen Find in page diff --git a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt index 0aec887dd..c4b1fbc68 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestComponents.kt @@ -16,7 +16,15 @@ class TestComponents(private val context: Context) : Components(context) { override val services by lazy { Services(backgroundServices.accountManager) } override val core by lazy { TestCore(context) } override val search by lazy { Search(context) } - override val useCases by lazy { UseCases(context, core.sessionManager, core.engine.settings, search.searchEngineManager) } + override val useCases by lazy { + UseCases( + context, + core.sessionManager, + core.engine.settings, + search.searchEngineManager, + core.client + ) + } override val utils by lazy { Utilities( context, diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index 235e9cd68..956b96d45 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -22,12 +22,12 @@ import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.tabs.TabsUseCases import org.junit.Before import org.junit.Test -import org.mozilla.fenix.browser.browsingmode.BrowsingMode -import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.BrowserFragment import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.collections.CreateCollectionViewModel import org.mozilla.fenix.components.Analytics import org.mozilla.fenix.components.metrics.Event @@ -211,6 +211,15 @@ class DefaultBrowserToolbarControllerTest { } } + @Test + fun handleToolbarAddToHomeScreenPress() { + val item = ToolbarMenu.Item.AddToHomeScreen + + controller.handleToolbarItemInteraction(item) + + verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN)) } + } + @Test fun handleToolbarSharePress() { val item = ToolbarMenu.Item.Share diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 6e5fa2102..fdfea5748 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -123,6 +123,7 @@ object Deps { const val mozilla_feature_storage = "org.mozilla.components:feature-storage:${Versions.mozilla_android_components}" const val mozilla_feature_prompts = "org.mozilla.components:feature-prompts:${Versions.mozilla_android_components}" const val mozilla_feature_push = "org.mozilla.components:feature-push:${Versions.mozilla_android_components}" + const val mozilla_feature_pwa = "org.mozilla.components:feature-pwa:${Versions.mozilla_android_components}" const val mozilla_feature_toolbar = "org.mozilla.components:feature-toolbar:${Versions.mozilla_android_components}" const val mozilla_feature_findinpage = "org.mozilla.components:feature-findinpage:${Versions.mozilla_android_components}" const val mozilla_feature_site_permissions = "org.mozilla.components:feature-sitepermissions:${Versions.mozilla_android_components}"