[tbb-commits] [Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.5-1] 3 commits: fixup! Implement Android-native Connection Assist UI

Dan Ballard (@dan) git at gitlab.torproject.org
Tue Mar 26 23:11:32 UTC 2024



Dan Ballard pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android


Commits:
333d9284 by clairehurst at 2024-03-26T17:06:53-06:00
fixup! Implement Android-native Connection Assist UI

- - - - -
86815386 by clairehurst at 2024-03-26T17:06:54-06:00
fixup! Bug 41878: Add standalone Tor Bootstrap

- - - - -
99f95c30 by clairehurst at 2024-03-26T17:06:54-06:00
fixup! Add Tor integration and UI

- - - - -


12 changed files:

- fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
- + fenix/app/src/main/res/drawable/connect_broken.xml
- + fenix/app/src/main/res/drawable/globe_broken.xml
- fenix/app/src/main/res/drawable/tor_bootstrap_background_gradient.xml
- fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
- fenix/app/src/main/res/values/colors.xml
- fenix/app/src/main/res/values/styles.xml


Changes:

=====================================
fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
=====================================
@@ -168,6 +168,7 @@ import java.util.Locale
 import androidx.navigation.fragment.findNavController
 import mozilla.components.browser.engine.gecko.GeckoEngine
 import mozilla.components.browser.state.selector.findCustomTab
