Merge pull request #431 from fork-maintainers/upstream-sync
Upstream sync with Mozilla Firefox v96.3.0fork-history iceraven-1.15.0
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,9 @@
|
|||||||
|
package org.mozilla.fenix.customannotations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom annotation to mark the smoke tests corresponding to the ones in TestRail:
|
||||||
|
* https://testrail.stage.mozaws.net/index.php?/suites/view/3192
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class SmokeTest
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.mozilla.fenix.helpers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import org.mozilla.fenix.ext.settings
|
||||||
|
|
||||||
|
class FeatureSettingsHelper {
|
||||||
|
private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
private val settings = context.settings()
|
||||||
|
|
||||||
|
// saving default values of feature flags
|
||||||
|
private var isPocketEnabled: Boolean = settings.showPocketRecommendationsFeature
|
||||||
|
private var isJumpBackInCFREnabled: Boolean = settings.shouldShowJumpBackInCFR
|
||||||
|
private var isRecentTabsFeatureEnabled: Boolean = settings.showRecentTabsFeature
|
||||||
|
|
||||||
|
fun setPocketEnabled(enabled: Boolean) {
|
||||||
|
settings.showPocketRecommendationsFeature = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setJumpBackCFREnabled(enabled: Boolean) {
|
||||||
|
settings.shouldShowJumpBackInCFR = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRecentTabsFeatureEnabled(enabled: Boolean) {
|
||||||
|
settings.showRecentTabsFeature = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setStrictETPEnabled() {
|
||||||
|
settings.setStrictETP()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Important:
|
||||||
|
// Use this after each test if you have modified these feature settings
|
||||||
|
// to make sure the app goes back to the default state
|
||||||
|
fun resetAllFeatureFlags() {
|
||||||
|
settings.showPocketRecommendationsFeature = isPocketEnabled
|
||||||
|
settings.shouldShowJumpBackInCFR = isJumpBackInCFREnabled
|
||||||
|
settings.showRecentTabsFeature = isRecentTabsFeatureEnabled
|
||||||
|
}
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* 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 org.mozilla.fenix.ui
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.uiautomator.UiDevice
|
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
|
||||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
|
||||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
|
||||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for verifying basic functionality of history
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ShareButtonTest {
|
|
||||||
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
|
|
||||||
|
|
||||||
private val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
|
||||||
private lateinit var mockWebServer: MockWebServer
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val activityTestRule = HomeActivityTestRule()
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
mockWebServer = MockWebServer().apply {
|
|
||||||
dispatcher = AndroidAssetDispatcher()
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun tearDown() {
|
|
||||||
mockWebServer.shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ShareButtonAppearanceTest() {
|
|
||||||
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
|
||||||
|
|
||||||
// - Visit a URL, wait until it's loaded
|
|
||||||
navigationToolbar {
|
|
||||||
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
|
|
||||||
mDevice.waitForIdle()
|
|
||||||
}
|
|
||||||
|
|
||||||
// From the 3-dot menu next to the Select share menu
|
|
||||||
navigationToolbar {
|
|
||||||
}.openThreeDotMenu {
|
|
||||||
clickShareButton()
|
|
||||||
verifyShareScrim()
|
|
||||||
verifySendToDeviceTitle()
|
|
||||||
verifyShareALinkTitle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,111 @@
|
|||||||
|
package org.mozilla.fenix.ui.robots
|
||||||
|
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.Visibility
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.helpers.click
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Robot Pattern for the settings Homepage sub menu.
|
||||||
|
*/
|
||||||
|
class SettingsSubMenuHomepageRobot {
|
||||||
|
|
||||||
|
fun verifyHomePageView() {
|
||||||
|
assertMostVisitedTopSitesButton()
|
||||||
|
assertJumpBackInButton()
|
||||||
|
assertRecentBookmarksButton()
|
||||||
|
assertRecentSearchesButton()
|
||||||
|
assertPocketButton()
|
||||||
|
assertOpeningScreenHeading()
|
||||||
|
assertHomepageButton()
|
||||||
|
assertLastTabButton()
|
||||||
|
assertHomepageAfterFourHoursButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clickJumpBackInButton() = jumpBackInButton().click()
|
||||||
|
fun clickRecentBookmarksButton() = recentBookmarksButton().click()
|
||||||
|
fun clickStartOnHomepageButton() = homepageButton().click()
|
||||||
|
fun clickStartOnLastTabButton() = lastTabButton().click()
|
||||||
|
|
||||||
|
class Transition {
|
||||||
|
|
||||||
|
fun goBack(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
|
||||||
|
goBackButton().click()
|
||||||
|
|
||||||
|
HomeScreenRobot().interact()
|
||||||
|
return HomeScreenRobot.Transition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mostVisitedTopSitesButton() =
|
||||||
|
onView(allOf(withText(R.string.top_sites_toggle_top_recent_sites_3)))
|
||||||
|
|
||||||
|
private fun jumpBackInButton() =
|
||||||
|
onView(allOf(withText(R.string.customize_toggle_jump_back_in)))
|
||||||
|
|
||||||
|
private fun recentBookmarksButton() =
|
||||||
|
onView(allOf(withText(R.string.customize_toggle_recent_bookmarks)))
|
||||||
|
|
||||||
|
private fun recentSearchesButton() =
|
||||||
|
onView(allOf(withText(R.string.customize_toggle_recently_visited)))
|
||||||
|
|
||||||
|
private fun pocketButton() =
|
||||||
|
onView(allOf(withText(R.string.customize_toggle_pocket)))
|
||||||
|
|
||||||
|
private fun openingScreenHeading() = onView(withText(R.string.preferences_opening_screen))
|
||||||
|
|
||||||
|
private fun homepageButton() =
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withId(R.id.title),
|
||||||
|
withText(R.string.opening_screen_homepage),
|
||||||
|
hasSibling(withId(R.id.radio_button))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun lastTabButton() =
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withId(R.id.title),
|
||||||
|
withText(R.string.opening_screen_last_tab),
|
||||||
|
hasSibling(withId(R.id.radio_button))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun homepageAfterFourHoursButton() =
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withId(R.id.title),
|
||||||
|
withText(R.string.opening_screen_after_four_hours_of_inactivity),
|
||||||
|
hasSibling(withId(R.id.radio_button))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun goBackButton() = onView(allOf(withContentDescription(R.string.action_bar_up_description)))
|
||||||
|
|
||||||
|
private fun assertMostVisitedTopSitesButton() =
|
||||||
|
mostVisitedTopSitesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertJumpBackInButton() =
|
||||||
|
jumpBackInButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertRecentBookmarksButton() =
|
||||||
|
recentBookmarksButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertRecentSearchesButton() =
|
||||||
|
recentSearchesButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertPocketButton() =
|
||||||
|
pocketButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertOpeningScreenHeading() =
|
||||||
|
openingScreenHeading().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertHomepageButton() =
|
||||||
|
homepageButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertLastTabButton() =
|
||||||
|
lastTabButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||||
|
private fun assertHomepageAfterFourHoursButton() =
|
||||||
|
homepageAfterFourHoursButton().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
@ -0,0 +1,91 @@
|
|||||||
|
package org.mozilla.fenix.ui.robots
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.intent.Intents
|
||||||
|
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.uiautomator.By
|
||||||
|
import androidx.test.uiautomator.UiSelector
|
||||||
|
import androidx.test.uiautomator.Until
|
||||||
|
import org.hamcrest.Matchers.allOf
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.helpers.ext.waitNotNull
|
||||||
|
|
||||||
|
class ShareOverlayRobot {
|
||||||
|
|
||||||
|
// This function verifies the share layout when more than one tab is shared - a list of tabs is shown
|
||||||
|
fun verifyShareTabsOverlay(vararg tabsTitles: String) {
|
||||||
|
onView(withId(R.id.shared_site_list))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
for (tabs in tabsTitles) {
|
||||||
|
onView(withText(tabs))
|
||||||
|
.check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
hasSibling(withId(R.id.share_tab_favicon)),
|
||||||
|
hasSibling(withId(R.id.share_tab_url))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function verifies the share layout when a single tab is shared - no tab info shown
|
||||||
|
fun verifyShareTabLayout() = assertShareTabLayout()
|
||||||
|
|
||||||
|
// this verifies the Android sharing layout - not customized for sharing tabs
|
||||||
|
fun verifyAndroidShareLayout() {
|
||||||
|
mDevice.waitNotNull(Until.findObject(By.res("android:id/resolver_list")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectAppToShareWith(appName: String) =
|
||||||
|
mDevice.findObject(UiSelector().text(appName)).clickAndWaitForNewWindow()
|
||||||
|
|
||||||
|
fun verifySendToDeviceTitle() = assertSendToDeviceTitle()
|
||||||
|
|
||||||
|
fun verifyShareALinkTitle() = assertShareALinkTitle()
|
||||||
|
|
||||||
|
fun verifySharedTabsIntent(text: String, subject: String) {
|
||||||
|
Intents.intended(
|
||||||
|
allOf(
|
||||||
|
IntentMatchers.hasExtra(Intent.EXTRA_TEXT, text),
|
||||||
|
IntentMatchers.hasExtra(Intent.EXTRA_SUBJECT, subject)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Transition
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareTabsLayout() = onView(withResourceName("shareWrapper"))
|
||||||
|
|
||||||
|
private fun assertShareTabLayout() =
|
||||||
|
shareTabsLayout().check(matches(isDisplayed()))
|
||||||
|
|
||||||
|
private fun sendToDeviceTitle() =
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withText("SEND TO DEVICE"),
|
||||||
|
withResourceName("accountHeaderText")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun assertSendToDeviceTitle() = sendToDeviceTitle()
|
||||||
|
.check(matches(ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||||
|
|
||||||
|
private fun shareALinkTitle() =
|
||||||
|
onView(
|
||||||
|
allOf(
|
||||||
|
withText("ALL ACTIONS"),
|
||||||
|
withResourceName("apps_link_header")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun assertShareALinkTitle() = shareALinkTitle()
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
@ -0,0 +1,22 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 org.mozilla.fenix.components
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.Middleware
|
||||||
|
import mozilla.components.lib.state.Store
|
||||||
|
import org.mozilla.fenix.components.appstate.AppAction
|
||||||
|
import org.mozilla.fenix.components.appstate.AppState
|
||||||
|
import org.mozilla.fenix.components.appstate.AppStoreReducer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Store] that holds the [AppState] for the app and reduces [AppAction]s
|
||||||
|
* dispatched to the store.
|
||||||
|
*
|
||||||
|
* This store is not persisted to disk and is scoped to the life-cycle of the application.
|
||||||
|
*/
|
||||||
|
class AppStore(
|
||||||
|
initialState: AppState = AppState(),
|
||||||
|
middlewares: List<Middleware<AppState, AppAction>> = emptyList()
|
||||||
|
) : Store<AppState, AppAction>(initialState, AppStoreReducer::reduce, middlewares)
|
@ -1,29 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* 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 org.mozilla.fenix.components
|
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.appwidget.AppWidgetManager
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import org.mozilla.gecko.search.SearchWidgetProvider
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the creation of the pinning search widget dialog.
|
|
||||||
*/
|
|
||||||
object SearchWidgetCreator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to display a prompt requesting the user pin the search widget
|
|
||||||
* Returns true if the prompt is displayed successfully, and false otherwise.
|
|
||||||
*/
|
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
|
||||||
fun createSearchWidget(context: Context): Boolean {
|
|
||||||
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
|
|
||||||
val myProvider = ComponentName(context, SearchWidgetProvider::class.java)
|
|
||||||
return appWidgetManager.requestPinAppWidget(myProvider, null, null)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,15 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 org.mozilla.fenix.components.appstate
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.Action
|
||||||
|
import org.mozilla.fenix.components.AppStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Action] implementation related to [AppStore].
|
||||||
|
*/
|
||||||
|
sealed class AppAction : Action {
|
||||||
|
data class UpdateInactiveExpanded(val expanded: Boolean) : AppAction()
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 org.mozilla.fenix.components.appstate
|
||||||
|
|
||||||
|
import mozilla.components.lib.state.State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value type that represents the state of the tabs tray.
|
||||||
|
*
|
||||||
|
* @property inactiveTabsExpanded A flag to know if the Inactive Tabs section of the Tabs Tray
|
||||||
|
* should be expanded when the tray is opened.
|
||||||
|
*/
|
||||||
|
data class AppState(
|
||||||
|
val inactiveTabsExpanded: Boolean = false
|
||||||
|
) : State
|
@ -0,0 +1,17 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 org.mozilla.fenix.components.appstate
|
||||||
|
|
||||||
|
import org.mozilla.fenix.components.AppStore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer for [AppStore].
|
||||||
|
*/
|
||||||
|
internal object AppStoreReducer {
|
||||||
|
fun reduce(state: AppState, action: AppAction): AppState = when (action) {
|
||||||
|
is AppAction.UpdateInactiveExpanded ->
|
||||||
|
state.copy(inactiveTabsExpanded = action.expanded)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 org.mozilla.fenix.compose
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import mozilla.components.browser.icons.IconRequest
|
||||||
|
import mozilla.components.browser.icons.compose.Loader
|
||||||
|
import mozilla.components.browser.icons.compose.Placeholder
|
||||||
|
import mozilla.components.browser.icons.compose.WithIcon
|
||||||
|
import mozilla.components.ui.colors.PhotonColors
|
||||||
|
import org.mozilla.fenix.components.components
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and display the favicon of a particular website.
|
||||||
|
*
|
||||||
|
* @param url Website [URL] for which the favicon will be shown.
|
||||||
|
* @param size [Dp] height and width of the image to be loaded.
|
||||||
|
* @param isPrivate Whether or not a private request (like in private browsing) should be used to
|
||||||
|
* download the icon (if needed).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun Favicon(
|
||||||
|
url: String,
|
||||||
|
size: Dp,
|
||||||
|
isPrivate: Boolean = false
|
||||||
|
) {
|
||||||
|
components.core.icons.Loader(
|
||||||
|
url = url,
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
size = size.toIconRequestSize()
|
||||||
|
) {
|
||||||
|
Placeholder {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.background(
|
||||||
|
color = when (isSystemInDarkTheme()) {
|
||||||
|
true -> PhotonColors.DarkGrey30
|
||||||
|
false -> PhotonColors.LightGrey30
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
WithIcon { icon ->
|
||||||
|
Image(
|
||||||
|
painter = icon.painter,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(RoundedCornerShape(2.dp)),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Dp.toIconRequestSize() = when {
|
||||||
|
value <= dimensionResource(IconRequest.Size.DEFAULT.dimen).value -> IconRequest.Size.DEFAULT
|
||||||
|
value <= dimensionResource(IconRequest.Size.LAUNCHER.dimen).value -> IconRequest.Size.LAUNCHER
|
||||||
|
else -> IconRequest.Size.LAUNCHER_ADAPTIVE
|
||||||
|
}
|