Loading mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +275 −3 Original line number Diff line number Diff line Loading @@ -12,33 +12,65 @@ import android.content.res.Configuration import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getColor import androidx.core.view.children import androidx.core.view.doOnLayout import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment Loading Loading @@ -108,6 +140,7 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.PrivateShortcutCreateManager import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.components import org.mozilla.fenix.components.menu.MenuAccessPoint import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.ToolbarPosition Loading Loading @@ -151,7 +184,6 @@ import org.mozilla.fenix.messaging.DefaultMessageController import org.mozilla.fenix.messaging.FenixMessageSurfaceId import org.mozilla.fenix.messaging.MessagingFeature import org.mozilla.fenix.microsurvey.ui.MicrosurveyRequestPrompt import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.SearchSelectorMenu Loading @@ -160,12 +192,16 @@ import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.tor.TorBootstrapFragmentDirections import org.mozilla.fenix.tor.TorBootstrapStatus import org.mozilla.fenix.tor.CampaignStrings import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.wallpapers.Wallpaper import java.lang.ref.WeakReference import org.mozilla.fenix.GleanMetrics.TabStrip as TabStripMetrics import java.text.SimpleDateFormat import java.util.Date @Suppress("TooManyFunctions", "LargeClass") class HomeFragment : Fragment(), UserInteractionHandler { private val args by navArgs<HomeFragmentArgs>() Loading Loading @@ -513,6 +549,8 @@ class HomeFragment : Fragment(), UserInteractionHandler { activity.themeManager.applyStatusBarTheme(activity) tryShowUX2025Survey() // FxNimbus.features.homescreen.recordExposure() // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! Loading @@ -524,6 +562,31 @@ class HomeFragment : Fragment(), UserInteractionHandler { return binding.root } private fun tryShowUX2025Survey() { val allowedLocales = arrayListOf("en", "es", "ru", "fr", "pt") val locale = CampaignStrings.getLocale() val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz") val startDate = dateFormat.parse("2025-04-14-12-UTC") val endDate = dateFormat.parse("2025-04-28-00-UTC") val currentDate = Date() if (currentDate.before(startDate) || currentDate.after(endDate)) { return // comment out to test } if (allowedLocales.contains(locale) && !requireContext().settings().hideCampaign) { binding.onionPatternImage.visibility = View.GONE binding.campaignBox.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { CampaignBox() } } } } private fun reinitializeNavBar() { initializeNavBar(activity = requireActivity() as HomeActivity) } Loading Loading @@ -1402,4 +1465,213 @@ class HomeFragment : Fragment(), UserInteractionHandler { requireActivity().finish() return true } @Composable fun CampaignBox() { BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .wrapContentHeight() ) { val alternateLayout = this.maxWidth >= 500.dp CampaignLayout( alternateLayout, maxWidth = this.maxWidth, modifier = Modifier .padding(top = if (alternateLayout) 65.dp else 55.dp, bottom = 56.dp), ) } } @Composable private fun CampaignLayout( alternateLayout: Boolean, maxWidth: Dp, modifier: Modifier ) { Column( modifier = modifier .padding(horizontal = 22.dp) .verticalScroll(rememberScrollState()) .fillMaxWidth(getVariableWidth(maxWidth)), horizontalAlignment = Alignment.CenterHorizontally, ) { PurpleBox(alternateLayout) } } private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f) @Composable private fun PurpleBox( alternateLayout: Boolean, ) { Box( modifier = Modifier.background(PhotonColors.Violet90, shape = RoundedCornerShape(8.dp)) .padding(16.dp), ) { Row( modifier = Modifier.fillMaxWidth(), ) { Emoji() Spacer(Modifier.weight(1f)) ExitIcon() } DynamicCampaignContent(alternateLayout) } } @Composable private fun Emoji() { val alpha38Violet40 = Color(PhotonColors.Violet40.red, PhotonColors.Violet40.green, PhotonColors.Violet40.blue, 0.38f) Image( painter = painterResource(id = R.drawable.campaign_hand), contentDescription = null, modifier = Modifier .size(64.dp) .padding(16.dp) .drawBehind { drawCircle( color = alpha38Violet40, radius = this.size.maxDimension ) } ) } @Composable private fun ExitIcon() { IconButton( onClick = { binding.campaignBox.visibility = View.GONE binding.onionPatternImage.visibility = View.VISIBLE context?.components?.settings?.hideCampaign = true }, ) { Icon( painter = painterResource(id = R.drawable.ic_close), tint = Color( getColor( requireContext(), R.color.photonWhite, ), ), contentDescription = CampaignStrings.get(CampaignStrings.CloseKey), modifier = Modifier .size(48.dp) .padding(8.dp) ) } } @Composable private fun DynamicCampaignContent( alternateLayout: Boolean ) { Row(verticalAlignment = Alignment.CenterVertically) { Column( modifier = Modifier.fillMaxWidth() .padding( top = 88.dp), horizontalAlignment = Alignment.Start, ) { TitleText() MainText() if (alternateLayout) { Row(modifier = Modifier.fillMaxWidth()) { Button1(alternateLayout) Button2() } } else { Button1(alternateLayout) Button2() } } } } @Composable private fun TitleText() { Text(text = CampaignStrings.get(CampaignStrings.HeaderKey), color = PhotonColors.LightGrey05, textAlign = TextAlign.Left, fontWeight = FontWeight.Bold, fontSize = 24.sp, lineHeight = 34.sp, modifier = Modifier.padding(bottom = 16.dp) ) } @Composable private fun MainText() { Text(text = CampaignStrings.get(CampaignStrings.BodyKey), modifier = Modifier .fillMaxWidth() .padding( start = 0.dp, end = 0.dp, bottom = 18.dp, ), color = PhotonColors.LightGrey05, fontSize = 18.sp, textAlign = TextAlign.Left, ) } @Composable private fun Button1(alternateLayout: Boolean) { Button( onClick = { var locale = CampaignStrings.getLocale() if (locale == "pt") { locale = "pt-BR" } (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = "https://survey.torproject.org/index.php/923269?lang=${locale}", newTab = true, from = BrowserDirection.FromHome, ) }, colors = ButtonDefaults.buttonColors( backgroundColor = PhotonColors.Violet60), shape = RoundedCornerShape(4.dp), modifier = Modifier.padding(0.dp) .fillMaxWidth(fraction = if (alternateLayout) 0.5f else 1f), ) { Text(text = CampaignStrings.get(CampaignStrings.CTAKey), color = PhotonColors.LightGrey05, textAlign = TextAlign.Center, fontSize = 18.sp, modifier = Modifier.padding(8.dp)) } } @Composable private fun Button2() { Button( onClick = { binding.campaignBox.visibility = View.GONE binding.onionPatternImage.visibility = View.VISIBLE context?.components?.settings?.hideCampaign = true }, colors = ButtonDefaults.buttonColors( backgroundColor = PhotonColors.Violet90), shape = RoundedCornerShape(4.dp), modifier = Modifier.padding(0.dp) .fillMaxWidth() ) { Text(text = CampaignStrings.get(CampaignStrings.DismissKey), color = PhotonColors.Violet20, textAlign = TextAlign.Center, fontSize = 18.sp, modifier = Modifier.padding(8.dp)) } } } mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt 0 → 100644 +64 −0 Original line number Diff line number Diff line package org.mozilla.fenix.tor import java.util.Locale object CampaignStrings { val HeaderKey = "key_header" val BodyKey = "key_body" val CTAKey = "key_cta" val DismissKey = "key_dismiss" val CloseKey = "key_close" private val translations: HashMap<String, HashMap<String, String>> = hashMapOf( "en" to hashMapOf( HeaderKey to "We’d love your feedback", BodyKey to "Help us improve Tor Browser by completing this 10-minute survey.", CTAKey to "Launch the survey", DismissKey to "Dismiss", CloseKey to "Close", ), "es" to hashMapOf( HeaderKey to "Danos tu opinión", BodyKey to "Ayúdanos a mejorar el Navegador Tor completando esta encuesta de 10 minutos.", CTAKey to "Iniciar la encuesta", DismissKey to "Descartar", CloseKey to "Cerrar", ), "ru" to hashMapOf( HeaderKey to "Мы будем рады вашим отзывам", BodyKey to "Помогите нам улучшить браузер Tor, пройдя 10-минутный опрос.", CTAKey to "Начать опрос", DismissKey to "Отклонить", CloseKey to "Закрыть", ), "fr" to hashMapOf( HeaderKey to "Nous serions ravis d’avoir votre avis !", BodyKey to "Aidez-nous à améliorer le navigateur Tor en répondant à cette enquête de 10 minutes.", CTAKey to "Lancer l'enquête", DismissKey to "Ignorer", CloseKey to "Fermer", ), "pt" to hashMapOf( HeaderKey to "Adoraríamos ouvir sua opinião", BodyKey to "Ajude-nos a melhorar o Navegador Tor respondendo a esta pesquisa de 10 minutos.", CTAKey to "Iniciar a pesquisa", DismissKey to "Dispensar", CloseKey to "Fechar" ), ) fun getLocale(): String { // TODO: do we care about spoofEnglish setting? return Locale.getDefault().getLanguage(); } fun get(key: String): String { val localeStrings = translations.get(getLocale()) if (localeStrings == null) { return "" } return localeStrings.get(key) ?: "" } } mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +5 −8 Original line number Diff line number Diff line Loading @@ -43,15 +43,7 @@ import org.mozilla.fenix.components.settings.lazyFeatureFlagPreference import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.nimbus.CookieBannersSection import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.HomeScreenSection import org.mozilla.fenix.nimbus.Mr2022Section import org.mozilla.fenix.nimbus.QueryParameterStrippingSection import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_ALLOW_LIST import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_PMB import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_STRIP_LIST import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu Loading Loading @@ -2111,4 +2103,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { appContext.getPreferenceKey(R.string.pref_key_use_html_connection_ui), default = false, ) var hideCampaign by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_hide_campaign_2025_ux_survey), default = false, ) } mobile/android/fenix/app/src/main/res/drawable/campaign_hand.xml 0 → 100644 +21 −0 Original line number Diff line number Diff line <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:viewportWidth="36" android:viewportHeight="36" android:width="36dp" android:height="36dp"> <path android:pathData="M4.861 9.147c0.94 -0.657 2.357 -0.531 3.201 0.166l-0.968 -1.407c-0.779 -1.111 -0.5 -2.313 0.612 -3.093 1.112 -0.777 4.263 1.312 4.263 1.312 -0.786 -1.122 -0.639 -2.544 0.483 -3.331 1.122 -0.784 2.67 -0.513 3.456 0.611l10.42 14.72L25 31l-11.083 -4.042L4.25 12.625c-0.793 -1.129 -0.519 -2.686 0.611 -3.478z" android:fillColor="#EF9645" /> <path android:pathData="M2.695 17.336s-1.132 -1.65 0.519 -2.781c1.649 -1.131 2.78 0.518 2.78 0.518l5.251 7.658c0.181 -0.302 0.379 -0.6 0.6 -0.894L4.557 11.21s-1.131 -1.649 0.519 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l6.855 9.997c0.255 -0.208 0.516 -0.417 0.785 -0.622L7.549 6.732s-1.131 -1.649 0.519 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l7.947 11.589c0.292 -0.179 0.581 -0.334 0.871 -0.498L12.238 4.729s-1.131 -1.649 0.518 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l7.854 11.454 1.194 1.742c-4.948 3.394 -5.419 9.779 -2.592 13.902 0.565 0.825 1.39 0.26 1.39 0.26 -3.393 -4.949 -2.357 -10.51 2.592 -13.903L24.515 8.62s-0.545 -1.924 1.378 -2.47c1.924 -0.545 2.47 1.379 2.47 1.379l1.685 5.004c0.668 1.984 1.379 3.961 2.32 5.831 2.657 5.28 1.07 11.842 -3.94 15.279 -5.465 3.747 -12.936 2.354 -16.684 -3.11L2.695 17.336z" android:fillColor="#FFDC5D" /> <path android:pathData="M12 32.042C8 32.042 3.958 28 3.958 24c0 -0.553 -0.405 -1 -0.958 -1s-1.042 0.447 -1.042 1C1.958 30 6 34.042 12 34.042c0.553 0 1 -0.489 1 -1.042s-0.447 -0.958 -1 -0.958z" android:fillColor="#5DADEC" /> <path android:pathData="M7 34c-3 0 -5 -2 -5 -5 0 -0.553 -0.447 -1 -1 -1s-1 0.447 -1 1c0 4 3 7 7 7 0.553 0 1 -0.447 1 -1s-0.447 -1 -1 -1zM24 2c-0.552 0 -1 0.448 -1 1s0.448 1 1 1c4 0 8 3.589 8 8 0 0.552 0.448 1 1 1s1 -0.448 1 -1c0 -5.514 -4 -10 -10 -10z" android:fillColor="#5DADEC" /> <path android:pathData="M29 0.042c-0.552 0 -1 0.406 -1 0.958s0.448 1.042 1 1.042c3 0 4.958 2.225 4.958 4.958 0 0.552 0.489 1 1.042 1s0.958 -0.448 0.958 -1C35.958 3.163 33 0.042 29 0.042z" android:fillColor="#5DADEC" /> </vector> No newline at end of file mobile/android/fenix/app/src/main/res/layout/fragment_home.xml +6 −0 Original line number Diff line number Diff line Loading @@ -134,6 +134,12 @@ </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/campaignBox" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/sessionControlRecyclerView" android:layout_width="match_parent" Loading Loading
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +275 −3 Original line number Diff line number Diff line Loading @@ -12,33 +12,65 @@ import android.content.res.Configuration import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.activity.result.ActivityResultLauncher import androidx.annotation.VisibleForTesting import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getColor import androidx.core.view.children import androidx.core.view.doOnLayout import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment Loading Loading @@ -108,6 +140,7 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.PrivateShortcutCreateManager import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.components import org.mozilla.fenix.components.menu.MenuAccessPoint import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.ToolbarPosition Loading Loading @@ -151,7 +184,6 @@ import org.mozilla.fenix.messaging.DefaultMessageController import org.mozilla.fenix.messaging.FenixMessageSurfaceId import org.mozilla.fenix.messaging.MessagingFeature import org.mozilla.fenix.microsurvey.ui.MicrosurveyRequestPrompt import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.SearchSelectorMenu Loading @@ -160,12 +192,16 @@ import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.tor.TorBootstrapFragmentDirections import org.mozilla.fenix.tor.TorBootstrapStatus import org.mozilla.fenix.tor.CampaignStrings import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.wallpapers.Wallpaper import java.lang.ref.WeakReference import org.mozilla.fenix.GleanMetrics.TabStrip as TabStripMetrics import java.text.SimpleDateFormat import java.util.Date @Suppress("TooManyFunctions", "LargeClass") class HomeFragment : Fragment(), UserInteractionHandler { private val args by navArgs<HomeFragmentArgs>() Loading Loading @@ -513,6 +549,8 @@ class HomeFragment : Fragment(), UserInteractionHandler { activity.themeManager.applyStatusBarTheme(activity) tryShowUX2025Survey() // FxNimbus.features.homescreen.recordExposure() // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! Loading @@ -524,6 +562,31 @@ class HomeFragment : Fragment(), UserInteractionHandler { return binding.root } private fun tryShowUX2025Survey() { val allowedLocales = arrayListOf("en", "es", "ru", "fr", "pt") val locale = CampaignStrings.getLocale() val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz") val startDate = dateFormat.parse("2025-04-14-12-UTC") val endDate = dateFormat.parse("2025-04-28-00-UTC") val currentDate = Date() if (currentDate.before(startDate) || currentDate.after(endDate)) { return // comment out to test } if (allowedLocales.contains(locale) && !requireContext().settings().hideCampaign) { binding.onionPatternImage.visibility = View.GONE binding.campaignBox.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { CampaignBox() } } } } private fun reinitializeNavBar() { initializeNavBar(activity = requireActivity() as HomeActivity) } Loading Loading @@ -1402,4 +1465,213 @@ class HomeFragment : Fragment(), UserInteractionHandler { requireActivity().finish() return true } @Composable fun CampaignBox() { BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .wrapContentHeight() ) { val alternateLayout = this.maxWidth >= 500.dp CampaignLayout( alternateLayout, maxWidth = this.maxWidth, modifier = Modifier .padding(top = if (alternateLayout) 65.dp else 55.dp, bottom = 56.dp), ) } } @Composable private fun CampaignLayout( alternateLayout: Boolean, maxWidth: Dp, modifier: Modifier ) { Column( modifier = modifier .padding(horizontal = 22.dp) .verticalScroll(rememberScrollState()) .fillMaxWidth(getVariableWidth(maxWidth)), horizontalAlignment = Alignment.CenterHorizontally, ) { PurpleBox(alternateLayout) } } private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f) @Composable private fun PurpleBox( alternateLayout: Boolean, ) { Box( modifier = Modifier.background(PhotonColors.Violet90, shape = RoundedCornerShape(8.dp)) .padding(16.dp), ) { Row( modifier = Modifier.fillMaxWidth(), ) { Emoji() Spacer(Modifier.weight(1f)) ExitIcon() } DynamicCampaignContent(alternateLayout) } } @Composable private fun Emoji() { val alpha38Violet40 = Color(PhotonColors.Violet40.red, PhotonColors.Violet40.green, PhotonColors.Violet40.blue, 0.38f) Image( painter = painterResource(id = R.drawable.campaign_hand), contentDescription = null, modifier = Modifier .size(64.dp) .padding(16.dp) .drawBehind { drawCircle( color = alpha38Violet40, radius = this.size.maxDimension ) } ) } @Composable private fun ExitIcon() { IconButton( onClick = { binding.campaignBox.visibility = View.GONE binding.onionPatternImage.visibility = View.VISIBLE context?.components?.settings?.hideCampaign = true }, ) { Icon( painter = painterResource(id = R.drawable.ic_close), tint = Color( getColor( requireContext(), R.color.photonWhite, ), ), contentDescription = CampaignStrings.get(CampaignStrings.CloseKey), modifier = Modifier .size(48.dp) .padding(8.dp) ) } } @Composable private fun DynamicCampaignContent( alternateLayout: Boolean ) { Row(verticalAlignment = Alignment.CenterVertically) { Column( modifier = Modifier.fillMaxWidth() .padding( top = 88.dp), horizontalAlignment = Alignment.Start, ) { TitleText() MainText() if (alternateLayout) { Row(modifier = Modifier.fillMaxWidth()) { Button1(alternateLayout) Button2() } } else { Button1(alternateLayout) Button2() } } } } @Composable private fun TitleText() { Text(text = CampaignStrings.get(CampaignStrings.HeaderKey), color = PhotonColors.LightGrey05, textAlign = TextAlign.Left, fontWeight = FontWeight.Bold, fontSize = 24.sp, lineHeight = 34.sp, modifier = Modifier.padding(bottom = 16.dp) ) } @Composable private fun MainText() { Text(text = CampaignStrings.get(CampaignStrings.BodyKey), modifier = Modifier .fillMaxWidth() .padding( start = 0.dp, end = 0.dp, bottom = 18.dp, ), color = PhotonColors.LightGrey05, fontSize = 18.sp, textAlign = TextAlign.Left, ) } @Composable private fun Button1(alternateLayout: Boolean) { Button( onClick = { var locale = CampaignStrings.getLocale() if (locale == "pt") { locale = "pt-BR" } (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = "https://survey.torproject.org/index.php/923269?lang=${locale}", newTab = true, from = BrowserDirection.FromHome, ) }, colors = ButtonDefaults.buttonColors( backgroundColor = PhotonColors.Violet60), shape = RoundedCornerShape(4.dp), modifier = Modifier.padding(0.dp) .fillMaxWidth(fraction = if (alternateLayout) 0.5f else 1f), ) { Text(text = CampaignStrings.get(CampaignStrings.CTAKey), color = PhotonColors.LightGrey05, textAlign = TextAlign.Center, fontSize = 18.sp, modifier = Modifier.padding(8.dp)) } } @Composable private fun Button2() { Button( onClick = { binding.campaignBox.visibility = View.GONE binding.onionPatternImage.visibility = View.VISIBLE context?.components?.settings?.hideCampaign = true }, colors = ButtonDefaults.buttonColors( backgroundColor = PhotonColors.Violet90), shape = RoundedCornerShape(4.dp), modifier = Modifier.padding(0.dp) .fillMaxWidth() ) { Text(text = CampaignStrings.get(CampaignStrings.DismissKey), color = PhotonColors.Violet20, textAlign = TextAlign.Center, fontSize = 18.sp, modifier = Modifier.padding(8.dp)) } } }
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt 0 → 100644 +64 −0 Original line number Diff line number Diff line package org.mozilla.fenix.tor import java.util.Locale object CampaignStrings { val HeaderKey = "key_header" val BodyKey = "key_body" val CTAKey = "key_cta" val DismissKey = "key_dismiss" val CloseKey = "key_close" private val translations: HashMap<String, HashMap<String, String>> = hashMapOf( "en" to hashMapOf( HeaderKey to "We’d love your feedback", BodyKey to "Help us improve Tor Browser by completing this 10-minute survey.", CTAKey to "Launch the survey", DismissKey to "Dismiss", CloseKey to "Close", ), "es" to hashMapOf( HeaderKey to "Danos tu opinión", BodyKey to "Ayúdanos a mejorar el Navegador Tor completando esta encuesta de 10 minutos.", CTAKey to "Iniciar la encuesta", DismissKey to "Descartar", CloseKey to "Cerrar", ), "ru" to hashMapOf( HeaderKey to "Мы будем рады вашим отзывам", BodyKey to "Помогите нам улучшить браузер Tor, пройдя 10-минутный опрос.", CTAKey to "Начать опрос", DismissKey to "Отклонить", CloseKey to "Закрыть", ), "fr" to hashMapOf( HeaderKey to "Nous serions ravis d’avoir votre avis !", BodyKey to "Aidez-nous à améliorer le navigateur Tor en répondant à cette enquête de 10 minutes.", CTAKey to "Lancer l'enquête", DismissKey to "Ignorer", CloseKey to "Fermer", ), "pt" to hashMapOf( HeaderKey to "Adoraríamos ouvir sua opinião", BodyKey to "Ajude-nos a melhorar o Navegador Tor respondendo a esta pesquisa de 10 minutos.", CTAKey to "Iniciar a pesquisa", DismissKey to "Dispensar", CloseKey to "Fechar" ), ) fun getLocale(): String { // TODO: do we care about spoofEnglish setting? return Locale.getDefault().getLanguage(); } fun get(key: String): String { val localeStrings = translations.get(getLocale()) if (localeStrings == null) { return "" } return localeStrings.get(key) ?: "" } }
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +5 −8 Original line number Diff line number Diff line Loading @@ -43,15 +43,7 @@ import org.mozilla.fenix.components.settings.lazyFeatureFlagPreference import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.nimbus.CookieBannersSection import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.nimbus.HomeScreenSection import org.mozilla.fenix.nimbus.Mr2022Section import org.mozilla.fenix.nimbus.QueryParameterStrippingSection import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_ALLOW_LIST import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_PMB import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_STRIP_LIST import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu Loading Loading @@ -2111,4 +2103,9 @@ class Settings(private val appContext: Context) : PreferencesHolder { appContext.getPreferenceKey(R.string.pref_key_use_html_connection_ui), default = false, ) var hideCampaign by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_hide_campaign_2025_ux_survey), default = false, ) }
mobile/android/fenix/app/src/main/res/drawable/campaign_hand.xml 0 → 100644 +21 −0 Original line number Diff line number Diff line <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:viewportWidth="36" android:viewportHeight="36" android:width="36dp" android:height="36dp"> <path android:pathData="M4.861 9.147c0.94 -0.657 2.357 -0.531 3.201 0.166l-0.968 -1.407c-0.779 -1.111 -0.5 -2.313 0.612 -3.093 1.112 -0.777 4.263 1.312 4.263 1.312 -0.786 -1.122 -0.639 -2.544 0.483 -3.331 1.122 -0.784 2.67 -0.513 3.456 0.611l10.42 14.72L25 31l-11.083 -4.042L4.25 12.625c-0.793 -1.129 -0.519 -2.686 0.611 -3.478z" android:fillColor="#EF9645" /> <path android:pathData="M2.695 17.336s-1.132 -1.65 0.519 -2.781c1.649 -1.131 2.78 0.518 2.78 0.518l5.251 7.658c0.181 -0.302 0.379 -0.6 0.6 -0.894L4.557 11.21s-1.131 -1.649 0.519 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l6.855 9.997c0.255 -0.208 0.516 -0.417 0.785 -0.622L7.549 6.732s-1.131 -1.649 0.519 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l7.947 11.589c0.292 -0.179 0.581 -0.334 0.871 -0.498L12.238 4.729s-1.131 -1.649 0.518 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l7.854 11.454 1.194 1.742c-4.948 3.394 -5.419 9.779 -2.592 13.902 0.565 0.825 1.39 0.26 1.39 0.26 -3.393 -4.949 -2.357 -10.51 2.592 -13.903L24.515 8.62s-0.545 -1.924 1.378 -2.47c1.924 -0.545 2.47 1.379 2.47 1.379l1.685 5.004c0.668 1.984 1.379 3.961 2.32 5.831 2.657 5.28 1.07 11.842 -3.94 15.279 -5.465 3.747 -12.936 2.354 -16.684 -3.11L2.695 17.336z" android:fillColor="#FFDC5D" /> <path android:pathData="M12 32.042C8 32.042 3.958 28 3.958 24c0 -0.553 -0.405 -1 -0.958 -1s-1.042 0.447 -1.042 1C1.958 30 6 34.042 12 34.042c0.553 0 1 -0.489 1 -1.042s-0.447 -0.958 -1 -0.958z" android:fillColor="#5DADEC" /> <path android:pathData="M7 34c-3 0 -5 -2 -5 -5 0 -0.553 -0.447 -1 -1 -1s-1 0.447 -1 1c0 4 3 7 7 7 0.553 0 1 -0.447 1 -1s-0.447 -1 -1 -1zM24 2c-0.552 0 -1 0.448 -1 1s0.448 1 1 1c4 0 8 3.589 8 8 0 0.552 0.448 1 1 1s1 -0.448 1 -1c0 -5.514 -4 -10 -10 -10z" android:fillColor="#5DADEC" /> <path android:pathData="M29 0.042c-0.552 0 -1 0.406 -1 0.958s0.448 1.042 1 1.042c3 0 4.958 2.225 4.958 4.958 0 0.552 0.489 1 1.042 1s0.958 -0.448 0.958 -1C35.958 3.163 33 0.042 29 0.042z" android:fillColor="#5DADEC" /> </vector> No newline at end of file
mobile/android/fenix/app/src/main/res/layout/fragment_home.xml +6 −0 Original line number Diff line number Diff line Loading @@ -134,6 +134,12 @@ </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/campaignBox" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/sessionControlRecyclerView" android:layout_width="match_parent" Loading