Dan Ballard pushed to branch tor-browser-128.14.0esr-14.5-1 at The Tor Project / Applications / Tor Browser

Commits:

18 changed files:

Changes:

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
    ... ... @@ -12,65 +12,33 @@ import android.content.res.Configuration
    12 12
     import android.graphics.drawable.ColorDrawable
    
    13 13
     import android.net.Uri
    
    14 14
     import android.os.Bundle
    
    15
    -import android.util.Log
    
    16 15
     import android.view.LayoutInflater
    
    17 16
     import android.view.View
    
    18 17
     import android.view.ViewGroup
    
    19
    -import android.widget.ImageView
    
    20 18
     import androidx.activity.result.ActivityResultLauncher
    
    21 19
     import androidx.annotation.VisibleForTesting
    
    22
    -import androidx.compose.foundation.Image
    
    23
    -import androidx.compose.foundation.background
    
    24
    -import androidx.compose.foundation.border
    
    25
    -import androidx.compose.foundation.layout.Arrangement
    
    26
    -import androidx.compose.foundation.layout.Box
    
    27
    -import androidx.compose.foundation.layout.BoxWithConstraints
    
    28 20
     import androidx.compose.foundation.layout.Column
    
    29
    -import androidx.compose.foundation.layout.Row
    
    30
    -import androidx.compose.foundation.layout.Spacer
    
    31 21
     import androidx.compose.foundation.layout.fillMaxWidth
    
    32 22
     import androidx.compose.foundation.layout.heightIn
    
    33 23
     import androidx.compose.foundation.layout.padding
    
    34
    -import androidx.compose.foundation.layout.size
    
    35
    -import androidx.compose.foundation.layout.wrapContentHeight
    
    36
    -import androidx.compose.foundation.layout.wrapContentSize
    
    37
    -import androidx.compose.foundation.rememberScrollState
    
    38
    -import androidx.compose.foundation.shape.CircleShape
    
    39 24
     import androidx.compose.foundation.shape.RoundedCornerShape
    
    40
    -import androidx.compose.foundation.verticalScroll
    
    41
    -import androidx.compose.material.Button
    
    42 25
     import androidx.compose.material.ButtonDefaults
    
    43
    -import androidx.compose.material.Icon
    
    44
    -import androidx.compose.material.IconButton
    
    45 26
     import androidx.compose.material.Text
    
    46 27
     import androidx.compose.material.TextButton
    
    47
    -import androidx.compose.runtime.Composable
    
    48
    -import androidx.compose.runtime.mutableStateOf
    
    49
    -import androidx.compose.runtime.remember
    
    50
    -import androidx.compose.ui.Alignment
    
    51 28
     import androidx.compose.ui.ExperimentalComposeUiApi
    
    52 29
     import androidx.compose.ui.Modifier
    
    53
    -import androidx.compose.ui.draw.clip
    
    54
    -import androidx.compose.ui.draw.drawBehind
    
    55
    -import androidx.compose.ui.draw.scale
    
    56
    -import androidx.compose.ui.graphics.Color
    
    57
    -import androidx.compose.ui.layout.ContentScale
    
    58 30
     import androidx.compose.ui.platform.ViewCompositionStrategy
    
    59
    -import androidx.compose.ui.res.painterResource
    
    60
    -import androidx.compose.ui.semantics.heading
    
    61 31
     import androidx.compose.ui.semantics.semantics
    
    62 32
     import androidx.compose.ui.semantics.testTag
    
    63 33
     import androidx.compose.ui.semantics.testTagsAsResourceId
    
    64
    -import androidx.compose.ui.text.TextLayoutResult
    
    65
    -import androidx.compose.ui.text.font.FontWeight
    
    66 34
     import androidx.compose.ui.text.style.TextAlign
    
    67
    -import androidx.compose.ui.unit.Dp
    
    68 35
     import androidx.compose.ui.unit.dp
    
    69
    -import androidx.compose.ui.unit.sp
    
    70 36
     import androidx.compose.ui.viewinterop.AndroidView
    
    71 37
     import androidx.coordinatorlayout.widget.CoordinatorLayout
    
    72 38
     import androidx.core.content.ContextCompat
    
    73 39
     import androidx.core.content.ContextCompat.getColor
    
    40
    +import androidx.core.view.children
    
    41
    +import androidx.core.view.doOnLayout
    
    74 42
     import androidx.core.view.isGone
    
    75 43
     import androidx.core.view.isVisible
    
    76 44
     import androidx.fragment.app.Fragment
    
    ... ... @@ -140,7 +108,6 @@ import org.mozilla.fenix.components.FenixSnackbar
    140 108
     import org.mozilla.fenix.components.PrivateShortcutCreateManager
    
    141 109
     import org.mozilla.fenix.components.TabCollectionStorage
    
    142 110
     import org.mozilla.fenix.components.appstate.AppAction
    
    143
    -import org.mozilla.fenix.components.components
    
    144 111
     import org.mozilla.fenix.components.menu.MenuAccessPoint
    
    145 112
     import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature
    
    146 113
     import org.mozilla.fenix.components.toolbar.ToolbarPosition
    
    ... ... @@ -184,23 +151,23 @@ import org.mozilla.fenix.messaging.DefaultMessageController
    184 151
     import org.mozilla.fenix.messaging.FenixMessageSurfaceId
    
    185 152
     import org.mozilla.fenix.messaging.MessagingFeature
    
    186 153
     import org.mozilla.fenix.microsurvey.ui.MicrosurveyRequestPrompt
    
    154
    +import org.mozilla.fenix.nimbus.FxNimbus
    
    187 155
     import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
    
    188 156
     import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
    
    189 157
     import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
    
    190 158
     import org.mozilla.fenix.tabstray.Page
    
    191 159
     import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
    
    192 160
     import org.mozilla.fenix.theme.FirefoxTheme
    
    193
    -import org.mozilla.fenix.tor.CampaignStrings
    
    161
    +import org.mozilla.fenix.tor.TorCampaignViewModel
    
    194 162
     import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
    
    195 163
     import org.mozilla.fenix.utils.allowUndo
    
    196 164
     import org.mozilla.fenix.wallpapers.Wallpaper
    
    197 165
     import java.lang.ref.WeakReference
    
    198 166
     import org.mozilla.fenix.GleanMetrics.TabStrip as TabStripMetrics
    
    199 167
     
    
    168
    +import org.mozilla.fenix.tor.TorHomePage
    
    200 169
     import org.mozilla.fenix.tor.UrlQuickLoadViewModel
    
    201
    -import java.text.SimpleDateFormat
    
    202
    -import java.time.LocalDate
    
    203
    -import java.util.Date
    
    170
    +import java.util.Locale
    
    204 171
     
    
    205 172
     @Suppress("TooManyFunctions", "LargeClass")
    
    206 173
     class HomeFragment : Fragment(), UserInteractionHandler {
    
    ... ... @@ -214,6 +181,7 @@ class HomeFragment : Fragment(), UserInteractionHandler {
    214 181
         internal var _binding: FragmentHomeBinding? = null
    
    215 182
         private val binding get() = _binding!!
    
    216 183
     
    
    184
    +    private val torCampaignViewModel: TorCampaignViewModel by activityViewModels()
    
    217 185
         private val homeViewModel: HomeScreenViewModel by activityViewModels()
    
    218 186
         private val urlQuickLoadViewModel: UrlQuickLoadViewModel by activityViewModels()
    
    219 187
     
    
    ... ... @@ -336,16 +304,6 @@ class HomeFragment : Fragment(), UserInteractionHandler {
    336 304
                 orientation = requireContext().resources.configuration.orientation,
    
    337 305
             )
    
    338 306
     
    
    339
    -        // Splits by full stops or commas and puts the parts in different lines.
    
    340
    -        // Ignoring separators at the end of the string, it is expected
    
    341
    -        // that there are at most two parts (e.g. "Explore. Privately.").
    
    342
    -        val localBinding = binding
    
    343
    -        binding.exploreprivately.text = localBinding
    
    344
    -            .exploreprivately
    
    345
    -            .text
    
    346
    -            ?.replace(" *([.,。।]) *".toRegex(), "$1\n")
    
    347
    -            ?.trim()
    
    348
    -
    
    349 307
             components.appStore.dispatch(AppAction.ModeChange(browsingModeManager.mode))
    
    350 308
     
    
    351 309
             lifecycleScope.launch(IO) {
    
    ... ... @@ -527,24 +485,12 @@ class HomeFragment : Fragment(), UserInteractionHandler {
    527 485
                 initializeNavBar(activity)
    
    528 486
             }
    
    529 487
     
    
    530
    -        if (!shouldAddNavigationBar && shouldShowMicrosurveyPrompt()) {
    
    531
    -            initializeMicrosurveyPrompt(requireContext())
    
    532
    -        }
    
    533
    -
    
    534
    -        sessionControlView = SessionControlView(
    
    535
    -            containerView = binding.sessionControlRecyclerView,
    
    536
    -            viewLifecycleOwner = viewLifecycleOwner,
    
    537
    -            interactor = sessionControlInteractor,
    
    538
    -        )
    
    539
    -
    
    540
    -        updateSessionControlView()
    
    488
    +        initComposeTorHomePageView()
    
    541 489
     
    
    542 490
             disableAppBarDragging()
    
    543 491
     
    
    544 492
             activity.themeManager.applyStatusBarTheme(activity)
    
    545 493
     
    
    546
    -        tryShowUX2025Survey()
    
    547
    -
    
    548 494
             // FxNimbus.features.homescreen.recordExposure()
    
    549 495
     
    
    550 496
             // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
    
    ... ... @@ -556,28 +502,6 @@ class HomeFragment : Fragment(), UserInteractionHandler {
    556 502
             return binding.root
    
    557 503
         }
    
    558 504
     
    
    559
    -    private fun tryShowUX2025Survey() {
    
    560
    -        val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz")
    
    561
    -        val startDate = dateFormat.parse("2025-04-14-12-UTC")
    
    562
    -
    
    563
    -        val endDate = dateFormat.parse("2025-04-28-00-UTC")
    
    564
    -        val currentDate = Date()
    
    565
    -
    
    566
    -        if (currentDate.before(startDate) || currentDate.after(endDate)) {
    
    567
    -            return // comment out to test
    
    568
    -        }
    
    569
    -
    
    570
    -        if (BuildConfig.BUILD_TYPE == "release" && !requireContext().settings().hideCampaign) {
    
    571
    -            binding.onionPatternImage.visibility = View.GONE
    
    572
    -            binding.campaignBox.apply {
    
    573
    -                setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
    
    574
    -                setContent {
    
    575
    -                    CampaignBox()
    
    576
    -                }
    
    577
    -            }
    
    578
    -        }
    
    579
    -    }
    
    580
    -
    
    581 505
         private fun reinitializeNavBar() {
    
    582 506
             initializeNavBar(activity = requireActivity() as HomeActivity)
    
    583 507
         }
    
    ... ... @@ -973,6 +897,28 @@ class HomeFragment : Fragment(), UserInteractionHandler {
    973 897
             )
    
    974 898
         }
    
    975 899
     
    
    900
    +    private fun initComposeTorHomePageView() {
    
    901
    +        binding.torHomepageView.apply {
    
    902
    +            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
    
    903
    +            setContent {
    
    904
    +                TorHomePage(
    
    905
    +                    torCampaignViewModel.shouldInitiallyShowPromo,
    
    906
    +                    onClicked = {
    
    907
    +                        val baseUrl =  "https://www.torproject.org/donate"
    
    908
    +                        val locale = Locale.getDefault().getLanguage()
    
    909
    +                        val donateUrl = "${baseUrl}/donate-${locale}-yec2025"
    
    910
    +                        (requireActivity() as HomeActivity).openToBrowserAndLoad(
    
    911
    +                            searchTermOrURL = donateUrl,
    
    912
    +                            newTab = true,
    
    913
    +                            from = BrowserDirection.FromHome,
    
    914
    +                        )
    
    915
    +                    },
    
    916
    +                    toolBarAtTop = requireContext().settings().toolbarPosition == ToolbarPosition.TOP
    
    917
    +                )
    
    918
    +            }
    
    919
    +        }
    
    920
    +    }
    
    921
    +
    
    976 922
         private fun initTabStrip() {
    
    977 923
             binding.tabStripView.isVisible = true
    
    978 924
             binding.tabStripView.apply {
    
    ... ... @@ -1456,217 +1402,4 @@ class HomeFragment : Fragment(), UserInteractionHandler {
    1456 1402
         override fun onBackPressed(): Boolean {
    
    1457 1403
             (requireActivity() as HomeActivity).shutDown()
    
    1458 1404
         }
    
    1459
    -
    
    1460
    -    @Composable
    
    1461
    -    fun CampaignBox() {
    
    1462
    -        BoxWithConstraints(
    
    1463
    -            contentAlignment = Alignment.Center,
    
    1464
    -            modifier = Modifier
    
    1465
    -                .fillMaxWidth()
    
    1466
    -                .wrapContentHeight()
    
    1467
    -        ) {
    
    1468
    -            val alternateLayout = this.maxWidth >= 500.dp
    
    1469
    -
    
    1470
    -            CampaignLayout(
    
    1471
    -                alternateLayout,
    
    1472
    -                maxWidth = this.maxWidth,
    
    1473
    -                modifier = Modifier
    
    1474
    -                    .padding(top = if (alternateLayout) 65.dp else 55.dp, bottom = 56.dp),
    
    1475
    -            )
    
    1476
    -        }
    
    1477
    -    }
    
    1478
    -
    
    1479
    -    @Composable
    
    1480
    -    private fun CampaignLayout(
    
    1481
    -        alternateLayout: Boolean,
    
    1482
    -        maxWidth: Dp,
    
    1483
    -        modifier: Modifier
    
    1484
    -    ) {
    
    1485
    -        Column(
    
    1486
    -            modifier = modifier
    
    1487
    -                .padding(horizontal = 24.dp)
    
    1488
    -                .verticalScroll(rememberScrollState())
    
    1489
    -                .fillMaxWidth(getVariableWidth(maxWidth)),
    
    1490
    -            horizontalAlignment = Alignment.CenterHorizontally,
    
    1491
    -        ) {
    
    1492
    -            PurpleBox(alternateLayout)
    
    1493
    -        }
    
    1494
    -    }
    
    1495
    -
    
    1496
    -    private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f)
    
    1497
    -
    
    1498
    -    @Composable
    
    1499
    -    private fun PurpleBox(
    
    1500
    -        alternateLayout: Boolean,
    
    1501
    -    ) {
    
    1502
    -        Box(
    
    1503
    -            modifier = Modifier.background(PhotonColors.Violet90, shape = RoundedCornerShape(8.dp))
    
    1504
    -        ) {
    
    1505
    -            Column {
    
    1506
    -                Row(
    
    1507
    -                    modifier = Modifier.fillMaxWidth().wrapContentHeight(),
    
    1508
    -                    verticalAlignment = Alignment.Top,
    
    1509
    -                ) {
    
    1510
    -                    Emoji()
    
    1511
    -                    Spacer(Modifier.weight(1f))
    
    1512
    -                    ExitIcon()
    
    1513
    -                }
    
    1514
    -                DynamicCampaignContent(alternateLayout)
    
    1515
    -            }
    
    1516
    -        }
    
    1517
    -    }
    
    1518
    -
    
    1519
    -    @Composable
    
    1520
    -    private fun Emoji() {
    
    1521
    -        val alpha38Violet40 = Color(PhotonColors.Violet40.red, PhotonColors.Violet40.green, PhotonColors.Violet40.blue, 0.38f)
    
    1522
    -        Image(
    
    1523
    -            painter = painterResource(id = R.drawable.campaign_hand),
    
    1524
    -            contentDescription = null,
    
    1525
    -            modifier = Modifier
    
    1526
    -                .size(48.dp)
    
    1527
    -                .padding(top = (16+8).dp, start = (16+8).dp)
    
    1528
    -                .drawBehind {
    
    1529
    -                    drawCircle(
    
    1530
    -                        color = alpha38Violet40,
    
    1531
    -                        radius = this.size.height
    
    1532
    -                    )
    
    1533
    -                }
    
    1534
    -        )
    
    1535
    -    }
    
    1536
    -
    
    1537
    -    @Composable
    
    1538
    -    private fun ExitIcon() {
    
    1539
    -        IconButton(
    
    1540
    -            modifier = Modifier.padding(8.dp),
    
    1541
    -            onClick = {
    
    1542
    -                binding.campaignBox.visibility = View.GONE
    
    1543
    -                binding.onionPatternImage.visibility = View.VISIBLE
    
    1544
    -                context?.components?.settings?.hideCampaign = true
    
    1545
    -            },
    
    1546
    -        ) {
    
    1547
    -            Icon(
    
    1548
    -                painter = painterResource(id = R.drawable.ic_close),
    
    1549
    -                tint = Color(
    
    1550
    -                    getColor(
    
    1551
    -                        requireContext(),
    
    1552
    -                        R.color.photonWhite,
    
    1553
    -                    ),
    
    1554
    -                ),
    
    1555
    -                contentDescription = CampaignStrings.get(CampaignStrings.CloseKey),
    
    1556
    -                modifier = Modifier
    
    1557
    -                    .padding(0.dp)
    
    1558
    -            )
    
    1559
    -        }
    
    1560
    -    }
    
    1561
    -
    
    1562
    -
    
    1563
    -    @Composable
    
    1564
    -    private fun DynamicCampaignContent(
    
    1565
    -        alternateLayout: Boolean
    
    1566
    -    ) {
    
    1567
    -        Row(
    
    1568
    -            modifier = Modifier.padding(16.dp),
    
    1569
    -            verticalAlignment = Alignment.CenterVertically
    
    1570
    -        ) {
    
    1571
    -            Column(
    
    1572
    -                modifier = Modifier.fillMaxWidth(),
    
    1573
    -                horizontalAlignment = Alignment.Start,
    
    1574
    -            ) {
    
    1575
    -                TitleText()
    
    1576
    -                MainText()
    
    1577
    -
    
    1578
    -                if (alternateLayout) {
    
    1579
    -                    Row(modifier = Modifier.fillMaxWidth()) {
    
    1580
    -                        Button1(alternateLayout)
    
    1581
    -                        Button2()
    
    1582
    -                    }
    
    1583
    -                } else {
    
    1584
    -                    Button1(alternateLayout)
    
    1585
    -                    Button2()
    
    1586
    -                }
    
    1587
    -
    
    1588
    -            }
    
    1589
    -        }
    
    1590
    -    }
    
    1591
    -
    
    1592
    -    @Composable
    
    1593
    -    private fun TitleText() {
    
    1594
    -
    
    1595
    -        Text(text = CampaignStrings.get(CampaignStrings.HeaderKey),
    
    1596
    -            color = PhotonColors.LightGrey05,
    
    1597
    -            textAlign = TextAlign.Left,
    
    1598
    -            fontWeight = FontWeight.Bold,
    
    1599
    -            fontSize = 20.sp,
    
    1600
    -            lineHeight = 24.sp,
    
    1601
    -            modifier =  Modifier.padding(bottom = 16.dp)
    
    1602
    -        )
    
    1603
    -    }
    
    1604
    -
    
    1605
    -    @Composable
    
    1606
    -    private fun MainText() {
    
    1607
    -
    
    1608
    -        Text(text =  CampaignStrings.get(CampaignStrings.BodyKey),
    
    1609
    -            modifier = Modifier
    
    1610
    -                .fillMaxWidth()
    
    1611
    -                .padding(
    
    1612
    -                    start = 0.dp,
    
    1613
    -                    end = 0.dp,
    
    1614
    -                    bottom = 18.dp,
    
    1615
    -                ),
    
    1616
    -            color = PhotonColors.LightGrey05,
    
    1617
    -            fontSize = 14.sp,
    
    1618
    -            textAlign = TextAlign.Left,
    
    1619
    -            )
    
    1620
    -    }
    
    1621
    -
    
    1622
    -    @Composable
    
    1623
    -    private fun Button1(alternateLayout: Boolean) {
    
    1624
    -        TextButton(
    
    1625
    -            onClick = {
    
    1626
    -                var locale = CampaignStrings.getLocale()
    
    1627
    -                if (locale == "pt") {
    
    1628
    -                    locale = "pt-BR"
    
    1629
    -                }
    
    1630
    -                (activity as HomeActivity).openToBrowserAndLoad(
    
    1631
    -                    searchTermOrURL = "https://survey.torproject.org/index.php/923269?lang=${locale}",
    
    1632
    -                    newTab = true,
    
    1633
    -                    from = BrowserDirection.FromHome,
    
    1634
    -                )
    
    1635
    -            },
    
    1636
    -            colors = ButtonDefaults.buttonColors(
    
    1637
    -                backgroundColor = PhotonColors.Violet60),
    
    1638
    -            shape = RoundedCornerShape(4.dp),
    
    1639
    -            modifier = Modifier.padding(0.dp)
    
    1640
    -                .fillMaxWidth(fraction = if (alternateLayout) 0.5f else 1f),
    
    1641
    -
    
    1642
    -        ) {
    
    1643
    -            Text(text = CampaignStrings.get(CampaignStrings.CTAKey),
    
    1644
    -                color = PhotonColors.LightGrey05,
    
    1645
    -                textAlign = TextAlign.Center,
    
    1646
    -                fontSize = 14.sp,
    
    1647
    -                modifier = Modifier.padding(8.dp))
    
    1648
    -        }
    
    1649
    -    }
    
    1650
    -
    
    1651
    -    @Composable
    
    1652
    -    private fun Button2() {
    
    1653
    -        TextButton(
    
    1654
    -            onClick = {
    
    1655
    -                binding.campaignBox.visibility = View.GONE
    
    1656
    -                binding.onionPatternImage.visibility = View.VISIBLE
    
    1657
    -                context?.components?.settings?.hideCampaign = true
    
    1658
    -            },
    
    1659
    -            colors = ButtonDefaults.buttonColors(
    
    1660
    -                backgroundColor = PhotonColors.Violet90),
    
    1661
    -            shape = RoundedCornerShape(4.dp),
    
    1662
    -            modifier = Modifier.padding(0.dp)
    
    1663
    -                .fillMaxWidth()
    
    1664
    -        ) {
    
    1665
    -            Text(text = CampaignStrings.get(CampaignStrings.DismissKey),
    
    1666
    -            color = PhotonColors.Violet20,
    
    1667
    -            textAlign = TextAlign.Center,
    
    1668
    -            fontSize = 14.sp,
    
    1669
    -            modifier = Modifier.padding(8.dp))
    
    1670
    -        }
    
    1671
    -    }
    
    1672 1405
     }

  • 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
    1
    +package org.mozilla.fenix.tor
    
    2
    +
    
    3
    +import androidx.compose.foundation.Image
    
    4
    +import androidx.compose.foundation.background
    
    5
    +import androidx.compose.foundation.layout.Arrangement
    
    6
    +import androidx.compose.foundation.layout.Box
    
    7
    +import androidx.compose.foundation.layout.BoxWithConstraints
    
    8
    +import androidx.compose.foundation.layout.Column
    
    9
    +import androidx.compose.foundation.layout.PaddingValues
    
    10
    +import androidx.compose.foundation.layout.Row
    
    11
    +import androidx.compose.foundation.layout.Spacer
    
    12
    +import androidx.compose.foundation.layout.fillMaxHeight
    
    13
    +import androidx.compose.foundation.layout.fillMaxWidth
    
    14
    +import androidx.compose.foundation.layout.padding
    
    15
    +import androidx.compose.foundation.layout.size
    
    16
    +import androidx.compose.foundation.layout.wrapContentHeight
    
    17
    +import androidx.compose.foundation.shape.RoundedCornerShape
    
    18
    +import androidx.compose.foundation.text.ClickableText
    
    19
    +import androidx.compose.material.Button
    
    20
    +import androidx.compose.material.ButtonDefaults
    
    21
    +import androidx.compose.material.Icon
    
    22
    +import androidx.compose.material.IconButton
    
    23
    +import androidx.compose.material.Text
    
    24
    +import androidx.compose.runtime.Composable
    
    25
    +import androidx.compose.runtime.MutableState
    
    26
    +import androidx.compose.ui.Alignment
    
    27
    +import androidx.compose.ui.Modifier
    
    28
    +import androidx.compose.ui.res.colorResource
    
    29
    +import androidx.compose.ui.res.painterResource
    
    30
    +import androidx.compose.ui.res.stringResource
    
    31
    +import androidx.compose.ui.text.SpanStyle
    
    32
    +import androidx.compose.ui.text.TextStyle
    
    33
    +import androidx.compose.ui.text.buildAnnotatedString
    
    34
    +import androidx.compose.ui.text.font.Font
    
    35
    +import androidx.compose.ui.text.font.FontFamily
    
    36
    +import androidx.compose.ui.text.font.FontWeight
    
    37
    +import androidx.compose.ui.text.style.TextAlign
    
    38
    +import androidx.compose.ui.text.withStyle
    
    39
    +import androidx.compose.ui.unit.Dp
    
    40
    +import androidx.compose.ui.unit.dp
    
    41
    +import androidx.compose.ui.unit.sp
    
    42
    +import mozilla.components.ui.colors.PhotonColors
    
    43
    +import org.mozilla.fenix.R
    
    44
    +
    
    45
    +
    
    46
    +private val alternateLayoutThreshHold = 500.dp
    
    47
    +
    
    48
    +@Composable
    
    49
    +fun CampaignBox(shouldShowYec: MutableState<Boolean>, onDonateButtonClicked: () -> Unit) {
    
    50
    +    BoxWithConstraints(
    
    51
    +        contentAlignment = Alignment.Center,
    
    52
    +        modifier = Modifier
    
    53
    +            .fillMaxWidth()
    
    54
    +            .fillMaxHeight(),
    
    55
    +    ) {
    
    56
    +        val alternateLayout = this.maxWidth >= alternateLayoutThreshHold
    
    57
    +
    
    58
    +        CampaignLayout(
    
    59
    +            alternateLayout,
    
    60
    +            maxWidth = this.maxWidth,
    
    61
    +            shouldShowYec,
    
    62
    +            onDonateButtonClicked = onDonateButtonClicked,
    
    63
    +        )
    
    64
    +    }
    
    65
    +}
    
    66
    +
    
    67
    +@Composable
    
    68
    +private fun CampaignLayout(
    
    69
    +    alternateLayout: Boolean,
    
    70
    +    maxWidth: Dp,
    
    71
    +    shouldShowYec: MutableState<Boolean>,
    
    72
    +    onDonateButtonClicked: () -> Unit,
    
    73
    +) {
    
    74
    +    Column(
    
    75
    +        modifier = Modifier
    
    76
    +            .padding(horizontal = 22.dp)
    
    77
    +            .fillMaxWidth(getVariableWidth(maxWidth))
    
    78
    +            .wrapContentHeight(),
    
    79
    +        horizontalAlignment = Alignment.CenterHorizontally,
    
    80
    +    ) {
    
    81
    +        PurpleBox(alternateLayout, shouldShowYec, onDonateButtonClicked = onDonateButtonClicked)
    
    82
    +        Spacer(Modifier.size(8.dp))
    
    83
    +        Text(
    
    84
    +            text = stringResource(R.string.no_donation_required_yec),
    
    85
    +            style = TextStyle(
    
    86
    +                fontSize = 12.5.sp,
    
    87
    +                lineHeight = 18.75.sp,
    
    88
    +                fontFamily = FontFamily.SansSerif,
    
    89
    +                fontWeight = FontWeight(400),
    
    90
    +                color = PhotonColors.LightGrey05,
    
    91
    +                textAlign = TextAlign.Center,
    
    92
    +            ),
    
    93
    +        )
    
    94
    +    }
    
    95
    +}
    
    96
    +
    
    97
    +private fun getVariableWidth(width: Dp): Float =
    
    98
    +    (alternateLayoutThreshHold / width).coerceIn(0.80f, 1.0f)
    
    99
    +
    
    100
    +@Composable
    
    101
    +private fun PurpleBox(
    
    102
    +    alternateLayout: Boolean,
    
    103
    +    shouldShowYec: MutableState<Boolean>,
    
    104
    +    onDonateButtonClicked: () -> Unit,
    
    105
    +) {
    
    106
    +    Box(
    
    107
    +        modifier = Modifier.background(
    
    108
    +            colorResource(R.color.yec_background), shape = RoundedCornerShape(8.dp),
    
    109
    +        ),
    
    110
    +    ) {
    
    111
    +        Row(
    
    112
    +            modifier = Modifier
    
    113
    +                .fillMaxWidth()
    
    114
    +                .wrapContentHeight(),
    
    115
    +            horizontalArrangement = Arrangement.End,
    
    116
    +        ) {
    
    117
    +            ExitIcon(shouldShowYec)
    
    118
    +        }
    
    119
    +        DynamicCampaignContent(alternateLayout, onDonateButtonClicked = onDonateButtonClicked)
    
    120
    +    }
    
    121
    +}
    
    122
    +
    
    123
    +@Composable
    
    124
    +private fun ExitIcon(shouldShowYec: MutableState<Boolean>) {
    
    125
    +    IconButton(
    
    126
    +        modifier = Modifier.padding(8.dp),
    
    127
    +        onClick = {
    
    128
    +            shouldShowYec.value = false
    
    129
    +        },
    
    130
    +    ) {
    
    131
    +        Icon(
    
    132
    +            painter = painterResource(id = R.drawable.ic_close),
    
    133
    +            tint = PhotonColors.White,
    
    134
    +            contentDescription = stringResource(R.string.close_yec_button_description),
    
    135
    +        )
    
    136
    +    }
    
    137
    +}
    
    138
    +
    
    139
    +
    
    140
    +@Composable
    
    141
    +private fun DynamicCampaignContent(
    
    142
    +    alternateLayout: Boolean,
    
    143
    +    onDonateButtonClicked: () -> Unit,
    
    144
    +) {
    
    145
    +    @Composable
    
    146
    +    fun Icon(shouldShow: Boolean) {
    
    147
    +        if (shouldShow) {
    
    148
    +            Image(
    
    149
    +                painterResource(R.drawable.globe_chain_burst_yec),
    
    150
    +                contentDescription = null,
    
    151
    +                alignment = Alignment.Center,
    
    152
    +            )
    
    153
    +        }
    
    154
    +    }
    
    155
    +    Row(
    
    156
    +        modifier = Modifier
    
    157
    +            .padding(16.dp)
    
    158
    +            .fillMaxWidth(),
    
    159
    +        verticalAlignment = Alignment.CenterVertically,
    
    160
    +    ) {
    
    161
    +        Column(
    
    162
    +            modifier = Modifier.fillMaxWidth(if (alternateLayout) 0.75f else 1.0f),
    
    163
    +            horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally,
    
    164
    +        ) {
    
    165
    +            Icon(shouldShow = !alternateLayout)
    
    166
    +            TitleText(alternateLayout)
    
    167
    +            MainText(onDonateButtonClicked)
    
    168
    +            Row(
    
    169
    +                horizontalArrangement = if (alternateLayout) Arrangement.Start else Arrangement.Center,
    
    170
    +                modifier = Modifier.fillMaxWidth(),
    
    171
    +            ) {
    
    172
    +                DonateButton(onDonateButtonClicked = onDonateButtonClicked)
    
    173
    +            }
    
    174
    +        }
    
    175
    +        Icon(shouldShow = alternateLayout)
    
    176
    +    }
    
    177
    +}
    
    178
    +
    
    179
    +
    
    180
    +@Composable
    
    181
    +private fun TitleText(alternateLayout: Boolean) {
    
    182
    +    Text(
    
    183
    +        text = stringResource(R.string.free_the_internet_yec),
    
    184
    +        style = TextStyle(
    
    185
    +            fontSize = 56.sp,
    
    186
    +            lineHeight = 47.6.sp,
    
    187
    +            fontFamily = FontFamily(Font(R.font.jacquard_12)),
    
    188
    +            fontWeight = FontWeight(400),
    
    189
    +            color = PhotonColors.White,
    
    190
    +            textAlign = if (alternateLayout) TextAlign.Start else TextAlign.Center,
    
    191
    +        ),
    
    192
    +    )
    
    193
    +}
    
    194
    +
    
    195
    +@Composable
    
    196
    +private fun MainText(onDonateButtonClicked: () -> Unit) {
    
    197
    +    Column {
    
    198
    +        val linkStart: Int = stringResource((R.string.body1_yec)).indexOf("%s")
    
    199
    +        val annotatedText = buildAnnotatedString {
    
    200
    +            withStyle(
    
    201
    +                SpanStyle(
    
    202
    +                    color = PhotonColors.LightGrey05,
    
    203
    +                    fontSize = 14.sp,
    
    204
    +                ),
    
    205
    +            ) {
    
    206
    +                append(
    
    207
    +                    stringResource(
    
    208
    +                        R.string.body1_yec, stringResource(R.string.body1_link_yec),
    
    209
    +                    ),
    
    210
    +                )
    
    211
    +                addStringAnnotation(
    
    212
    +                    tag = "onClick",
    
    213
    +                    annotation = "onClick",
    
    214
    +                    start = linkStart,
    
    215
    +                    end = linkStart + stringResource(R.string.body1_link_yec).length,
    
    216
    +                )
    
    217
    +            }
    
    218
    +            addStyle(
    
    219
    +                style = SpanStyle(colorResource(R.color.yec_green)),
    
    220
    +                linkStart,
    
    221
    +                linkStart + stringResource(R.string.body1_link_yec).length,
    
    222
    +            )
    
    223
    +        }
    
    224
    +        ClickableText(
    
    225
    +            modifier = Modifier
    
    226
    +                .fillMaxWidth()
    
    227
    +                .padding(bottom = 8.dp),
    
    228
    +            text = annotatedText,
    
    229
    +            onClick = { offset ->
    
    230
    +                annotatedText.getStringAnnotations(
    
    231
    +                    tag = "onClick",
    
    232
    +                    start = offset,
    
    233
    +                    end = offset,
    
    234
    +                )[0].let {
    
    235
    +                    onDonateButtonClicked()
    
    236
    +                }
    
    237
    +            },
    
    238
    +        )
    
    239
    +        Text(
    
    240
    +            text = stringResource(R.string.body2_yec),
    
    241
    +            modifier = Modifier
    
    242
    +                .fillMaxWidth()
    
    243
    +                .padding(
    
    244
    +                    bottom = 16.dp,
    
    245
    +                ),
    
    246
    +            color = PhotonColors.LightGrey05,
    
    247
    +            fontSize = 14.sp,
    
    248
    +            textAlign = TextAlign.Start,
    
    249
    +        )
    
    250
    +    }
    
    251
    +}
    
    252
    +
    
    253
    +@Composable
    
    254
    +private fun DonateButton(onDonateButtonClicked: () -> Unit) {
    
    255
    +    Button(
    
    256
    +        onClick = onDonateButtonClicked,
    
    257
    +        colors = ButtonDefaults.buttonColors(
    
    258
    +            backgroundColor = colorResource(R.color.yec_green),
    
    259
    +        ),
    
    260
    +        shape = RoundedCornerShape(4.dp),
    
    261
    +        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 9.dp),
    
    262
    +    ) {
    
    263
    +        Text(
    
    264
    +            text = stringResource(R.string.donate_now_yec),
    
    265
    +            textAlign = TextAlign.Center,
    
    266
    +            fontSize = 16.sp,
    
    267
    +            fontWeight = FontWeight.SemiBold,
    
    268
    +            color = PhotonColors.DarkGrey80,
    
    269
    +        )
    
    270
    +        Spacer(
    
    271
    +            Modifier.size(4.dp),
    
    272
    +        )
    
    273
    +        Image(
    
    274
    +            painterResource(R.drawable.heart),
    
    275
    +            contentDescription = null,
    
    276
    +        )
    
    277
    +    }
    
    278
    +}

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt deleted
    1
    -package org.mozilla.fenix.tor
    
    2
    -
    
    3
    -import java.util.Locale
    
    4
    -
    
    5
    -object CampaignStrings {
    
    6
    -
    
    7
    -    val HeaderKey = "key_header"
    
    8
    -    val BodyKey = "key_body"
    
    9
    -    val CTAKey = "key_cta"
    
    10
    -    val DismissKey = "key_dismiss"
    
    11
    -    val CloseKey = "key_close"
    
    12
    -
    
    13
    -    private val translations: HashMap<String, HashMap<String, String>> = hashMapOf(
    
    14
    -        "en" to hashMapOf(
    
    15
    -            HeaderKey to "We’d love your feedback",
    
    16
    -            BodyKey to "Help us improve Tor Browser by completing this 10-minute survey.",
    
    17
    -            CTAKey to "Launch the survey",
    
    18
    -            DismissKey to "Dismiss",
    
    19
    -            CloseKey to "Close",
    
    20
    -        ),
    
    21
    -        "es" to hashMapOf(
    
    22
    -            HeaderKey to "Danos tu opinión",
    
    23
    -            BodyKey to "Ayúdanos a mejorar el Navegador Tor completando esta encuesta de 10 minutos.",
    
    24
    -            CTAKey to "Iniciar la encuesta",
    
    25
    -            DismissKey to "Descartar",
    
    26
    -            CloseKey to "Cerrar",
    
    27
    -        ),
    
    28
    -        "ru" to hashMapOf(
    
    29
    -            HeaderKey to "Мы будем рады вашим отзывам",
    
    30
    -            BodyKey to "Помогите нам улучшить браузер Tor, пройдя 10-минутный опрос.",
    
    31
    -            CTAKey to "Начать опрос",
    
    32
    -            DismissKey to "Отклонить",
    
    33
    -            CloseKey to "Закрыть",
    
    34
    -        ),
    
    35
    -        "fr" to hashMapOf(
    
    36
    -            HeaderKey to "Nous serions ravis d’avoir votre avis !",
    
    37
    -            BodyKey to "Aidez-nous à améliorer le navigateur Tor en répondant à cette enquête de 10 minutes.",
    
    38
    -            CTAKey to "Lancer l'enquête",
    
    39
    -            DismissKey to "Ignorer",
    
    40
    -            CloseKey to "Fermer",
    
    41
    -        ),
    
    42
    -        "pt" to hashMapOf(
    
    43
    -            HeaderKey to "Adoraríamos ouvir sua opinião",
    
    44
    -            BodyKey to "Ajude-nos a melhorar o Navegador Tor respondendo a esta pesquisa de 10 minutos.",
    
    45
    -            CTAKey to "Iniciar a pesquisa",
    
    46
    -            DismissKey to "Dispensar",
    
    47
    -            CloseKey to "Fechar"
    
    48
    -        ),
    
    49
    -    )
    
    50
    -
    
    51
    -    fun getLocale(): String {
    
    52
    -        val locale = Locale.getDefault().getLanguage()
    
    53
    -        if (translations.containsKey(locale)) {
    
    54
    -            return locale
    
    55
    -        }
    
    56
    -        return "en"
    
    57
    -    }
    
    58
    -
    
    59
    -
    
    60
    -    fun get(key: String): String {
    
    61
    -        val localeStrings = translations.get(getLocale())
    
    62
    -        if (localeStrings == null) {
    
    63
    -            return ""
    
    64
    -        }
    
    65
    -        return localeStrings.get(key) ?: ""
    
    66
    -    }
    
    67
    -}

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt
    1
    +package org.mozilla.fenix.tor
    
    2
    +
    
    3
    +import androidx.compose.runtime.MutableState
    
    4
    +import androidx.compose.runtime.mutableStateOf
    
    5
    +import androidx.lifecycle.ViewModel
    
    6
    +import java.text.SimpleDateFormat
    
    7
    +import java.util.Date
    
    8
    +import kotlin.getValue
    
    9
    +import org.mozilla.geckoview.BuildConfig
    
    10
    +
    
    11
    +class TorCampaignViewModel : ViewModel() {
    
    12
    +    val shouldInitiallyShowPromo: MutableState<Boolean> by lazy {
    
    13
    +        mutableStateOf(shouldInitiallyShowPromo())
    
    14
    +    }
    
    15
    +
    
    16
    +    fun shouldInitiallyShowPromo(): Boolean {
    
    17
    +//        return true // uncomment to test
    
    18
    +
    
    19
    +        val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz")
    
    20
    +        val startDate =
    
    21
    +            dateFormat.parse("2025-10-14-15-UTC") // from https://gitlab.torproject.org/tpo/web/team/-/issues/66
    
    22
    +        val endDate =
    
    23
    +            dateFormat.parse("2026-01-02-00-UTC") // from https://gitlab.torproject.org/tpo/web/team/-/issues/66#note_3257224
    
    24
    +        val currentDate = Date()
    
    25
    +
    
    26
    +        if (currentDate.before(startDate) || currentDate.after(endDate)) {
    
    27
    +            return false
    
    28
    +        }
    
    29
    +        if (BuildConfig.BUILD_TYPE == "release") {
    
    30
    +            return true
    
    31
    +        }
    
    32
    +        return false
    
    33
    +    }
    
    34
    +}

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt
    1
    +package org.mozilla.fenix.tor
    
    2
    +
    
    3
    +import androidx.compose.foundation.Image
    
    4
    +import androidx.compose.foundation.layout.Column
    
    5
    +import androidx.compose.foundation.layout.Row
    
    6
    +import androidx.compose.foundation.layout.Spacer
    
    7
    +import androidx.compose.foundation.layout.fillMaxSize
    
    8
    +import androidx.compose.foundation.layout.fillMaxWidth
    
    9
    +import androidx.compose.foundation.layout.padding
    
    10
    +import androidx.compose.foundation.layout.size
    
    11
    +import androidx.compose.foundation.rememberScrollState
    
    12
    +import androidx.compose.foundation.verticalScroll
    
    13
    +import androidx.compose.material.Text
    
    14
    +import androidx.compose.runtime.Composable
    
    15
    +import androidx.compose.runtime.MutableState
    
    16
    +import androidx.compose.runtime.mutableStateOf
    
    17
    +import androidx.compose.runtime.saveable.rememberSaveable
    
    18
    +import androidx.compose.ui.Alignment
    
    19
    +import androidx.compose.ui.Modifier
    
    20
    +import androidx.compose.ui.draw.paint
    
    21
    +import androidx.compose.ui.draw.scale
    
    22
    +import androidx.compose.ui.geometry.Offset
    
    23
    +import androidx.compose.ui.graphics.Brush
    
    24
    +import androidx.compose.ui.graphics.Color
    
    25
    +import androidx.compose.ui.graphics.painter.BrushPainter
    
    26
    +import androidx.compose.ui.res.colorResource
    
    27
    +import androidx.compose.ui.res.painterResource
    
    28
    +import androidx.compose.ui.res.stringResource
    
    29
    +import androidx.compose.ui.text.TextStyle
    
    30
    +import androidx.compose.ui.text.font.FontWeight
    
    31
    +import androidx.compose.ui.text.style.TextAlign
    
    32
    +import androidx.compose.ui.tooling.preview.Preview
    
    33
    +import androidx.compose.ui.unit.dp
    
    34
    +import androidx.compose.ui.unit.sp
    
    35
    +import org.mozilla.fenix.R
    
    36
    +
    
    37
    +@Composable
    
    38
    +@Preview
    
    39
    +fun TorHomePage(
    
    40
    +    shouldInitiallyShowPromo: MutableState<Boolean> = mutableStateOf(false),
    
    41
    +    onClicked: () -> Unit = {},
    
    42
    +    toolBarAtTop: Boolean = true,
    
    43
    +) {
    
    44
    +    val shouldShowPromo = rememberSaveable {
    
    45
    +        shouldInitiallyShowPromo
    
    46
    +    }
    
    47
    +    Column(
    
    48
    +        modifier = Modifier
    
    49
    +            .fillMaxSize()
    
    50
    +            .padding(
    
    51
    +                top = if (toolBarAtTop) 48.dp else 0.dp,
    
    52
    +                bottom = if (!toolBarAtTop) 48.dp else 0.dp,
    
    53
    +            )
    
    54
    +            .paint(
    
    55
    +                BrushPainter(
    
    56
    +                    Brush.linearGradient(
    
    57
    +                        colors = listOf(
    
    58
    +                            colorResource(R.color.tor_homepage_gradient_start),
    
    59
    +                            colorResource(R.color.tor_homepage_gradient_middle),
    
    60
    +                            colorResource(R.color.tor_homepage_gradient_end),
    
    61
    +                        ),
    
    62
    +                        start = Offset(0f, Float.POSITIVE_INFINITY),
    
    63
    +                        end = Offset(Float.POSITIVE_INFINITY, 0f),
    
    64
    +                    ),
    
    65
    +                ),
    
    66
    +            )
    
    67
    +            .padding(
    
    68
    +                start = 19.dp,
    
    69
    +                end = 19.dp,
    
    70
    +            )
    
    71
    +            .verticalScroll(rememberScrollState()),
    
    72
    +    ) {
    
    73
    +        Spacer(modifier = Modifier.size(17.dp))
    
    74
    +        Row(
    
    75
    +            modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically,
    
    76
    +        ) {
    
    77
    +            Image(
    
    78
    +                painter = painterResource(R.mipmap.ic_launcher_foreground),
    
    79
    +                contentDescription = null,
    
    80
    +                Modifier.size(35.dp).scale(2f),
    
    81
    +            )
    
    82
    +            Spacer(modifier = Modifier.size(6.dp))
    
    83
    +            Text(
    
    84
    +                text = stringResource(R.string.app_name),
    
    85
    +                style = TextStyle(
    
    86
    +                    fontSize = 20.sp,
    
    87
    +                    color = Color(0xDEFFFFFF),
    
    88
    +                    fontWeight = FontWeight.Bold,
    
    89
    +                ),
    
    90
    +            )
    
    91
    +        }
    
    92
    +        Spacer(Modifier.weight(1f))
    
    93
    +        if (shouldShowPromo.value) {
    
    94
    +            CampaignBox(shouldShowPromo, onDonateButtonClicked = onClicked)
    
    95
    +            Spacer(Modifier.weight(1f))
    
    96
    +        } else {
    
    97
    +            Text(
    
    98
    +                // Moved from the commit 5bb3cc6b93346dabd8d46677fae7f86a8f8a4fc2
    
    99
    +                // "[android] Modify UI/UX", and the file HomeFragment.
    
    100
    +                // Splits by full stops or commas and puts the parts in different lines.
    
    101
    +                // Ignoring separators at the end of the string, it is expected
    
    102
    +                // that there are at most two parts (e.g. "Explore. Privately.").
    
    103
    +                text = stringResource(R.string.tor_explore_privately).replace(
    
    104
    +                    " *([.,。।]) *".toRegex(),
    
    105
    +                    "$1\n",
    
    106
    +                ).trim(),
    
    107
    +                style = TextStyle(
    
    108
    +                    color = Color(color = 0xDEFFFFFF),
    
    109
    +                    fontSize = 40.sp,
    
    110
    +                    textAlign = TextAlign.Start,
    
    111
    +                ),
    
    112
    +                modifier = Modifier.align(Alignment.CenterHorizontally),
    
    113
    +            )
    
    114
    +            Spacer(Modifier.weight(1f))
    
    115
    +            Image(
    
    116
    +                painter = painterResource(
    
    117
    +                    id = R.drawable.ic_onion_pattern,
    
    118
    +                ),
    
    119
    +                contentDescription = null, Modifier.fillMaxWidth(),
    
    120
    +            )
    
    121
    +        }
    
    122
    +        Spacer(modifier = Modifier.size(17.dp))
    
    123
    +    }
    
    124
    +}

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePagePreview.kt
    1
    +package org.mozilla.fenix.tor
    
    2
    +
    
    3
    +import android.annotation.SuppressLint
    
    4
    +import androidx.compose.foundation.layout.Box
    
    5
    +import androidx.compose.foundation.layout.fillMaxSize
    
    6
    +import androidx.compose.runtime.Composable
    
    7
    +import androidx.compose.runtime.mutableStateOf
    
    8
    +import androidx.compose.ui.Alignment
    
    9
    +import androidx.compose.ui.Modifier
    
    10
    +import androidx.compose.ui.tooling.preview.PreviewParameter
    
    11
    +import androidx.compose.ui.tooling.preview.PreviewParameterProvider
    
    12
    +import androidx.compose.ui.tooling.preview.Preview
    
    13
    +
    
    14
    +@SuppressLint("UnrememberedMutableState")
    
    15
    +@FlexibleWindowPreviewPortraitLandscapeEnglishArabicGerman
    
    16
    +@Composable
    
    17
    +/**
    
    18
    + * Relevant documentation https://developer.android.com/develop/ui/compose/tooling/previews#preview-viewmodel
    
    19
    + */
    
    20
    +private fun TorHomePagePreview(
    
    21
    +    @PreviewParameter(BooleanBooleanPreviewParameterProvider::class) booleanMatrix: Pair<Boolean, Boolean>,
    
    22
    +) {
    
    23
    +    val toolbarAtTop = booleanMatrix.second
    
    24
    +    Box(
    
    25
    +        contentAlignment = if (toolbarAtTop) Alignment.Companion.TopStart else Alignment.Companion.BottomStart,
    
    26
    +        modifier = Modifier.Companion.fillMaxSize(),
    
    27
    +    ) {
    
    28
    +        TorHomePage(
    
    29
    +            shouldInitiallyShowPromo = mutableStateOf(booleanMatrix.first),
    
    30
    +            toolBarAtTop = toolbarAtTop,
    
    31
    +        )
    
    32
    +    }
    
    33
    +}
    
    34
    +
    
    35
    +private class BooleanBooleanPreviewParameterProvider :
    
    36
    +    PreviewParameterProvider<Pair<Boolean, Boolean>> {
    
    37
    +    override val values: Sequence<Pair<Boolean, Boolean>>
    
    38
    +        get() = sequenceOf(
    
    39
    +            Pair(true, true),
    
    40
    +            Pair(true, false),
    
    41
    +            Pair(false, true),
    
    42
    +            Pair(false, false),
    
    43
    +        )
    
    44
    +}
    
    45
    +
    
    46
    +/**
    
    47
    + * A wrapper annotation for creating a preview that renders a preview for each value of portrait and landscape for english, arabic, and german.
    
    48
    + */
    
    49
    +@Preview(
    
    50
    +    name = "mobile portrait",
    
    51
    +    group = "english",
    
    52
    +    widthDp = 360,
    
    53
    +    heightDp = 780,
    
    54
    +    locale = "en",
    
    55
    +    device = "id:pixel_5",
    
    56
    +)
    
    57
    +@Preview(
    
    58
    +    name = "mobile landscape",
    
    59
    +    group = "english",
    
    60
    +    widthDp = 780,
    
    61
    +    heightDp = 360,
    
    62
    +    locale = "en",
    
    63
    +    device = "spec:parent=pixel_5,orientation=landscape",
    
    64
    +)
    
    65
    +@Preview(
    
    66
    +    name = "mobile portrait",
    
    67
    +    group = "arabic",
    
    68
    +    widthDp = 360,
    
    69
    +    heightDp = 780,
    
    70
    +    locale = "ar",
    
    71
    +    device = "id:pixel_5",
    
    72
    +)
    
    73
    +@Preview(
    
    74
    +    name = "mobile landscape",
    
    75
    +    group = "arabic",
    
    76
    +    widthDp = 780,
    
    77
    +    heightDp = 360,
    
    78
    +    locale = "ar",
    
    79
    +    device = "spec:parent=pixel_5,orientation=landscape",
    
    80
    +)
    
    81
    +@Preview(
    
    82
    +    name = "mobile portrait",
    
    83
    +    group = "german",
    
    84
    +    widthDp = 360,
    
    85
    +    heightDp = 780,
    
    86
    +    locale = "de",
    
    87
    +    device = "id:pixel_5",
    
    88
    +)
    
    89
    +@Preview(
    
    90
    +    name = "mobile landscape",
    
    91
    +    group = "german",
    
    92
    +    widthDp = 780,
    
    93
    +    heightDp = 360,
    
    94
    +    locale = "de",
    
    95
    +    device = "spec:parent=pixel_5,orientation=landscape",
    
    96
    +)
    
    97
    +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
    43 43
     import org.mozilla.fenix.components.toolbar.ToolbarPosition
    
    44 44
     import org.mozilla.fenix.ext.components
    
    45 45
     import org.mozilla.fenix.ext.getPreferenceKey
    
    46
    +import org.mozilla.fenix.nimbus.CookieBannersSection
    
    46 47
     import org.mozilla.fenix.nimbus.FxNimbus
    
    48
    +import org.mozilla.fenix.nimbus.HomeScreenSection
    
    49
    +import org.mozilla.fenix.nimbus.Mr2022Section
    
    50
    +import org.mozilla.fenix.nimbus.QueryParameterStrippingSection
    
    51
    +import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING
    
    52
    +import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_ALLOW_LIST
    
    53
    +import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_PMB
    
    54
    +import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_STRIP_LIST
    
    47 55
     import org.mozilla.fenix.settings.PhoneFeature
    
    48 56
     import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
    
    49 57
     import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
    
    ... ... @@ -2133,9 +2141,4 @@ class Settings(private val appContext: Context) : PreferencesHolder {
    2133 2141
             appContext.getPreferenceKey(R.string.pref_key_quick_start),
    
    2134 2142
             default = false,
    
    2135 2143
         )
    
    2136
    -
    
    2137
    -    var hideCampaign by booleanPreference(
    
    2138
    -        appContext.getPreferenceKey(R.string.pref_key_hide_campaign_2025_ux_survey),
    
    2139
    -        default = false,
    
    2140
    -    )
    
    2141 2144
     }

  • mobile/android/fenix/app/src/main/res/drawable/campaign_hand.xml deleted
    1
    -<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
    
    2
    -    android:viewportWidth="36"
    
    3
    -    android:viewportHeight="36"
    
    4
    -    android:width="36dp"
    
    5
    -    android:height="36dp">
    
    6
    -    <path
    
    7
    -        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"
    
    8
    -        android:fillColor="#EF9645" />
    
    9
    -    <path
    
    10
    -        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"
    
    11
    -        android:fillColor="#FFDC5D" />
    
    12
    -    <path
    
    13
    -        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"
    
    14
    -        android:fillColor="#5DADEC" />
    
    15
    -    <path
    
    16
    -        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"
    
    17
    -        android:fillColor="#5DADEC" />
    
    18
    -    <path
    
    19
    -        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"
    
    20
    -        android:fillColor="#5DADEC" />
    
    21
    -</vector>
    \ No newline at end of file

  • mobile/android/fenix/app/src/main/res/drawable/globe_chain_burst_yec.xml
    1
    +<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">
    
    2
    +      
    
    3
    +    <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"/>
    
    4
    +      
    
    5
    +    <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"/>
    
    6
    +      
    
    7
    +    <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"/>
    
    8
    +      
    
    9
    +    <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M62.37,70.25H60.98V68.84H62.37V70.25Z" android:strokeAlpha="0.8"/>
    
    10
    +      
    
    11
    +    <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M95.1,67.44H93.7V55.17H95.1V67.44Z" android:strokeAlpha="0.8"/>
    
    12
    +      
    
    13
    +    <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"/>
    
    14
    +      
    
    15
    +    <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"/>
    
    16
    +      
    
    17
    +    <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"/>
    
    18
    +      
    
    19
    +    <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"/>
    
    20
    +      
    
    21
    +    <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"/>
    
    22
    +      
    
    23
    +    <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"/>
    
    24
    +      
    
    25
    +    <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"/>
    
    26
    +      
    
    27
    +    <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"/>
    
    28
    +      
    
    29
    +    <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"/>
    
    30
    +      
    
    31
    +    <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"/>
    
    32
    +      
    
    33
    +    <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"/>
    
    34
    +    
    
    35
    +</vector>

  • mobile/android/fenix/app/src/main/res/drawable/heart.xml
    1
    +<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">
    
    2
    +      
    
    3
    +    <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"/>
    
    4
    +      
    
    5
    +    <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"/>
    
    6
    +    
    
    7
    +</vector>

  • mobile/android/fenix/app/src/main/res/font/jacquard_12.ttf
    No preview for this file type
  • mobile/android/fenix/app/src/main/res/layout/fragment_home.xml
    ... ... @@ -47,98 +47,14 @@
    47 47
                     app:layout_collapseMode="parallax"
    
    48 48
                     app:layout_collapseParallaxMultiplier=".167"/>
    
    49 49
                 <!-- This value needs to be 1.67 * the wordmark parallax value as its 24dp vs 40 -->
    
    50
    -
    
    51
    -            <androidx.constraintlayout.widget.ConstraintLayout
    
    52
    -                android:id="@+id/wordmark"
    
    53
    -                android:layout_width="wrap_content"
    
    54
    -                android:layout_height="wrap_content"
    
    55
    -                android:layout_marginStart="16dp"
    
    56
    -                android:layout_marginTop="18dp"
    
    57
    -                android:layout_marginBottom="32dp"
    
    58
    -                android:clickable="false"
    
    59
    -                android:focusable="false"
    
    60
    -                android:importantForAccessibility="no"
    
    61
    -                android:orientation="horizontal"
    
    62
    -                app:srcCompat="@mipmap/ic_launcher_foreground"
    
    63
    -                app:layout_collapseMode="parallax"
    
    64
    -                app:layout_collapseParallaxMultiplier=".1">
    
    65
    -
    
    66
    -                <ImageView
    
    67
    -                    android:id="@+id/wordmarkLogo"
    
    68
    -                    android:layout_width="50dp"
    
    69
    -                    android:layout_height="50dp"
    
    70
    -                    android:adjustViewBounds="true"
    
    71
    -                    android:contentDescription="@null"
    
    72
    -                    app:srcCompat="@mipmap/ic_launcher_foreground"
    
    73
    -                    tools:ignore="ImageContrastCheck"
    
    74
    -                    app:layout_constraintBottom_toBottomOf="parent"
    
    75
    -                    app:layout_constraintStart_toStartOf="parent"
    
    76
    -                    app:layout_constraintTop_toTopOf="parent"
    
    77
    -                    app:layout_constraintEnd_toStartOf="@id/app_name"
    
    78
    -                    android:scaleX="1.5"
    
    79
    -                    android:scaleY="1.5" />
    
    80
    -
    
    81
    -                <!--
    
    82
    -                tor-browser#42590
    
    83
    -                <ImageView
    
    84
    -                    android:id="@+id/wordmarkText"
    
    85
    -                    android:layout_width="wrap_content"
    
    86
    -                    android:layout_height="@dimen/wordmark_text_height"
    
    87
    -                    android:adjustViewBounds="true"
    
    88
    -                    android:contentDescription="@null"
    
    89
    -                    android:layout_marginTop="@dimen/wordmark_text_margin_top"
    
    90
    -                    app:srcCompat="?fenixWordmarkText" />
    
    91
    -                -->
    
    92
    -
    
    93
    -                <TextView
    
    94
    -                    android:id="@+id/app_name"
    
    95
    -                    android:layout_width="wrap_content"
    
    96
    -                    android:layout_height="wrap_content"
    
    97
    -                    android:clickable="false"
    
    98
    -                    android:focusable="false"
    
    99
    -                    android:fontFamily="Roboto-Medium"
    
    100
    -                    android:gravity="start"
    
    101
    -                    android:importantForAccessibility="no"
    
    102
    -                    android:maxLines="2"
    
    103
    -                    android:paddingStart="16dp"
    
    104
    -                    android:paddingEnd="16dp"
    
    105
    -                    android:text="@string/app_name"
    
    106
    -                    android:textColor="#DEFFFFFF"
    
    107
    -                    android:textSize="20sp"
    
    108
    -                    app:layout_constrainedWidth="true"
    
    109
    -                    app:layout_constraintBottom_toBottomOf="parent"
    
    110
    -                    app:layout_constraintEnd_toEndOf="parent"
    
    111
    -                    app:layout_constraintStart_toEndOf="@id/wordmarkLogo"
    
    112
    -                    app:layout_constraintTop_toTopOf="parent" />
    
    113
    -
    
    114
    -            </androidx.constraintlayout.widget.ConstraintLayout>
    
    115
    -
    
    116 50
             </com.google.android.material.appbar.CollapsingToolbarLayout>
    
    117 51
     
    
    118
    -        <TextView
    
    119
    -            android:id="@+id/exploreprivately"
    
    120
    -            android:layout_width="wrap_content"
    
    121
    -            android:layout_height="wrap_content"
    
    122
    -            android:layout_gravity="center|center_vertical"
    
    123
    -            android:gravity="center_horizontal"
    
    124
    -            android:clickable="false"
    
    125
    -            android:ellipsize="end"
    
    126
    -            android:focusable="false"
    
    127
    -            android:importantForAccessibility="no"
    
    128
    -            android:text="@string/tor_explore_privately"
    
    129
    -            android:fontFamily="Roboto-Medium"
    
    130
    -            android:textColor="#DEFFFFFF"
    
    131
    -            android:textSize="40sp"
    
    132
    -            android:lineSpacingMultiplier="1.1"
    
    133
    -            app:layout_scrollFlags="scroll" />
    
    134
    -
    
    135 52
         </com.google.android.material.appbar.AppBarLayout>
    
    136 53
     
    
    137 54
         <androidx.compose.ui.platform.ComposeView
    
    138
    -        android:id="@+id/campaignBox"
    
    55
    +        android:id="@+id/torHomepageView"
    
    139 56
             android:layout_width="match_parent"
    
    140
    -        android:layout_height="wrap_content"
    
    141
    -        android:layout_gravity="center"/>
    
    57
    +        android:layout_height="match_parent" />
    
    142 58
     
    
    143 59
         <androidx.recyclerview.widget.RecyclerView
    
    144 60
             android:id="@+id/sessionControlRecyclerView"
    
    ... ... @@ -155,17 +71,6 @@
    155 71
             tools:itemCount="3"
    
    156 72
             app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
    
    157 73
     
    
    158
    -    <ImageView
    
    159
    -        android:id="@+id/onion_pattern_image"
    
    160
    -        android:layout_width="match_parent"
    
    161
    -        android:layout_height="wrap_content"
    
    162
    -        android:layout_gravity="bottom"
    
    163
    -        app:srcCompat="@drawable/ic_onion_pattern"
    
    164
    -        tools:ignore="ContentDescription"
    
    165
    -        app:layout_constraintBottom_toTopOf="@id/toolbarLayout"
    
    166
    -        app:layout_constraintEnd_toEndOf="parent"
    
    167
    -        app:layout_constraintStart_toStartOf="parent" />
    
    168
    -
    
    169 74
         <androidx.constraintlayout.widget.ConstraintLayout
    
    170 75
             android:id="@+id/toolbarLayout"
    
    171 76
             android:elevation="5dp"
    

  • mobile/android/fenix/app/src/main/res/values-ar/strings.xml
    ... ... @@ -1506,5 +1506,11 @@
    1506 1506
     
    
    1507 1507
         <!-- Translations feature-->
    
    1508 1508
     
    
    1509
    -
    
    1510
    -    </resources>
    1509
    +    <!--   Not meant for production. Uncomment for the compose preview in [TorHomePage] -->
    
    1510
    +    <!--  <string name="donate_now_yec">تبرع الآن</string>-->
    
    1511
    +    <!--  <string name="free_the_internet_yec">حرروا الإنترنت</string>-->
    
    1512
    +    <!--  <string name="body1_yec">دعم الأدوات التي تكسر قيود الرقابة والمراقبة. %s</string>-->
    
    1513
    +    <!--  <string name="body1_link_yec">تبرع لمشروع Tor اليوم.</string>-->
    
    1514
    +    <!--  <string name="body2_yec">حتى 31 ديسمبر، سيتم مضاعفة تبرعك،  بحد أقصى 250,000 دولار!</string>-->
    
    1515
    +    <!--  <string name="no_donation_required_yec">سيكون متصفح Tor لنظام Android مجانيا دائما للاستخدام - لا يلزم التبرع لاستخدام هذا التطبيق.</string>-->
    
    1516
    +</resources>

  • mobile/android/fenix/app/src/main/res/values-de/strings.xml
    ... ... @@ -73,8 +73,8 @@
    73 73
             The first parameter is the name of the app defined in app_name (for example: Fenix) -->
    
    74 74
         <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>
    
    75 75
         <string name="private_browsing_common_myths">
    
    76
    -       
    
    77
    -       
    
    76
    +
    
    77
    +
    
    78 78
         Häufige Missverständnisse über das Surfen im Privaten Modus</string>
    
    79 79
     
    
    80 80
         <!-- True Private Browsing Mode -->
    
    ... ... @@ -1413,7 +1413,7 @@
    1413 1413
         <string name="toast_copy_link_to_clipboard">In Zwischenablage kopiert</string>
    
    1414 1414
         <!-- An option from the share dialog to sign into sync -->
    
    1415 1415
         <string name="sync_sign_in">Bei Sync anmelden</string>
    
    1416
    -     <!-- An option from the three dot menu to sync and save data -->
    
    1416
    +    <!-- An option from the three dot menu to sync and save data -->
    
    1417 1417
         <string name="sync_menu_sync_and_save_data">Daten synchronisieren und speichern</string>
    
    1418 1418
         <!-- An option from the share dialog to send link to all other sync devices -->
    
    1419 1419
         <string name="sync_send_to_all">An alle Geräte senden</string>
    
    ... ... @@ -1800,10 +1800,10 @@
    1800 1800
         <string name="browser_toolbar_long_press_popup_paste_and_go">Einfügen &amp; Los</string>
    
    1801 1801
         <!-- Paste the text in the clipboard -->
    
    1802 1802
         <string name="browser_toolbar_long_press_popup_paste">Einfügen</string>
    
    1803
    -  
    
    1803
    +
    
    1804 1804
         <!-- Snackbar message shown after an URL has been copied to clipboard. -->
    
    1805 1805
         <string name="browser_toolbar_url_copied_to_clipboard_snackbar">URL in Zwischenablage kopiert</string>
    
    1806
    -  
    
    1806
    +
    
    1807 1807
         <!-- Title text for the Add To Homescreen dialog -->
    
    1808 1808
         <string name="add_to_homescreen_title">Zum Startbildschirm hinzufügen</string>
    
    1809 1809
         <!-- Cancel button text for the Add to Homescreen dialog -->
    
    ... ... @@ -2784,4 +2784,13 @@
    2784 2784
         <string name="exit_fullscreen_with_gesture">Um den Vollbildmodus zu beenden, ziehen Sie von oben und verwenden Sie die Zurück-Geste</string>
    
    2785 2785
         <!-- Message shown to explain how to exit fullscreen mode when using back button navigation -->
    
    2786 2786
         <string name="exit_fullscreen_with_back_button">Um den Vollbildmodus zu beenden, ziehen Sie von oben und drücken Sie auf „Zurück“</string>
    
    2787
    +
    
    2788
    +    <!-- Not meant for production. Uncomment for the compose preview in [TorHomePage] -->
    
    2789
    +    <!--  <string name="donate_now_yec">Jetzt spenden</string>-->
    
    2790
    +    <!--  <string name="free_the_internet_yec">Befreit das Internet</string>-->
    
    2791
    +    <!--  <string name="body1_yec">Unterstützen Sie Tools, die die Ketten der Zensur und Überwachung sprengen. %s</string>-->
    
    2792
    +    <!--  <string name="body1_link_yec">Spenden Sie noch heute für das Tor-Projekt.</string>-->
    
    2793
    +    <!--  <string name="body2_yec">Bis zum 31. Dezember wird Ihre Spende bis zu einer Höhe von 250.000 Dollar verdoppelt!</string>-->
    
    2794
    +    <!--  <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>-->
    
    2795
    +
    
    2787 2796
     </resources>

  • mobile/android/fenix/app/src/main/res/values/colors.xml
    ... ... @@ -377,6 +377,11 @@
    377 377
     
    
    378 378
         <!-- Private Mode mask icon circle fill colors -->
    
    379 379
         <color name="mozac_ui_private_mode_circle_fill" tools:ignore="UnusedResources">@color/photonPurple60</color>
    
    380
    +    
    
    381
    +    <!-- TorHomepage gradient colors -->
    
    382
    +    <color name="tor_homepage_gradient_start">#7529A7</color>
    
    383
    +    <color name="tor_homepage_gradient_middle">#492E85</color>
    
    384
    +    <color name="tor_homepage_gradient_end">#383372</color>
    
    380 385
     
    
    381 386
         <!-- Connection Assist -->
    
    382 387
         <color name="connect_button_purple">#9059FF</color>
    
    ... ... @@ -385,4 +390,10 @@
    385 390
         <color name="configure_connection_button_white">#E1E0E7</color>
    
    386 391
         <color name="warning_yellow">#FFA436</color>
    
    387 392
         <color name="progress_background_tint">#55148C</color>
    
    393
    +
    
    394
    +    <!-- 2025 YEC -->
    
    395
    +    <color name="yec_green">#B6E368</color>
    
    396
    +    <color name="yec_purple">#C272FF</color>
    
    397
    +    <color name="yec_black">#15141A</color>
    
    398
    +    <color name="yec_background">#1D1133</color>
    
    388 399
     </resources>

  • mobile/android/fenix/app/src/main/res/values/preference_keys.xml
    ... ... @@ -433,5 +433,4 @@
    433 433
         <string name="pref_key_tor_network_settings_bridges_enabled">pref_key_tor_network_settings_bridges_enabled</string>
    
    434 434
     
    
    435 435
         <string name="pref_key_spoof_english" translatable="false">pref_key_spoof_english</string>
    
    436
    -    <string name="pref_key_hide_campaign_2025_ux_survey" translatable="false">pref_key_hide_campaign_2025_ux_survey_test</string>
    
    437 436
     </resources>

  • mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
    ... ... @@ -134,4 +134,20 @@
    134 134
         <!-- Connection assist. Content Description for back button. Button will start the connection assist process again -->
    
    135 135
         <string name="connection_assist_back_button_content_description_start_again">Start again</string>
    
    136 136
     
    
    137
    +    <!-- 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. -->
    
    138
    +    <string name="donate_now_yec">Donate Now</string>
    
    139
    +    <!-- Year End Campaign. White text to be shown below the purple rectangle that the rest of the content is in. -->
    
    140
    +    <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>
    
    141
    +    <!-- 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. -->
    
    142
    +    <string name="close_yec_button_description">Close</string>
    
    143
    +
    
    144
    +    <!-- 2025 Year End Campaign. Title text that will show up bigger and in a different font -->
    
    145
    +    <string name="free_the_internet_yec">Free the internet</string>
    
    146
    +    <!-- 2025 Year End Campaign. %s will be replaced with the value for body1_link_yec -->
    
    147
    +    <string name="body1_yec">Support tools that break the chains of censorship and surveillance. %s</string>
    
    148
    +    <!-- 2025 Year End Campaign. The part to replace %s in body1_yec -->
    
    149
    +    <string name="body1_link_yec">Donate to the Tor Project today.</string>
    
    150
    +    <!-- 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. -->
    
    151
    +    <string name="body2_yec">Through December 31, your gift will be matched, up to $250,000!</string>
    
    152
    +
    
    137 153
     </resources>