... |
... |
@@ -12,33 +12,65 @@ 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
|
15
|
16
|
import android.view.LayoutInflater
|
16
|
17
|
import android.view.View
|
17
|
18
|
import android.view.ViewGroup
|
|
19
|
+import android.widget.ImageView
|
18
|
20
|
import androidx.activity.result.ActivityResultLauncher
|
19
|
21
|
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
|
20
|
28
|
import androidx.compose.foundation.layout.Column
|
|
29
|
+import androidx.compose.foundation.layout.Row
|
|
30
|
+import androidx.compose.foundation.layout.Spacer
|
21
|
31
|
import androidx.compose.foundation.layout.fillMaxWidth
|
22
|
32
|
import androidx.compose.foundation.layout.heightIn
|
23
|
33
|
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
|
24
|
39
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
40
|
+import androidx.compose.foundation.verticalScroll
|
|
41
|
+import androidx.compose.material.Button
|
25
|
42
|
import androidx.compose.material.ButtonDefaults
|
|
43
|
+import androidx.compose.material.Icon
|
|
44
|
+import androidx.compose.material.IconButton
|
26
|
45
|
import androidx.compose.material.Text
|
27
|
46
|
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
|
28
|
51
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
29
|
52
|
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
|
30
|
58
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
|
59
|
+import androidx.compose.ui.res.painterResource
|
|
60
|
+import androidx.compose.ui.semantics.heading
|
31
|
61
|
import androidx.compose.ui.semantics.semantics
|
32
|
62
|
import androidx.compose.ui.semantics.testTag
|
33
|
63
|
import androidx.compose.ui.semantics.testTagsAsResourceId
|
|
64
|
+import androidx.compose.ui.text.TextLayoutResult
|
|
65
|
+import androidx.compose.ui.text.font.FontWeight
|
34
|
66
|
import androidx.compose.ui.text.style.TextAlign
|
|
67
|
+import androidx.compose.ui.unit.Dp
|
35
|
68
|
import androidx.compose.ui.unit.dp
|
|
69
|
+import androidx.compose.ui.unit.sp
|
36
|
70
|
import androidx.compose.ui.viewinterop.AndroidView
|
37
|
71
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
38
|
72
|
import androidx.core.content.ContextCompat
|
39
|
73
|
import androidx.core.content.ContextCompat.getColor
|
40
|
|
-import androidx.core.view.children
|
41
|
|
-import androidx.core.view.doOnLayout
|
42
|
74
|
import androidx.core.view.isGone
|
43
|
75
|
import androidx.core.view.isVisible
|
44
|
76
|
import androidx.fragment.app.Fragment
|
... |
... |
@@ -108,6 +140,7 @@ import org.mozilla.fenix.components.FenixSnackbar |
108
|
140
|
import org.mozilla.fenix.components.PrivateShortcutCreateManager
|
109
|
141
|
import org.mozilla.fenix.components.TabCollectionStorage
|
110
|
142
|
import org.mozilla.fenix.components.appstate.AppAction
|
|
143
|
+import org.mozilla.fenix.components.components
|
111
|
144
|
import org.mozilla.fenix.components.menu.MenuAccessPoint
|
112
|
145
|
import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature
|
113
|
146
|
import org.mozilla.fenix.components.toolbar.ToolbarPosition
|
... |
... |
@@ -151,7 +184,6 @@ import org.mozilla.fenix.messaging.DefaultMessageController |
151
|
184
|
import org.mozilla.fenix.messaging.FenixMessageSurfaceId
|
152
|
185
|
import org.mozilla.fenix.messaging.MessagingFeature
|
153
|
186
|
import org.mozilla.fenix.microsurvey.ui.MicrosurveyRequestPrompt
|
154
|
|
-import org.mozilla.fenix.nimbus.FxNimbus
|
155
|
187
|
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
|
156
|
188
|
import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
|
157
|
189
|
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
|
... |
... |
@@ -160,12 +192,16 @@ import org.mozilla.fenix.tabstray.TabsTrayAccessPoint |
160
|
192
|
import org.mozilla.fenix.theme.FirefoxTheme
|
161
|
193
|
import org.mozilla.fenix.tor.TorBootstrapFragmentDirections
|
162
|
194
|
import org.mozilla.fenix.tor.TorBootstrapStatus
|
|
195
|
+import org.mozilla.fenix.tor.CampaignStrings
|
163
|
196
|
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
|
164
|
197
|
import org.mozilla.fenix.utils.allowUndo
|
165
|
198
|
import org.mozilla.fenix.wallpapers.Wallpaper
|
166
|
199
|
import java.lang.ref.WeakReference
|
167
|
200
|
import org.mozilla.fenix.GleanMetrics.TabStrip as TabStripMetrics
|
168
|
201
|
|
|
202
|
+import java.text.SimpleDateFormat
|
|
203
|
+import java.util.Date
|
|
204
|
+
|
169
|
205
|
@Suppress("TooManyFunctions", "LargeClass")
|
170
|
206
|
class HomeFragment : Fragment(), UserInteractionHandler {
|
171
|
207
|
private val args by navArgs<HomeFragmentArgs>()
|
... |
... |
@@ -513,6 +549,8 @@ class HomeFragment : Fragment(), UserInteractionHandler { |
513
|
549
|
|
514
|
550
|
activity.themeManager.applyStatusBarTheme(activity)
|
515
|
551
|
|
|
552
|
+ tryShowUX2025Survey()
|
|
553
|
+
|
516
|
554
|
// FxNimbus.features.homescreen.recordExposure()
|
517
|
555
|
|
518
|
556
|
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
|
... |
... |
@@ -524,6 +562,31 @@ class HomeFragment : Fragment(), UserInteractionHandler { |
524
|
562
|
return binding.root
|
525
|
563
|
}
|
526
|
564
|
|
|
565
|
+ private fun tryShowUX2025Survey() {
|
|
566
|
+ val allowedLocales = arrayListOf("en", "es", "ru", "fr", "pt")
|
|
567
|
+ val locale = CampaignStrings.getLocale()
|
|
568
|
+
|
|
569
|
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz")
|
|
570
|
+ val startDate = dateFormat.parse("2025-04-14-12-UTC")
|
|
571
|
+
|
|
572
|
+ val endDate = dateFormat.parse("2025-04-28-00-UTC")
|
|
573
|
+ val currentDate = Date()
|
|
574
|
+
|
|
575
|
+ if (currentDate.before(startDate) || currentDate.after(endDate)) {
|
|
576
|
+ return // comment out to test
|
|
577
|
+ }
|
|
578
|
+
|
|
579
|
+ if (allowedLocales.contains(locale) && !requireContext().settings().hideCampaign) {
|
|
580
|
+ binding.onionPatternImage.visibility = View.GONE
|
|
581
|
+ binding.campaignBox.apply {
|
|
582
|
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
583
|
+ setContent {
|
|
584
|
+ CampaignBox()
|
|
585
|
+ }
|
|
586
|
+ }
|
|
587
|
+ }
|
|
588
|
+ }
|
|
589
|
+
|
527
|
590
|
private fun reinitializeNavBar() {
|
528
|
591
|
initializeNavBar(activity = requireActivity() as HomeActivity)
|
529
|
592
|
}
|
... |
... |
@@ -1402,4 +1465,213 @@ class HomeFragment : Fragment(), UserInteractionHandler { |
1402
|
1465
|
requireActivity().finish()
|
1403
|
1466
|
return true
|
1404
|
1467
|
}
|
|
1468
|
+
|
|
1469
|
+ @Composable
|
|
1470
|
+ fun CampaignBox() {
|
|
1471
|
+ BoxWithConstraints(
|
|
1472
|
+ contentAlignment = Alignment.Center,
|
|
1473
|
+ modifier = Modifier
|
|
1474
|
+ .fillMaxWidth()
|
|
1475
|
+ .wrapContentHeight()
|
|
1476
|
+ ) {
|
|
1477
|
+ val alternateLayout = this.maxWidth >= 500.dp
|
|
1478
|
+
|
|
1479
|
+ CampaignLayout(
|
|
1480
|
+ alternateLayout,
|
|
1481
|
+ maxWidth = this.maxWidth,
|
|
1482
|
+ modifier = Modifier
|
|
1483
|
+ .padding(top = if (alternateLayout) 65.dp else 55.dp, bottom = 56.dp),
|
|
1484
|
+ )
|
|
1485
|
+ }
|
|
1486
|
+ }
|
|
1487
|
+
|
|
1488
|
+ @Composable
|
|
1489
|
+ private fun CampaignLayout(
|
|
1490
|
+ alternateLayout: Boolean,
|
|
1491
|
+ maxWidth: Dp,
|
|
1492
|
+ modifier: Modifier
|
|
1493
|
+ ) {
|
|
1494
|
+ Column(
|
|
1495
|
+ modifier = modifier
|
|
1496
|
+ .padding(horizontal = 22.dp)
|
|
1497
|
+ .verticalScroll(rememberScrollState())
|
|
1498
|
+ .fillMaxWidth(getVariableWidth(maxWidth)),
|
|
1499
|
+ horizontalAlignment = Alignment.CenterHorizontally,
|
|
1500
|
+ ) {
|
|
1501
|
+ PurpleBox(alternateLayout)
|
|
1502
|
+ }
|
|
1503
|
+ }
|
|
1504
|
+
|
|
1505
|
+ private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f)
|
|
1506
|
+
|
|
1507
|
+ @Composable
|
|
1508
|
+ private fun PurpleBox(
|
|
1509
|
+ alternateLayout: Boolean,
|
|
1510
|
+ ) {
|
|
1511
|
+ Box(
|
|
1512
|
+ modifier = Modifier.background(PhotonColors.Violet90, shape = RoundedCornerShape(8.dp))
|
|
1513
|
+ .padding(16.dp),
|
|
1514
|
+ ) {
|
|
1515
|
+ Row(
|
|
1516
|
+ modifier = Modifier.fillMaxWidth(),
|
|
1517
|
+ ) {
|
|
1518
|
+ Emoji()
|
|
1519
|
+ Spacer(Modifier.weight(1f))
|
|
1520
|
+ ExitIcon()
|
|
1521
|
+ }
|
|
1522
|
+ DynamicCampaignContent(alternateLayout)
|
|
1523
|
+ }
|
|
1524
|
+ }
|
|
1525
|
+
|
|
1526
|
+ @Composable
|
|
1527
|
+ private fun Emoji() {
|
|
1528
|
+ val alpha38Violet40 = Color(PhotonColors.Violet40.red, PhotonColors.Violet40.green, PhotonColors.Violet40.blue, 0.38f)
|
|
1529
|
+ Image(
|
|
1530
|
+ painter = painterResource(id = R.drawable.campaign_hand),
|
|
1531
|
+ contentDescription = null,
|
|
1532
|
+ modifier = Modifier
|
|
1533
|
+ .size(64.dp)
|
|
1534
|
+ .padding(16.dp)
|
|
1535
|
+ .drawBehind {
|
|
1536
|
+ drawCircle(
|
|
1537
|
+ color = alpha38Violet40,
|
|
1538
|
+ radius = this.size.maxDimension
|
|
1539
|
+ )
|
|
1540
|
+ }
|
|
1541
|
+ )
|
|
1542
|
+ }
|
|
1543
|
+
|
|
1544
|
+ @Composable
|
|
1545
|
+ private fun ExitIcon() {
|
|
1546
|
+ IconButton(
|
|
1547
|
+ onClick = {
|
|
1548
|
+ binding.campaignBox.visibility = View.GONE
|
|
1549
|
+ binding.onionPatternImage.visibility = View.VISIBLE
|
|
1550
|
+ context?.components?.settings?.hideCampaign = true
|
|
1551
|
+ },
|
|
1552
|
+ ) {
|
|
1553
|
+ Icon(
|
|
1554
|
+ painter = painterResource(id = R.drawable.ic_close),
|
|
1555
|
+ tint = Color(
|
|
1556
|
+ getColor(
|
|
1557
|
+ requireContext(),
|
|
1558
|
+ R.color.photonWhite,
|
|
1559
|
+ ),
|
|
1560
|
+ ),
|
|
1561
|
+ contentDescription = CampaignStrings.get(CampaignStrings.CloseKey),
|
|
1562
|
+ modifier = Modifier
|
|
1563
|
+ .size(48.dp)
|
|
1564
|
+ .padding(8.dp)
|
|
1565
|
+ )
|
|
1566
|
+ }
|
|
1567
|
+ }
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+ @Composable
|
|
1571
|
+ private fun DynamicCampaignContent(
|
|
1572
|
+ alternateLayout: Boolean
|
|
1573
|
+ ) {
|
|
1574
|
+ Row(verticalAlignment = Alignment.CenterVertically) {
|
|
1575
|
+ Column(
|
|
1576
|
+ modifier = Modifier.fillMaxWidth()
|
|
1577
|
+ .padding( top = 88.dp),
|
|
1578
|
+ horizontalAlignment = Alignment.Start,
|
|
1579
|
+ ) {
|
|
1580
|
+ TitleText()
|
|
1581
|
+ MainText()
|
|
1582
|
+
|
|
1583
|
+ if (alternateLayout) {
|
|
1584
|
+ Row(modifier = Modifier.fillMaxWidth()) {
|
|
1585
|
+ Button1(alternateLayout)
|
|
1586
|
+ Button2()
|
|
1587
|
+ }
|
|
1588
|
+ } else {
|
|
1589
|
+ Button1(alternateLayout)
|
|
1590
|
+ Button2()
|
|
1591
|
+ }
|
|
1592
|
+
|
|
1593
|
+ }
|
|
1594
|
+ }
|
|
1595
|
+ }
|
|
1596
|
+
|
|
1597
|
+ @Composable
|
|
1598
|
+ private fun TitleText() {
|
|
1599
|
+
|
|
1600
|
+ Text(text = CampaignStrings.get(CampaignStrings.HeaderKey),
|
|
1601
|
+ color = PhotonColors.LightGrey05,
|
|
1602
|
+ textAlign = TextAlign.Left,
|
|
1603
|
+ fontWeight = FontWeight.Bold,
|
|
1604
|
+ fontSize = 24.sp,
|
|
1605
|
+ lineHeight = 34.sp,
|
|
1606
|
+ modifier = Modifier.padding(bottom = 16.dp)
|
|
1607
|
+ )
|
|
1608
|
+ }
|
|
1609
|
+
|
|
1610
|
+ @Composable
|
|
1611
|
+ private fun MainText() {
|
|
1612
|
+
|
|
1613
|
+ Text(text = CampaignStrings.get(CampaignStrings.BodyKey),
|
|
1614
|
+ modifier = Modifier
|
|
1615
|
+ .fillMaxWidth()
|
|
1616
|
+ .padding(
|
|
1617
|
+ start = 0.dp,
|
|
1618
|
+ end = 0.dp,
|
|
1619
|
+ bottom = 18.dp,
|
|
1620
|
+ ),
|
|
1621
|
+ color = PhotonColors.LightGrey05,
|
|
1622
|
+ fontSize = 18.sp,
|
|
1623
|
+ textAlign = TextAlign.Left,
|
|
1624
|
+ )
|
|
1625
|
+ }
|
|
1626
|
+
|
|
1627
|
+ @Composable
|
|
1628
|
+ private fun Button1(alternateLayout: Boolean) {
|
|
1629
|
+ Button(
|
|
1630
|
+ onClick = {
|
|
1631
|
+ var locale = CampaignStrings.getLocale()
|
|
1632
|
+ if (locale == "pt") {
|
|
1633
|
+ locale = "pt-BR"
|
|
1634
|
+ }
|
|
1635
|
+ (activity as HomeActivity).openToBrowserAndLoad(
|
|
1636
|
+ searchTermOrURL = "https://survey.torproject.org/index.php/923269?lang=${locale}",
|
|
1637
|
+ newTab = true,
|
|
1638
|
+ from = BrowserDirection.FromHome,
|
|
1639
|
+ )
|
|
1640
|
+ },
|
|
1641
|
+ colors = ButtonDefaults.buttonColors(
|
|
1642
|
+ backgroundColor = PhotonColors.Violet60),
|
|
1643
|
+ shape = RoundedCornerShape(4.dp),
|
|
1644
|
+ modifier = Modifier.padding(0.dp)
|
|
1645
|
+ .fillMaxWidth(fraction = if (alternateLayout) 0.5f else 1f),
|
|
1646
|
+
|
|
1647
|
+ ) {
|
|
1648
|
+ Text(text = CampaignStrings.get(CampaignStrings.CTAKey),
|
|
1649
|
+ color = PhotonColors.LightGrey05,
|
|
1650
|
+ textAlign = TextAlign.Center,
|
|
1651
|
+ fontSize = 18.sp,
|
|
1652
|
+ modifier = Modifier.padding(8.dp))
|
|
1653
|
+ }
|
|
1654
|
+ }
|
|
1655
|
+
|
|
1656
|
+ @Composable
|
|
1657
|
+ private fun Button2() {
|
|
1658
|
+ Button(
|
|
1659
|
+ onClick = {
|
|
1660
|
+ binding.campaignBox.visibility = View.GONE
|
|
1661
|
+ binding.onionPatternImage.visibility = View.VISIBLE
|
|
1662
|
+ context?.components?.settings?.hideCampaign = true
|
|
1663
|
+ },
|
|
1664
|
+ colors = ButtonDefaults.buttonColors(
|
|
1665
|
+ backgroundColor = PhotonColors.Violet90),
|
|
1666
|
+ shape = RoundedCornerShape(4.dp),
|
|
1667
|
+ modifier = Modifier.padding(0.dp)
|
|
1668
|
+ .fillMaxWidth()
|
|
1669
|
+ ) {
|
|
1670
|
+ Text(text = CampaignStrings.get(CampaignStrings.DismissKey),
|
|
1671
|
+ color = PhotonColors.Violet20,
|
|
1672
|
+ textAlign = TextAlign.Center,
|
|
1673
|
+ fontSize = 18.sp,
|
|
1674
|
+ modifier = Modifier.padding(8.dp))
|
|
1675
|
+ }
|
|
1676
|
+ }
|
1405
|
1677
|
} |