+import org.mozilla.fenix.home.HomeFragment
 import org.mozilla.geckoview.TorIntegrationAndroid
 
 /**
@@ -815,6 +816,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn
 
     final override fun onBackPressed() {
         supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
+            if (it is HomeFragment){
+                finish()
+                return
+            }
             if (it is UserInteractionHandler && it.onBackPressed()) {
                 return
             }


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
=====================================
@@ -4,11 +4,19 @@
 
 package org.mozilla.fenix.tor
 
+import android.graphics.Color
 import android.os.Build
 import android.os.Bundle
+import android.text.SpannableString
+import android.text.Spanned
+import android.text.TextPaint
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.viewModels
 import androidx.lifecycle.Lifecycle
@@ -22,6 +30,7 @@ import org.mozilla.fenix.ext.hideToolbar
 
 class TorConnectionAssistFragment : Fragment() {
 
+    private val TAG = "TorConnectionAssistFrag"
     private var _binding: FragmentTorConnectionAssistBinding? = null
     private val binding get() = _binding!!
 
@@ -49,13 +58,14 @@ class TorConnectionAssistFragment : Fragment() {
 
         lifecycleScope.launch {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
-                viewModel.torConnectState.collect {
-                    when (it) {
-                        TorConnectState.Initial -> showConfiguring()
+                viewModel.torConnectState.collect { torConnectState ->
+                    Log.d(TAG, "torConnectState is ${torConnectState.state}")
+                    when (torConnectState) {
+                        TorConnectState.Initial -> showSplash()
                         TorConnectState.Configuring -> showConfiguring()
                         TorConnectState.AutoBootstrapping -> showBootstrapping()
                         TorConnectState.Bootstrapping -> showBootstrapping()
-                        TorConnectState.Error -> TODO()
+                        TorConnectState.Error -> showError()
                         TorConnectState.Bootstrapped -> openHome()
                         TorConnectState.Disabled -> openHome()
                     }
@@ -73,59 +83,302 @@ class TorConnectionAssistFragment : Fragment() {
             }
         }
 
-        viewModel.quickconnectToggle().observe(
-            viewLifecycleOwner
+        viewModel.quickstartToggle().observe(
+            viewLifecycleOwner,
         ) {
-            binding.quickstartSwitch.isChecked = it
+            binding.quickstartSwitch.isChecked = it == true
+        }
+
+        binding.quickstartSwitch.setOnCheckedChangeListener { _, isChecked ->
+            viewModel.handleQuickstartChecked(isChecked)
         }
+    }
 
+
+    private fun showSplash() {
+        binding.torBootstrapProgressBar.visibility = View.GONE
+        binding.settingsButton.visibility = View.GONE
+        binding.backButton.visibility = View.GONE
+        binding.torConnectImage.visibility = View.GONE
+        binding.titleLargeTextView.visibility = View.GONE
+        binding.titleDescription.visibility = View.GONE
+        binding.quickStartDescription.visibility = View.GONE
+        binding.quickstartSwitch.visibility = View.GONE
+        binding.torBootstrapButton1.visibility = View.GONE
+        binding.torBootstrapButton2.visibility = View.GONE
+        binding.wordmarkLogo.visibility = View.VISIBLE
+    }
+
+
+    private fun showConfiguring() {
+        binding.wordmarkLogo.visibility = View.GONE
+
+        binding.torBootstrapProgressBar.visibility = View.INVISIBLE
+        binding.torBootstrapProgressBar.progress = 0
+        binding.backButton.visibility = View.INVISIBLE
+        binding.settingsButton.visibility = View.VISIBLE
         binding.settingsButton.setOnClickListener {
             viewModel.cancelTorBootstrap()
             openSettings()
         }
+        binding.torConnectImage.visibility = View.VISIBLE
+        binding.torConnectImage.setImageResource(R.drawable.connect)
+        binding.titleLargeTextView.visibility = View.VISIBLE
+        binding.titleLargeTextView.text = getString(R.string.connection_assist_tor_connect_title)
+        binding.titleDescription.visibility = View.VISIBLE
+        binding.titleDescription.text =
+            getString(R.string.preferences_tor_network_settings_explanation)
+        binding.quickStartDescription.visibility = View.VISIBLE
+        binding.quickstartSwitch.visibility = View.VISIBLE
+        binding.quickstartSwitch.isChecked = viewModel.quickstartToggle().value == true
+
+        binding.unblockTheInternetInCountryDescription.visibility = View.GONE
+        binding.countryDropDown.visibility = View.GONE
 
-        binding.torBootstrapConnectButton.setOnClickListener {
+        binding.torBootstrapButton1.visibility = View.VISIBLE
+        binding.torBootstrapButton1.text = getString(R.string.tor_bootstrap_connect)
+        binding.torBootstrapButton1.setOnClickListener {
             viewModel.handleConnect(lifecycleScope = lifecycleScope)
+            showBootstrapping()
         }
 
-        binding.quickstartSwitch.setOnCheckedChangeListener { _, isChecked ->
-            viewModel.handleQuickstartChecked(isChecked)
+        binding.torBootstrapButton2.visibility = View.VISIBLE
+        binding.torBootstrapButton2.text =
+            getString(R.string.connection_assist_configure_connection_button)
+        binding.torBootstrapButton2.setOnClickListener {
+            viewModel.cancelTorBootstrap()
+            openTorNetworkSettings()
         }
     }
 
-    private fun showConfiguring() {
-        binding.torBootstrapConnectButton.visibility = View.VISIBLE
-        binding.torBootstrapNetworkSettingsButton.text =
+    private fun showBootstrapping() {
+        binding.wordmarkLogo.visibility = View.GONE
+
+        binding.torBootstrapProgressBar.visibility = View.VISIBLE
+        binding.torBootstrapProgressBar.progress = 0
+        binding.backButton.visibility = View.INVISIBLE
+        binding.settingsButton.visibility = View.VISIBLE
+        binding.settingsButton.setOnClickListener {
+            viewModel.cancelTorBootstrap()
+            openSettings()
+        }
+        binding.torConnectImage.visibility = View.VISIBLE
+        binding.torConnectImage.setImageResource(R.drawable.connect)
+        binding.titleLargeTextView.visibility = View.VISIBLE
+        binding.titleLargeTextView.text = getString(R.string.connection_assist_connecting_title)
+        binding.titleDescription.visibility = View.VISIBLE
+        binding.titleDescription.text =
+            getString(R.string.preferences_tor_network_settings_explanation)
+        binding.quickstartSwitch.visibility = View.VISIBLE
+        binding.quickstartSwitch.isChecked = viewModel.quickstartToggle().value == true
+        binding.quickstartSwitch.jumpDrawablesToCurrentState()
+        binding.quickStartDescription.visibility = View.VISIBLE
+        binding.torBootstrapButton1.visibility = View.INVISIBLE
+        binding.torBootstrapButton2.visibility = View.VISIBLE
+        binding.torBootstrapButton2.text = getString(R.string.btn_cancel)
+        binding.torBootstrapButton2.setOnClickListener { viewModel.cancelTorBootstrap() }
+    }
+
+    private suspend fun showError() {
+        viewModel.torError.collect {
+            Log.d(
+                TAG,
+                "TorError: details = ${it?.details ?: "null details"}, message = ${it?.message ?: "null message"}",
+            )
+            when (viewModel.handleError(it)) {
+                ErrorScreen.CantConnectToInternet -> showCantConnectToInternet()
+                ErrorScreen.CantConnectToTorDirectly -> showCantConnectToTorDirectly()
+                ErrorScreen.WeCouldntFindYourLocation -> showWeCouldntFindYourLocation()
+                ErrorScreen.WereStillHavingTroubleConnecting -> showWereStillHavingTroubleConnecting()
+                ErrorScreen.WeWerentAbleToConnectAutomatically -> showWeWerentAbleToConnectAutomatically()
+                null -> {
+                    // no op
+                    Log.d(TAG, "ErrorScreen: null, nothing shown")
+                }
+            }
+        }
+    }
+
+    private fun showCantConnectToInternet() {
+        Log.d(TAG, "showCantConnectToInternet()")
+        binding.torBootstrapProgressBar.visibility = View.VISIBLE
+        binding.torBootstrapProgressBar.progressTintList =
+            AppCompatResources.getColorStateList(requireContext(), R.color.warning_yellow)
+        binding.torBootstrapProgressBar.progress = 100
+
+        binding.backButton.visibility = View.VISIBLE
+        binding.backButton.setOnClickListener {
+            showConfiguring()
+        }
+
+        binding.torConnectImage.setImageResource(R.drawable.globe_broken)
+        binding.titleLargeTextView.text = getString(R.string.connection_assist_internet_error_title)
+
+        val learnMore: String = getString(R.string.connection_assist_internet_error_learn_more)
+        val internetErrorDescription: String = getString(
+            R.string.connection_assist_internet_error_description,
+            learnMore,
+        )
+        handleDescriptionWithClickable(internetErrorDescription, learnMore)
+
+        binding.quickStartDescription.visibility = View.GONE
+        binding.quickstartSwitch.visibility = View.GONE
+
+        binding.torBootstrapButton1.visibility = View.VISIBLE
+        binding.torBootstrapButton1.text =
+            getString(R.string.connection_assist_internet_error_try_again)
+        binding.torBootstrapButton1.setOnClickListener {
+            showTryingAgain()
+            viewModel.handleConnect(lifecycleScope = lifecycleScope)
+        }
+
+        binding.torBootstrapButton2.text =
             getString(R.string.connection_assist_configure_connection_button)
-        binding.torBootstrapNetworkSettingsButton.setOnClickListener {
+        binding.torBootstrapButton2.setOnClickListener {
             openTorNetworkSettings()
         }
     }
 
-    private fun showBootstrapping() {
-        binding.torBootstrapConnectButton.visibility = View.INVISIBLE
-        binding.torBootstrapNetworkSettingsButton.text = getString(R.string.btn_cancel)
-        binding.torBootstrapNetworkSettingsButton.setOnClickListener {
+    private fun showTryingAgain() {
+        Log.d(TAG, "showTryingAgain()")
+        binding.torBootstrapProgressBar.progress = 0
+        binding.torBootstrapProgressBar.visibility = View.VISIBLE
+        binding.torBootstrapProgressBar.progressTintList = null
+        binding.torConnectImage.setImageResource(R.drawable.connect)
+        binding.titleLargeTextView.text =
+            getString(R.string.connection_assist_trying_again_waiting_title)
+
+        binding.quickstartSwitch.visibility = View.GONE
+        binding.quickStartDescription.visibility = View.GONE
+        binding.torBootstrapButton1.visibility = View.INVISIBLE
+        binding.torBootstrapButton2.visibility = View.VISIBLE
+        binding.torBootstrapButton2.text = getString(R.string.btn_cancel)
+        binding.torBootstrapButton2.setOnClickListener {
             viewModel.cancelTorBootstrap()
+            showConfiguring()
         }
     }
 
-    private fun openSettings(preferenceToScrollTo: String? = null) {
-        findNavController().navigate(
-            TorConnectionAssistFragmentDirections
-                .actionTorConnectionAssistFragmentToSettingsFragment(preferenceToScrollTo),
+    private fun showCantConnectToTorDirectly() {
+        Log.d(TAG, "showCantConnectToTorDirectly()")
+        binding.torBootstrapProgressBar.visibility = View.VISIBLE
+        binding.torBootstrapProgressBar.progressTintList =
+            AppCompatResources.getColorStateList(requireContext(), R.color.warning_yellow)
+        binding.torBootstrapProgressBar.progress = 100
+
+        binding.backButton.visibility = View.VISIBLE
+        binding.backButton.setOnClickListener {
+            showConfiguring()
+        }
+
+        binding.torConnectImage.setImageResource(R.drawable.globe_broken)
+        binding.titleLargeTextView.text =
+            getString(R.string.connection_assist_cant_connect_to_tor_title)
+
+        val learnMore: String = getString(R.string.connection_assist_internet_error_learn_more)
+        val tryABridge: String = getString(
+            R.string.connection_assist_try_a_bridge_description,
+            learnMore,
         )
+        handleDescriptionWithClickable(tryABridge, learnMore)
+
+        binding.quickStartDescription.visibility = View.GONE
+        binding.quickstartSwitch.visibility = View.GONE
+        binding.unblockTheInternetInCountryDescription.visibility = View.VISIBLE
+        binding.countryDropDown.visibility = View.VISIBLE
+        // TODO implement countryDropDown
+
+        binding.torBootstrapButton1.visibility = View.VISIBLE
+        binding.torBootstrapButton1.text = getString(R.string.connection_assist_try_a_bridge_button)
+        binding.torBootstrapButton1.setOnClickListener {
+            viewModel.tryABridge()
+            showTryingABridge()
+        }
+        binding.torBootstrapButton2.visibility = View.GONE
     }
 
-    private fun openTorNetworkSettings() {
-        findNavController().navigate(
-            TorConnectionAssistFragmentDirections
-                .actionTorConnectionAssistFragmentToTorNetworkSettings(),
+    private fun showTryingABridge() {
+        Log.d(TAG, "showTryingABridge()")
+        // TODO(Not implemented)
+        binding.torBootstrapButton2.setOnClickListener {
+            showTryingABridge()
+        }
+    }
+
+    private fun showWeCouldntFindYourLocation() {
+        Log.d(TAG, "showWeCouldntFindYourLocation()")
+        // TODO(Not implemented)
+        binding.torBootstrapButton2.setOnClickListener {
+            showTryingABridge()
+        }
+    }
+
+    private fun showWereStillHavingTroubleConnecting() {
+        Log.d(TAG, "showWereStillHavingTroubleConnecting()")
+        TODO("Not yet implemented")
+    }
+
+    private fun showTryingOneMoreTime() {
+        Log.d(TAG, "showTryingOneMoreTime()")
+        TODO("Not yet implemented")
+    }
+
+    private fun showWeWerentAbleToConnectAutomatically() {
+        Log.d(TAG, "showWeWerentAbleToConnectAutomatically()")
+        TODO("Not yet implemented")
+    }
+
+    private fun showUnknownError() {
+        Log.d(TAG, "showUnknownError()")
+        TODO("Not yet implemented")
+    }
+
+    /**
+     * from https://stackoverflow.com/questions/10696986/how-to-set-the-part-of-the-text-view-is-clickable
+     */
+    private fun handleDescriptionWithClickable(errorDescription: String, learnMore: String) {
+        val errorDescriptionSpannableString = SpannableString(errorDescription)
+        val clickableSpan: ClickableSpan = object : ClickableSpan() {
+            override fun onClick(textView: View) {
+                showLearnMore()
+            }
+
+            override fun updateDrawState(drawState: TextPaint) {
+                super.updateDrawState(drawState)
+                drawState.isUnderlineText = true
+            }
+        }
+        errorDescriptionSpannableString.setSpan(
+            clickableSpan,
+            errorDescription.length - learnMore.length,
+            errorDescription.length,
+            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
         )
+        binding.titleDescription.text = errorDescriptionSpannableString
+        binding.titleDescription.movementMethod = LinkMovementMethod.getInstance()
+        binding.titleDescription.highlightColor = Color.TRANSPARENT
+    }
+
+    private fun showLearnMore() {
+        //TODO("Not yet implemented")
     }
 
     private fun openHome() {
+        Log.d(TAG, "openHome()") //This doesn't seem to be ever called
         findNavController().navigate(TorConnectionAssistFragmentDirections.actionStartupHome())
     }
 
