
Dan Ballard pushed to branch tor-browser-128.14.0esr-14.5-1 at The Tor Project / Applications / Tor Browser Commits: c6b62627 by Dan Ballard at 2025-10-06T14:31:08-07:00 Revert "TB 43505 [android]: Add 2025 UX Survey Campaign" This reverts commit f649c3d3cffa66434ae67d3eaf50fa94615d0163. - - - - - 073a3d57 by clairehurst at 2025-10-06T19:55:05-06:00 fixup! [android] TBA strings - - - - - e024013c by clairehurst at 2025-10-06T19:55:06-06:00 TB 44031 [android]: Implement YEC 2025 Takeover for Android Stable #44031 Implement strings - - - - - a60c837b by clairehurst at 2025-10-06T21:25:08-06:00 fixup! [android] Modify UI/UX TB 43650 [android]: Homepage changes - - - - - 4340c10b by clairehurst at 2025-10-06T21:25:09-06:00 TB 44031 [android]: 2025 YEC - - - - - 9bc57028 by clairehurst at 2025-10-06T21:25:10-06:00 TB 44031 [android]: 2025 YEC preview - - - - - 18 changed files: - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt - + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt - + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt - − mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt - + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt - + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt - + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePagePreview.kt - mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt - − mobile/android/fenix/app/src/main/res/drawable/campaign_hand.xml - + mobile/android/fenix/app/src/main/res/drawable/globe_chain_burst_yec.xml - + mobile/android/fenix/app/src/main/res/drawable/heart.xml - + mobile/android/fenix/app/src/main/res/font/jacquard_12.ttf - mobile/android/fenix/app/src/main/res/layout/fragment_home.xml - mobile/android/fenix/app/src/main/res/values-ar/strings.xml - mobile/android/fenix/app/src/main/res/values-de/strings.xml - mobile/android/fenix/app/src/main/res/values/colors.xml - mobile/android/fenix/app/src/main/res/values/preference_keys.xml - mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml Changes: ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt ===================================== @@ -12,65 +12,33 @@ 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 @@ -140,7 +108,6 @@ 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 @@ -184,23 +151,23 @@ 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 import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme -import org.mozilla.fenix.tor.CampaignStrings +import org.mozilla.fenix.tor.TorCampaignViewModel 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 org.mozilla.fenix.tor.TorHomePage import org.mozilla.fenix.tor.UrlQuickLoadViewModel -import java.text.SimpleDateFormat -import java.time.LocalDate -import java.util.Date +import java.util.Locale @Suppress("TooManyFunctions", "LargeClass") class HomeFragment : Fragment(), UserInteractionHandler { @@ -214,6 +181,7 @@ class HomeFragment : Fragment(), UserInteractionHandler { internal var _binding: FragmentHomeBinding? = null private val binding get() = _binding!! + private val torCampaignViewModel: TorCampaignViewModel by activityViewModels() private val homeViewModel: HomeScreenViewModel by activityViewModels() private val urlQuickLoadViewModel: UrlQuickLoadViewModel by activityViewModels() @@ -336,16 +304,6 @@ class HomeFragment : Fragment(), UserInteractionHandler { orientation = requireContext().resources.configuration.orientation, ) - // Splits by full stops or commas and puts the parts in different lines. - // Ignoring separators at the end of the string, it is expected - // that there are at most two parts (e.g. "Explore. Privately."). - val localBinding = binding - binding.exploreprivately.text = localBinding - .exploreprivately - .text - ?.replace(" *([.,。।]) *".toRegex(), "$1\n") - ?.trim() - components.appStore.dispatch(AppAction.ModeChange(browsingModeManager.mode)) lifecycleScope.launch(IO) { @@ -527,24 +485,12 @@ class HomeFragment : Fragment(), UserInteractionHandler { initializeNavBar(activity) } - if (!shouldAddNavigationBar && shouldShowMicrosurveyPrompt()) { - initializeMicrosurveyPrompt(requireContext()) - } - - sessionControlView = SessionControlView( - containerView = binding.sessionControlRecyclerView, - viewLifecycleOwner = viewLifecycleOwner, - interactor = sessionControlInteractor, - ) - - updateSessionControlView() + initComposeTorHomePageView() disableAppBarDragging() activity.themeManager.applyStatusBarTheme(activity) - tryShowUX2025Survey() - // FxNimbus.features.homescreen.recordExposure() // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! @@ -556,28 +502,6 @@ class HomeFragment : Fragment(), UserInteractionHandler { return binding.root } - private fun tryShowUX2025Survey() { - 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 (BuildConfig.BUILD_TYPE == "release" && !requireContext().settings().hideCampaign) { - binding.onionPatternImage.visibility = View.GONE - binding.campaignBox.apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { - CampaignBox() - } - } - } - } - private fun reinitializeNavBar() { initializeNavBar(activity = requireActivity() as HomeActivity) } @@ -973,6 +897,28 @@ class HomeFragment : Fragment(), UserInteractionHandler { ) } + private fun initComposeTorHomePageView() { + binding.torHomepageView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + TorHomePage( + torCampaignViewModel.shouldInitiallyShowPromo, + onClicked = { + val baseUrl = "https://www.torproject.org/donate" + val locale = Locale.getDefault().getLanguage() + val donateUrl = "${baseUrl}/donate-${locale}-yec2025" + (requireActivity() as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = donateUrl, + newTab = true, + from = BrowserDirection.FromHome, + ) + }, + toolBarAtTop = requireContext().settings().toolbarPosition == ToolbarPosition.TOP + ) + } + } + } + private fun initTabStrip() { binding.tabStripView.isVisible = true binding.tabStripView.apply { @@ -1456,217 +1402,4 @@ class HomeFragment : Fragment(), UserInteractionHandler { override fun onBackPressed(): Boolean { (requireActivity() as HomeActivity).shutDown() } - - @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 = 24.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)) - ) { - Column { - Row( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), - verticalAlignment = Alignment.Top, - ) { - 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(48.dp) - .padding(top = (16+8).dp, start = (16+8).dp) - .drawBehind { - drawCircle( - color = alpha38Violet40, - radius = this.size.height - ) - } - ) - } - - @Composable - private fun ExitIcon() { - IconButton( - modifier = Modifier.padding(8.dp), - 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 - .padding(0.dp) - ) - } - } - - - @Composable - private fun DynamicCampaignContent( - alternateLayout: Boolean - ) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier.fillMaxWidth(), - 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 = 20.sp, - lineHeight = 24.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 = 14.sp, - textAlign = TextAlign.Left, - ) - } - - @Composable - private fun Button1(alternateLayout: Boolean) { - TextButton( - 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 = 14.sp, - modifier = Modifier.padding(8.dp)) - } - } - - @Composable - private fun Button2() { - TextButton( - 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 = 14.sp, - modifier = Modifier.padding(8.dp)) - } - } } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt ===================================== ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt ===================================== @@ -0,0 +1,278 @@ +package org.mozilla.fenix.tor + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.ClickableText +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.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mozilla.components.ui.colors.PhotonColors +import org.mozilla.fenix.R + + +private val alternateLayoutThreshHold = 500.dp + +@Composable +fun CampaignBox(shouldShowYec: MutableState<Boolean>, onDonateButtonClicked: () -> Unit) { + BoxWithConstraints( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(), + ) { + val alternateLayout = this.maxWidth >= alternateLayoutThreshHold + + CampaignLayout( + alternateLayout, + maxWidth = this.maxWidth, + shouldShowYec, + onDonateButtonClicked = onDonateButtonClicked, + ) + } +} + +@Composable +private fun CampaignLayout( + alternateLayout: Boolean, + maxWidth: Dp, + shouldShowYec: MutableState<Boolean>, + onDonateButtonClicked: () -> Unit, +) { + Column( + modifier = Modifier + .padding(horizontal = 22.dp) + .fillMaxWidth(getVariableWidth(maxWidth)) + .wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PurpleBox(alternateLayout, shouldShowYec, onDonateButtonClicked = onDonateButtonClicked) + Spacer(Modifier.size(8.dp)) + Text( + text = stringResource(R.string.no_donation_required_yec), + style = TextStyle( + fontSize = 12.5.sp, + lineHeight = 18.75.sp, + fontFamily = FontFamily.SansSerif, + fontWeight = FontWeight(400), + color = PhotonColors.LightGrey05, + textAlign = TextAlign.Center, + ), + ) + } +} + +private fun getVariableWidth(width: Dp): Float = + (alternateLayoutThreshHold / width).coerceIn(0.80f, 1.0f) + +@Composable +private fun PurpleBox( + alternateLayout: Boolean, + shouldShowYec: MutableState<Boolean>, + onDonateButtonClicked: () -> Unit, +) { + Box( + modifier = Modifier.background( + colorResource(R.color.yec_background), shape = RoundedCornerShape(8.dp), + ), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + horizontalArrangement = Arrangement.End, + ) { + ExitIcon(shouldShowYec) + } + DynamicCampaignContent(alternateLayout, onDonateButtonClicked = onDonateButtonClicked) + } +} + +@Composable +private fun ExitIcon(shouldShowYec: MutableState<Boolean>) { + IconButton( + modifier = Modifier.padding(8.dp), + onClick = { + shouldShowYec.value = false + }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_close), + tint = PhotonColors.White, + contentDescription = stringResource(R.string.close_yec_button_description), + ) + } +} + + +@Composable +private fun DynamicCampaignContent( + alternateLayout: Boolean, + onDonateButtonClicked: () -> Unit, +) { + @Composable + fun Icon(shouldShow: Boolean) { + if (shouldShow) { + Image( + painterResource(R.drawable.globe_chain_burst_yec), + contentDescription = null, + alignment = Alignment.Center, + ) + } + } + Row( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Column( + modifier = Modifier.fillMaxWidth(if (alternateLayout) 0.75f else 1.0f), + horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally, + ) { + Icon(shouldShow = !alternateLayout) + TitleText(alternateLayout) + MainText(onDonateButtonClicked) + Row( + horizontalArrangement = if (alternateLayout) Arrangement.Start else Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + DonateButton(onDonateButtonClicked = onDonateButtonClicked) + } + } + Icon(shouldShow = alternateLayout) + } +} + + +@Composable +private fun TitleText(alternateLayout: Boolean) { + Text( + text = stringResource(R.string.free_the_internet_yec), + style = TextStyle( + fontSize = 56.sp, + lineHeight = 47.6.sp, + fontFamily = FontFamily(Font(R.font.jacquard_12)), + fontWeight = FontWeight(400), + color = PhotonColors.White, + textAlign = if (alternateLayout) TextAlign.Start else TextAlign.Center, + ), + ) +} + +@Composable +private fun MainText(onDonateButtonClicked: () -> Unit) { + Column { + val linkStart: Int = stringResource((R.string.body1_yec)).indexOf("%s") + val annotatedText = buildAnnotatedString { + withStyle( + SpanStyle( + color = PhotonColors.LightGrey05, + fontSize = 14.sp, + ), + ) { + append( + stringResource( + R.string.body1_yec, stringResource(R.string.body1_link_yec), + ), + ) + addStringAnnotation( + tag = "onClick", + annotation = "onClick", + start = linkStart, + end = linkStart + stringResource(R.string.body1_link_yec).length, + ) + } + addStyle( + style = SpanStyle(colorResource(R.color.yec_green)), + linkStart, + linkStart + stringResource(R.string.body1_link_yec).length, + ) + } + ClickableText( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + text = annotatedText, + onClick = { offset -> + annotatedText.getStringAnnotations( + tag = "onClick", + start = offset, + end = offset, + )[0].let { + onDonateButtonClicked() + } + }, + ) + Text( + text = stringResource(R.string.body2_yec), + modifier = Modifier + .fillMaxWidth() + .padding( + bottom = 16.dp, + ), + color = PhotonColors.LightGrey05, + fontSize = 14.sp, + textAlign = TextAlign.Start, + ) + } +} + +@Composable +private fun DonateButton(onDonateButtonClicked: () -> Unit) { + Button( + onClick = onDonateButtonClicked, + colors = ButtonDefaults.buttonColors( + backgroundColor = colorResource(R.color.yec_green), + ), + shape = RoundedCornerShape(4.dp), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 9.dp), + ) { + Text( + text = stringResource(R.string.donate_now_yec), + textAlign = TextAlign.Center, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + color = PhotonColors.DarkGrey80, + ) + Spacer( + Modifier.size(4.dp), + ) + Image( + painterResource(R.drawable.heart), + contentDescription = null, + ) + } +} ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt deleted ===================================== @@ -1,67 +0,0 @@ -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 { - val locale = Locale.getDefault().getLanguage() - if (translations.containsKey(locale)) { - return locale - } - return "en" - } - - - 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/tor/TorCampaignViewModel.kt ===================================== @@ -0,0 +1,34 @@ +package org.mozilla.fenix.tor + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import java.text.SimpleDateFormat +import java.util.Date +import kotlin.getValue +import org.mozilla.geckoview.BuildConfig + +class TorCampaignViewModel : ViewModel() { + val shouldInitiallyShowPromo: MutableState<Boolean> by lazy { + mutableStateOf(shouldInitiallyShowPromo()) + } + + fun shouldInitiallyShowPromo(): Boolean { +// return true // uncomment to test + + val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz") + val startDate = + dateFormat.parse("2025-10-14-15-UTC") // from https://gitlab.torproject.org/tpo/web/team/-/issues/66 + val endDate = + dateFormat.parse("2026-01-02-00-UTC") // from https://gitlab.torproject.org/tpo/web/team/-/issues/66#note_3257224 + val currentDate = Date() + + if (currentDate.before(startDate) || currentDate.after(endDate)) { + return false + } + if (BuildConfig.BUILD_TYPE == "release") { + return true + } + return false + } +} ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt ===================================== @@ -0,0 +1,124 @@ +package org.mozilla.fenix.tor + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.paint +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.BrushPainter +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.mozilla.fenix.R + +@Composable +@Preview +fun TorHomePage( + shouldInitiallyShowPromo: MutableState<Boolean> = mutableStateOf(false), + onClicked: () -> Unit = {}, + toolBarAtTop: Boolean = true, +) { + val shouldShowPromo = rememberSaveable { + shouldInitiallyShowPromo + } + Column( + modifier = Modifier + .fillMaxSize() + .padding( + top = if (toolBarAtTop) 48.dp else 0.dp, + bottom = if (!toolBarAtTop) 48.dp else 0.dp, + ) + .paint( + BrushPainter( + Brush.linearGradient( + colors = listOf( + colorResource(R.color.tor_homepage_gradient_start), + colorResource(R.color.tor_homepage_gradient_middle), + colorResource(R.color.tor_homepage_gradient_end), + ), + start = Offset(0f, Float.POSITIVE_INFINITY), + end = Offset(Float.POSITIVE_INFINITY, 0f), + ), + ), + ) + .padding( + start = 19.dp, + end = 19.dp, + ) + .verticalScroll(rememberScrollState()), + ) { + Spacer(modifier = Modifier.size(17.dp)) + Row( + modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, + ) { + Image( + painter = painterResource(R.mipmap.ic_launcher_foreground), + contentDescription = null, + Modifier.size(35.dp).scale(2f), + ) + Spacer(modifier = Modifier.size(6.dp)) + Text( + text = stringResource(R.string.app_name), + style = TextStyle( + fontSize = 20.sp, + color = Color(0xDEFFFFFF), + fontWeight = FontWeight.Bold, + ), + ) + } + Spacer(Modifier.weight(1f)) + if (shouldShowPromo.value) { + CampaignBox(shouldShowPromo, onDonateButtonClicked = onClicked) + Spacer(Modifier.weight(1f)) + } else { + Text( + // Moved from the commit 5bb3cc6b93346dabd8d46677fae7f86a8f8a4fc2 + // "[android] Modify UI/UX", and the file HomeFragment. + // Splits by full stops or commas and puts the parts in different lines. + // Ignoring separators at the end of the string, it is expected + // that there are at most two parts (e.g. "Explore. Privately."). + text = stringResource(R.string.tor_explore_privately).replace( + " *([.,。।]) *".toRegex(), + "$1\n", + ).trim(), + style = TextStyle( + color = Color(color = 0xDEFFFFFF), + fontSize = 40.sp, + textAlign = TextAlign.Start, + ), + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + Spacer(Modifier.weight(1f)) + Image( + painter = painterResource( + id = R.drawable.ic_onion_pattern, + ), + contentDescription = null, Modifier.fillMaxWidth(), + ) + } + Spacer(modifier = Modifier.size(17.dp)) + } +} ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePagePreview.kt ===================================== @@ -0,0 +1,97 @@ +package org.mozilla.fenix.tor + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.tooling.preview.Preview + +@SuppressLint("UnrememberedMutableState") +@FlexibleWindowPreviewPortraitLandscapeEnglishArabicGerman +@Composable +/** + * Relevant documentation https://developer.android.com/develop/ui/compose/tooling/previews#preview-vi... + */ +private fun TorHomePagePreview( + @PreviewParameter(BooleanBooleanPreviewParameterProvider::class) booleanMatrix: Pair<Boolean, Boolean>, +) { + val toolbarAtTop = booleanMatrix.second + Box( + contentAlignment = if (toolbarAtTop) Alignment.Companion.TopStart else Alignment.Companion.BottomStart, + modifier = Modifier.Companion.fillMaxSize(), + ) { + TorHomePage( + shouldInitiallyShowPromo = mutableStateOf(booleanMatrix.first), + toolBarAtTop = toolbarAtTop, + ) + } +} + +private class BooleanBooleanPreviewParameterProvider : + PreviewParameterProvider<Pair<Boolean, Boolean>> { + override val values: Sequence<Pair<Boolean, Boolean>> + get() = sequenceOf( + Pair(true, true), + Pair(true, false), + Pair(false, true), + Pair(false, false), + ) +} + +/** + * A wrapper annotation for creating a preview that renders a preview for each value of portrait and landscape for english, arabic, and german. + */ +@Preview( + name = "mobile portrait", + group = "english", + widthDp = 360, + heightDp = 780, + locale = "en", + device = "id:pixel_5", +) +@Preview( + name = "mobile landscape", + group = "english", + widthDp = 780, + heightDp = 360, + locale = "en", + device = "spec:parent=pixel_5,orientation=landscape", +) +@Preview( + name = "mobile portrait", + group = "arabic", + widthDp = 360, + heightDp = 780, + locale = "ar", + device = "id:pixel_5", +) +@Preview( + name = "mobile landscape", + group = "arabic", + widthDp = 780, + heightDp = 360, + locale = "ar", + device = "spec:parent=pixel_5,orientation=landscape", +) +@Preview( + name = "mobile portrait", + group = "german", + widthDp = 360, + heightDp = 780, + locale = "de", + device = "id:pixel_5", +) +@Preview( + name = "mobile landscape", + group = "german", + widthDp = 780, + heightDp = 360, + locale = "de", + device = "spec:parent=pixel_5,orientation=landscape", +) +annotation class FlexibleWindowPreviewPortraitLandscapeEnglishArabicGerman ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt ===================================== @@ -43,7 +43,15 @@ 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 @@ -2133,9 +2141,4 @@ class Settings(private val appContext: Context) : PreferencesHolder { appContext.getPreferenceKey(R.string.pref_key_quick_start), 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 deleted ===================================== @@ -1,21 +0,0 @@ -<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/drawable/globe_chain_burst_yec.xml ===================================== @@ -0,0 +1,35 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="124dp" android:viewportHeight="124" android:viewportWidth="160" android:width="160dp"> + + <path android:fillColor="#B6E368" android:pathData="M79.97,86.16C65.19,89.07 56.28,80.34 53.67,75.61L34.02,84.97L55.14,84.24L42.62,106.51L63.16,91.39L61.15,117.92L73.64,93L84.86,111.85L79.98,86.16H79.97Z"/> + + <path android:fillColor="#B6E368" android:pathData="M72.38,32.02C87.13,28.91 96.16,37.52 98.83,42.21L118.35,32.58L97.24,33.6L109.46,11.17L89.13,26.57L90.78,0L78.63,25.09L67.15,6.4L72.38,32.02Z"/> + + <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:fillType="evenOdd" android:pathData="M82.86,42.77H84.19V44.18H82.79V42.84H80.14V44.18H81.46V45.52H82.79V48.27H84.19V51.09H82.79V48.34H81.4V45.59H80.06V44.18H78.74V42.84H77.34V53.77H84.19V51.09H85.59V53.77H89.58V55.18H92.31V56.59H89.51V55.18H85.6V67.44H89.58V66.1H92.38V67.44H93.71V68.85H92.38V70.26H91.05V71.67H89.65V73.01H88.25V74.35H86.99V75.76H85.6V74.35H86.86V72.94H88.25V71.6H89.65V70.26H90.98V68.85H92.31V67.51H89.58V68.85H85.6V71.67H84.2V74.42H82.8V75.76H85.6V77.17H81.4V74.35H82.8V71.6H84.19V68.85H77.34V77.17H81.4V78.43H71.88V77.17H67.76V75.76H66.43V74.35H65.1V73.01H63.7V71.67H62.38V70.26H63.78V71.6H65.1V72.94H66.5V74.35H67.83V75.76H70.56V74.42H69.16V71.67H67.83V68.85H63.7V67.51H60.98V68.85H59.58V67.44H58.25V55.18H59.65V67.44H60.98V66.1H63.78V67.44H67.83V55.18H63.78V56.59H60.98V55.18H59.65V52.36H60.98V49.61H62.38V48.27H63.78V46.93H65.1V45.59H66.43V44.18H69.23V45.59H66.5V47H65.17V48.34H63.77V49.68H62.37V52.43H61.04V55.18H63.7V53.77H67.83V51.09H69.22V53.77H75.94V42.84H74.68V44.18H73.28V42.84H70.62V44.18H69.22V42.77H70.55V41.43H82.86V42.77V42.77ZM69.22,71.59H70.55V74.34H71.95V77.16H75.94V68.84H69.23V71.59H69.22ZM77.34,67.44H84.19V55.17H77.34V67.44ZM69.22,67.44H75.94V55.17H69.22V67.44Z" android:strokeAlpha="0.8"/> + + <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M62.37,70.25H60.98V68.84H62.37V70.25Z" android:strokeAlpha="0.8"/> + + <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M95.1,67.44H93.7V55.17H95.1V67.44Z" android:strokeAlpha="0.8"/> + + <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M86.99,45.58H88.25V46.92H89.64V48.26H90.97V49.6H92.37V52.35H93.7V55.17H92.3V52.42H90.97V49.67H89.58V48.33H88.25V46.99H86.85V45.58H84.19V44.17H86.99V45.58V45.58Z" android:strokeAlpha="0.8"/> + + <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M73.28,45.58H71.95V48.33H70.62V51.08H69.22V48.26H70.55V45.51H71.88V44.17H73.28V45.58Z" android:strokeAlpha="0.8"/> + + <path android:fillAlpha="0.8" android:fillColor="#00000000" android:pathData="M84.19,44.18V42.77H82.86V41.43H70.55V42.77H69.22V44.18M84.19,44.18H82.79V42.84H80.14V44.18H81.46V45.52H82.79V48.27H84.19V51.09M84.19,44.18V45.59H86.85V47H88.25V48.34H89.58V49.68H90.97V52.43H92.3V55.18M84.19,44.18H86.99V45.59H88.25V46.93H89.64V48.27H90.97V49.61H92.37V52.35H93.7V55.17M69.22,44.18H66.43V45.59H65.1V46.93H63.77V48.27H62.37V49.61H60.98V52.35H59.65V55.17M69.22,44.18V45.59H66.5V47H65.17V48.34H63.77V49.68H62.37V52.43H61.04V55.18H63.7V53.77H67.82V51.09H69.22M69.22,44.18H70.62V42.84H73.28V44.18M93.7,55.17H95.1V67.44H93.7M93.7,55.17V67.44M84.19,51.08H82.79V48.33H81.4V45.58H80.06V44.17H78.74V42.84H77.34V53.76H84.19V51.08H84.19ZM84.19,51.08H85.59V53.76H89.58V55.17H92.3M93.7,67.44H92.37V66.1H89.58V67.44H85.59V55.17H89.5V56.58H92.3V55.17H93.7M93.7,67.44V68.85H92.37V70.26H91.04V71.67H89.64V73.01H88.25V74.35H86.99V75.76H85.59M85.59,75.75V74.34H86.85V72.93H88.25V71.59H89.64V70.25H90.97V68.84H92.3V67.5H89.58V68.84H85.59V71.66H84.19V74.41H82.79V75.75H85.59V75.75ZM85.59,75.75V77.16H81.39M81.39,77.16V74.34H82.79V71.59H84.19V68.84H77.33V77.16H81.39H81.39ZM81.39,77.16V78.43H71.88V77.16H67.75V75.75H66.43V74.34H65.1V73H63.7V71.66H62.37V70.25M62.37,70.25H63.77V71.59H65.1V72.93H66.5V74.34H67.83V75.75H70.55V74.41H69.16V71.66H67.83V68.85H63.7V67.51H60.97V68.85M62.37,70.25H60.98V68.84H62.37V70.25ZM60.97,68.84H59.57V67.43H58.25V55.17H59.65M59.65,55.17V67.44H60.98V66.1H63.77V67.44H67.83V55.17H63.77V56.58H60.98V55.17H59.65ZM69.22,51.08V53.76H75.93V42.84H74.68V44.17H73.28M69.22,51.08H70.62V48.33H71.95V45.58H73.28V44.17H71.88V45.51H70.55V48.26H69.22V51.08ZM69.22,71.59H70.55V74.34H71.95V77.16H75.94V68.84H69.23V71.59H69.22ZM77.34,67.44H84.19V55.17H77.34V67.44ZM69.22,67.44H75.93V55.17H69.22V67.44Z" android:strokeAlpha="0.8" android:strokeColor="#C272FF" android:strokeWidth="0.23451"/> + + <path android:fillColor="#ffffff" android:pathData="M53.84,42.78H56.64L54.84,40.42L58.14,39.7L55.15,36.24L57.62,36.17L57.73,35.5L41.11,29.32C40.64,29.14 40.17,29 39.7,28.88L31.58,7.75C29.81,3.15 25.32,0.06 20.41,0.06C18.94,0.06 17.49,0.33 16.11,0.87L7.65,4.16C1.5,6.55 -1.57,13.54 0.81,19.73L12.4,49.92C14.17,54.53 18.66,57.62 23.56,57.62C24.22,57.62 24.87,57.57 25.51,57.46C26.74,58.58 28.17,59.46 29.77,60.05L46.5,66.27L44.54,62.76H48.38L47.08,59.56H51.23L49.53,56.39L38.61,52.33C42.24,49.92 44.27,45.7 43.94,41.38L56.68,46.11L53.84,42.78ZM49.04,56.96L50.07,58.86H46.04L47.35,62.07H43.36L44.97,64.96L30.01,59.4C26.89,58.24 24.41,55.93 23.03,52.89C21.64,49.86 21.52,46.48 22.67,43.35L24.89,37.34C25.95,34.46 27.98,32.2 30.46,30.8L33.78,39.46C33.55,39.74 33.36,40.07 33.23,40.44L31.01,46.45C30.68,47.35 30.72,48.31 31.11,49.17C31.5,50.04 32.21,50.7 33.1,51.03L49.04,56.96ZM21.23,46.86C21.17,46.76 21.12,46.65 21.08,46.54L9.48,16.35C9.13,15.45 9.36,14.68 9.53,14.31C9.7,13.93 10.11,13.24 11.01,12.89L19.46,9.6C19.78,9.48 20.1,9.42 20.41,9.42C21.32,9.42 22.44,9.95 22.9,11.14L30.21,30.15C27.54,31.61 25.37,34.02 24.24,37.1L22.02,43.11C21.57,44.33 21.3,45.6 21.23,46.86ZM33.88,40.68C33.93,40.52 34,40.38 34.08,40.24L34.5,41.32C34.85,42.23 34.62,42.99 34.45,43.37H34.45C34.28,43.75 33.88,44.43 32.98,44.78L32.26,45.06L33.88,40.68ZM37.79,52.02L33.34,50.37C32.62,50.11 32.06,49.58 31.74,48.88C31.42,48.19 31.39,47.41 31.66,46.7L31.94,45.93L33.23,45.43C34.36,45 34.87,44.13 35.08,43.65C35.29,43.17 35.58,42.21 35.14,41.07L34.58,39.61V39.59L34.35,39.01L34.32,38.91L31.07,30.48L31.06,30.46L30.85,29.91L30.82,29.82L23.55,10.88C22.97,9.39 21.56,8.72 20.41,8.72C20.01,8.72 19.61,8.8 19.21,8.95L10.76,12.24C9.63,12.68 9.11,13.55 8.9,14.02C8.69,14.5 8.4,15.47 8.84,16.61L20.43,46.8C20.61,47.27 20.88,47.67 21.2,47.97C21.24,49.75 21.64,51.52 22.4,53.18C23.03,54.56 23.87,55.79 24.88,56.85C24.44,56.9 24,56.93 23.56,56.93C18.94,56.93 14.71,54.01 13.04,49.67L1.45,19.48C-0.79,13.65 2.1,7.07 7.9,4.81L16.35,1.52C17.66,1.01 19.03,0.75 20.41,0.75C25.04,0.75 29.26,3.67 30.93,8.01L38.88,28.7L38.91,28.78L39.16,29.43L39.18,29.47L42.53,38.2C42.8,38.9 42.99,39.61 43.11,40.33C43.12,40.35 43.13,40.37 43.13,40.4C43.17,40.61 43.2,40.83 43.22,41.05C43.22,41.07 43.22,41.09 43.22,41.11C43.68,45.46 41.58,49.76 37.79,52.02ZM43.86,40.6C43.74,39.71 43.51,38.81 43.18,37.94L40.01,29.69C40.29,29.77 40.58,29.86 40.87,29.97L55.81,35.53L53.67,35.59L56.85,39.27L53.63,39.97L55.24,42.09H52.33L54.41,44.53L43.86,40.6Z"/> + + <path android:fillColor="#ffffff" android:pathData="M157.76,99.65L136.71,69.02C134.41,65.69 130.67,63.66 126.65,63.57C125.48,62.52 124.09,61.66 122.51,61.08L107.43,55.47L107.36,55.44L103.68,55.55L106.41,58.69L102.99,59.44L105.06,62.15H102.4L104.54,64.66L115.19,68.62L112.76,70.31C109.62,72.5 107.78,75.86 107.41,79.41L97.62,75.77L99.32,78.92H94.98L96.29,82.12H92.65L94.1,84.72L111.42,91.16C111.59,91.22 111.75,91.28 111.92,91.33L130.63,118.54C132.98,121.96 136.85,124 140.98,124C143.54,124 146.01,123.22 148.12,121.75L154.58,117.26C157.34,115.33 159.19,112.44 159.79,109.12C160.4,105.79 159.68,102.43 157.76,99.65ZM104.95,64.06L103.91,62.85H106.46L104.21,59.89L107.7,59.13L105.17,56.2L107.25,56.14L122.27,61.73C123.5,62.19 124.62,62.82 125.59,63.59C123.31,63.73 121.12,64.49 119.22,65.82L115.89,68.13L104.95,64.06ZM111.65,90.5L111.25,90.35L94.58,84.15L93.84,82.82H97.32L96.01,79.61H100.48L99.1,77.06L107.36,80.13L108.04,80.38L114.5,82.79C115.37,83.11 116.28,83.1 117.1,82.82C117.32,82.75 117.54,82.65 117.74,82.53C118.53,82.1 119.17,81.39 119.5,80.48L120.95,76.56L124.77,73.9C125.25,73.57 125.79,73.39 126.36,73.39C126.8,73.39 127.92,73.51 128.68,74.61L129.76,76.19C129.69,76.47 129.61,76.75 129.51,77.02L127.19,83.33L127.08,83.63C126.11,86.07 124.45,88.03 122.42,89.34C122.23,89.47 122.03,89.59 121.83,89.7C119.04,91.29 115.64,91.75 112.36,90.74C112.13,90.67 111.89,90.59 111.65,90.5ZM117.15,80.21C117.22,79.78 117.48,78.97 118.31,78.39L119.95,77.26L118.85,80.24C118.57,81 118.04,81.58 117.38,81.94C117.04,81.24 117.08,80.57 117.15,80.21ZM130.06,77.56L130.17,77.25C130.2,77.14 130.24,77.04 130.27,76.93L149.73,105.24C150.31,106.08 150.27,106.93 150.19,107.36C150.11,107.79 149.85,108.6 149.02,109.18L142.57,113.67C142.09,114.01 141.55,114.17 140.98,114.17C140.53,114.17 139.41,114.06 138.66,112.95L122.82,89.91C124.96,88.52 126.7,86.46 127.72,83.89L130.06,77.56ZM159.11,108.99C158.54,112.14 156.79,114.87 154.18,116.68L147.73,121.18C145.73,122.57 143.4,123.3 140.98,123.3C137.07,123.3 133.42,121.38 131.2,118.15L112.97,91.63C113.95,91.86 114.94,91.97 115.91,91.97C118.15,91.97 120.32,91.38 122.23,90.28L138.09,113.35C139.03,114.72 140.42,114.87 140.98,114.87C141.7,114.87 142.36,114.66 142.96,114.25L149.42,109.75C150.45,109.03 150.77,108.02 150.87,107.48C150.96,106.95 151.02,105.89 150.3,104.85L130.51,76.06L129.97,75.26L129.25,74.22C128.3,72.84 126.91,72.7 126.36,72.7H126.36C125.64,72.7 124.97,72.91 124.38,73.32L121.37,75.42L120.37,76.11L117.92,77.82C116.88,78.54 116.56,79.55 116.46,80.08C116.39,80.53 116.33,81.34 116.74,82.2C116.1,82.39 115.41,82.38 114.74,82.13L108.08,79.66C108.39,76.25 110.14,72.99 113.15,70.89L115.99,68.91L116.68,68.43L119.61,66.39C121.6,65 123.94,64.27 126.36,64.27H126.37C126.72,64.27 127.07,64.28 127.41,64.31C130.91,64.62 134.12,66.48 136.13,69.42L157.19,100.05C159,102.68 159.68,105.85 159.11,108.99Z"/> + + <path android:fillColor="#C272FF" android:pathData="M26.73,77.81L30.91,77.83L30.91,79.04L26.73,79.03V84.32H25.51V79.03L21.2,79.02L21.2,77.8L25.51,77.81V72.54H26.73V77.81Z"/> + + <path android:fillColor="#C272FF" android:pathData="M132.82,37.87L137,37.88L137,39.08L132.82,39.07V44.31H131.6V39.07L127.29,39.06L127.29,37.85L131.6,37.86V32.65H132.82V37.87Z"/> + + <path android:fillColor="#C272FF" android:pathData="M20.17,99.2C20.17,98.79 19.84,98.46 19.43,98.46C19.02,98.46 18.69,98.79 18.69,99.2C18.69,99.62 19.02,99.95 19.43,99.95V101.17C18.36,101.17 17.49,100.29 17.49,99.2C17.49,98.12 18.36,97.24 19.43,97.24C20.51,97.24 21.38,98.12 21.38,99.2C21.38,100.29 20.51,101.17 19.43,101.17V99.95C19.84,99.95 20.17,99.62 20.17,99.2Z"/> + + <path android:fillColor="#C272FF" android:pathData="M116.28,43.22C117.35,43.22 118.22,44.1 118.22,45.18C118.22,46.27 117.35,47.15 116.28,47.15C115.2,47.15 114.33,46.27 114.33,45.18C114.33,44.1 115.2,43.22 116.28,43.22Z"/> + + <path android:fillColor="#C272FF" android:pathData="M134.74,51.08C135.81,51.08 136.68,51.96 136.68,53.04C136.68,54.12 135.81,55 134.74,55C133.66,55 132.79,54.12 132.79,53.04C132.79,51.96 133.66,51.08 134.74,51.08Z"/> + + <path android:fillColor="#C272FF" android:pathData="M32.81,91.67C32.81,91.26 32.47,90.93 32.06,90.93C31.66,90.93 31.32,91.26 31.32,91.67C31.32,92.09 31.66,92.42 32.06,92.42V93.64C30.99,93.64 30.12,92.76 30.12,91.67C30.12,90.59 30.99,89.71 32.06,89.71C33.14,89.71 34.01,90.59 34.01,91.67C34.01,92.76 33.14,93.64 32.06,93.64V92.42C32.47,92.42 32.81,92.09 32.81,91.67Z"/> + +</vector> ===================================== mobile/android/fenix/app/src/main/res/drawable/heart.xml ===================================== @@ -0,0 +1,7 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="16dp" android:viewportHeight="16" android:viewportWidth="16" android:width="16dp"> + + <path android:fillColor="#15141A" android:pathData="M8,6C8,6 8,2 11.5,2C15,2 15,5 15,6C15,10.5 8,15 8,15V6Z"/> + + <path android:fillColor="#15141A" android:pathData="M8,6C8,6 8,2 4.5,2C1,2 1,5 1,6C1,10.5 8,15 8,15L9,9L8,6Z"/> + +</vector> ===================================== mobile/android/fenix/app/src/main/res/font/jacquard_12.ttf ===================================== Binary files /dev/null and b/mobile/android/fenix/app/src/main/res/font/jacquard_12.ttf differ ===================================== mobile/android/fenix/app/src/main/res/layout/fragment_home.xml ===================================== @@ -47,98 +47,14 @@ app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier=".167"/> <!-- This value needs to be 1.67 * the wordmark parallax value as its 24dp vs 40 --> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/wordmark" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="18dp" - android:layout_marginBottom="32dp" - android:clickable="false" - android:focusable="false" - android:importantForAccessibility="no" - android:orientation="horizontal" - app:srcCompat="@mipmap/ic_launcher_foreground" - app:layout_collapseMode="parallax" - app:layout_collapseParallaxMultiplier=".1"> - - <ImageView - android:id="@+id/wordmarkLogo" - android:layout_width="50dp" - android:layout_height="50dp" - android:adjustViewBounds="true" - android:contentDescription="@null" - app:srcCompat="@mipmap/ic_launcher_foreground" - tools:ignore="ImageContrastCheck" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintEnd_toStartOf="@id/app_name" - android:scaleX="1.5" - android:scaleY="1.5" /> - - <!-- - tor-browser#42590 - <ImageView - android:id="@+id/wordmarkText" - android:layout_width="wrap_content" - android:layout_height="@dimen/wordmark_text_height" - android:adjustViewBounds="true" - android:contentDescription="@null" - android:layout_marginTop="@dimen/wordmark_text_margin_top" - app:srcCompat="?fenixWordmarkText" /> - --> - - <TextView - android:id="@+id/app_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:clickable="false" - android:focusable="false" - android:fontFamily="Roboto-Medium" - android:gravity="start" - android:importantForAccessibility="no" - android:maxLines="2" - android:paddingStart="16dp" - android:paddingEnd="16dp" - android:text="@string/app_name" - android:textColor="#DEFFFFFF" - android:textSize="20sp" - app:layout_constrainedWidth="true" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/wordmarkLogo" - app:layout_constraintTop_toTopOf="parent" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - </com.google.android.material.appbar.CollapsingToolbarLayout> - <TextView - android:id="@+id/exploreprivately" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center|center_vertical" - android:gravity="center_horizontal" - android:clickable="false" - android:ellipsize="end" - android:focusable="false" - android:importantForAccessibility="no" - android:text="@string/tor_explore_privately" - android:fontFamily="Roboto-Medium" - android:textColor="#DEFFFFFF" - android:textSize="40sp" - android:lineSpacingMultiplier="1.1" - app:layout_scrollFlags="scroll" /> - </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView - android:id="@+id/campaignBox" + android:id="@+id/torHomepageView" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center"/> + android:layout_height="match_parent" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/sessionControlRecyclerView" @@ -155,17 +71,6 @@ tools:itemCount="3" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/> - <ImageView - android:id="@+id/onion_pattern_image" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom" - app:srcCompat="@drawable/ic_onion_pattern" - tools:ignore="ContentDescription" - app:layout_constraintBottom_toTopOf="@id/toolbarLayout" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" /> - <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/toolbarLayout" android:elevation="5dp" ===================================== mobile/android/fenix/app/src/main/res/values-ar/strings.xml ===================================== @@ -1506,5 +1506,11 @@ <!-- Translations feature--> - - </resources> + <!-- Not meant for production. Uncomment for the compose preview in [TorHomePage] --> + <!-- <string name="donate_now_yec">تبرع الآن</string>--> + <!-- <string name="free_the_internet_yec">حرروا الإنترنت</string>--> + <!-- <string name="body1_yec">دعم الأدوات التي تكسر قيود الرقابة والمراقبة. %s</string>--> + <!-- <string name="body1_link_yec">تبرع لمشروع Tor اليوم.</string>--> + <!-- <string name="body2_yec">حتى 31 ديسمبر، سيتم مضاعفة تبرعك، بحد أقصى 250,000 دولار!</string>--> + <!-- <string name="no_donation_required_yec">سيكون متصفح Tor لنظام Android مجانيا دائما للاستخدام - لا يلزم التبرع لاستخدام هذا التطبيق.</string>--> +</resources> ===================================== mobile/android/fenix/app/src/main/res/values-de/strings.xml ===================================== @@ -73,8 +73,8 @@ The first parameter is the name of the app defined in app_name (for example: Fenix) --> <string name="private_browsing_placeholder_description_2">%1$s leert die eingegebenen Suchbegriffe und besuchten Webseiten aller privaten Tabs wenn Sie diese schließen oder die App beenden. Das macht Sie gegenüber Website-Betreibern und Internetanbietern nicht anonym, aber erleichtert es Ihnen, dass andere Nutzer dieses Geräts Ihre Aktivitäten nicht einsehen können.</string> <string name="private_browsing_common_myths"> - - + + Häufige Missverständnisse über das Surfen im Privaten Modus</string> <!-- True Private Browsing Mode --> @@ -1413,7 +1413,7 @@ <string name="toast_copy_link_to_clipboard">In Zwischenablage kopiert</string> <!-- An option from the share dialog to sign into sync --> <string name="sync_sign_in">Bei Sync anmelden</string> - <!-- An option from the three dot menu to sync and save data --> + <!-- An option from the three dot menu to sync and save data --> <string name="sync_menu_sync_and_save_data">Daten synchronisieren und speichern</string> <!-- An option from the share dialog to send link to all other sync devices --> <string name="sync_send_to_all">An alle Geräte senden</string> @@ -1800,10 +1800,10 @@ <string name="browser_toolbar_long_press_popup_paste_and_go">Einfügen & Los</string> <!-- Paste the text in the clipboard --> <string name="browser_toolbar_long_press_popup_paste">Einfügen</string> - + <!-- Snackbar message shown after an URL has been copied to clipboard. --> <string name="browser_toolbar_url_copied_to_clipboard_snackbar">URL in Zwischenablage kopiert</string> - + <!-- Title text for the Add To Homescreen dialog --> <string name="add_to_homescreen_title">Zum Startbildschirm hinzufügen</string> <!-- Cancel button text for the Add to Homescreen dialog --> @@ -2784,4 +2784,13 @@ <string name="exit_fullscreen_with_gesture">Um den Vollbildmodus zu beenden, ziehen Sie von oben und verwenden Sie die Zurück-Geste</string> <!-- Message shown to explain how to exit fullscreen mode when using back button navigation --> <string name="exit_fullscreen_with_back_button">Um den Vollbildmodus zu beenden, ziehen Sie von oben und drücken Sie auf „Zurück“</string> + + <!-- Not meant for production. Uncomment for the compose preview in [TorHomePage] --> + <!-- <string name="donate_now_yec">Jetzt spenden</string>--> + <!-- <string name="free_the_internet_yec">Befreit das Internet</string>--> + <!-- <string name="body1_yec">Unterstützen Sie Tools, die die Ketten der Zensur und Überwachung sprengen. %s</string>--> + <!-- <string name="body1_link_yec">Spenden Sie noch heute für das Tor-Projekt.</string>--> + <!-- <string name="body2_yec">Bis zum 31. Dezember wird Ihre Spende bis zu einer Höhe von 250.000 Dollar verdoppelt!</string>--> + <!-- <string name="no_donation_required_yec">Der Tor-Browser für Android wird immer kostenlos sein – es ist keine Spende erforderlich, um diese App zu nutzen.</string>--> + </resources> ===================================== mobile/android/fenix/app/src/main/res/values/colors.xml ===================================== @@ -377,6 +377,11 @@ <!-- Private Mode mask icon circle fill colors --> <color name="mozac_ui_private_mode_circle_fill" tools:ignore="UnusedResources">@color/photonPurple60</color> + + <!-- TorHomepage gradient colors --> + <color name="tor_homepage_gradient_start">#7529A7</color> + <color name="tor_homepage_gradient_middle">#492E85</color> + <color name="tor_homepage_gradient_end">#383372</color> <!-- Connection Assist --> <color name="connect_button_purple">#9059FF</color> @@ -385,4 +390,10 @@ <color name="configure_connection_button_white">#E1E0E7</color> <color name="warning_yellow">#FFA436</color> <color name="progress_background_tint">#55148C</color> + + <!-- 2025 YEC --> + <color name="yec_green">#B6E368</color> + <color name="yec_purple">#C272FF</color> + <color name="yec_black">#15141A</color> + <color name="yec_background">#1D1133</color> </resources> ===================================== mobile/android/fenix/app/src/main/res/values/preference_keys.xml ===================================== @@ -433,5 +433,4 @@ <string name="pref_key_tor_network_settings_bridges_enabled">pref_key_tor_network_settings_bridges_enabled</string> <string name="pref_key_spoof_english" translatable="false">pref_key_spoof_english</string> - <string name="pref_key_hide_campaign_2025_ux_survey" translatable="false">pref_key_hide_campaign_2025_ux_survey_test</string> </resources> ===================================== mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml ===================================== @@ -134,4 +134,20 @@ <!-- Connection assist. Content Description for back button. Button will start the connection assist process again --> <string name="connection_assist_back_button_content_description_start_again">Start again</string> + <!-- Year End Campaign. Text on the "Donate Now" button that takes the user to the donation page. A small black heart image will be added to the "end" of the text but within the button. Here end" means to the right of the test for LTR and to the left of the text for RTL, android handles this. --> + <string name="donate_now_yec">Donate Now</string> + <!-- Year End Campaign. White text to be shown below the purple rectangle that the rest of the content is in. --> + <string name="no_donation_required_yec">Tor Browser for Android will always be free to use—no donation is required to use this app.</string> + <!-- Year End Campaign. Here "Close" is a verb, referring to closing the banner. This is used primarily for screen readers to give the small "x" button a name. --> + <string name="close_yec_button_description">Close</string> + + <!-- 2025 Year End Campaign. Title text that will show up bigger and in a different font --> + <string name="free_the_internet_yec">Free the internet</string> + <!-- 2025 Year End Campaign. %s will be replaced with the value for body1_link_yec --> + <string name="body1_yec">Support tools that break the chains of censorship and surveillance. %s</string> + <!-- 2025 Year End Campaign. The part to replace %s in body1_yec --> + <string name="body1_link_yec">Donate to the Tor Project today.</string> + <!-- 2025 Year End Campaign. The end date should match the end of the year 2025. The "$250,000" should be localised: it should be shown as USD (United States dollar) currency, but you may use numbers and formatting appropriate to your locale. --> + <string name="body2_yec">Through December 31, your gift will be matched, up to $250,000!</string> + </resources> View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/38f3710... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/38f3710... You're receiving this email because of your account on gitlab.torproject.org.
participants (1)
-
Dan Ballard (@dan)