Loading mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +360 −0 Original line number Diff line number Diff line Loading @@ -16,23 +16,58 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat Loading Loading @@ -87,6 +122,7 @@ import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.locale.LocaleManager import mozilla.components.support.utils.ext.isLandscape import mozilla.components.ui.colors.PhotonColors import org.mozilla.fenix.BrowserDirection Loading Loading @@ -155,6 +191,8 @@ import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.SearchSelectorMenu import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme Loading Loading @@ -513,6 +551,8 @@ class HomeFragment : Fragment(), UserInteractionHandler { activity.themeManager.applyStatusBarTheme(activity) setDonationScreen() // FxNimbus.features.homescreen.recordExposure() // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! Loading @@ -524,6 +564,19 @@ class HomeFragment : Fragment(), UserInteractionHandler { return binding.root } private fun setDonationScreen() { if (homeViewModel.shouldShowDonationScreen()) { binding.exploreprivately.visibility = View.GONE binding.onionPatternImage.visibility = View.GONE binding.composeYec2024.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { DonationScreen() } } } } private fun reinitializeNavBar() { initializeNavBar(activity = requireActivity() as HomeActivity) } Loading Loading @@ -1402,4 +1455,311 @@ class HomeFragment : Fragment(), UserInteractionHandler { requireActivity().finish() return true } @Composable fun DonationScreen() { BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .wrapContentHeight() ) { val alternateLayout = this.maxWidth >= 500.dp YecLayout( alternateLayout, maxWidth = this.maxWidth, getYecStyle(), modifier = Modifier .padding(top = 55.dp, bottom = 56.dp), ) } } @Composable private fun YecLayout( alternateLayout: Boolean, maxWidth: Dp, yecStyle: YecStyle, modifier: Modifier ) { Column( modifier = modifier .padding(horizontal = 22.dp) .verticalScroll(rememberScrollState()) .fillMaxWidth(getVariableWidth(maxWidth)), horizontalAlignment = Alignment.CenterHorizontally, ) { PurpleYecBox(alternateLayout, yecStyle, horizontalPadding = 16.dp) AlwaysFreeText() } } private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f) @Composable private fun PurpleYecBox( alternateLayout: Boolean, yecStyle: YecStyle, horizontalPadding: Dp, ) { Box( modifier = Modifier.background(PhotonColors.Ink90, shape = RoundedCornerShape(8.dp)), ) { ExitIcon() DynamicYecContent(alternateLayout, yecStyle, horizontalPadding) } } @Composable private fun AlwaysFreeText() { Text( text = getString( R.string.YEC_2024_tor_browser_for_android_will_always_be_free_no_donation_required, getString(R.string.app_name), ), modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), color = Color(0xFFFBFBFE), fontSize = 12.5.sp, textAlign = TextAlign.Center, lineHeight = 18.75.sp, ) } @Composable private fun ExitIcon() { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, ) { IconButton( onClick = { binding.exploreprivately.visibility = View.VISIBLE binding.onionPatternImage.visibility = View.VISIBLE binding.composeYec2024.visibility = View.GONE homeViewModel.yecDismissed = true }, ) { Icon( painter = painterResource(id = R.drawable.ic_close), tint = Color( getColor( requireContext(), R.color.photonWhite, ), ), contentDescription = getString(R.string.YEC_2024_close), modifier = Modifier .size(48.dp) .padding(8.dp) .scale(0.65f), ) } } } @Composable private fun DynamicYecContent( alternateLayout: Boolean, yecStyle: YecStyle, horizontalPadding: Dp, ) { Row(verticalAlignment = Alignment.CenterVertically) { Column( modifier = Modifier.fillMaxWidth(fraction = if (alternateLayout) .70f else 1f), horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally, ) { if (!alternateLayout) { IlloYecImage(yecStyle, false, horizontalPadding) } YecRightsText(yecStyle, alternateLayout, horizontalPadding) DonationEncouragementText(alternateLayout, horizontalPadding) DonationMatchText(alternateLayout, horizontalPadding) DonateNowButton(alternateLayout, horizontalPadding) } if (alternateLayout) { IlloYecImage(yecStyle, true, horizontalPadding) } } } @Composable private fun IlloYecImage(yecStyle: YecStyle, alternateLayout: Boolean, horizontalPadding: Dp) { Image( painter = painterResource(id = yecStyle.illo), contentDescription = null, modifier = Modifier .fillMaxWidth() .padding( top = if (alternateLayout) 0.dp else 41.dp, bottom = if (alternateLayout) 0.dp else 25.dp, end = if (alternateLayout) horizontalPadding else 0.dp, ), ) } @Composable private fun YecRightsText( yecStyle: YecStyle, alternateLayout: Boolean, horizontalPadding: Dp, ) { var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) } Text( text = getString(yecStyle.text), color = PhotonColors.Ink90, textAlign = if (alternateLayout) TextAlign.Left else TextAlign.Center, fontFamily = FontFamily(Font(R.font.spacegrotesk_bold, FontWeight.Bold)), fontSize = 18.sp, lineHeight = 30.sp, onTextLayout = { textLayoutResult = it }, modifier = Modifier .padding( top = if (alternateLayout) 24.dp else 0.dp, start = horizontalPadding, end = if (alternateLayout) 0.dp else horizontalPadding, bottom = if (alternateLayout) 8.dp else 16.dp, ) .drawBehind { textLayoutResult?.let { result -> for (i in 0 until result.lineCount) { val endOfLineSpacing = 4.dp val lineTop = result.getLineTop(i) val lineBottom = result.getLineBottom(i) val lineLeft = result.getLineLeft(i) - endOfLineSpacing.toPx() val lineRight = result.getLineRight(i) + endOfLineSpacing.toPx() drawRoundRect( color = Color(getColor(requireContext(), yecStyle.color)), topLeft = Offset( lineLeft, lineTop + 2.dp.toPx(), ), size = Size( lineRight - lineLeft, lineBottom - lineTop - 4.dp.toPx(), ), cornerRadius = CornerRadius(2.dp.toPx(), 2.dp.toPx()), ) } } }, ) } @Composable private fun DonationEncouragementText(alternateLayout: Boolean, horizontalPadding: Dp) { Text( text = getString(R.string.YEC_2024_donation_encouragement), modifier = Modifier .fillMaxWidth() .padding( start = horizontalPadding, end = if (alternateLayout) 0.dp else horizontalPadding, bottom = if (alternateLayout) 8.dp else 16.dp, ), color = Color(0xFFFBFBFE), textAlign = TextAlign.Left, ) } @Composable private fun DonationMatchText(alternateLayout: Boolean, horizontalPadding: Dp) { Text( text = getString(R.string.YEC_2024_donation_match_text), modifier = Modifier .fillMaxWidth() .padding( start = horizontalPadding, end = if (alternateLayout) horizontalPadding else 0.dp, bottom = 16.dp, ), color = Color(0xFFFBFBFE), fontWeight = FontWeight.Bold, textAlign = TextAlign.Left, ) } @Composable private fun DonateNowButton(alternateLayout: Boolean, horizontalPadding: Dp) { var locale = LocaleManager.getSelectedLocale(requireContext()).language if (locale.isNotEmpty()) { locale += '-' } Button( onClick = { (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = "https://www.torproject.org/donate/donate-${locale}mobile-yec2024", newTab = true, from = BrowserDirection.FromHome, ) }, modifier = Modifier .padding(bottom = if (alternateLayout) 24.dp else 40.dp) .padding(horizontal = horizontalPadding), colors = ButtonDefaults.buttonColors( backgroundColor = Color( 0xFFFFBD4F, ), ), shape = RoundedCornerShape(4.dp), ) { Text( text = getString(R.string.YEC_2024_donate_now), color = PhotonColors.DarkGrey80, modifier = Modifier.padding( horizontal = if (alternateLayout) 6.dp else 8.dp, vertical = if (alternateLayout) 3.dp else 6.dp, ), fontSize = 16.sp, fontWeight = FontWeight.Bold, letterSpacing = 0.sp, ) Icon( painter = painterResource(R.drawable.heart), contentDescription = null, modifier = Modifier.size(20.dp), ) } } private fun getYecStyle(): YecStyle { if (homeViewModel.firstYecLoad) { homeViewModel.firstYecLoad = false homeViewModel.yecStyleIndex = requireContext().settings().yecStyleIndex requireContext().settings().yecStyleIndex = (requireContext().settings().yecStyleIndex + 1) % 3 } return YecStyle.entries[homeViewModel.yecStyleIndex] } enum class YecStyle( @DrawableRes val illo: Int, @StringRes val text: Int, @ColorRes val color: Int, ) { PurpleSpeak( illo = R.drawable.illo_purple_speak, text = R.string.YEC_2024_right_to_speak, color = R.color.YEC_2024_speak_purple, ), GreenBrowse( illo = R.drawable.illo_green_browse, text = R.string.YEC_2024_right_to_BROWSE, color = R.color.YEC_2024_browse_green, ), RedSearch( illo = R.drawable.illo_red_search, text = R.string.YEC_2024_right_to_SEARCH, color = R.color.YEC_2024_search_red, ) } } mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeScreenViewModel.kt +24 −0 Original line number Diff line number Diff line Loading @@ -4,11 +4,35 @@ package org.mozilla.fenix.home import android.annotation.SuppressLint import androidx.annotation.IntRange import androidx.lifecycle.ViewModel import org.mozilla.fenix.BuildConfig import java.text.SimpleDateFormat import java.util.Date class HomeScreenViewModel : ViewModel() { /** * Used to delete a specific session once the home screen is resumed */ var sessionToDelete: String? = null @IntRange(0, 2) var yecStyleIndex: Int = 0 var firstYecLoad: Boolean = true var yecDismissed: Boolean = false fun shouldShowDonationScreen(): Boolean { @SuppressLint("SimpleDateFormat") val dateFormat = SimpleDateFormat("yyyy-MM-dd") val startDate = dateFormat.parse("2024-10-14") // Change to a date in the past to test val endDate = dateFormat.parse("2025-1-2") val currentDate = Date() @Suppress("KotlinConstantConditions") return !yecDismissed && BuildConfig.BUILD_TYPE == "release" // Comment this line out to test && currentDate.after(startDate) && currentDate.before(endDate) } } mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +5 −0 Original line number Diff line number Diff line Loading @@ -339,6 +339,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) var yecStyleIndex by intPreference( appContext.getPreferenceKey(R.string.pref_key_yec_style_index), default = 0 ) var defaultSearchEngineName by stringPreference( appContext.getPreferenceKey(R.string.pref_key_search_engine), default = "", Loading mobile/android/fenix/app/src/main/res/drawable/heart.xml 0 → 100644 +7 −0 Original line number Diff line number Diff line <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="17" android:viewportWidth="17" android:width="16dp"> <path android:fillColor="#1C1B22" android:pathData="M8.5,6.5C8.5,6.5 8.5,2.5 12,2.5C15.5,2.5 15.5,5.5 15.5,6.5C15.5,11 8.5,15.5 8.5,15.5V6.5Z"/> <path android:fillColor="#1C1B22" android:pathData="M8.5,6.5C8.5,6.5 8.5,2.5 5,2.5C1.5,2.5 1.5,5.5 1.5,6.5C1.5,11 8.5,15.5 8.5,15.5L9.5,9.5L8.5,6.5Z"/> </vector> mobile/android/fenix/app/src/main/res/drawable/illo_green_browse.xml 0 → 100644 +59 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +360 −0 Original line number Diff line number Diff line Loading @@ -16,23 +16,58 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.ActivityResultLauncher import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat Loading Loading @@ -87,6 +122,7 @@ import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.locale.LocaleManager import mozilla.components.support.utils.ext.isLandscape import mozilla.components.ui.colors.PhotonColors import org.mozilla.fenix.BrowserDirection Loading Loading @@ -155,6 +191,8 @@ import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.SearchSelectorMenu import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.advanced.getSelectedLocale import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme Loading Loading @@ -513,6 +551,8 @@ class HomeFragment : Fragment(), UserInteractionHandler { activity.themeManager.applyStatusBarTheme(activity) setDonationScreen() // FxNimbus.features.homescreen.recordExposure() // DO NOT MOVE ANYTHING BELOW THIS addMarker CALL! Loading @@ -524,6 +564,19 @@ class HomeFragment : Fragment(), UserInteractionHandler { return binding.root } private fun setDonationScreen() { if (homeViewModel.shouldShowDonationScreen()) { binding.exploreprivately.visibility = View.GONE binding.onionPatternImage.visibility = View.GONE binding.composeYec2024.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { DonationScreen() } } } } private fun reinitializeNavBar() { initializeNavBar(activity = requireActivity() as HomeActivity) } Loading Loading @@ -1402,4 +1455,311 @@ class HomeFragment : Fragment(), UserInteractionHandler { requireActivity().finish() return true } @Composable fun DonationScreen() { BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .wrapContentHeight() ) { val alternateLayout = this.maxWidth >= 500.dp YecLayout( alternateLayout, maxWidth = this.maxWidth, getYecStyle(), modifier = Modifier .padding(top = 55.dp, bottom = 56.dp), ) } } @Composable private fun YecLayout( alternateLayout: Boolean, maxWidth: Dp, yecStyle: YecStyle, modifier: Modifier ) { Column( modifier = modifier .padding(horizontal = 22.dp) .verticalScroll(rememberScrollState()) .fillMaxWidth(getVariableWidth(maxWidth)), horizontalAlignment = Alignment.CenterHorizontally, ) { PurpleYecBox(alternateLayout, yecStyle, horizontalPadding = 16.dp) AlwaysFreeText() } } private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f) @Composable private fun PurpleYecBox( alternateLayout: Boolean, yecStyle: YecStyle, horizontalPadding: Dp, ) { Box( modifier = Modifier.background(PhotonColors.Ink90, shape = RoundedCornerShape(8.dp)), ) { ExitIcon() DynamicYecContent(alternateLayout, yecStyle, horizontalPadding) } } @Composable private fun AlwaysFreeText() { Text( text = getString( R.string.YEC_2024_tor_browser_for_android_will_always_be_free_no_donation_required, getString(R.string.app_name), ), modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), color = Color(0xFFFBFBFE), fontSize = 12.5.sp, textAlign = TextAlign.Center, lineHeight = 18.75.sp, ) } @Composable private fun ExitIcon() { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, ) { IconButton( onClick = { binding.exploreprivately.visibility = View.VISIBLE binding.onionPatternImage.visibility = View.VISIBLE binding.composeYec2024.visibility = View.GONE homeViewModel.yecDismissed = true }, ) { Icon( painter = painterResource(id = R.drawable.ic_close), tint = Color( getColor( requireContext(), R.color.photonWhite, ), ), contentDescription = getString(R.string.YEC_2024_close), modifier = Modifier .size(48.dp) .padding(8.dp) .scale(0.65f), ) } } } @Composable private fun DynamicYecContent( alternateLayout: Boolean, yecStyle: YecStyle, horizontalPadding: Dp, ) { Row(verticalAlignment = Alignment.CenterVertically) { Column( modifier = Modifier.fillMaxWidth(fraction = if (alternateLayout) .70f else 1f), horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally, ) { if (!alternateLayout) { IlloYecImage(yecStyle, false, horizontalPadding) } YecRightsText(yecStyle, alternateLayout, horizontalPadding) DonationEncouragementText(alternateLayout, horizontalPadding) DonationMatchText(alternateLayout, horizontalPadding) DonateNowButton(alternateLayout, horizontalPadding) } if (alternateLayout) { IlloYecImage(yecStyle, true, horizontalPadding) } } } @Composable private fun IlloYecImage(yecStyle: YecStyle, alternateLayout: Boolean, horizontalPadding: Dp) { Image( painter = painterResource(id = yecStyle.illo), contentDescription = null, modifier = Modifier .fillMaxWidth() .padding( top = if (alternateLayout) 0.dp else 41.dp, bottom = if (alternateLayout) 0.dp else 25.dp, end = if (alternateLayout) horizontalPadding else 0.dp, ), ) } @Composable private fun YecRightsText( yecStyle: YecStyle, alternateLayout: Boolean, horizontalPadding: Dp, ) { var textLayoutResult by remember { mutableStateOf<TextLayoutResult?>(null) } Text( text = getString(yecStyle.text), color = PhotonColors.Ink90, textAlign = if (alternateLayout) TextAlign.Left else TextAlign.Center, fontFamily = FontFamily(Font(R.font.spacegrotesk_bold, FontWeight.Bold)), fontSize = 18.sp, lineHeight = 30.sp, onTextLayout = { textLayoutResult = it }, modifier = Modifier .padding( top = if (alternateLayout) 24.dp else 0.dp, start = horizontalPadding, end = if (alternateLayout) 0.dp else horizontalPadding, bottom = if (alternateLayout) 8.dp else 16.dp, ) .drawBehind { textLayoutResult?.let { result -> for (i in 0 until result.lineCount) { val endOfLineSpacing = 4.dp val lineTop = result.getLineTop(i) val lineBottom = result.getLineBottom(i) val lineLeft = result.getLineLeft(i) - endOfLineSpacing.toPx() val lineRight = result.getLineRight(i) + endOfLineSpacing.toPx() drawRoundRect( color = Color(getColor(requireContext(), yecStyle.color)), topLeft = Offset( lineLeft, lineTop + 2.dp.toPx(), ), size = Size( lineRight - lineLeft, lineBottom - lineTop - 4.dp.toPx(), ), cornerRadius = CornerRadius(2.dp.toPx(), 2.dp.toPx()), ) } } }, ) } @Composable private fun DonationEncouragementText(alternateLayout: Boolean, horizontalPadding: Dp) { Text( text = getString(R.string.YEC_2024_donation_encouragement), modifier = Modifier .fillMaxWidth() .padding( start = horizontalPadding, end = if (alternateLayout) 0.dp else horizontalPadding, bottom = if (alternateLayout) 8.dp else 16.dp, ), color = Color(0xFFFBFBFE), textAlign = TextAlign.Left, ) } @Composable private fun DonationMatchText(alternateLayout: Boolean, horizontalPadding: Dp) { Text( text = getString(R.string.YEC_2024_donation_match_text), modifier = Modifier .fillMaxWidth() .padding( start = horizontalPadding, end = if (alternateLayout) horizontalPadding else 0.dp, bottom = 16.dp, ), color = Color(0xFFFBFBFE), fontWeight = FontWeight.Bold, textAlign = TextAlign.Left, ) } @Composable private fun DonateNowButton(alternateLayout: Boolean, horizontalPadding: Dp) { var locale = LocaleManager.getSelectedLocale(requireContext()).language if (locale.isNotEmpty()) { locale += '-' } Button( onClick = { (activity as HomeActivity).openToBrowserAndLoad( searchTermOrURL = "https://www.torproject.org/donate/donate-${locale}mobile-yec2024", newTab = true, from = BrowserDirection.FromHome, ) }, modifier = Modifier .padding(bottom = if (alternateLayout) 24.dp else 40.dp) .padding(horizontal = horizontalPadding), colors = ButtonDefaults.buttonColors( backgroundColor = Color( 0xFFFFBD4F, ), ), shape = RoundedCornerShape(4.dp), ) { Text( text = getString(R.string.YEC_2024_donate_now), color = PhotonColors.DarkGrey80, modifier = Modifier.padding( horizontal = if (alternateLayout) 6.dp else 8.dp, vertical = if (alternateLayout) 3.dp else 6.dp, ), fontSize = 16.sp, fontWeight = FontWeight.Bold, letterSpacing = 0.sp, ) Icon( painter = painterResource(R.drawable.heart), contentDescription = null, modifier = Modifier.size(20.dp), ) } } private fun getYecStyle(): YecStyle { if (homeViewModel.firstYecLoad) { homeViewModel.firstYecLoad = false homeViewModel.yecStyleIndex = requireContext().settings().yecStyleIndex requireContext().settings().yecStyleIndex = (requireContext().settings().yecStyleIndex + 1) % 3 } return YecStyle.entries[homeViewModel.yecStyleIndex] } enum class YecStyle( @DrawableRes val illo: Int, @StringRes val text: Int, @ColorRes val color: Int, ) { PurpleSpeak( illo = R.drawable.illo_purple_speak, text = R.string.YEC_2024_right_to_speak, color = R.color.YEC_2024_speak_purple, ), GreenBrowse( illo = R.drawable.illo_green_browse, text = R.string.YEC_2024_right_to_BROWSE, color = R.color.YEC_2024_browse_green, ), RedSearch( illo = R.drawable.illo_red_search, text = R.string.YEC_2024_right_to_SEARCH, color = R.color.YEC_2024_search_red, ) } }
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeScreenViewModel.kt +24 −0 Original line number Diff line number Diff line Loading @@ -4,11 +4,35 @@ package org.mozilla.fenix.home import android.annotation.SuppressLint import androidx.annotation.IntRange import androidx.lifecycle.ViewModel import org.mozilla.fenix.BuildConfig import java.text.SimpleDateFormat import java.util.Date class HomeScreenViewModel : ViewModel() { /** * Used to delete a specific session once the home screen is resumed */ var sessionToDelete: String? = null @IntRange(0, 2) var yecStyleIndex: Int = 0 var firstYecLoad: Boolean = true var yecDismissed: Boolean = false fun shouldShowDonationScreen(): Boolean { @SuppressLint("SimpleDateFormat") val dateFormat = SimpleDateFormat("yyyy-MM-dd") val startDate = dateFormat.parse("2024-10-14") // Change to a date in the past to test val endDate = dateFormat.parse("2025-1-2") val currentDate = Date() @Suppress("KotlinConstantConditions") return !yecDismissed && BuildConfig.BUILD_TYPE == "release" // Comment this line out to test && currentDate.after(startDate) && currentDate.before(endDate) } }
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +5 −0 Original line number Diff line number Diff line Loading @@ -339,6 +339,11 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = false ) var yecStyleIndex by intPreference( appContext.getPreferenceKey(R.string.pref_key_yec_style_index), default = 0 ) var defaultSearchEngineName by stringPreference( appContext.getPreferenceKey(R.string.pref_key_search_engine), default = "", Loading
mobile/android/fenix/app/src/main/res/drawable/heart.xml 0 → 100644 +7 −0 Original line number Diff line number Diff line <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="17" android:viewportWidth="17" android:width="16dp"> <path android:fillColor="#1C1B22" android:pathData="M8.5,6.5C8.5,6.5 8.5,2.5 12,2.5C15.5,2.5 15.5,5.5 15.5,6.5C15.5,11 8.5,15.5 8.5,15.5V6.5Z"/> <path android:fillColor="#1C1B22" android:pathData="M8.5,6.5C8.5,6.5 8.5,2.5 5,2.5C1.5,2.5 1.5,5.5 1.5,6.5C1.5,11 8.5,15.5 8.5,15.5L9.5,9.5L8.5,6.5Z"/> </vector>
mobile/android/fenix/app/src/main/res/drawable/illo_green_browse.xml 0 → 100644 +59 −0 File added.Preview size limit exceeded, changes collapsed. Show changes