+    private fun openSettings(preferenceToScrollTo: String? = null) {
+        findNavController().navigate(
+            TorConnectionAssistFragmentDirections.actionTorConnectionAssistFragmentToSettingsFragment(
+                preferenceToScrollTo,
+            ),
+        )
+    }
+
+    private fun openTorNetworkSettings() {
+        findNavController().navigate(
+            TorConnectionAssistFragmentDirections.actionTorConnectionAssistFragmentToTorNetworkSettings(),
+        )
+    }
 }


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
=====================================
@@ -26,24 +26,30 @@ class TorConnectionAssistViewModel(
     private val _torConnectState = MutableStateFlow(TorConnectState.Initial)
     internal val torConnectState: StateFlow<TorConnectState> = _torConnectState
 
-    init {
-        _torController.registerTorListener(this)
-    }
+    private val _torError = MutableStateFlow(_torController.getLastErrorState())
+    internal val torError: StateFlow<TorError?> = _torError
 
     private val _progress = MutableLiveData(0)
     fun progress(): LiveData<Int> {
         return _progress
     }
 
-    private val _quickconnectToggle = MutableLiveData(_torController.quickstart)
-    fun quickconnectToggle(): LiveData<Boolean> {
-        return _quickconnectToggle
+    private val _quickStartToggle = MutableLiveData<Boolean>() // don't initialize with quickstart off the bat
+    fun quickstartToggle(): LiveData<Boolean?> {
+        _quickStartToggle.value = _torController.quickstart // quickstart isn't ready until torSettings is ready
+        return _quickStartToggle
+    }
+
+    init {
+        Log.d(TAG, "initiating TorConnectionAssistViewModel")
+        _torController.registerTorListener(this)
     }
 
     fun handleConnect(
         withDebugLogging: Boolean = false,
         lifecycleScope: LifecycleCoroutineScope? = null,
     ) {
+        Log.d(TAG, "handleConnect initiatingTorBootstrap with lifecycleScope = $lifecycleScope")
         _torController.initiateTorBootstrap(
             withDebugLogging = withDebugLogging,
             lifecycleScope = lifecycleScope,
@@ -52,6 +58,7 @@ class TorConnectionAssistViewModel(
 
     fun handleQuickstartChecked(checked: Boolean) {
         _torController.quickstart = checked
+        _quickStartToggle.value = checked
     }
 
     fun cancelTorBootstrap() {
@@ -66,7 +73,6 @@ class TorConnectionAssistViewModel(
     override fun onTorConnected() {
         Log.d(TAG, "onTorConnected()")
         _torController.unregisterTorListener(this)
-        _torConnectState.value = _torController.lastKnownStatus
     }
 
     override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) {
@@ -75,12 +81,32 @@ class TorConnectionAssistViewModel(
             _progress.value = progress.toInt()
         }
         _torConnectState.value = _torController.lastKnownStatus
+        _torError.value = _torController.getLastErrorState()
     }
 
     override fun onTorStopped() {
         Log.d(TAG, "onTorStopped()")
-        _progress.value = 0
-        _torConnectState.value = _torController.lastKnownStatus
     }
 
+    internal fun handleError(it: TorError?): ErrorScreen? {
+        // TODO(Only partly implemented)
+        if (it?.message == null){
+            return null
+        }
+        return ErrorScreen.CantConnectToInternet
+    }
+
+    fun tryABridge() {
+        // TODO("Try a bridge not enabled")
+        // connect to bridge based on country
+        // try connecting
+    }
+}
+
+internal enum class ErrorScreen {
+    CantConnectToInternet,
+    CantConnectToTorDirectly,
+    WeCouldntFindYourLocation,
+    WereStillHavingTroubleConnecting,
+    WeWerentAbleToConnectAutomatically,
 }


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
=====================================
@@ -12,6 +12,10 @@ interface TorEvents {
     fun onTorStatusUpdate(entry: String?, status: String?, progress: Double? = 0.0)
     fun onTorStopped()
 }
+class TorError(
+    var message: String,
+    var details: String
+) { }
 
 internal enum class TorStatus(val status: String) {
     OFF("OFF"),
@@ -59,6 +63,8 @@ interface TorController: TorEvents {
     override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?)
     override fun onTorStopped()
 
+    fun getLastErrorState() : TorError?
+
     fun registerTorListener(l: TorEvents)
     fun unregisterTorListener(l: TorEvents)
 


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
=====================================
@@ -53,6 +53,7 @@ class TorControllerGV(
     private var torListeners = mutableListOf<TorEvents>()
 
     internal var lastKnownStatus = TorConnectState.Initial
+    internal var lastKnownError: TorError? = null
     private var wasTorBootstrapped = false
     private var isTorRestarting = false
 
@@ -210,6 +211,7 @@ class TorControllerGV(
 
     override fun setTorStopped() {
         lastKnownStatus = TorConnectState.Configuring
+        onTorStatusUpdate(null, lastKnownStatus.toString(), 0.0)
         onTorStopped()
     }
 
@@ -227,6 +229,10 @@ class TorControllerGV(
         }
     }
 
+    override fun getLastErrorState() : TorError? {
+        return lastKnownError
+    }
+
     // TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents)
     // Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events
     // and state for firefox-android (designed for tor-android-service)
@@ -263,7 +269,7 @@ class TorControllerGV(
         }
 
         lastKnownStatus = newState
-
+        onTorStatusUpdate(null, newStateVal, null)
     }
 
     // TorEventsBootstrapStateChangeListener
@@ -290,7 +296,7 @@ class TorControllerGV(
 
     // TorEventsBootstrapStateChangeListener
     override fun onBootstrapError(message: String?, details: String?) {
-        lastKnownStatus = TorConnectState.Error
+        lastKnownError = TorError(message ?: "", details ?: "")
         onBootstrapStateChange(TorConnectState.Error.state)
     }
 


=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
=====================================
@@ -330,4 +330,10 @@ class TorControllerTAS (private val context: Context): TorController {
     companion object {
         const val torServiceResponseTimeout = 5000L
     }
+
+    // Compat with TorControlGV Stubs
+
+    override fun getLastErrorState() : TorError? {
+        return null;
+    }
 }


=====================================
fenix/app/src/main/res/drawable/connect_broken.xml
=====================================
@@ -0,0 +1,37 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="40dp"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+  <group>
+    <clip-path
+        android:pathData="M0,0h40v40h-40z"/>
+    <path
+        android:pathData="M8.317,5.337C11.521,2.781 15.582,1.253 19.999,1.253C30.352,1.253 38.745,9.647 38.745,20C38.745,24.418 37.218,28.478 34.662,31.681L32.577,29.597C34.611,26.937 35.819,23.611 35.819,20C35.819,11.26 28.739,4.18 19.999,4.18C16.389,4.18 13.063,5.388 10.401,7.421L8.317,5.337Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M5.89,7.656C3.002,10.954 1.252,15.273 1.252,20C1.252,28.967 7.545,36.46 15.959,38.307C16.839,38.5 17.732,38.633 18.652,38.693V24.373C16.785,23.8 15.425,22.06 15.425,20C15.425,19.19 15.635,18.43 16.004,17.771L13.887,15.653C13.013,16.88 12.499,18.38 12.499,20C12.499,22.653 13.879,24.987 15.959,26.32V35.293C9.179,33.513 4.179,27.347 4.179,20C4.179,16.08 5.603,12.493 7.963,9.73L5.89,7.656Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M16.399,13.419L18.618,15.638C19.054,15.501 19.517,15.427 19.998,15.427C22.525,15.427 24.572,17.473 24.572,20C24.572,20.481 24.498,20.945 24.36,21.38L26.579,23.599C27.165,22.531 27.498,21.304 27.498,20C27.498,15.86 24.138,12.5 19.998,12.5C18.694,12.5 17.468,12.833 16.399,13.419Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M24.349,26.112L22.232,23.995C21.954,24.151 21.658,24.278 21.349,24.373V38.693C22.269,38.633 23.162,38.5 24.042,38.307C27.176,37.619 30.015,36.147 32.345,34.109L30.271,32.034C28.492,33.552 26.372,34.681 24.042,35.293V26.32C24.146,26.253 24.249,26.184 24.349,26.112Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M30.653,27.67C32.21,25.514 33.127,22.864 33.127,20C33.127,12.753 27.247,6.873 20,6.873C17.138,6.873 14.488,7.791 12.33,9.348L14.437,11.455C16.037,10.412 17.947,9.807 20,9.807C25.634,9.807 30.194,14.367 30.194,20C30.194,22.051 29.587,23.962 28.544,25.562L30.653,27.67Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M26.272,28.037L28.357,30.121C27.095,31.163 25.635,31.973 24.041,32.487V29.36C24.844,29.014 25.593,28.568 26.272,28.037Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M11.962,13.727L9.878,11.643C8.001,13.914 6.873,16.826 6.873,20C6.873,25.84 10.686,30.787 15.96,32.487V29.36C12.34,27.8 9.806,24.193 9.806,20C9.806,17.633 10.611,15.457 11.962,13.727Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M17.922,19.688L20.311,22.077C20.21,22.092 20.105,22.1 19.999,22.1C18.84,22.1 17.899,21.16 17.899,20C17.899,19.894 17.907,19.79 17.922,19.688Z"
+        android:fillColor="#FBFBFE"/>
+    <path
+        android:pathData="M2.89,4.642L35.228,36.98C35.879,37.632 35.879,38.688 35.228,39.339L35.228,39.339C34.576,39.991 33.52,39.991 32.868,39.339L0.53,7.001C-0.121,6.35 -0.121,5.294 0.53,4.642C1.182,3.991 2.238,3.991 2.89,4.642Z"
+        android:fillColor="#FBFBFE"/>
+  </group>
+</vector>


=====================================
fenix/app/src/main/res/drawable/globe_broken.xml
=====================================
@@ -0,0 +1,18 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="40dp"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+  <path
+      android:pathData="M4.209,1.999L37.355,35.145L35.145,37.355L1.999,4.209L4.209,1.999Z"
+      android:fillColor="#FBFBFE"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M7.869,5.703C3.82,9.142 1.25,14.271 1.25,20C1.25,30.03 9.126,38.221 19.031,38.725L19.041,38.733L19.047,38.726C19.363,38.742 19.681,38.75 20,38.75C20.32,38.75 20.638,38.742 20.954,38.726L20.96,38.733L20.97,38.725C26.306,38.453 31.053,35.951 34.297,32.132L32.079,29.913C30.228,32.166 27.759,33.891 24.931,34.831C26.854,32.438 28.243,29.75 29.097,26.931L26.534,24.368C25.642,28.517 23.465,32.438 20,35.474C15.763,31.76 13.451,26.722 13.063,21.563H23.728L20.603,18.438H13.063C13.22,16.35 13.692,14.282 14.479,12.313L12.102,9.936C10.844,12.632 10.12,15.52 9.93,18.438H4.453C4.872,14.209 6.978,10.477 10.087,7.922L7.869,5.703ZM15.069,34.831C11.952,30.951 10.239,26.295 9.93,21.563H4.453C5.07,27.779 9.331,32.924 15.069,34.831Z"
+      android:fillColor="#FBFBFE"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M13.678,7.093C14.106,6.433 14.569,5.791 15.069,5.169C14.263,5.437 13.486,5.769 12.744,6.159L10.448,3.863C12.985,2.358 15.907,1.434 19.031,1.275L19.041,1.267L19.047,1.274C19.363,1.258 19.681,1.25 20,1.25C20.32,1.25 20.638,1.258 20.954,1.274L20.96,1.267L20.97,1.275C30.875,1.779 38.75,9.97 38.75,20C38.75,23.489 37.798,26.755 36.138,29.553L33.842,27.257C34.752,25.525 35.346,23.601 35.548,21.563H30.071C30.033,22.146 29.974,22.728 29.893,23.308L25.023,18.438H26.938C26.55,13.278 24.238,8.24 20,4.526C18.361,5.963 17.01,7.598 15.947,9.361L13.678,7.093ZM30.071,18.438H35.548C34.931,12.221 30.67,7.076 24.931,5.169C28.049,9.049 29.762,13.705 30.071,18.438Z"
+      android:fillColor="#FBFBFE"
+      android:fillType="evenOdd"/>
+</vector>


=====================================
fenix/app/src/main/res/drawable/tor_bootstrap_background_gradient.xml
=====================================
@@ -7,9 +7,9 @@
         <shape>
             <gradient
                     android:angle="225"
-                    android:startColor="#FF7329A4"
-                    android:centerColor="#FF3A3274"
-                    android:endColor="#FF3A3274"
+                    android:startColor="@color/backgroundGradientLight"
+                    android:centerColor="@color/backgroundGradientDark"
+                    android:endColor="@color/backgroundGradientDark"
                     android:type="linear" />
         </shape>
     </item>


=====================================
fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
=====================================
@@ -3,29 +3,63 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/tor_bootstrap_background_gradient">
+    android:background="@drawable/tor_bootstrap_background_gradient"
+    android:paddingBottom="16dp">
 
     <ProgressBar
         android:id="@+id/tor_bootstrap_progress_bar"
         style="?android:attr/progressBarStyleHorizontal"
         android:layout_width="match_parent"
         android:layout_height="6dp"
+        android:visibility="invisible"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
-    <ImageView
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/settings_button"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_marginTop="26dp"
-        android:layout_marginEnd="20dp"
-        android:contentDescription="@string/settings"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:srcCompat="@drawable/mozac_ic_settings" />
+        app:layout_constraintTop_toTopOf="parent">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/settings"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/mozac_ic_settings" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/back_button"
+        android:layout_width="48dp"
+        android:layout_height="48dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:visibility="invisible"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/settings"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/mozac_ic_back" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
 
     <ImageView
         android:id="@+id/tor_connect_image"
@@ -45,99 +79,139 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginStart="24dp"
-        android:layout_marginTop="24dp"
         android:layout_marginEnd="24dp"
         android:text="@string/connection_assist_tor_connect_title"
         android:textColor="#FBFBFE"
-        android:textFontWeight="400"
         android:textSize="22sp"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/tor_connect_image" />
+        app:layout_constraintTop_toBottomOf="@id/tor_connect_image"
+        app:layout_constraintVertical_bias="0.03" />
 
     <TextView
-        android:id="@+id/connect_to_tor_description"
+        android:id="@+id/title_description"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginStart="24dp"
-        android:layout_marginTop="16dp"
         android:layout_marginEnd="24dp"
+        android:lineSpacingExtra="6dp"
         android:text="@string/preferences_tor_network_settings_explanation"
         android:textColor="#FBFBFE"
-        android:textFontWeight="400"
         android:textSize="14sp"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/title_large_text_view" />
+        app:layout_constraintTop_toBottomOf="@id/title_large_text_view"
+        app:layout_constraintVertical_bias="0.03" />
+
 
     <TextView
-        android:id="@+id/connect_automatically"
-        android:layout_width="wrap_content"
+        android:id="@+id/quick_start_description"
+        android:layout_width="230dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="24dp"
-        android:layout_marginTop="24dp"
         android:text="@string/connection_assist_always_connect_automatically_toggle_description"
-        android:textColor="#80FBFBFE"
+        android:textColor="#FBFBFE"
         android:textSize="14sp"
-        app:layout_constraintBottom_toBottomOf="@+id/quickstart_switch"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/quickstart_switch"
-        app:layout_constraintVertical_bias="1.25" />
+        app:layout_constraintTop_toBottomOf="@+id/title_description"
+        app:layout_constraintVertical_bias=".03" />
 
     <androidx.appcompat.widget.SwitchCompat
         android:id="@+id/quickstart_switch"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginTop="24dp"
+        android:layout_marginStart="100dp"
         android:layout_marginEnd="24dp"
         android:layout_marginBottom="24dp"
-        android:enabled="false"
         android:gravity="center"
-        app:thumbTint="#7D6298"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/connect_to_tor_description"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toEndOf="@+id/quick_start_description"
+        app:layout_constraintTop_toBottomOf="@id/title_description"
+        app:layout_constraintVertical_bias=".023"
         app:layout_goneMarginEnd="6dp"
         app:layout_goneMarginTop="9dp" />
 
-    <Button
-        android:id="@+id/tor_bootstrap_connect_button"
+    <TextView
+        android:id="@+id/unblock_the_internet_in_country_description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_marginTop="24dp"
+        android:layout_marginEnd="24dp"
+        android:text="@string/connection_assist_unblock_the_internet_in_country_or_region"
+        android:textColor="#FBFBFE"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title_description" />
+
+    <androidx.appcompat.widget.AppCompatSpinner
+        android:id="@+id/country_drop_down"
+        style="@style/Widget.AppCompat.Spinner.Underlined"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginStart="24dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="24dp"
+        android:textColor="#FBFBFE"
+        android:tooltipText="@string/connection_assist_share_my_location_country_or_region"
+        android:visibility="invisible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/unblock_the_internet_in_country_description" />
+
+    <ImageView
+        android:id="@+id/wordmarkLogo"
+        android:layout_width="160dp"
+        android:layout_height="160dp"
+        android:src="@mipmap/ic_launcher_round"
+
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        android:contentDescription="" />
+
+    <Button
+        android:id="@+id/tor_bootstrap_button_1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
         android:layout_marginBottom="8dp"
         android:background="@drawable/rounded_corners"
         android:backgroundTint="@color/connect_button_purple"
-        android:maxWidth="312dp"
+        android:minWidth="360dp"
         android:text="@string/tor_bootstrap_connect"
         android:textAllCaps="false"
         android:textColor="#FBFBFE"
-        android:textFontWeight="500"
         android:textSize="14sp"
         android:textStyle="bold"
-        app:layout_constraintBottom_toTopOf="@id/tor_bootstrap_network_settings_button"
+        app:layout_constraintBottom_toTopOf="@id/tor_bootstrap_button_2"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/quickstart_switch"
         app:layout_constraintVertical_bias="1" />
 
-
     <Button
-        android:id="@+id/tor_bootstrap_network_settings_button"
-        android:layout_width="match_parent"
+        android:id="@+id/tor_bootstrap_button_2"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="24dp"
         android:layout_marginEnd="24dp"
-        android:layout_marginBottom="24dp"
+        android:layout_marginBottom="8dp"
         android:background="@drawable/rounded_corners"
         android:backgroundTint="@color/configure_connection_button_white"
-        android:maxWidth="312dp"
+        android:minWidth="360dp"
         android:text="@string/connection_assist_configure_connection_button"
         android:textAllCaps="false"
         android:textColor="#15141A"
-        android:textFontWeight="500"
         android:textSize="14sp"
         android:textStyle="bold"
         app:layout_constraintBottom_toBottomOf="parent"


=====================================
fenix/app/src/main/res/values/colors.xml
=====================================
@@ -273,6 +273,8 @@
     <color name="sync_disconnected_background_private_theme">#5B5846</color>
     <color name="onboarding_illustration_deselected_private_theme">#99FBFBFE</color>
     <color name="prompt_login_edit_text_cursor_color_private_theme">@color/photonViolet50</color>
+    <color name="backgroundGradientDark">#FF3A3274</color>
+    <color name="backgroundGradientLight">#FF7329A4</color>
 
     <!-- Normal theme colors for light mode -->
     <color name="accent_normal_theme">@color/photonInk20</color>
@@ -344,5 +346,6 @@
     <!-- Connection Assist -->
     <color name="connect_button_purple">#9059FF</color>
     <color name="configure_connection_button_white">#E1E0E7</color>
+    <color name="warning_yellow">#FFA436</color>
 
 </resources>


=====================================
fenix/app/src/main/res/values/styles.xml
=====================================
@@ -12,7 +12,7 @@
         <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
         <item name="android:progressBarStyleHorizontal">@style/progressBarStyleHorizontal</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
-        <item name="android:windowBackground">@color/fx_mobile_layer_color_1</item>
+        <item name="android:windowBackground">@color/backgroundGradientDark</item>
         <item name="android:colorEdgeEffect">@color/accent_normal_theme</item>
         <item name="android:colorAccent">@color/fx_mobile_text_color_primary</item>
         <item name="android:textColorPrimary">@color/state_list_text_color</item>



View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/compare/5ab6ae0ae3c863b8915547584a7fdbedb84745fb...99f95c3012970dce8d608fb683a41f4c4a8c242f

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/compare/5ab6ae0ae3c863b8915547584a7fdbedb84745fb...99f95c3012970dce8d608fb683a41f4c4a8c242f
You're receiving this email because of your account on gitlab.torproject.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tbb-commits/attachments/20240326/be243f3c/attachment-0001.htm>


More information about the tbb-commits mailing list