Loading mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +19 −1 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt: 19 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -164,6 +164,9 @@ import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.tor.TorHomePage import org.mozilla.fenix.tor.UrlQuickLoadViewModel import org.mozilla.fenix.tor.TorCampaignViewModel import java.util.Locale @Suppress("TooManyFunctions", "LargeClass") class HomeFragment : Fragment(), UserInteractionHandler { private val args by navArgs<HomeFragmentArgs>() Loading @@ -179,6 +182,7 @@ class HomeFragment : Fragment(), UserInteractionHandler { private val homeViewModel: HomeScreenViewModel by activityViewModels() private val urlQuickLoadViewModel: UrlQuickLoadViewModel by activityViewModels() private val torCampaignViewModel: TorCampaignViewModel by activityViewModels() private var _bottomToolbarContainerView: BottomToolbarContainerView? = null private val bottomToolbarContainerView: BottomToolbarContainerView Loading Loading @@ -973,12 +977,26 @@ class HomeFragment : Fragment(), UserInteractionHandler { binding.torHomepageView.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { FirefoxTheme { val isToolbarAtTop = components.settings.toolbarPosition == ToolbarPosition.TOP TorHomePage( toolBarAtTop = settings().toolbarPosition == ToolbarPosition.TOP toolBarAtTop = isToolbarAtTop, shouldInitiallyShowPromo = torCampaignViewModel.shouldInitiallyShowPromo, onClicked = { val locale = Locale.getDefault().getLanguage() var url = "https://www.torproject.org/donate/yec2026-tor-browser-android-${locale}" (requireActivity() as HomeActivity).openToBrowserAndLoad( // from https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/45217#note_3464207 url, newTab = true, from = BrowserDirection.FromHome, ) }, ) } } } } private fun initComposeHomepage() { binding.sessionControlRecyclerView.isVisible = false Loading mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt 0 → 100644 +283 −0 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt: 283 added lines, 0 removed lines. Original line number Diff line number Diff line package org.mozilla.fenix.tor import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import mozilla.components.ui.colors.PhotonColors import org.mozilla.fenix.R private val alternateLayoutThreshHold = 500.dp @Composable @CampaignComposePreview fun CampaignBox( shouldShowPromo: MutableState<Boolean> = mutableStateOf(true), onDonateButtonClicked: () -> Unit = {}, ) { BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .fillMaxHeight(), ) { val alternateLayout = this.maxWidth >= alternateLayoutThreshHold CampaignLayout( alternateLayout, maxWidth = this.maxWidth, shouldShowPromo, onDonateButtonClicked = onDonateButtonClicked, ) } } @Composable private fun CampaignLayout( alternateLayout: Boolean, maxWidth: Dp, shouldShowPromo: MutableState<Boolean>, onDonateButtonClicked: () -> Unit, ) { Column( modifier = Modifier .fillMaxWidth(getVariableWidth(maxWidth)) .wrapContentHeight(), horizontalAlignment = Alignment.CenterHorizontally, ) { PurpleBox( alternateLayout, shouldShowPromo, onDonateButtonClicked = onDonateButtonClicked, ) Spacer(Modifier.size(8.dp)) Text( text = stringResource(R.string.no_donation_required_yec), style = TextStyle( fontSize = 12.5.sp, lineHeight = 18.75.sp, fontFamily = FontFamily.SansSerif, fontWeight = FontWeight(400), color = PhotonColors.LightGrey05, textAlign = TextAlign.Center, ), ) } } private fun getVariableWidth(width: Dp): Float = (alternateLayoutThreshHold / width).coerceIn(0.80f, 1.0f) @Composable private fun PurpleBox( alternateLayout: Boolean, shouldShowPromo: MutableState<Boolean>, onDonateButtonClicked: () -> Unit, ) { Box( modifier = Modifier.background( colorResource(mozilla.components.ui.colors.R.color.photonViolet90), shape = RoundedCornerShape(8.dp), ), ) { Row( modifier = Modifier .fillMaxWidth() .wrapContentHeight(), horizontalArrangement = Arrangement.End, ) { ExitIcon(shouldShowPromo) } DynamicCampaignContent( alternateLayout, onDonateButtonClicked = onDonateButtonClicked, ) } } @Composable private fun ExitIcon(shouldShowYec: MutableState<Boolean>) { IconButton( onClick = { shouldShowYec.value = false }, ) { Icon( painter = painterResource(id = mozilla.components.ui.icons.R.drawable.mozac_ic_cross_20), tint = PhotonColors.White, contentDescription = stringResource(R.string.close_yec_button_description), ) } } @Composable private fun DynamicCampaignContent( alternateLayout: Boolean, onDonateButtonClicked: () -> Unit, ) { @Composable fun Icon(shouldShow: Boolean, @DrawableRes drawable: Int, contentDescription: String? = null) { if (shouldShow) { Image( painterResource(drawable), contentDescription = contentDescription, alignment = Alignment.Center, ) } } Row( modifier = Modifier .padding(start = 16.dp, top = if (alternateLayout) 24.dp else 32.dp, end = 16.dp, bottom = 24.dp) .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally, ) { Icon(shouldShow = !alternateLayout, R.drawable.bulb_illo_yec, contentDescription = stringResource(R.string.bulb_illo_yec_description)) Spacer(Modifier.size (if (alternateLayout) 0.dp else 16.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Icon(shouldShow = alternateLayout, R.drawable.circled_lighning_yec) Spacer(Modifier.size(if (alternateLayout) 8.dp else 0.dp)) TitleText() } Spacer(Modifier.size(8.dp)) MainText() Spacer(Modifier.size(if (alternateLayout) 16.dp else 24.dp)) Row( horizontalArrangement = if (alternateLayout) Arrangement.Start else Arrangement.Center, modifier = Modifier.fillMaxWidth(), ) { DonateButton(onDonateButtonClicked = onDonateButtonClicked, alternateLayout) } } } } @Composable private fun TitleText() { Text( text = stringResource(R.string.yec26_tor_powered_by_you), style = TextStyle( fontSize = 24.sp, lineHeight = 32.sp, fontWeight = FontWeight(400), color = PhotonColors.White, textAlign = TextAlign.Center, letterSpacing = 0.18.sp, ), ) } @Composable private fun MainText() { Column { Text( AnnotatedString.fromHtml( // Relevant documentation on HTML markup for android https://developer.android.com/guide/topics/resources/string-resource?utm_source=android-studio-app&utm_medium=app#StylingWithHTML stringResource( R.string.yec26_call_to_action, ), ), style = TextStyle( fontSize = 14.sp, lineHeight = 20.sp, color = PhotonColors.White, fontWeight = FontWeight(400), letterSpacing = 0.25.sp, ) ) } } @Composable private fun DonateButton(onDonateButtonClicked: () -> Unit, alternateLayout: Boolean) { Button( onClick = onDonateButtonClicked, colors = ButtonDefaults.buttonColors( colorResource(mozilla.components.ui.colors.R.color.photonViolet60), ), shape = RoundedCornerShape(4.dp), contentPadding = PaddingValues(horizontal = 16.dp), modifier = if (alternateLayout) Modifier.fillMaxWidth() else Modifier.fillMaxWidth() ) { Text( text = stringResource(R.string.donate_now_yec), textAlign = TextAlign.Center, fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = PhotonColors.LightGrey05, ) Spacer( Modifier.size(8.dp), ) Image( painterResource(R.drawable.heart), contentDescription = null, ) } } @Preview( name = "Small Window", widthDp = 400, ) @Preview( name = "Medium Window", widthDp = 700, ) @Preview( name = "Large Window", widthDp = 1000, ) @Preview( name = "RTL Small Window", locale = "ar", widthDp = 400, ) @Preview( name = "RTL Large Window", locale = "ar", widthDp = 1000 ) annotation class CampaignComposePreview mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt 0 → 100644 +37 −0 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt: 37 added lines, 0 removed lines. Original line number Diff line number Diff line package org.mozilla.fenix.tor import android.annotation.SuppressLint import android.app.Application import android.util.Log import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel import java.text.SimpleDateFormat import java.util.Date class TorCampaignViewModel(application: Application) : AndroidViewModel(application) { val shouldInitiallyShowPromo: MutableState<Boolean> by lazy { mutableStateOf(shouldInitiallyShowPromo()) } @SuppressLint("SimpleDateFormat") fun shouldInitiallyShowPromo(): Boolean { val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz") // From https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/45217#note_3462659 val startDate = dateFormat.parse("2026-10-13-08-UTC") // From https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/45217#note_3463954 val endDate = dateFormat.parse("2027-01-05-00-UTC") val currentDate = Date() if (currentDate.before(startDate) || currentDate.after(endDate)) { return false } Log.d( "TorCampaignViewModel", "org.mozilla.fenix.BuildConfig.BUILD_TYPE = ${org.mozilla.fenix.BuildConfig.BUILD_TYPE}" ) return (org.mozilla.fenix.BuildConfig.BUILD_TYPE == "release") || (org.mozilla.fenix.BuildConfig.BUILD_TYPE == "debug") } } mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt +61 −1 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt: 61 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -31,11 +31,28 @@ import androidx.compose.ui.unit.sp import mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview import org.mozilla.fenix.R import android.annotation.SuppressLint import androidx.compose.foundation.layout.Box import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider @Composable @FlexibleWindowLightDarkPreview fun TorHomePage( toolBarAtTop: Boolean = true, shouldInitiallyShowPromo: MutableState<Boolean> = mutableStateOf(true), onClicked: () -> Unit = {}, ) { // Will persist across a single session, but not multiple sessions. // Tapping the close button 'X' will hide the promo for the duration of the session val shouldShowPromo = rememberSaveable { shouldInitiallyShowPromo } Column( modifier = Modifier .fillMaxSize() Loading Loading @@ -64,7 +81,7 @@ fun TorHomePage( ) { Spacer(modifier = Modifier.size(17.dp)) Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), verticalAlignment = Alignment.CenterVertically, ) { Image( painter = painterResource(R.drawable.tor_browser_app_icon), Loading @@ -82,6 +99,13 @@ fun TorHomePage( ) } Spacer(Modifier.weight(1f)) if (shouldShowPromo.value) { CampaignBox( shouldShowPromo, onDonateButtonClicked = onClicked, ) Spacer(Modifier.weight(1f)) } else { Text( // Moved from the commit 5bb3cc6b93346dabd8d46677fae7f86a8f8a4fc2 // "[android] Modify UI/UX", and the file HomeFragment. Loading @@ -99,6 +123,8 @@ fun TorHomePage( ), modifier = Modifier.align(Alignment.CenterHorizontally), ) } if (!shouldShowPromo.value) { Spacer(Modifier.weight(1f)) Image( painter = painterResource( Loading @@ -107,5 +133,39 @@ fun TorHomePage( contentDescription = null, Modifier.fillMaxWidth(), ) } } Spacer(modifier = Modifier.size(17.dp)) } @SuppressLint("UnrememberedMutableState") @Composable @Preview /** * Relevant documentation * https://developer.android.com/develop/ui/compose/tooling/previews#preview-viewmodel */ private fun TorHomePagePreview( @PreviewParameter( BooleanPreviewParameterProvider::class, ) shouldShowPromo: Boolean, ) { Box( contentAlignment = Alignment.TopStart, modifier = Modifier.fillMaxSize(), ) { TorHomePage( // restricted vertically so will not follow contentAlignment shouldInitiallyShowPromo = mutableStateOf( shouldShowPromo), toolBarAtTop = true, onClicked = {}, ) } } private class BooleanPreviewParameterProvider : PreviewParameterProvider<Boolean> { override val values: Sequence<Boolean> get() = sequenceOf( true, false ) } mobile/android/fenix/app/src/main/res/drawable/bulb_illo_yec.xml 0 → 100644 +107 −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 +19 −1 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt: 19 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -164,6 +164,9 @@ import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.tor.TorHomePage import org.mozilla.fenix.tor.UrlQuickLoadViewModel import org.mozilla.fenix.tor.TorCampaignViewModel import java.util.Locale @Suppress("TooManyFunctions", "LargeClass") class HomeFragment : Fragment(), UserInteractionHandler { private val args by navArgs<HomeFragmentArgs>() Loading @@ -179,6 +182,7 @@ class HomeFragment : Fragment(), UserInteractionHandler { private val homeViewModel: HomeScreenViewModel by activityViewModels() private val urlQuickLoadViewModel: UrlQuickLoadViewModel by activityViewModels() private val torCampaignViewModel: TorCampaignViewModel by activityViewModels() private var _bottomToolbarContainerView: BottomToolbarContainerView? = null private val bottomToolbarContainerView: BottomToolbarContainerView Loading Loading @@ -973,12 +977,26 @@ class HomeFragment : Fragment(), UserInteractionHandler { binding.torHomepageView.apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { FirefoxTheme { val isToolbarAtTop = components.settings.toolbarPosition == ToolbarPosition.TOP TorHomePage( toolBarAtTop = settings().toolbarPosition == ToolbarPosition.TOP toolBarAtTop = isToolbarAtTop, shouldInitiallyShowPromo = torCampaignViewModel.shouldInitiallyShowPromo, onClicked = { val locale = Locale.getDefault().getLanguage() var url = "https://www.torproject.org/donate/yec2026-tor-browser-android-${locale}" (requireActivity() as HomeActivity).openToBrowserAndLoad( // from https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/45217#note_3464207 url, newTab = true, from = BrowserDirection.FromHome, ) }, ) } } } } private fun initComposeHomepage() { binding.sessionControlRecyclerView.isVisible = false Loading
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt 0 → 100644 +283 −0 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt: 283 added lines, 0 removed lines. Original line number Diff line number Diff line package org.mozilla.fenix.tor import androidx.annotation.DrawableRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import mozilla.components.ui.colors.PhotonColors import org.mozilla.fenix.R private val alternateLayoutThreshHold = 500.dp @Composable @CampaignComposePreview fun CampaignBox( shouldShowPromo: MutableState<Boolean> = mutableStateOf(true), onDonateButtonClicked: () -> Unit = {}, ) { BoxWithConstraints( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .fillMaxHeight(), ) { val alternateLayout = this.maxWidth >= alternateLayoutThreshHold CampaignLayout( alternateLayout, maxWidth = this.maxWidth, shouldShowPromo, onDonateButtonClicked = onDonateButtonClicked, ) } } @Composable private fun CampaignLayout( alternateLayout: Boolean, maxWidth: Dp, shouldShowPromo: MutableState<Boolean>, onDonateButtonClicked: () -> Unit, ) { Column( modifier = Modifier .fillMaxWidth(getVariableWidth(maxWidth)) .wrapContentHeight(), horizontalAlignment = Alignment.CenterHorizontally, ) { PurpleBox( alternateLayout, shouldShowPromo, onDonateButtonClicked = onDonateButtonClicked, ) Spacer(Modifier.size(8.dp)) Text( text = stringResource(R.string.no_donation_required_yec), style = TextStyle( fontSize = 12.5.sp, lineHeight = 18.75.sp, fontFamily = FontFamily.SansSerif, fontWeight = FontWeight(400), color = PhotonColors.LightGrey05, textAlign = TextAlign.Center, ), ) } } private fun getVariableWidth(width: Dp): Float = (alternateLayoutThreshHold / width).coerceIn(0.80f, 1.0f) @Composable private fun PurpleBox( alternateLayout: Boolean, shouldShowPromo: MutableState<Boolean>, onDonateButtonClicked: () -> Unit, ) { Box( modifier = Modifier.background( colorResource(mozilla.components.ui.colors.R.color.photonViolet90), shape = RoundedCornerShape(8.dp), ), ) { Row( modifier = Modifier .fillMaxWidth() .wrapContentHeight(), horizontalArrangement = Arrangement.End, ) { ExitIcon(shouldShowPromo) } DynamicCampaignContent( alternateLayout, onDonateButtonClicked = onDonateButtonClicked, ) } } @Composable private fun ExitIcon(shouldShowYec: MutableState<Boolean>) { IconButton( onClick = { shouldShowYec.value = false }, ) { Icon( painter = painterResource(id = mozilla.components.ui.icons.R.drawable.mozac_ic_cross_20), tint = PhotonColors.White, contentDescription = stringResource(R.string.close_yec_button_description), ) } } @Composable private fun DynamicCampaignContent( alternateLayout: Boolean, onDonateButtonClicked: () -> Unit, ) { @Composable fun Icon(shouldShow: Boolean, @DrawableRes drawable: Int, contentDescription: String? = null) { if (shouldShow) { Image( painterResource(drawable), contentDescription = contentDescription, alignment = Alignment.Center, ) } } Row( modifier = Modifier .padding(start = 16.dp, top = if (alternateLayout) 24.dp else 32.dp, end = 16.dp, bottom = 24.dp) .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally, ) { Icon(shouldShow = !alternateLayout, R.drawable.bulb_illo_yec, contentDescription = stringResource(R.string.bulb_illo_yec_description)) Spacer(Modifier.size (if (alternateLayout) 0.dp else 16.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Icon(shouldShow = alternateLayout, R.drawable.circled_lighning_yec) Spacer(Modifier.size(if (alternateLayout) 8.dp else 0.dp)) TitleText() } Spacer(Modifier.size(8.dp)) MainText() Spacer(Modifier.size(if (alternateLayout) 16.dp else 24.dp)) Row( horizontalArrangement = if (alternateLayout) Arrangement.Start else Arrangement.Center, modifier = Modifier.fillMaxWidth(), ) { DonateButton(onDonateButtonClicked = onDonateButtonClicked, alternateLayout) } } } } @Composable private fun TitleText() { Text( text = stringResource(R.string.yec26_tor_powered_by_you), style = TextStyle( fontSize = 24.sp, lineHeight = 32.sp, fontWeight = FontWeight(400), color = PhotonColors.White, textAlign = TextAlign.Center, letterSpacing = 0.18.sp, ), ) } @Composable private fun MainText() { Column { Text( AnnotatedString.fromHtml( // Relevant documentation on HTML markup for android https://developer.android.com/guide/topics/resources/string-resource?utm_source=android-studio-app&utm_medium=app#StylingWithHTML stringResource( R.string.yec26_call_to_action, ), ), style = TextStyle( fontSize = 14.sp, lineHeight = 20.sp, color = PhotonColors.White, fontWeight = FontWeight(400), letterSpacing = 0.25.sp, ) ) } } @Composable private fun DonateButton(onDonateButtonClicked: () -> Unit, alternateLayout: Boolean) { Button( onClick = onDonateButtonClicked, colors = ButtonDefaults.buttonColors( colorResource(mozilla.components.ui.colors.R.color.photonViolet60), ), shape = RoundedCornerShape(4.dp), contentPadding = PaddingValues(horizontal = 16.dp), modifier = if (alternateLayout) Modifier.fillMaxWidth() else Modifier.fillMaxWidth() ) { Text( text = stringResource(R.string.donate_now_yec), textAlign = TextAlign.Center, fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = PhotonColors.LightGrey05, ) Spacer( Modifier.size(8.dp), ) Image( painterResource(R.drawable.heart), contentDescription = null, ) } } @Preview( name = "Small Window", widthDp = 400, ) @Preview( name = "Medium Window", widthDp = 700, ) @Preview( name = "Large Window", widthDp = 1000, ) @Preview( name = "RTL Small Window", locale = "ar", widthDp = 400, ) @Preview( name = "RTL Large Window", locale = "ar", widthDp = 1000 ) annotation class CampaignComposePreview
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt 0 → 100644 +37 −0 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt: 37 added lines, 0 removed lines. Original line number Diff line number Diff line package org.mozilla.fenix.tor import android.annotation.SuppressLint import android.app.Application import android.util.Log import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.AndroidViewModel import java.text.SimpleDateFormat import java.util.Date class TorCampaignViewModel(application: Application) : AndroidViewModel(application) { val shouldInitiallyShowPromo: MutableState<Boolean> by lazy { mutableStateOf(shouldInitiallyShowPromo()) } @SuppressLint("SimpleDateFormat") fun shouldInitiallyShowPromo(): Boolean { val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz") // From https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/45217#note_3462659 val startDate = dateFormat.parse("2026-10-13-08-UTC") // From https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/45217#note_3463954 val endDate = dateFormat.parse("2027-01-05-00-UTC") val currentDate = Date() if (currentDate.before(startDate) || currentDate.after(endDate)) { return false } Log.d( "TorCampaignViewModel", "org.mozilla.fenix.BuildConfig.BUILD_TYPE = ${org.mozilla.fenix.BuildConfig.BUILD_TYPE}" ) return (org.mozilla.fenix.BuildConfig.BUILD_TYPE == "release") || (org.mozilla.fenix.BuildConfig.BUILD_TYPE == "debug") } }
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt +61 −1 Changes for mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt: 61 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -31,11 +31,28 @@ import androidx.compose.ui.unit.sp import mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview import org.mozilla.fenix.R import android.annotation.SuppressLint import androidx.compose.foundation.layout.Box import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider @Composable @FlexibleWindowLightDarkPreview fun TorHomePage( toolBarAtTop: Boolean = true, shouldInitiallyShowPromo: MutableState<Boolean> = mutableStateOf(true), onClicked: () -> Unit = {}, ) { // Will persist across a single session, but not multiple sessions. // Tapping the close button 'X' will hide the promo for the duration of the session val shouldShowPromo = rememberSaveable { shouldInitiallyShowPromo } Column( modifier = Modifier .fillMaxSize() Loading Loading @@ -64,7 +81,7 @@ fun TorHomePage( ) { Spacer(modifier = Modifier.size(17.dp)) Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), verticalAlignment = Alignment.CenterVertically, ) { Image( painter = painterResource(R.drawable.tor_browser_app_icon), Loading @@ -82,6 +99,13 @@ fun TorHomePage( ) } Spacer(Modifier.weight(1f)) if (shouldShowPromo.value) { CampaignBox( shouldShowPromo, onDonateButtonClicked = onClicked, ) Spacer(Modifier.weight(1f)) } else { Text( // Moved from the commit 5bb3cc6b93346dabd8d46677fae7f86a8f8a4fc2 // "[android] Modify UI/UX", and the file HomeFragment. Loading @@ -99,6 +123,8 @@ fun TorHomePage( ), modifier = Modifier.align(Alignment.CenterHorizontally), ) } if (!shouldShowPromo.value) { Spacer(Modifier.weight(1f)) Image( painter = painterResource( Loading @@ -107,5 +133,39 @@ fun TorHomePage( contentDescription = null, Modifier.fillMaxWidth(), ) } } Spacer(modifier = Modifier.size(17.dp)) } @SuppressLint("UnrememberedMutableState") @Composable @Preview /** * Relevant documentation * https://developer.android.com/develop/ui/compose/tooling/previews#preview-viewmodel */ private fun TorHomePagePreview( @PreviewParameter( BooleanPreviewParameterProvider::class, ) shouldShowPromo: Boolean, ) { Box( contentAlignment = Alignment.TopStart, modifier = Modifier.fillMaxSize(), ) { TorHomePage( // restricted vertically so will not follow contentAlignment shouldInitiallyShowPromo = mutableStateOf( shouldShowPromo), toolBarAtTop = true, onClicked = {}, ) } } private class BooleanPreviewParameterProvider : PreviewParameterProvider<Boolean> { override val values: Sequence<Boolean> get() = sequenceOf( true, false ) }
mobile/android/fenix/app/src/main/res/drawable/bulb_illo_yec.xml 0 → 100644 +107 −0 File added.Preview size limit exceeded, changes collapsed. Show changes