Merge remote-tracking branch 'origin/fenix/113.0' into iceraven
commit
75f90e48e6
@ -1,5 +0,0 @@
|
||||
# .git-blame-ignore-revs
|
||||
# For #27667 - Remove import-ordering from the list of disabled ktlint rules (#27680)
|
||||
9654b4dfb122b54b04369fe80a2f9c95811478e8
|
||||
# For #26844: Fix ktlint issues and remove them from baseline. (#26901)
|
||||
ffcef5ff2e3f78b6972dd16551f3f653b7035ccc
|
@ -1,267 +0,0 @@
|
||||
---
|
||||
features:
|
||||
nimbus-system:
|
||||
description: |
|
||||
Configuration of the Nimbus System in Fenix.
|
||||
variables:
|
||||
refresh-interval-foreground:
|
||||
description: |
|
||||
The minimum interval in minutes between fetching experiment
|
||||
recipes in the foreground.
|
||||
type: Int
|
||||
default: 60 # 1 hour
|
||||
|
||||
messaging:
|
||||
description: |
|
||||
Configuration for the messaging system.
|
||||
|
||||
In practice this is a set of growable lookup tables for the
|
||||
message controller to piece together.
|
||||
|
||||
variables:
|
||||
message-under-experiment:
|
||||
description: Id or prefix of the message under experiment.
|
||||
type: Option<String>
|
||||
default: null
|
||||
|
||||
messages:
|
||||
description: A growable collection of messages
|
||||
type: Map<String, MessageData>
|
||||
default: {}
|
||||
|
||||
triggers:
|
||||
description: >
|
||||
A collection of out the box trigger
|
||||
expressions. Each entry maps to a
|
||||
valid JEXL expression.
|
||||
type: Map<String, String>
|
||||
default: {}
|
||||
styles:
|
||||
description: >
|
||||
A map of styles to configure message
|
||||
appearance.
|
||||
type: Map<String, StyleData>
|
||||
default: {}
|
||||
|
||||
actions:
|
||||
type: Map<String, String>
|
||||
description: A growable map of action URLs.
|
||||
default: {}
|
||||
on-control:
|
||||
type: ControlMessageBehavior
|
||||
description: What should be displayed when a control message is selected.
|
||||
default: show-next-message
|
||||
notification-config:
|
||||
description: Configuration of the notification worker for all notification messages.
|
||||
type: NotificationConfig
|
||||
default: {}
|
||||
defaults:
|
||||
- value:
|
||||
triggers:
|
||||
# Using attributes built into the Nimbus SDK
|
||||
USER_RECENTLY_INSTALLED: days_since_install < 7
|
||||
USER_RECENTLY_UPDATED: days_since_update < 7 && days_since_install != days_since_update
|
||||
USER_TIER_ONE_COUNTRY: ('US' in locale || 'GB' in locale || 'CA' in locale || 'DE' in locale || 'FR' in locale)
|
||||
USER_EN_SPEAKER: "'en' in locale"
|
||||
USER_ES_SPEAKER: "'es' in locale"
|
||||
USER_DE_SPEAKER: "'de' in locale"
|
||||
USER_FR_SPEAKER: "'fr' in locale"
|
||||
DEVICE_ANDROID: os == 'Android'
|
||||
DEVICE_IOS: os == 'iOS'
|
||||
ALWAYS: "true"
|
||||
NEVER: "false"
|
||||
DAY_1_AFTER_INSTALL: days_since_install == 1
|
||||
DAY_2_AFTER_INSTALL: days_since_install == 2
|
||||
DAY_3_AFTER_INSTALL: days_since_install == 3
|
||||
DAY_4_AFTER_INSTALL: days_since_install == 4
|
||||
DAY_5_AFTER_INSTALL: days_since_install == 5
|
||||
|
||||
# Using custom attributes for the browser
|
||||
I_AM_DEFAULT_BROWSER: "is_default_browser"
|
||||
I_AM_NOT_DEFAULT_BROWSER: "is_default_browser == false"
|
||||
USER_ESTABLISHED_INSTALL: "number_of_app_launches >=4"
|
||||
|
||||
FUNNEL_PAID: "adjust_campaign != ''"
|
||||
FUNNEL_ORGANIC: "adjust_campaign == ''"
|
||||
|
||||
# Using Glean events, specific to the browser
|
||||
INACTIVE_1_DAY: "'app_launched'|eventLastSeen('Hours') >= 24"
|
||||
INACTIVE_2_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 2"
|
||||
INACTIVE_3_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 3"
|
||||
INACTIVE_4_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 4"
|
||||
INACTIVE_5_DAYS: "'app_launched'|eventLastSeen('Days', 0) >= 5"
|
||||
|
||||
# Has the user signed in the last 4 years
|
||||
FXA_SIGNED_IN: "'sync_auth.sign_in'|eventLastSeen('Years', 0) <= 4"
|
||||
FXA_NOT_SIGNED_IN: "'sync_auth.sign_in'|eventLastSeen('Years', 0) > 4"
|
||||
|
||||
# https://mozilla-hub.atlassian.net/wiki/spaces/FJT/pages/11469471/Core+Active
|
||||
USER_INFREQUENT: "'app_launched'|eventCountNonZero('Days', 28) >= 1 && 'app_launched'|eventCountNonZero('Days', 28) < 7"
|
||||
USER_CASUAL: "'app_launched'|eventCountNonZero('Days', 28) >= 7 && 'app_launched'|eventCountNonZero('Days', 28) < 14"
|
||||
USER_REGULAR: "'app_launched'|eventCountNonZero('Days', 28) >= 14 && 'app_launched'|eventCountNonZero('Days', 28) < 21"
|
||||
USER_CORE_ACTIVE: "'app_launched'|eventCountNonZero('Days', 28) >= 21"
|
||||
|
||||
LAUNCHED_ONCE_THIS_WEEK: "'app_launched'|eventSum('Days', 7) == 1"
|
||||
|
||||
actions:
|
||||
ENABLE_PRIVATE_BROWSING: ://enable_private_browsing
|
||||
INSTALL_SEARCH_WIDGET: ://install_search_widget
|
||||
MAKE_DEFAULT_BROWSER: ://make_default_browser
|
||||
VIEW_BOOKMARKS: ://urls_bookmarks
|
||||
VIEW_COLLECTIONS: ://home_collections
|
||||
VIEW_HISTORY: ://urls_history
|
||||
VIEW_HOMESCREEN: ://home
|
||||
OPEN_SETTINGS_ACCESSIBILITY: ://settings_accessibility
|
||||
OPEN_SETTINGS_ADDON_MANAGER: ://settings_addon_manager
|
||||
OPEN_SETTINGS_DELETE_BROWSING_DATA: ://settings_delete_browsing_data
|
||||
OPEN_SETTINGS_LOGINS: ://settings_logins
|
||||
OPEN_SETTINGS_NOTIFICATIONS: ://settings_notifications
|
||||
OPEN_SETTINGS_PRIVACY: ://settings_privacy
|
||||
OPEN_SETTINGS_SEARCH_ENGINE: ://settings_search_engine
|
||||
OPEN_SETTINGS_TRACKING_PROTECTION: ://settings_tracking_protection
|
||||
OPEN_SETTINGS_WALLPAPERS: ://settings_wallpapers
|
||||
OPEN_SETTINGS: ://settings
|
||||
TURN_ON_SYNC: ://turn_on_sync
|
||||
styles:
|
||||
DEFAULT:
|
||||
priority: 50
|
||||
max-display-count: 5
|
||||
SURVEY:
|
||||
priority: 55
|
||||
max-display-count: 10
|
||||
PERSISTENT:
|
||||
priority: 50
|
||||
max-display-count: 20
|
||||
WARNING:
|
||||
priority: 60
|
||||
max-display-count: 10
|
||||
URGENT:
|
||||
priority: 100
|
||||
max-display-count: 10
|
||||
NOTIFICATION:
|
||||
priority: 50
|
||||
max-display-count: 1
|
||||
messages:
|
||||
default-browser:
|
||||
text: default_browser_experiment_card_text
|
||||
surface: homescreen
|
||||
action: "MAKE_DEFAULT_BROWSER"
|
||||
trigger: [ "I_AM_NOT_DEFAULT_BROWSER","USER_ESTABLISHED_INSTALL" ]
|
||||
style: "PERSISTENT"
|
||||
button-label: preferences_set_as_default_browser
|
||||
default-browser-notification:
|
||||
title: nimbus_notification_default_browser_title
|
||||
text: nimbus_notification_default_browser_text
|
||||
surface: notification
|
||||
style: NOTIFICATION
|
||||
trigger:
|
||||
- I_AM_NOT_DEFAULT_BROWSER
|
||||
- DAY_3_AFTER_INSTALL
|
||||
action: MAKE_DEFAULT_BROWSER
|
||||
|
||||
- channel: developer
|
||||
value:
|
||||
styles:
|
||||
DEFAULT:
|
||||
priority: 50
|
||||
max-display-count: 100
|
||||
EXPIRES_QUICKLY:
|
||||
priority: 100
|
||||
max-display-count: 1
|
||||
notification-config:
|
||||
refresh-interval: 120 # minutes (2 hours)
|
||||
|
||||
objects:
|
||||
MessageData:
|
||||
description: >
|
||||
An object to describe a message. It uses human
|
||||
readable strings to describe the triggers, action and
|
||||
style of the message as well as the text of the message
|
||||
and call to action.
|
||||
fields:
|
||||
action:
|
||||
type: Text
|
||||
description: >
|
||||
A URL of a page or a deeplink.
|
||||
This may have substitution variables in.
|
||||
# This should never be defaulted.
|
||||
default: empty_string
|
||||
title:
|
||||
type: Option<Text>
|
||||
description: "The title text displayed to the user"
|
||||
default: null
|
||||
text:
|
||||
type: Text
|
||||
description: "The message text displayed to the user"
|
||||
# This should never be defaulted.
|
||||
default: empty_string
|
||||
is-control:
|
||||
type: Boolean
|
||||
description: "Indicates if this message is the control message, if true shouldn't be displayed"
|
||||
default: false
|
||||
button-label:
|
||||
type: Option<Text>
|
||||
description: >
|
||||
The text on the button. If no text
|
||||
is present, the whole message is clickable.
|
||||
default: null
|
||||
style:
|
||||
type: String
|
||||
description: >
|
||||
The style as described in a
|
||||
`StyleData` from the styles table.
|
||||
default: DEFAULT
|
||||
surface:
|
||||
description:
|
||||
The surface identifier for this message.
|
||||
type: MessageSurfaceId
|
||||
default: homescreen
|
||||
trigger:
|
||||
type: List<String>
|
||||
description: >
|
||||
A list of strings corresponding to
|
||||
targeting expressions. The message will be
|
||||
shown if all expressions `true`.
|
||||
default: []
|
||||
StyleData:
|
||||
description: >
|
||||
A group of properties (predominantly visual) to
|
||||
describe the style of the message.
|
||||
fields:
|
||||
priority:
|
||||
type: Int
|
||||
description: >
|
||||
The importance of this message.
|
||||
0 is not very important, 100 is very important.
|
||||
default: 50
|
||||
max-display-count:
|
||||
type: Int
|
||||
description: >
|
||||
How many sessions will this message be shown to the user
|
||||
before it is expired.
|
||||
default: 5
|
||||
NotificationConfig:
|
||||
description: Attributes controlling the global configuration of notification messages.
|
||||
fields:
|
||||
refresh-interval:
|
||||
type: Int
|
||||
description: >
|
||||
How often, in minutes, the notification message worker will wake up and check for new
|
||||
messages.
|
||||
default: 240 # 4 hours
|
||||
|
||||
enums:
|
||||
ControlMessageBehavior:
|
||||
description: An enum to influence what should be displayed when a control message is selected.
|
||||
variants:
|
||||
show-next-message:
|
||||
description: The next eligible message should be shown.
|
||||
show-none:
|
||||
description: The surface should show no message.
|
||||
MessageSurfaceId:
|
||||
description: The identity of a message surface
|
||||
variants:
|
||||
homescreen:
|
||||
description: A banner in the homescreen.
|
||||
notification:
|
||||
description: A local notification in the background, like a push notification.
|
@ -0,0 +1,121 @@
|
||||
/* 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 android.content.Intent
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import mozilla.components.service.nimbus.messaging.FxNimbusMessaging
|
||||
import mozilla.components.service.nimbus.messaging.MessageData
|
||||
import mozilla.components.service.nimbus.messaging.Messaging
|
||||
import mozilla.components.service.nimbus.messaging.StyleData
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.experiments.nimbus.Res
|
||||
import org.mozilla.fenix.FenixApplication
|
||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||
import org.mozilla.fenix.helpers.RetryTestRule
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
import org.mozilla.fenix.nimbus.HomeScreenSection
|
||||
import org.mozilla.fenix.nimbus.Homescreen
|
||||
import org.mozilla.fenix.ui.robots.homeScreen
|
||||
|
||||
/**
|
||||
* Tests for verifying basic functionality of the Nimbus Home Screen message
|
||||
*
|
||||
* Verifies a message can be displayed with all of the correct components
|
||||
**/
|
||||
|
||||
class NimbusMessagingHomescreenTest {
|
||||
private lateinit var mDevice: UiDevice
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
|
||||
private var messageButtonLabel = "CLICK ME"
|
||||
private var messageText = "Some Nimbus Messaging text"
|
||||
private var messageTitle = "A Nimbus title"
|
||||
|
||||
@get:Rule
|
||||
val homeActivityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(
|
||||
skipOnboarding = true,
|
||||
).withIntent(
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
},
|
||||
)
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val retryTestRule = RetryTestRule(3)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
// Set up nimbus message
|
||||
FxNimbusMessaging.features.messaging.withInitializer {
|
||||
// FML generated objects.
|
||||
Messaging(
|
||||
messages = mapOf(
|
||||
"test-message" to MessageData(
|
||||
action = Res.string("TEST ACTION"),
|
||||
style = "TEST STYLE",
|
||||
buttonLabel = Res.string(messageButtonLabel),
|
||||
text = Res.string(messageText),
|
||||
title = Res.string(messageTitle),
|
||||
trigger = listOf("ALWAYS"),
|
||||
),
|
||||
),
|
||||
styles = mapOf(
|
||||
"TEST STYLE" to StyleData(),
|
||||
),
|
||||
actions = mapOf(
|
||||
"TEST ACTION" to "https://example.com",
|
||||
),
|
||||
triggers = mapOf(
|
||||
"ALWAYS" to "true",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Remove some homescreen features not needed for testing
|
||||
FxNimbus.features.homescreen.withInitializer {
|
||||
// These are FML generated objects and enums
|
||||
Homescreen(
|
||||
sectionsEnabled = mapOf(
|
||||
HomeScreenSection.JUMP_BACK_IN to false,
|
||||
HomeScreenSection.POCKET to false,
|
||||
HomeScreenSection.POCKET_SPONSORED_STORIES to false,
|
||||
HomeScreenSection.RECENT_EXPLORATIONS to false,
|
||||
HomeScreenSection.RECENTLY_SAVED to false,
|
||||
HomeScreenSection.TOP_SITES to false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
mockWebServer = MockWebServer().apply {
|
||||
dispatcher = AndroidAssetDispatcher()
|
||||
start()
|
||||
}
|
||||
// refresh message store
|
||||
val application = (homeActivityTestRule.activity.application as FenixApplication)
|
||||
application.restoreMessaging()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNimbusMessageIsDisplayed() {
|
||||
// Checks the home screen card message is displayed correctly
|
||||
homeScreen {
|
||||
verifyNimbusMessageCard(messageTitle, messageText, messageButtonLabel)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/* 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 android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.rule.GrantPermissionRule.grant
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import mozilla.components.service.nimbus.messaging.FxNimbusMessaging
|
||||
import org.json.JSONObject
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.experiments.nimbus.HardcodedNimbusFeatures
|
||||
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||
import org.mozilla.fenix.helpers.TestHelper
|
||||
import org.mozilla.fenix.nimbus.FxNimbus
|
||||
import org.mozilla.fenix.ui.robots.notificationShade
|
||||
|
||||
/**
|
||||
* A UI test for testing the notification surface for Nimbus Messaging.
|
||||
*/
|
||||
class NimbusMessagingNotificationTest {
|
||||
private lateinit var mDevice: UiDevice
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var hardcodedNimbus: HardcodedNimbusFeatures
|
||||
|
||||
@get:Rule
|
||||
val activityTestRule =
|
||||
HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
|
||||
|
||||
@get:Rule
|
||||
val grantPermissionRule: GrantPermissionRule =
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
grant("android.permission.POST_NOTIFICATIONS")
|
||||
} else {
|
||||
grant()
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = TestHelper.appContext
|
||||
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowingNotificationMessage() {
|
||||
hardcodedNimbus = HardcodedNimbusFeatures(
|
||||
context,
|
||||
"messaging" to JSONObject(
|
||||
"""
|
||||
{
|
||||
"message-under-experiment": "test-default-browser-notification",
|
||||
"messages": {
|
||||
"test-default-browser-notification": {
|
||||
"title": "preferences_set_as_default_browser",
|
||||
"text": "default_browser_experiment_card_text",
|
||||
"surface": "notification",
|
||||
"style": "NOTIFICATION",
|
||||
"action": "MAKE_DEFAULT_BROWSER",
|
||||
"trigger": [
|
||||
"ALWAYS"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
),
|
||||
)
|
||||
// The scheduling of the Messaging Notification Worker happens in the HomeActivity
|
||||
// onResume().
|
||||
// We need to have connected FxNimbus to hardcodedNimbus by the time it is scheduled, so
|
||||
// we finishActivity, connect, _then_ re-launch the activity so that the worker has
|
||||
// hardcodedNimbus by the time its re-scheduled.
|
||||
// Because the scheduling happens for a second time, the work request needs to replace the
|
||||
// existing one.
|
||||
activityTestRule.finishActivity()
|
||||
hardcodedNimbus.connectWith(FxNimbus)
|
||||
activityTestRule.launchActivity(null)
|
||||
|
||||
mDevice.openNotification()
|
||||
notificationShade {
|
||||
val data =
|
||||
FxNimbusMessaging.features.messaging.value().messages["test-default-browser-notification"]
|
||||
verifySystemNotificationExists(data!!.title!!)
|
||||
verifySystemNotificationExists(data.text)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/* 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.ui.robots.homeScreen
|
||||
|
||||
/**
|
||||
* Tests for verifying the the privacy and security section of the Settings menu
|
||||
*
|
||||
*/
|
||||
|
||||
class SettingsPrivacyTest {
|
||||
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
|
||||
|
||||
private lateinit var mDevice: UiDevice
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
|
||||
@get:Rule
|
||||
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
mockWebServer = MockWebServer().apply {
|
||||
dispatcher = AndroidAssetDispatcher()
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsPrivacyItemsTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
verifySettingsToolbar()
|
||||
verifyPrivacyHeading()
|
||||
verifyPrivateBrowsingButton()
|
||||
verifyHTTPSOnlyModeButton()
|
||||
verifySettingsOptionSummary("HTTPS-Only Mode", "Off")
|
||||
verifyCookieBannerReductionButton()
|
||||
verifySettingsOptionSummary("Cookie Banner Reduction", "Off")
|
||||
verifyEnhancedTrackingProtectionButton()
|
||||
verifySettingsOptionSummary("Enhanced Tracking Protection", "Standard")
|
||||
verifySitePermissionsButton()
|
||||
verifyDeleteBrowsingDataButton()
|
||||
verifyDeleteBrowsingDataOnQuitButton()
|
||||
verifySettingsOptionSummary("Delete browsing data on quit", "Off")
|
||||
verifyNotificationsButton()
|
||||
verifySettingsOptionSummary("Notifications", "Allowed")
|
||||
verifyDataCollectionButton()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyDataCollectionTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openSettingsSubMenuDataCollection {
|
||||
verifyDataCollectionView(
|
||||
true,
|
||||
true,
|
||||
"On",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyUsageAndTechnicalDataToggleTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openSettingsSubMenuDataCollection {
|
||||
verifyUsageAndTechnicalDataToggle(true)
|
||||
clickUsageAndTechnicalDataToggle()
|
||||
verifyUsageAndTechnicalDataToggle(false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyMarketingDataToggleTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openSettingsSubMenuDataCollection {
|
||||
verifyMarketingDataToggle(true)
|
||||
clickMarketingDataToggle()
|
||||
verifyMarketingDataToggle(false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyStudiesToggleTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openSettingsSubMenuDataCollection {
|
||||
verifyDataCollectionView(
|
||||
true,
|
||||
true,
|
||||
"On",
|
||||
)
|
||||
clickStudiesOption()
|
||||
verifyStudiesToggle(true)
|
||||
clickStudiesToggle()
|
||||
verifyStudiesDialog()
|
||||
clickStudiesDialogCancelButton()
|
||||
verifyStudiesToggle(true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sitePermissionsItemsTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openSettingsSubMenuSitePermissions {
|
||||
verifySitePermissionsToolbarTitle()
|
||||
verifyToolbarGoBackButton()
|
||||
verifySitePermissionOption("Autoplay", "Block audio only")
|
||||
verifySitePermissionOption("Camera", "Blocked by Android")
|
||||
verifySitePermissionOption("Location", "Blocked by Android")
|
||||
verifySitePermissionOption("Microphone", "Blocked by Android")
|
||||
verifySitePermissionOption("Notification", "Ask to allow")
|
||||
verifySitePermissionOption("Persistent Storage", "Ask to allow")
|
||||
verifySitePermissionOption("Cross-site cookies", "Ask to allow")
|
||||
verifySitePermissionOption("DRM-controlled content", "Ask to allow")
|
||||
verifySitePermissionOption("Exceptions")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notificationPermissionsItemsTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
}.openSettingsSubMenuSitePermissions {
|
||||
}.openNotification {
|
||||
verifyNotificationSubMenuItems()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,236 +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.Ignore
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||
import org.mozilla.fenix.ui.robots.homeScreen
|
||||
|
||||
/**
|
||||
* Tests for verifying the main three dot menu options
|
||||
*
|
||||
*/
|
||||
|
||||
class SettingsTest {
|
||||
/* ktlint-disable no-blank-line-before-rbrace */ // This imposes unreadable grouping.
|
||||
|
||||
private lateinit var mDevice: UiDevice
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
|
||||
@get:Rule
|
||||
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
mockWebServer = MockWebServer().apply {
|
||||
dispatcher = AndroidAssetDispatcher()
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
|
||||
// Walks through settings privacy menu and sub-menus to ensure all items are present
|
||||
@Test
|
||||
fun settingsPrivacyItemsTest() {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
// PRIVACY
|
||||
verifyPrivacyHeading()
|
||||
|
||||
// PRIVATE BROWSING
|
||||
verifyPrivateBrowsingButton()
|
||||
}.openPrivateBrowsingSubMenu {
|
||||
verifyNavigationToolBarHeader()
|
||||
}.goBack {
|
||||
// HTTPS-Only Mode
|
||||
verifyHTTPSOnlyModeButton()
|
||||
verifyHTTPSOnlyModeState("Off")
|
||||
|
||||
// ENHANCED TRACKING PROTECTION
|
||||
verifyEnhancedTrackingProtectionButton()
|
||||
verifyEnhancedTrackingProtectionState("Standard")
|
||||
}.openEnhancedTrackingProtectionSubMenu {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifyEnhancedTrackingProtectionProtectionSubMenuItems()
|
||||
|
||||
// ENHANCED TRACKING PROTECTION EXCEPTION
|
||||
}.openExceptions {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifyEnhancedTrackingProtectionProtectionExceptionsSubMenuItems()
|
||||
}.goBack {
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS
|
||||
verifySitePermissionsButton()
|
||||
}.openSettingsSubMenuSitePermissions {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifySitePermissionsSubMenuItems()
|
||||
|
||||
// SITE PERMISSIONS AUTOPLAY
|
||||
}.openAutoPlay {
|
||||
verifyNavigationToolBarHeader("Autoplay")
|
||||
verifySitePermissionsAutoPlaySubMenuItems()
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS CAMERA
|
||||
}.openCamera {
|
||||
verifyNavigationToolBarHeader("Camera")
|
||||
verifySitePermissionsCommonSubMenuItems()
|
||||
verifyToggleNameToON("3. Toggle Camera to ON")
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS LOCATION
|
||||
}.openLocation {
|
||||
verifyNavigationToolBarHeader("Location")
|
||||
verifySitePermissionsCommonSubMenuItems()
|
||||
verifyToggleNameToON("3. Toggle Location to ON")
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS MICROPHONE
|
||||
}.openMicrophone {
|
||||
verifyNavigationToolBarHeader("Microphone")
|
||||
verifySitePermissionsCommonSubMenuItems()
|
||||
verifyToggleNameToON("3. Toggle Microphone to ON")
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS NOTIFICATION
|
||||
}.openNotification {
|
||||
verifyNavigationToolBarHeader("Notification")
|
||||
verifySitePermissionsNotificationSubMenuItems()
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS PERSISTENT STORAGE
|
||||
}.openPersistentStorage {
|
||||
verifyNavigationToolBarHeader("Persistent Storage")
|
||||
verifySitePermissionsPersistentStorageSubMenuItems()
|
||||
}.goBack {
|
||||
// SITE PERMISSIONS EXCEPTIONS
|
||||
}.openExceptions {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifySitePermissionsExceptionSubMenuItems()
|
||||
}.goBack {
|
||||
}.goBack {
|
||||
// DELETE BROWSING DATA
|
||||
verifyDeleteBrowsingDataButton()
|
||||
}.openSettingsSubMenuDeleteBrowsingData {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifyDeleteBrowsingDataSubMenuItems()
|
||||
}.goBack {
|
||||
// DELETE BROWSING DATA ON QUIT
|
||||
verifyDeleteBrowsingDataOnQuitButton()
|
||||
verifyDeleteBrowsingDataOnQuitState("Off")
|
||||
}.openSettingsSubMenuDeleteBrowsingDataOnQuit {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifyDeleteBrowsingDataOnQuitSubMenuItems()
|
||||
}.goBack {
|
||||
// NOTIFICATIONS
|
||||
verifyNotificationsButton()
|
||||
}.openSettingsSubMenuNotifications {
|
||||
verifySystemNotificationsView()
|
||||
}.goBack {
|
||||
// DATA COLLECTION
|
||||
verifyDataCollectionButton()
|
||||
}.openSettingsSubMenuDataCollection {
|
||||
verifyNavigationToolBarHeader()
|
||||
verifyDataCollectionSubMenuItems()
|
||||
}.goBack {
|
||||
}.goBack {
|
||||
verifyHomeComponent()
|
||||
}
|
||||
}
|
||||
|
||||
// Walks through settings menu and sub-menus to ensure all items are present
|
||||
@Ignore("This is a stub test, ignore for now")
|
||||
@Test
|
||||
fun settingsMenusItemsTest() {
|
||||
// SYNC
|
||||
|
||||
// see: SettingsSyncTest
|
||||
|
||||
// BASICS
|
||||
|
||||
// see: SettingsBasicsTest
|
||||
|
||||
// PRIVACY
|
||||
|
||||
// see: SettingsPrivacyTest
|
||||
|
||||
// DEVELOPER TOOLS
|
||||
|
||||
// Verify header: "Developer Tools"
|
||||
// Verify item: "Remote debugging via USB" and default toggle value: "Off"
|
||||
|
||||
// ABOUT
|
||||
|
||||
// Verify header: "About"
|
||||
// Verify item: "Help"
|
||||
// Verify item: "Rate on Google Play"
|
||||
// Verify item: "About Firefox Preview"
|
||||
//
|
||||
}
|
||||
|
||||
// SYNC
|
||||
// see: SettingsSyncTest
|
||||
|
||||
// BASICS
|
||||
// see: SettingsBasicsTest
|
||||
//
|
||||
// PRIVACY
|
||||
// see: SettingsPrivacyTest
|
||||
|
||||
// DEVELOPER TOOLS
|
||||
@Ignore("This is a stub test, ignore for now")
|
||||
@Test
|
||||
fun turnOnRemoteDebuggingViaUsb() {
|
||||
// Open terminal
|
||||
// Verify USB debugging is off
|
||||
// Open 3dot (main) menu
|
||||
// Select settings
|
||||
// Toggle Remote debugging via USB to 'on'
|
||||
// Open terminal
|
||||
// Verify USB debugging is on
|
||||
}
|
||||
|
||||
// ABOUT
|
||||
@Ignore("This is a stub test, ignore for now")
|
||||
@Test
|
||||
fun verifyHelpRedirect() {
|
||||
// Open 3dot (main) menu
|
||||
// Select settings
|
||||
// Click on "Help"
|
||||
// Verify redirect to: https://support.mozilla.org/
|
||||
}
|
||||
|
||||
@Ignore("This is a stub test, ignore for now")
|
||||
@Test
|
||||
fun verifyRateOnGooglePlayRedirect() {
|
||||
// Open 3dot (main) menu
|
||||
// Select settings
|
||||
// Click on "Rate on Google Play"
|
||||
// Verify Android "Open with Google Play Store" sub menu
|
||||
}
|
||||
|
||||
@Ignore("This is a stub test, ignore for now")
|
||||
@Test
|
||||
fun verifyAboutFirefoxPreview() {
|
||||
// Open 3dot (main) menu
|
||||
// Select settings
|
||||
// Click on "Verify About Firefox Preview"
|
||||
// Verify about page contains....
|
||||
// Build #
|
||||
// Version #
|
||||
// "Firefox Preview is produced by Mozilla"
|
||||
// Day, Date, timestamp
|
||||
// "Open source libraries we use"
|
||||
}
|
||||
}
|
@ -0,0 +1,225 @@
|
||||
package org.mozilla.fenix.ui
|
||||
|
||||
import androidx.core.net.toUri
|
||||
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.customannotations.SmokeTest
|
||||
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
|
||||
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||
import org.mozilla.fenix.helpers.RetryTestRule
|
||||
import org.mozilla.fenix.helpers.TestAssetHelper
|
||||
import org.mozilla.fenix.ui.robots.homeScreen
|
||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||
import org.mozilla.fenix.ui.robots.openEditURLView
|
||||
import org.mozilla.fenix.ui.robots.searchScreen
|
||||
|
||||
class TextSelectionTest {
|
||||
private lateinit var mDevice: UiDevice
|
||||
private lateinit var mockWebServer: MockWebServer
|
||||
private val downloadTestPage =
|
||||
"https://storage.googleapis.com/mobile_test_assets/test_app/downloads.html"
|
||||
private val pdfFileName = "washington.pdf"
|
||||
private val pdfFileURL = "storage.googleapis.com/mobile_test_assets/public/washington.pdf"
|
||||
private val pdfFileContent = "Washington Crossing the Delaware"
|
||||
|
||||
@get:Rule
|
||||
val activityIntentTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val retryTestRule = RetryTestRule(3)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
mockWebServer = MockWebServer().apply {
|
||||
dispatcher = AndroidAssetDispatcher()
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
mockWebServer.shutdown()
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun selectAllAndCopyTextTest() {
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
longClickAndCopyText("content", true)
|
||||
}.openNavigationToolbar {
|
||||
openEditURLView()
|
||||
}
|
||||
|
||||
searchScreen {
|
||||
clickClearButton()
|
||||
longClickToolbar()
|
||||
clickPasteText()
|
||||
// With Select all, white spaces are copied
|
||||
// Potential bug https://bugzilla.mozilla.org/show_bug.cgi?id=1821310
|
||||
verifyTypedToolbarText(" Page content: 1 ")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun copyTextTest() {
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
longClickAndCopyText("content")
|
||||
}.openNavigationToolbar {
|
||||
openEditURLView()
|
||||
}
|
||||
|
||||
searchScreen {
|
||||
clickClearButton()
|
||||
longClickToolbar()
|
||||
clickPasteText()
|
||||
verifyTypedToolbarText("content")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun shareSelectedTextTest() {
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
longClickLink(genericURL.content)
|
||||
}.clickShareSelectedText {
|
||||
verifyAndroidShareLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun selectAndSearchTextTest() {
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
longClickAndSearchText("Search", "content")
|
||||
mDevice.waitForIdle()
|
||||
verifyTabCounter("2")
|
||||
verifyUrl("google")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun privateSelectAndSearchTextTest() {
|
||||
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
|
||||
|
||||
homeScreen {
|
||||
}.togglePrivateBrowsingMode()
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(genericURL.url) {
|
||||
longClickAndSearchText("Private Search", "content")
|
||||
mDevice.waitForIdle()
|
||||
verifyTabCounter("2")
|
||||
verifyUrl("google")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun selectAllAndCopyPDFTextTest() {
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
|
||||
clickLinkMatchingText(pdfFileName)
|
||||
verifyUrl(pdfFileURL)
|
||||
verifyPageContent(pdfFileContent)
|
||||
longClickAndCopyText("Crossing", true)
|
||||
}.openNavigationToolbar {
|
||||
openEditURLView()
|
||||
}
|
||||
|
||||
searchScreen {
|
||||
clickClearButton()
|
||||
longClickToolbar()
|
||||
clickPasteText()
|
||||
// With Select all, white spaces are copied
|
||||
// Potential bug https://bugzilla.mozilla.org/show_bug.cgi?id=1821310
|
||||
verifyTypedToolbarText(" Washington Crossing the Delaware ")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun copyPDFTextTest() {
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
|
||||
clickLinkMatchingText(pdfFileName)
|
||||
verifyUrl(pdfFileURL)
|
||||
verifyPageContent(pdfFileContent)
|
||||
longClickAndCopyText("Crossing")
|
||||
}.openNavigationToolbar {
|
||||
openEditURLView()
|
||||
}
|
||||
|
||||
searchScreen {
|
||||
clickClearButton()
|
||||
longClickToolbar()
|
||||
clickPasteText()
|
||||
verifyTypedToolbarText("Crossing")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun shareSelectedPDFTextTest() {
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
|
||||
clickLinkMatchingText(pdfFileName)
|
||||
verifyUrl(pdfFileURL)
|
||||
verifyPageContent(pdfFileContent)
|
||||
longClickMatchingText("Crossing")
|
||||
}.clickShareSelectedText {
|
||||
verifyAndroidShareLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun selectAndSearchPDFTextTest() {
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
|
||||
clickLinkMatchingText(pdfFileName)
|
||||
verifyUrl(pdfFileURL)
|
||||
verifyPageContent(pdfFileContent)
|
||||
longClickAndSearchText("Search", "Crossing")
|
||||
verifyTabCounter("3")
|
||||
verifyUrl("google")
|
||||
}
|
||||
}
|
||||
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun privateSelectAndSearchPDFTextTest() {
|
||||
homeScreen {
|
||||
}.togglePrivateBrowsingMode()
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser(downloadTestPage.toUri()) {
|
||||
clickLinkMatchingText(pdfFileName)
|
||||
verifyUrl(pdfFileURL)
|
||||
verifyPageContent(pdfFileContent)
|
||||
longClickAndSearchText("Private Search", "Crossing")
|
||||
verifyTabCounter("3")
|
||||
verifyUrl("google")
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,100 @@
|
||||
/* 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.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* Popup action dropdown menu.
|
||||
*
|
||||
* @param menuItems List of items to be displayed in the menu.
|
||||
* @param showMenu Whether or not the menu is currently displayed to the user.
|
||||
* @param onDismissRequest Invoked when user dismisses the menu or on orientation changes.
|
||||
* @param modifier Modifier to be applied to the menu.
|
||||
*/
|
||||
@Composable
|
||||
fun DropdownMenu(
|
||||
menuItems: List<MenuItem>,
|
||||
showMenu: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DisposableEffect(LocalConfiguration.current.orientation) {
|
||||
onDispose { onDismissRequest() }
|
||||
}
|
||||
|
||||
MaterialTheme(shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(2.dp))) {
|
||||
DropdownMenu(
|
||||
expanded = showMenu && menuItems.isNotEmpty(),
|
||||
onDismissRequest = { onDismissRequest() },
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer2)
|
||||
.then(modifier),
|
||||
) {
|
||||
for (item in menuItems) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
item.onClick()
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
color = item.color ?: FirefoxTheme.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
style = FirefoxTheme.typography.subtitle1,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a text item from the dropdown menu.
|
||||
*
|
||||
* @property title Text the item should display.
|
||||
* @property color Color used to display the text.
|
||||
* @property onClick Callback to be called when the item is clicked.
|
||||
*/
|
||||
data class MenuItem(
|
||||
val title: String,
|
||||
val color: Color? = null,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@LightDarkPreview
|
||||
@Composable
|
||||
private fun DropdownMenuPreview() {
|
||||
FirefoxTheme {
|
||||
DropdownMenu(
|
||||
listOf(
|
||||
MenuItem("Rename") {},
|
||||
MenuItem("Share") {},
|
||||
MenuItem("Remove", FirefoxTheme.colors.textWarning) {},
|
||||
),
|
||||
true,
|
||||
{},
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/* 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.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* An horizontally laid out indicator for a [HorizontalPager] with the ability to leave the trail of
|
||||
* indicators to show progress, instead of just showing the current one as active.
|
||||
*
|
||||
* @param pagerState The state object of your [HorizontalPager] to be used to observe the list's state.
|
||||
* @param modifier The modifier to apply to this layout.
|
||||
* @param pageCount The size of indicators should be displayed, defaults to [PagerState.pageCount].
|
||||
* If you are implementing a looping pager with a much larger [PagerState.pageCount]
|
||||
* than indicators should displayed, e.g. [Int.MAX_VALUE], specify you real size in this param.
|
||||
* @param activeColor The color of the active page indicator, and the color of previous page
|
||||
* indicators in case [leaveTrail] is set to true.
|
||||
* @param inactiveColor The color of page indicators that are inactive.
|
||||
* @param leaveTrail Whether to leave the trail of indicators to show progress.
|
||||
* This defaults to false and just shows the current one as active.
|
||||
*/
|
||||
@Composable
|
||||
fun PagerIndicator(
|
||||
pagerState: PagerState,
|
||||
modifier: Modifier = Modifier,
|
||||
pageCount: Int = pagerState.pageCount,
|
||||
activeColor: Color,
|
||||
inactiveColor: Color,
|
||||
leaveTrail: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val showActiveModifier: (pageIndex: Int) -> Boolean =
|
||||
if (leaveTrail) {
|
||||
{ it <= pagerState.currentPage }
|
||||
} else {
|
||||
{ it == pagerState.currentPage }
|
||||
}
|
||||
|
||||
repeat(pageCount) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(6.dp)
|
||||
.background(
|
||||
shape = CircleShape,
|
||||
color = if (showActiveModifier(it)) {
|
||||
activeColor
|
||||
} else {
|
||||
inactiveColor
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@LightDarkPreview
|
||||
@Composable
|
||||
private fun PagerIndicatorPreview() {
|
||||
FirefoxTheme {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(FirefoxTheme.colors.layer1)
|
||||
.padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = "Without trail",
|
||||
style = FirefoxTheme.typography.caption,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
PagerIndicator(
|
||||
pagerState = rememberPagerState(1),
|
||||
pageCount = 3,
|
||||
activeColor = FirefoxTheme.colors.actionPrimary,
|
||||
inactiveColor = FirefoxTheme.colors.actionSecondary,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = "With trail",
|
||||
style = FirefoxTheme.typography.caption,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
PagerIndicator(
|
||||
pagerState = rememberPagerState(1),
|
||||
pageCount = 3,
|
||||
activeColor = FirefoxTheme.colors.actionPrimary,
|
||||
inactiveColor = FirefoxTheme.colors.actionSecondary,
|
||||
leaveTrail = true,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/* 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.button
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material.FloatingActionButton
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* Floating action button.
|
||||
*
|
||||
* @param icon [Painter] icon to be displayed inside the action button.
|
||||
* @param modifier [Modifier] to be applied to the action button.
|
||||
* @param label Text to be displayed next to the icon.
|
||||
* @param onClick Invoked when the button is clicked.
|
||||
*/
|
||||
@Composable
|
||||
fun FloatingActionButton(
|
||||
icon: Painter,
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null,
|
||||
label: String? = null,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.testTag("button.fab").then(modifier),
|
||||
backgroundColor = FirefoxTheme.colors.actionPrimary,
|
||||
contentColor = FirefoxTheme.colors.textActionPrimary,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(16.dp)
|
||||
.animateContentSize(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = icon,
|
||||
contentDescription = contentDescription,
|
||||
tint = FirefoxTheme.colors.iconOnColor,
|
||||
)
|
||||
|
||||
if (!label.isNullOrBlank()) {
|
||||
Spacer(Modifier.width(12.dp))
|
||||
|
||||
Text(
|
||||
text = label,
|
||||
style = FirefoxTheme.typography.button,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@LightDarkPreview
|
||||
@Composable
|
||||
private fun FloatingActionButtonPreview() {
|
||||
var label by remember { mutableStateOf<String?>("LABEL") }
|
||||
|
||||
FirefoxTheme {
|
||||
Box(Modifier.wrapContentSize()) {
|
||||
FloatingActionButton(
|
||||
label = label,
|
||||
icon = painterResource(R.drawable.ic_new),
|
||||
onClick = {
|
||||
label = if (label == null) "LABEL" else null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/* 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.experiments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.experiments.view.ResearchSurfaceSurvey
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* Dialog displaying the fullscreen research surface message.
|
||||
*/
|
||||
|
||||
class ResearchSurfaceDialogFragment : DialogFragment() {
|
||||
private val args by navArgs<ResearchSurfaceDialogFragmentArgs>()
|
||||
private lateinit var bundleArgs: Bundle
|
||||
|
||||
/**
|
||||
* A callback to trigger the 'Take Survey' button of the dialog.
|
||||
*/
|
||||
var onAccept: () -> Unit = {}
|
||||
|
||||
/**
|
||||
* A callback to trigger the 'No Thanks' button of the dialog.
|
||||
*/
|
||||
var onDismiss: () -> Unit = {}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, R.style.ResearchSurfaceDialogStyle)
|
||||
|
||||
bundleArgs = args.toBundle()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?,
|
||||
): View = ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
|
||||
val messageText = bundleArgs.getString(KEY_MESSAGE_TEXT)
|
||||
?: getString(R.string.nimbus_survey_message_text)
|
||||
val acceptButtonText = bundleArgs.getString(KEY_ACCEPT_BUTTON_TEXT)
|
||||
?: getString(R.string.preferences_take_survey)
|
||||
val dismissButtonText = bundleArgs.getString(KEY_DISMISS_BUTTON_TEXT)
|
||||
?: getString(R.string.preferences_not_take_survey)
|
||||
|
||||
setContent {
|
||||
FirefoxTheme {
|
||||
ResearchSurfaceSurvey(
|
||||
messageText = messageText,
|
||||
onAcceptButtonText = acceptButtonText,
|
||||
onDismissButtonText = dismissButtonText,
|
||||
onDismiss = {
|
||||
onDismiss()
|
||||
dismiss()
|
||||
},
|
||||
onAccept = {
|
||||
onAccept()
|
||||
dismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* A builder method for creating a [ResearchSurfaceDialogFragment]
|
||||
*/
|
||||
fun newInstance(
|
||||
keyMessageText: String?,
|
||||
keyAcceptButtonText: String?,
|
||||
keyDismissButtonText: String?,
|
||||
): ResearchSurfaceDialogFragment {
|
||||
val fragment = ResearchSurfaceDialogFragment()
|
||||
val arguments = fragment.arguments ?: Bundle()
|
||||
|
||||
with(arguments) {
|
||||
putString(KEY_MESSAGE_TEXT, keyMessageText)
|
||||
|
||||
putString(KEY_ACCEPT_BUTTON_TEXT, keyAcceptButtonText)
|
||||
|
||||
putString(KEY_DISMISS_BUTTON_TEXT, keyDismissButtonText)
|
||||
}
|
||||
|
||||
fragment.arguments = arguments
|
||||
fragment.isCancelable = false
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
private const val KEY_MESSAGE_TEXT = "KEY_MESSAGE_TEXT"
|
||||
private const val KEY_ACCEPT_BUTTON_TEXT = "KEY_ACCEPT_BUTTON_TEXT"
|
||||
private const val KEY_DISMISS_BUTTON_TEXT = "KEY_DISMISS_BUTTON_TEXT"
|
||||
const val FRAGMENT_TAG = "MOZAC_RESEARCH_SURFACE_DIALOG_FRAGMENT"
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
/* 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.experiments.view
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.compose.button.PrimaryButton
|
||||
import org.mozilla.fenix.compose.button.SecondaryButton
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* The ratio of the content height to screen height. This was determined from the designs in figma
|
||||
* taking the top and bottom padding to be 10% of screen height.
|
||||
*/
|
||||
private const val FULLSCREEN_HEIGHT = 0.8f
|
||||
|
||||
/**
|
||||
* The ratio of the button width to screen width. This was determined from the designs in figma
|
||||
* taking the horizontal button paddings to be 5% of the screen width.
|
||||
*/
|
||||
private const val BUTTON_WIDTH = 0.9f
|
||||
|
||||
/**
|
||||
* Values used in slide in animation.
|
||||
* These values were confirmed through demo builds and UX review.
|
||||
*/
|
||||
private const val INITIAL_OFFSET = 1000
|
||||
private const val ANIMATION_DURATION_MS = 500
|
||||
|
||||
@Composable
|
||||
private fun SlideInFromBottomAnimation(
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
var offsetY by remember { mutableStateOf(INITIAL_OFFSET) }
|
||||
val offsetState by animateDpAsState(
|
||||
targetValue = offsetY.dp,
|
||||
animationSpec = tween(durationMillis = ANIMATION_DURATION_MS),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.offset(y = offsetState),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
offsetY = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A full screen for displaying a research surface.
|
||||
*
|
||||
* @param messageText The research surface message text to be displayed.
|
||||
* @param onAcceptButtonText A positive button text of the fullscreen message.
|
||||
* @param onDismissButtonText A negative button text of the fullscreen message.
|
||||
* @param onDismiss Invoked when the user clicks on the "No Thanks" button.
|
||||
* @param onAccept Invoked when the user clicks on the "Take Survey" button
|
||||
*/
|
||||
@Composable
|
||||
fun ResearchSurfaceSurvey(
|
||||
messageText: String,
|
||||
onAcceptButtonText: String,
|
||||
onDismissButtonText: String,
|
||||
onDismiss: () -> Unit,
|
||||
onAccept: () -> Unit,
|
||||
) {
|
||||
SlideInFromBottomAnimation {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.statusBarsPadding()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(FULLSCREEN_HEIGHT)
|
||||
.align(Alignment.Center)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Spacer(Modifier)
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_firefox),
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = messageText,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Center,
|
||||
style = FirefoxTheme.typography.headline6,
|
||||
)
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth(BUTTON_WIDTH),
|
||||
) {
|
||||
PrimaryButton(
|
||||
text = onAcceptButtonText,
|
||||
onClick = onAccept,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SecondaryButton(
|
||||
text = onDismissButtonText,
|
||||
onClick = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@LightDarkPreview
|
||||
private fun SurveyPreview() {
|
||||
FirefoxTheme {
|
||||
ResearchSurfaceSurvey(
|
||||
messageText = stringResource(id = R.string.nimbus_survey_message_text),
|
||||
onAcceptButtonText = stringResource(id = R.string.preferences_take_survey),
|
||||
onDismissButtonText = stringResource(id = R.string.preferences_not_take_survey),
|
||||
onDismiss = {},
|
||||
onAccept = {},
|
||||
)
|
||||
}
|
||||
}
|
@ -1,62 +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.gleanplumb
|
||||
|
||||
import org.mozilla.fenix.nimbus.MessageData
|
||||
import org.mozilla.fenix.nimbus.MessageSurfaceId
|
||||
import org.mozilla.fenix.nimbus.StyleData
|
||||
|
||||
/**
|
||||
* A data class that holds a representation of GleanPlum message from Nimbus.
|
||||
*
|
||||
* @param id identifies a message as unique.
|
||||
* @param data Data information provided from Nimbus.
|
||||
* @param action A strings that represents which action should be performed
|
||||
* after a message is clicked.
|
||||
* @param style Indicates how a message should be styled.
|
||||
* @param triggers A list of strings corresponding to targeting expressions. The message
|
||||
* will be shown if all expressions `true`.
|
||||
* @param metadata Metadata that help to identify if a message should shown.
|
||||
*/
|
||||
data class Message(
|
||||
val id: String,
|
||||
val data: MessageData,
|
||||
val action: String,
|
||||
val style: StyleData,
|
||||
val triggers: List<String>,
|
||||
val metadata: Metadata,
|
||||
) {
|
||||
val maxDisplayCount: Int
|
||||
get() = style.maxDisplayCount
|
||||
|
||||
val priority: Int
|
||||
get() = style.priority
|
||||
|
||||
val surface: MessageSurfaceId
|
||||
get() = data.surface
|
||||
|
||||
val isExpired: Boolean
|
||||
get() = metadata.displayCount >= maxDisplayCount
|
||||
|
||||
/**
|
||||
* A data class that holds metadata that help to identify if a message should shown.
|
||||
*
|
||||
* @param id identifies a message as unique.
|
||||
* @param displayCount Indicates how many times a message is displayed.
|
||||
* @param pressed Indicates if a message has been clicked.
|
||||
* @param dismissed Indicates if a message has been closed.
|
||||
* @param lastTimeShown A timestamp indicating when was the last time, the message was shown.
|
||||
* @param latestBootIdentifier A unique boot identifier for when the message was last displayed
|
||||
* (this may be a boot count or a boot id).
|
||||
*/
|
||||
data class Metadata(
|
||||
val id: String,
|
||||
val displayCount: Int = 0,
|
||||
val pressed: Boolean = false,
|
||||
val dismissed: Boolean = false,
|
||||
val lastTimeShown: Long = 0L,
|
||||
val latestBootIdentifier: String? = null,
|
||||
)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue