| 
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:
Changes:
fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
 
| ... | ... | @@ -168,6 +168,7 @@ import java.util.Locale |  
| 168 | 168 |  import androidx.navigation.fragment.findNavController
 |  
| 169 | 169 |  import mozilla.components.browser.engine.gecko.GeckoEngine
 |  
| 170 | 170 |  import mozilla.components.browser.state.selector.findCustomTab
 |  
|  | 171 | +import org.mozilla.fenix.home.HomeFragment
 |  
| 171 | 172 |  import org.mozilla.geckoview.TorIntegrationAndroid
 |  
| 172 | 173 |  
 |  
| 173 | 174 |  /**
 |  
| ... | ... | @@ -815,6 +816,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn |  
| 815 | 816 |  
 |  
| 816 | 817 |      final override fun onBackPressed() {
 |  
| 817 | 818 |          supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
 |  
|  | 819 | +            if (it is HomeFragment){
 |  
|  | 820 | +                finish()
 |  
|  | 821 | +                return
 |  
|  | 822 | +            }
 |  
| 818 | 823 |              if (it is UserInteractionHandler && it.onBackPressed()) {
 |  
| 819 | 824 |                  return
 |  
| 820 | 825 |              }
 |  fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
 
 
| ... | ... | @@ -4,11 +4,19 @@ |  
| 4 | 4 |  
 |  
| 5 | 5 |  package org.mozilla.fenix.tor
 |  
| 6 | 6 |  
 |  
|  | 7 | +import android.graphics.Color
 |  
| 7 | 8 |  import android.os.Build
 |  
| 8 | 9 |  import android.os.Bundle
 |  
|  | 10 | +import android.text.SpannableString
 |  
|  | 11 | +import android.text.Spanned
 |  
|  | 12 | +import android.text.TextPaint
 |  
|  | 13 | +import android.text.method.LinkMovementMethod
 |  
|  | 14 | +import android.text.style.ClickableSpan
 |  
|  | 15 | +import android.util.Log
 |  
| 9 | 16 |  import android.view.LayoutInflater
 |  
| 10 | 17 |  import android.view.View
 |  
| 11 | 18 |  import android.view.ViewGroup
 |  
|  | 19 | +import androidx.appcompat.content.res.AppCompatResources
 |  
| 12 | 20 |  import androidx.fragment.app.Fragment
 |  
| 13 | 21 |  import androidx.fragment.app.viewModels
 |  
| 14 | 22 |  import androidx.lifecycle.Lifecycle
 |  
| ... | ... | @@ -22,6 +30,7 @@ import org.mozilla.fenix.ext.hideToolbar |  
| 22 | 30 |  
 |  
| 23 | 31 |  class TorConnectionAssistFragment : Fragment() {
 |  
| 24 | 32 |  
 |  
|  | 33 | +    private val TAG = "TorConnectionAssistFrag"
 |  
| 25 | 34 |      private var _binding: FragmentTorConnectionAssistBinding? = null
 |  
| 26 | 35 |      private val binding get() = _binding!!
 |  
| 27 | 36 |  
 |  
| ... | ... | @@ -49,13 +58,14 @@ class TorConnectionAssistFragment : Fragment() { |  
| 49 | 58 |  
 |  
| 50 | 59 |          lifecycleScope.launch {
 |  
| 51 | 60 |              repeatOnLifecycle(Lifecycle.State.STARTED) {
 |  
| 52 |  | -                viewModel.torConnectState.collect {
 |  
| 53 |  | -                    when (it) {
 |  
| 54 |  | -                        TorConnectState.Initial -> showConfiguring()
 |  
|  | 61 | +                viewModel.torConnectState.collect { torConnectState ->
 |  
|  | 62 | +                    Log.d(TAG, "torConnectState is ${torConnectState.state}")
 |  
|  | 63 | +                    when (torConnectState) {
 |  
|  | 64 | +                        TorConnectState.Initial -> showSplash()
 |  
| 55 | 65 |                          TorConnectState.Configuring -> showConfiguring()
 |  
| 56 | 66 |                          TorConnectState.AutoBootstrapping -> showBootstrapping()
 |  
| 57 | 67 |                          TorConnectState.Bootstrapping -> showBootstrapping()
 |  
| 58 |  | -                        TorConnectState.Error -> TODO()
 |  
|  | 68 | +                        TorConnectState.Error -> showError()
 |  
| 59 | 69 |                          TorConnectState.Bootstrapped -> openHome()
 |  
| 60 | 70 |                          TorConnectState.Disabled -> openHome()
 |  
| 61 | 71 |                      }
 |  
| ... | ... | @@ -73,59 +83,302 @@ class TorConnectionAssistFragment : Fragment() { |  
| 73 | 83 |              }
 |  
| 74 | 84 |          }
 |  
| 75 | 85 |  
 |  
| 76 |  | -        viewModel.quickconnectToggle().observe(
 |  
| 77 |  | -            viewLifecycleOwner
 |  
|  | 86 | +        viewModel.quickstartToggle().observe(
 |  
|  | 87 | +            viewLifecycleOwner,
 |  
| 78 | 88 |          ) {
 |  
| 79 |  | -            binding.quickstartSwitch.isChecked = it
 |  
|  | 89 | +            binding.quickstartSwitch.isChecked = it == true
 |  
|  | 90 | +        }
 |  
|  | 91 | +
 |  
|  | 92 | +        binding.quickstartSwitch.setOnCheckedChangeListener { _, isChecked ->
 |  
|  | 93 | +            viewModel.handleQuickstartChecked(isChecked)
 |  
| 80 | 94 |          }
 |  
|  | 95 | +    }
 |  
| 81 | 96 |  
 |  
|  | 97 | +
 |  
|  | 98 | +    private fun showSplash() {
 |  
|  | 99 | +        binding.torBootstrapProgressBar.visibility = View.GONE
 |  
|  | 100 | +        binding.settingsButton.visibility = View.GONE
 |  
|  | 101 | +        binding.backButton.visibility = View.GONE
 |  
|  | 102 | +        binding.torConnectImage.visibility = View.GONE
 |  
|  | 103 | +        binding.titleLargeTextView.visibility = View.GONE
 |  
|  | 104 | +        binding.titleDescription.visibility = View.GONE
 |  
|  | 105 | +        binding.quickStartDescription.visibility = View.GONE
 |  
|  | 106 | +        binding.quickstartSwitch.visibility = View.GONE
 |  
|  | 107 | +        binding.torBootstrapButton1.visibility = View.GONE
 |  
|  | 108 | +        binding.torBootstrapButton2.visibility = View.GONE
 |  
|  | 109 | +        binding.wordmarkLogo.visibility = View.VISIBLE
 |  
|  | 110 | +    }
 |  
|  | 111 | +
 |  
|  | 112 | +
 |  
|  | 113 | +    private fun showConfiguring() {
 |  
|  | 114 | +        binding.wordmarkLogo.visibility = View.GONE
 |  
|  | 115 | +
 |  
|  | 116 | +        binding.torBootstrapProgressBar.visibility = View.INVISIBLE
 |  
|  | 117 | +        binding.torBootstrapProgressBar.progress = 0
 |  
|  | 118 | +        binding.backButton.visibility = View.INVISIBLE
 |  
|  | 119 | +        binding.settingsButton.visibility = View.VISIBLE
 |  
| 82 | 120 |          binding.settingsButton.setOnClickListener {
 |  
| 83 | 121 |              viewModel.cancelTorBootstrap()
 |  
| 84 | 122 |              openSettings()
 |  
| 85 | 123 |          }
 |  
|  | 124 | +        binding.torConnectImage.visibility = View.VISIBLE
 |  
|  | 125 | +        binding.torConnectImage.setImageResource(R.drawable.connect)
 |  
|  | 126 | +        binding.titleLargeTextView.visibility = View.VISIBLE
 |  
|  | 127 | +        binding.titleLargeTextView.text = getString(R.string.connection_assist_tor_connect_title)
 |  
|  | 128 | +        binding.titleDescription.visibility = View.VISIBLE
 |  
|  | 129 | +        binding.titleDescription.text =
 |  
|  | 130 | +            getString(R.string.preferences_tor_network_settings_explanation)
 |  
|  | 131 | +        binding.quickStartDescription.visibility = View.VISIBLE
 |  
|  | 132 | +        binding.quickstartSwitch.visibility = View.VISIBLE
 |  
|  | 133 | +        binding.quickstartSwitch.isChecked = viewModel.quickstartToggle().value == true
 |  
|  | 134 | +
 |  
|  | 135 | +        binding.unblockTheInternetInCountryDescription.visibility = View.GONE
 |  
|  | 136 | +        binding.countryDropDown.visibility = View.GONE
 |  
| 86 | 137 |  
 |  
| 87 |  | -        binding.torBootstrapConnectButton.setOnClickListener {
 |  
|  | 138 | +        binding.torBootstrapButton1.visibility = View.VISIBLE
 |  
|  | 139 | +        binding.torBootstrapButton1.text = getString(R.string.tor_bootstrap_connect)
 |  
|  | 140 | +        binding.torBootstrapButton1.setOnClickListener {
 |  
| 88 | 141 |              viewModel.handleConnect(lifecycleScope = lifecycleScope)
 |  
|  | 142 | +            showBootstrapping()
 |  
| 89 | 143 |          }
 |  
| 90 | 144 |  
 |  
| 91 |  | -        binding.quickstartSwitch.setOnCheckedChangeListener { _, isChecked ->
 |  
| 92 |  | -            viewModel.handleQuickstartChecked(isChecked)
 |  
|  | 145 | +        binding.torBootstrapButton2.visibility = View.VISIBLE
 |  
|  | 146 | +        binding.torBootstrapButton2.text =
 |  
|  | 147 | +            getString(R.string.connection_assist_configure_connection_button)
 |  
|  | 148 | +        binding.torBootstrapButton2.setOnClickListener {
 |  
|  | 149 | +            viewModel.cancelTorBootstrap()
 |  
|  | 150 | +            openTorNetworkSettings()
 |  
| 93 | 151 |          }
 |  
| 94 | 152 |      }
 |  
| 95 | 153 |  
 |  
| 96 |  | -    private fun showConfiguring() {
 |  
| 97 |  | -        binding.torBootstrapConnectButton.visibility = View.VISIBLE
 |  
| 98 |  | -        binding.torBootstrapNetworkSettingsButton.text =
 |  
|  | 154 | +    private fun showBootstrapping() {
 |  
|  | 155 | +        binding.wordmarkLogo.visibility = View.GONE
 |  
|  | 156 | +
 |  
|  | 157 | +        binding.torBootstrapProgressBar.visibility = View.VISIBLE
 |  
|  | 158 | +        binding.torBootstrapProgressBar.progress = 0
 |  
|  | 159 | +        binding.backButton.visibility = View.INVISIBLE
 |  
|  | 160 | +        binding.settingsButton.visibility = View.VISIBLE
 |  
|  | 161 | +        binding.settingsButton.setOnClickListener {
 |  
|  | 162 | +            viewModel.cancelTorBootstrap()
 |  
|  | 163 | +            openSettings()
 |  
|  | 164 | +        }
 |  
|  | 165 | +        binding.torConnectImage.visibility = View.VISIBLE
 |  
|  | 166 | +        binding.torConnectImage.setImageResource(R.drawable.connect)
 |  
|  | 167 | +        binding.titleLargeTextView.visibility = View.VISIBLE
 |  
|  | 168 | +        binding.titleLargeTextView.text = getString(R.string.connection_assist_connecting_title)
 |  
|  | 169 | +        binding.titleDescription.visibility = View.VISIBLE
 |  
|  | 170 | +        binding.titleDescription.text =
 |  
|  | 171 | +            getString(R.string.preferences_tor_network_settings_explanation)
 |  
|  | 172 | +        binding.quickstartSwitch.visibility = View.VISIBLE
 |  
|  | 173 | +        binding.quickstartSwitch.isChecked = viewModel.quickstartToggle().value == true
 |  
|  | 174 | +        binding.quickstartSwitch.jumpDrawablesToCurrentState()
 |  
|  | 175 | +        binding.quickStartDescription.visibility = View.VISIBLE
 |  
|  | 176 | +        binding.torBootstrapButton1.visibility = View.INVISIBLE
 |  
|  | 177 | +        binding.torBootstrapButton2.visibility = View.VISIBLE
 |  
|  | 178 | +        binding.torBootstrapButton2.text = getString(R.string.btn_cancel)
 |  
|  | 179 | +        binding.torBootstrapButton2.setOnClickListener { viewModel.cancelTorBootstrap() }
 |  
|  | 180 | +    }
 |  
|  | 181 | +
 |  
|  | 182 | +    private suspend fun showError() {
 |  
|  | 183 | +        viewModel.torError.collect {
 |  
|  | 184 | +            Log.d(
 |  
|  | 185 | +                TAG,
 |  
|  | 186 | +                "TorError: details = ${it?.details ?: "null details"}, message = ${it?.message ?: "null message"}",
 |  
|  | 187 | +            )
 |  
|  | 188 | +            when (viewModel.handleError(it)) {
 |  
|  | 189 | +                ErrorScreen.CantConnectToInternet -> showCantConnectToInternet()
 |  
|  | 190 | +                ErrorScreen.CantConnectToTorDirectly -> showCantConnectToTorDirectly()
 |  
|  | 191 | +                ErrorScreen.WeCouldntFindYourLocation -> showWeCouldntFindYourLocation()
 |  
|  | 192 | +                ErrorScreen.WereStillHavingTroubleConnecting -> showWereStillHavingTroubleConnecting()
 |  
|  | 193 | +                ErrorScreen.WeWerentAbleToConnectAutomatically -> showWeWerentAbleToConnectAutomatically()
 |  
|  | 194 | +                null -> {
 |  
|  | 195 | +                    // no op
 |  
|  | 196 | +                    Log.d(TAG, "ErrorScreen: null, nothing shown")
 |  
|  | 197 | +                }
 |  
|  | 198 | +            }
 |  
|  | 199 | +        }
 |  
|  | 200 | +    }
 |  
|  | 201 | +
 |  
|  | 202 | +    private fun showCantConnectToInternet() {
 |  
|  | 203 | +        Log.d(TAG, "showCantConnectToInternet()")
 |  
|  | 204 | +        binding.torBootstrapProgressBar.visibility = View.VISIBLE
 |  
|  | 205 | +        binding.torBootstrapProgressBar.progressTintList =
 |  
|  | 206 | +            AppCompatResources.getColorStateList(requireContext(), R.color.warning_yellow)
 |  
|  | 207 | +        binding.torBootstrapProgressBar.progress = 100
 |  
|  | 208 | +
 |  
|  | 209 | +        binding.backButton.visibility = View.VISIBLE
 |  
|  | 210 | +        binding.backButton.setOnClickListener {
 |  
|  | 211 | +            showConfiguring()
 |  
|  | 212 | +        }
 |  
|  | 213 | +
 |  
|  | 214 | +        binding.torConnectImage.setImageResource(R.drawable.globe_broken)
 |  
|  | 215 | +        binding.titleLargeTextView.text = getString(R.string.connection_assist_internet_error_title)
 |  
|  | 216 | +
 |  
|  | 217 | +        val learnMore: String = getString(R.string.connection_assist_internet_error_learn_more)
 |  
|  | 218 | +        val internetErrorDescription: String = getString(
 |  
|  | 219 | +            R.string.connection_assist_internet_error_description,
 |  
|  | 220 | +            learnMore,
 |  
|  | 221 | +        )
 |  
|  | 222 | +        handleDescriptionWithClickable(internetErrorDescription, learnMore)
 |  
|  | 223 | +
 |  
|  | 224 | +        binding.quickStartDescription.visibility = View.GONE
 |  
|  | 225 | +        binding.quickstartSwitch.visibility = View.GONE
 |  
|  | 226 | +
 |  
|  | 227 | +        binding.torBootstrapButton1.visibility = View.VISIBLE
 |  
|  | 228 | +        binding.torBootstrapButton1.text =
 |  
|  | 229 | +            getString(R.string.connection_assist_internet_error_try_again)
 |  
|  | 230 | +        binding.torBootstrapButton1.setOnClickListener {
 |  
|  | 231 | +            showTryingAgain()
 |  
|  | 232 | +            viewModel.handleConnect(lifecycleScope = lifecycleScope)
 |  
|  | 233 | +        }
 |  
|  | 234 | +
 |  
|  | 235 | +        binding.torBootstrapButton2.text =
 |  
| 99 | 236 |              getString(R.string.connection_assist_configure_connection_button)
 |  
| 100 |  | -        binding.torBootstrapNetworkSettingsButton.setOnClickListener {
 |  
|  | 237 | +        binding.torBootstrapButton2.setOnClickListener {
 |  
| 101 | 238 |              openTorNetworkSettings()
 |  
| 102 | 239 |          }
 |  
| 103 | 240 |      }
 |  
| 104 | 241 |  
 |  
| 105 |  | -    private fun showBootstrapping() {
 |  
| 106 |  | -        binding.torBootstrapConnectButton.visibility = View.INVISIBLE
 |  
| 107 |  | -        binding.torBootstrapNetworkSettingsButton.text = getString(R.string.btn_cancel)
 |  
| 108 |  | -        binding.torBootstrapNetworkSettingsButton.setOnClickListener {
 |  
|  | 242 | +    private fun showTryingAgain() {
 |  
|  | 243 | +        Log.d(TAG, "showTryingAgain()")
 |  
|  | 244 | +        binding.torBootstrapProgressBar.progress = 0
 |  
|  | 245 | +        binding.torBootstrapProgressBar.visibility = View.VISIBLE
 |  
|  | 246 | +        binding.torBootstrapProgressBar.progressTintList = null
 |  
|  | 247 | +        binding.torConnectImage.setImageResource(R.drawable.connect)
 |  
|  | 248 | +        binding.titleLargeTextView.text =
 |  
|  | 249 | +            getString(R.string.connection_assist_trying_again_waiting_title)
 |  
|  | 250 | +
 |  
|  | 251 | +        binding.quickstartSwitch.visibility = View.GONE
 |  
|  | 252 | +        binding.quickStartDescription.visibility = View.GONE
 |  
|  | 253 | +        binding.torBootstrapButton1.visibility = View.INVISIBLE
 |  
|  | 254 | +        binding.torBootstrapButton2.visibility = View.VISIBLE
 |  
|  | 255 | +        binding.torBootstrapButton2.text = getString(R.string.btn_cancel)
 |  
|  | 256 | +        binding.torBootstrapButton2.setOnClickListener {
 |  
| 109 | 257 |              viewModel.cancelTorBootstrap()
 |  
|  | 258 | +            showConfiguring()
 |  
| 110 | 259 |          }
 |  
| 111 | 260 |      }
 |  
| 112 | 261 |  
 |  
| 113 |  | -    private fun openSettings(preferenceToScrollTo: String? = null) {
 |  
| 114 |  | -        findNavController().navigate(
 |  
| 115 |  | -            TorConnectionAssistFragmentDirections
 |  
| 116 |  | -                .actionTorConnectionAssistFragmentToSettingsFragment(preferenceToScrollTo),
 |  
|  | 262 | +    private fun showCantConnectToTorDirectly() {
 |  
|  | 263 | +        Log.d(TAG, "showCantConnectToTorDirectly()")
 |  
|  | 264 | +        binding.torBootstrapProgressBar.visibility = View.VISIBLE
 |  
|  | 265 | +        binding.torBootstrapProgressBar.progressTintList =
 |  
|  | 266 | +            AppCompatResources.getColorStateList(requireContext(), R.color.warning_yellow)
 |  
|  | 267 | +        binding.torBootstrapProgressBar.progress = 100
 |  
|  | 268 | +
 |  
|  | 269 | +        binding.backButton.visibility = View.VISIBLE
 |  
|  | 270 | +        binding.backButton.setOnClickListener {
 |  
|  | 271 | +            showConfiguring()
 |  
|  | 272 | +        }
 |  
|  | 273 | +
 |  
|  | 274 | +        binding.torConnectImage.setImageResource(R.drawable.globe_broken)
 |  
|  | 275 | +        binding.titleLargeTextView.text =
 |  
|  | 276 | +            getString(R.string.connection_assist_cant_connect_to_tor_title)
 |  
|  | 277 | +
 |  
|  | 278 | +        val learnMore: String = getString(R.string.connection_assist_internet_error_learn_more)
 |  
|  | 279 | +        val tryABridge: String = getString(
 |  
|  | 280 | +            R.string.connection_assist_try_a_bridge_description,
 |  
|  | 281 | +            learnMore,
 |  
| 117 | 282 |          )
 |  
|  | 283 | +        handleDescriptionWithClickable(tryABridge, learnMore)
 |  
|  | 284 | +
 |  
|  | 285 | +        binding.quickStartDescription.visibility = View.GONE
 |  
|  | 286 | +        binding.quickstartSwitch.visibility = View.GONE
 |  
|  | 287 | +        binding.unblockTheInternetInCountryDescription.visibility = View.VISIBLE
 |  
|  | 288 | +        binding.countryDropDown.visibility = View.VISIBLE
 |  
|  | 289 | +        // TODO implement countryDropDown
 |  
|  | 290 | +
 |  
|  | 291 | +        binding.torBootstrapButton1.visibility = View.VISIBLE
 |  
|  | 292 | +        binding.torBootstrapButton1.text = getString(R.string.connection_assist_try_a_bridge_button)
 |  
|  | 293 | +        binding.torBootstrapButton1.setOnClickListener {
 |  
|  | 294 | +            viewModel.tryABridge()
 |  
|  | 295 | +            showTryingABridge()
 |  
|  | 296 | +        }
 |  
|  | 297 | +        binding.torBootstrapButton2.visibility = View.GONE
 |  
| 118 | 298 |      }
 |  
| 119 | 299 |  
 |  
| 120 |  | -    private fun openTorNetworkSettings() {
 |  
| 121 |  | -        findNavController().navigate(
 |  
| 122 |  | -            TorConnectionAssistFragmentDirections
 |  
| 123 |  | -                .actionTorConnectionAssistFragmentToTorNetworkSettings(),
 |  
|  | 300 | +    private fun showTryingABridge() {
 |  
|  | 301 | +        Log.d(TAG, "showTryingABridge()")
 |  
|  | 302 | +        // TODO(Not implemented)
 |  
|  | 303 | +        binding.torBootstrapButton2.setOnClickListener {
 |  
|  | 304 | +            showTryingABridge()
 |  
|  | 305 | +        }
 |  
|  | 306 | +    }
 |  
|  | 307 | +
 |  
|  | 308 | +    private fun showWeCouldntFindYourLocation() {
 |  
|  | 309 | +        Log.d(TAG, "showWeCouldntFindYourLocation()")
 |  
|  | 310 | +        // TODO(Not implemented)
 |  
|  | 311 | +        binding.torBootstrapButton2.setOnClickListener {
 |  
|  | 312 | +            showTryingABridge()
 |  
|  | 313 | +        }
 |  
|  | 314 | +    }
 |  
|  | 315 | +
 |  
|  | 316 | +    private fun showWereStillHavingTroubleConnecting() {
 |  
|  | 317 | +        Log.d(TAG, "showWereStillHavingTroubleConnecting()")
 |  
|  | 318 | +        TODO("Not yet implemented")
 |  
|  | 319 | +    }
 |  
|  | 320 | +
 |  
|  | 321 | +    private fun showTryingOneMoreTime() {
 |  
|  | 322 | +        Log.d(TAG, "showTryingOneMoreTime()")
 |  
|  | 323 | +        TODO("Not yet implemented")
 |  
|  | 324 | +    }
 |  
|  | 325 | +
 |  
|  | 326 | +    private fun showWeWerentAbleToConnectAutomatically() {
 |  
|  | 327 | +        Log.d(TAG, "showWeWerentAbleToConnectAutomatically()")
 |  
|  | 328 | +        TODO("Not yet implemented")
 |  
|  | 329 | +    }
 |  
|  | 330 | +
 |  
|  | 331 | +    private fun showUnknownError() {
 |  
|  | 332 | +        Log.d(TAG, "showUnknownError()")
 |  
|  | 333 | +        TODO("Not yet implemented")
 |  
|  | 334 | +    }
 |  
|  | 335 | +
 |  
|  | 336 | +    /**
 |  
|  | 337 | +     * from https://stackoverflow.com/questions/10696986/how-to-set-the-part-of-the-text-view-is-clickable
 |  
|  | 338 | +     */
 |  
|  | 339 | +    private fun handleDescriptionWithClickable(errorDescription: String, learnMore: String) {
 |  
|  | 340 | +        val errorDescriptionSpannableString = SpannableString(errorDescription)
 |  
|  | 341 | +        val clickableSpan: ClickableSpan = object : ClickableSpan() {
 |  
|  | 342 | +            override fun onClick(textView: View) {
 |  
|  | 343 | +                showLearnMore()
 |  
|  | 344 | +            }
 |  
|  | 345 | +
 |  
|  | 346 | +            override fun updateDrawState(drawState: TextPaint) {
 |  
|  | 347 | +                super.updateDrawState(drawState)
 |  
|  | 348 | +                drawState.isUnderlineText = true
 |  
|  | 349 | +            }
 |  
|  | 350 | +        }
 |  
|  | 351 | +        errorDescriptionSpannableString.setSpan(
 |  
|  | 352 | +            clickableSpan,
 |  
|  | 353 | +            errorDescription.length - learnMore.length,
 |  
|  | 354 | +            errorDescription.length,
 |  
|  | 355 | +            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
 |  
| 124 | 356 |          )
 |  
|  | 357 | +        binding.titleDescription.text = errorDescriptionSpannableString
 |  
|  | 358 | +        binding.titleDescription.movementMethod = LinkMovementMethod.getInstance()
 |  
|  | 359 | +        binding.titleDescription.highlightColor = Color.TRANSPARENT
 |  
|  | 360 | +    }
 |  
|  | 361 | +
 |  
|  | 362 | +    private fun showLearnMore() {
 |  
|  | 363 | +        //TODO("Not yet implemented")
 |  
| 125 | 364 |      }
 |  
| 126 | 365 |  
 |  
| 127 | 366 |      private fun openHome() {
 |  
|  | 367 | +        Log.d(TAG, "openHome()") //This doesn't seem to be ever called
 |  
| 128 | 368 |          findNavController().navigate(TorConnectionAssistFragmentDirections.actionStartupHome())
 |  
| 129 | 369 |      }
 |  
| 130 | 370 |  
 |  
|  | 371 | +    private fun openSettings(preferenceToScrollTo: String? = null) {
 |  
|  | 372 | +        findNavController().navigate(
 |  
|  | 373 | +            TorConnectionAssistFragmentDirections.actionTorConnectionAssistFragmentToSettingsFragment(
 |  
|  | 374 | +                preferenceToScrollTo,
 |  
|  | 375 | +            ),
 |  
|  | 376 | +        )
 |  
|  | 377 | +    }
 |  
|  | 378 | +
 |  
|  | 379 | +    private fun openTorNetworkSettings() {
 |  
|  | 380 | +        findNavController().navigate(
 |  
|  | 381 | +            TorConnectionAssistFragmentDirections.actionTorConnectionAssistFragmentToTorNetworkSettings(),
 |  
|  | 382 | +        )
 |  
|  | 383 | +    }
 |  
| 131 | 384 |  } |  fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
 
 
| ... | ... | @@ -26,24 +26,30 @@ class TorConnectionAssistViewModel( |  
| 26 | 26 |      private val _torConnectState = MutableStateFlow(TorConnectState.Initial)
 |  
| 27 | 27 |      internal val torConnectState: StateFlow<TorConnectState> = _torConnectState
 |  
| 28 | 28 |  
 |  
| 29 |  | -    init {
 |  
| 30 |  | -        _torController.registerTorListener(this)
 |  
| 31 |  | -    }
 |  
|  | 29 | +    private val _torError = MutableStateFlow(_torController.getLastErrorState())
 |  
|  | 30 | +    internal val torError: StateFlow<TorError?> = _torError
 |  
| 32 | 31 |  
 |  
| 33 | 32 |      private val _progress = MutableLiveData(0)
 |  
| 34 | 33 |      fun progress(): LiveData<Int> {
 |  
| 35 | 34 |          return _progress
 |  
| 36 | 35 |      }
 |  
| 37 | 36 |  
 |  
| 38 |  | -    private val _quickconnectToggle = MutableLiveData(_torController.quickstart)
 |  
| 39 |  | -    fun quickconnectToggle(): LiveData<Boolean> {
 |  
| 40 |  | -        return _quickconnectToggle
 |  
|  | 37 | +    private val _quickStartToggle = MutableLiveData<Boolean>() // don't initialize with quickstart off the bat
 |  
|  | 38 | +    fun quickstartToggle(): LiveData<Boolean?> {
 |  
|  | 39 | +        _quickStartToggle.value = _torController.quickstart // quickstart isn't ready until torSettings is ready
 |  
|  | 40 | +        return _quickStartToggle
 |  
|  | 41 | +    }
 |  
|  | 42 | +
 |  
|  | 43 | +    init {
 |  
|  | 44 | +        Log.d(TAG, "initiating TorConnectionAssistViewModel")
 |  
|  | 45 | +        _torController.registerTorListener(this)
 |  
| 41 | 46 |      }
 |  
| 42 | 47 |  
 |  
| 43 | 48 |      fun handleConnect(
 |  
| 44 | 49 |          withDebugLogging: Boolean = false,
 |  
| 45 | 50 |          lifecycleScope: LifecycleCoroutineScope? = null,
 |  
| 46 | 51 |      ) {
 |  
|  | 52 | +        Log.d(TAG, "handleConnect initiatingTorBootstrap with lifecycleScope = $lifecycleScope")
 |  
| 47 | 53 |          _torController.initiateTorBootstrap(
 |  
| 48 | 54 |              withDebugLogging = withDebugLogging,
 |  
| 49 | 55 |              lifecycleScope = lifecycleScope,
 |  
| ... | ... | @@ -52,6 +58,7 @@ class TorConnectionAssistViewModel( |  
| 52 | 58 |  
 |  
| 53 | 59 |      fun handleQuickstartChecked(checked: Boolean) {
 |  
| 54 | 60 |          _torController.quickstart = checked
 |  
|  | 61 | +        _quickStartToggle.value = checked
 |  
| 55 | 62 |      }
 |  
| 56 | 63 |  
 |  
| 57 | 64 |      fun cancelTorBootstrap() {
 |  
| ... | ... | @@ -66,7 +73,6 @@ class TorConnectionAssistViewModel( |  
| 66 | 73 |      override fun onTorConnected() {
 |  
| 67 | 74 |          Log.d(TAG, "onTorConnected()")
 |  
| 68 | 75 |          _torController.unregisterTorListener(this)
 |  
| 69 |  | -        _torConnectState.value = _torController.lastKnownStatus
 |  
| 70 | 76 |      }
 |  
| 71 | 77 |  
 |  
| 72 | 78 |      override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) {
 |  
| ... | ... | @@ -75,12 +81,32 @@ class TorConnectionAssistViewModel( |  
| 75 | 81 |              _progress.value = progress.toInt()
 |  
| 76 | 82 |          }
 |  
| 77 | 83 |          _torConnectState.value = _torController.lastKnownStatus
 |  
|  | 84 | +        _torError.value = _torController.getLastErrorState()
 |  
| 78 | 85 |      }
 |  
| 79 | 86 |  
 |  
| 80 | 87 |      override fun onTorStopped() {
 |  
| 81 | 88 |          Log.d(TAG, "onTorStopped()")
 |  
| 82 |  | -        _progress.value = 0
 |  
| 83 |  | -        _torConnectState.value = _torController.lastKnownStatus
 |  
| 84 | 89 |      }
 |  
| 85 | 90 |  
 |  
|  | 91 | +    internal fun handleError(it: TorError?): ErrorScreen? {
 |  
|  | 92 | +        // TODO(Only partly implemented)
 |  
|  | 93 | +        if (it?.message == null){
 |  
|  | 94 | +            return null
 |  
|  | 95 | +        }
 |  
|  | 96 | +        return ErrorScreen.CantConnectToInternet
 |  
|  | 97 | +    }
 |  
|  | 98 | +
 |  
|  | 99 | +    fun tryABridge() {
 |  
|  | 100 | +        // TODO("Try a bridge not enabled")
 |  
|  | 101 | +        // connect to bridge based on country
 |  
|  | 102 | +        // try connecting
 |  
|  | 103 | +    }
 |  
|  | 104 | +}
 |  
|  | 105 | +
 |  
|  | 106 | +internal enum class ErrorScreen {
 |  
|  | 107 | +    CantConnectToInternet,
 |  
|  | 108 | +    CantConnectToTorDirectly,
 |  
|  | 109 | +    WeCouldntFindYourLocation,
 |  
|  | 110 | +    WereStillHavingTroubleConnecting,
 |  
|  | 111 | +    WeWerentAbleToConnectAutomatically,
 |  
| 86 | 112 |  } |  fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
 
 
| ... | ... | @@ -12,6 +12,10 @@ interface TorEvents { |  
| 12 | 12 |      fun onTorStatusUpdate(entry: String?, status: String?, progress: Double? = 0.0)
 |  
| 13 | 13 |      fun onTorStopped()
 |  
| 14 | 14 |  }
 |  
|  | 15 | +class TorError(
 |  
|  | 16 | +    var message: String,
 |  
|  | 17 | +    var details: String
 |  
|  | 18 | +) { }
 |  
| 15 | 19 |  
 |  
| 16 | 20 |  internal enum class TorStatus(val status: String) {
 |  
| 17 | 21 |      OFF("OFF"),
 |  
| ... | ... | @@ -59,6 +63,8 @@ interface TorController: TorEvents { |  
| 59 | 63 |      override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?)
 |  
| 60 | 64 |      override fun onTorStopped()
 |  
| 61 | 65 |  
 |  
|  | 66 | +    fun getLastErrorState() : TorError?
 |  
|  | 67 | +
 |  
| 62 | 68 |      fun registerTorListener(l: TorEvents)
 |  
| 63 | 69 |      fun unregisterTorListener(l: TorEvents)
 |  
| 64 | 70 |  
 |  fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
 
 
| ... | ... | @@ -53,6 +53,7 @@ class TorControllerGV( |  
| 53 | 53 |      private var torListeners = mutableListOf<TorEvents>()
 |  
| 54 | 54 |  
 |  
| 55 | 55 |      internal var lastKnownStatus = TorConnectState.Initial
 |  
|  | 56 | +    internal var lastKnownError: TorError? = null
 |  
| 56 | 57 |      private var wasTorBootstrapped = false
 |  
| 57 | 58 |      private var isTorRestarting = false
 |  
| 58 | 59 |  
 |  
| ... | ... | @@ -210,6 +211,7 @@ class TorControllerGV( |  
| 210 | 211 |  
 |  
| 211 | 212 |      override fun setTorStopped() {
 |  
| 212 | 213 |          lastKnownStatus = TorConnectState.Configuring
 |  
|  | 214 | +        onTorStatusUpdate(null, lastKnownStatus.toString(), 0.0)
 |  
| 213 | 215 |          onTorStopped()
 |  
| 214 | 216 |      }
 |  
| 215 | 217 |  
 |  
| ... | ... | @@ -227,6 +229,10 @@ class TorControllerGV( |  
| 227 | 229 |          }
 |  
| 228 | 230 |      }
 |  
| 229 | 231 |  
 |  
|  | 232 | +    override fun getLastErrorState() : TorError? {
 |  
|  | 233 | +        return lastKnownError
 |  
|  | 234 | +    }
 |  
|  | 235 | +
 |  
| 230 | 236 |      // TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents)
 |  
| 231 | 237 |      // Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events
 |  
| 232 | 238 |      // and state for firefox-android (designed for tor-android-service)
 |  
| ... | ... | @@ -263,7 +269,7 @@ class TorControllerGV( |  
| 263 | 269 |          }
 |  
| 264 | 270 |  
 |  
| 265 | 271 |          lastKnownStatus = newState
 |  
| 266 |  | -
 |  
|  | 272 | +        onTorStatusUpdate(null, newStateVal, null)
 |  
| 267 | 273 |      }
 |  
| 268 | 274 |  
 |  
| 269 | 275 |      // TorEventsBootstrapStateChangeListener
 |  
| ... | ... | @@ -290,7 +296,7 @@ class TorControllerGV( |  
| 290 | 296 |  
 |  
| 291 | 297 |      // TorEventsBootstrapStateChangeListener
 |  
| 292 | 298 |      override fun onBootstrapError(message: String?, details: String?) {
 |  
| 293 |  | -        lastKnownStatus = TorConnectState.Error
 |  
|  | 299 | +        lastKnownError = TorError(message ?: "", details ?: "")
 |  
| 294 | 300 |          onBootstrapStateChange(TorConnectState.Error.state)
 |  
| 295 | 301 |      }
 |  
| 296 | 302 |  
 |  fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
 
 
| ... | ... | @@ -330,4 +330,10 @@ class TorControllerTAS (private val context: Context): TorController { |  
| 330 | 330 |      companion object {
 |  
| 331 | 331 |          const val torServiceResponseTimeout = 5000L
 |  
| 332 | 332 |      }
 |  
|  | 333 | +
 |  
|  | 334 | +    // Compat with TorControlGV Stubs
 |  
|  | 335 | +
 |  
|  | 336 | +    override fun getLastErrorState() : TorError? {
 |  
|  | 337 | +        return null;
 |  
|  | 338 | +    }
 |  
| 333 | 339 |  } |  fenix/app/src/main/res/drawable/connect_broken.xml
 
 
|  | 1 | +<vector xmlns:android="http://schemas.android.com/apk/res/android"
 |  
|  | 2 | +    android:width="40dp"
 |  
|  | 3 | +    android:height="40dp"
 |  
|  | 4 | +    android:viewportWidth="40"
 |  
|  | 5 | +    android:viewportHeight="40">
 |  
|  | 6 | +  <group>
 |  
|  | 7 | +    <clip-path
 |  
|  | 8 | +        android:pathData="M0,0h40v40h-40z"/>
 |  
|  | 9 | +    <path
 |  
|  | 10 | +        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"
 |  
|  | 11 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 12 | +    <path
 |  
|  | 13 | +        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"
 |  
|  | 14 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 15 | +    <path
 |  
|  | 16 | +        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"
 |  
|  | 17 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 18 | +    <path
 |  
|  | 19 | +        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"
 |  
|  | 20 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 21 | +    <path
 |  
|  | 22 | +        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"
 |  
|  | 23 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 24 | +    <path
 |  
|  | 25 | +        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"
 |  
|  | 26 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 27 | +    <path
 |  
|  | 28 | +        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"
 |  
|  | 29 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 30 | +    <path
 |  
|  | 31 | +        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"
 |  
|  | 32 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 33 | +    <path
 |  
|  | 34 | +        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"
 |  
|  | 35 | +        android:fillColor="#FBFBFE"/>
 |  
|  | 36 | +  </group>
 |  
|  | 37 | +</vector> |  fenix/app/src/main/res/drawable/globe_broken.xml
 
 
|  | 1 | +<vector xmlns:android="http://schemas.android.com/apk/res/android"
 |  
|  | 2 | +    android:width="40dp"
 |  
|  | 3 | +    android:height="40dp"
 |  
|  | 4 | +    android:viewportWidth="40"
 |  
|  | 5 | +    android:viewportHeight="40">
 |  
|  | 6 | +  <path
 |  
|  | 7 | +      android:pathData="M4.209,1.999L37.355,35.145L35.145,37.355L1.999,4.209L4.209,1.999Z"
 |  
|  | 8 | +      android:fillColor="#FBFBFE"
 |  
|  | 9 | +      android:fillType="evenOdd"/>
 |  
|  | 10 | +  <path
 |  
|  | 11 | +      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"
 |  
|  | 12 | +      android:fillColor="#FBFBFE"
 |  
|  | 13 | +      android:fillType="evenOdd"/>
 |  
|  | 14 | +  <path
 |  
|  | 15 | +      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"
 |  
|  | 16 | +      android:fillColor="#FBFBFE"
 |  
|  | 17 | +      android:fillType="evenOdd"/>
 |  
|  | 18 | +</vector> |  fenix/app/src/main/res/drawable/tor_bootstrap_background_gradient.xml
 
 
| ... | ... | @@ -7,9 +7,9 @@ |  
| 7 | 7 |          <shape>
 |  
| 8 | 8 |              <gradient
 |  
| 9 | 9 |                      android:angle="225"
 |  
| 10 |  | -                    android:startColor="#FF7329A4"
 |  
| 11 |  | -                    android:centerColor="#FF3A3274"
 |  
| 12 |  | -                    android:endColor="#FF3A3274"
 |  
|  | 10 | +                    android:startColor="@color/backgroundGradientLight"
 |  
|  | 11 | +                    android:centerColor="@color/backgroundGradientDark"
 |  
|  | 12 | +                    android:endColor="@color/backgroundGradientDark"
 |  
| 13 | 13 |                      android:type="linear" />
 |  
| 14 | 14 |          </shape>
 |  
| 15 | 15 |      </item>
 |  fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
 
 
| ... | ... | @@ -3,29 +3,63 @@ |  
| 3 | 3 |     - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 |  
| 4 | 4 |  <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 |  
| 5 | 5 |      xmlns:app="http://schemas.android.com/apk/res-auto"
 |  
|  | 6 | +    xmlns:tools="http://schemas.android.com/tools"
 |  
| 6 | 7 |      android:layout_width="match_parent"
 |  
| 7 | 8 |      android:layout_height="match_parent"
 |  
| 8 |  | -    android:background="">"@drawable/tor_bootstrap_background_gradient">
 |  
|  | 9 | +    android:background="">"@drawable/tor_bootstrap_background_gradient"
 |  
|  | 10 | +    android:paddingBottom="16dp">
 |  
| 9 | 11 |  
 |  
| 10 | 12 |      <ProgressBar
 |  
| 11 | 13 |          android:id="@+id/tor_bootstrap_progress_bar"
 |  
| 12 | 14 |          style="?android:attr/progressBarStyleHorizontal"
 |  
| 13 | 15 |          android:layout_width="match_parent"
 |  
| 14 | 16 |          android:layout_height="6dp"
 |  
|  | 17 | +        android:visibility="invisible"
 |  
| 15 | 18 |          app:layout_constraintEnd_toEndOf="parent"
 |  
| 16 | 19 |          app:layout_constraintStart_toStartOf="parent"
 |  
| 17 | 20 |          app:layout_constraintTop_toTopOf="parent" />
 |  
| 18 | 21 |  
 |  
| 19 |  | -    <ImageView
 |  
|  | 22 | +    <androidx.constraintlayout.widget.ConstraintLayout
 |  
| 20 | 23 |          android:id="@+id/settings_button"
 |  
| 21 |  | -        android:layout_width="24dp"
 |  
| 22 |  | -        android:layout_height="24dp"
 |  
| 23 |  | -        android:layout_marginTop="26dp"
 |  
| 24 |  | -        android:layout_marginEnd="20dp"
 |  
| 25 |  | -        android:contentDescription="@string/settings"
 |  
|  | 24 | +        android:layout_width="48dp"
 |  
|  | 25 | +        android:layout_height="48dp"
 |  
|  | 26 | +        android:layout_marginTop="8dp"
 |  
|  | 27 | +        android:layout_marginEnd="8dp"
 |  
| 26 | 28 |          app:layout_constraintEnd_toEndOf="parent"
 |  
| 27 |  | -        app:layout_constraintTop_toTopOf="parent"
 |  
| 28 |  | -        app:srcCompat="@drawable/mozac_ic_settings" />
 |  
|  | 29 | +        app:layout_constraintTop_toTopOf="parent">
 |  
|  | 30 | +
 |  
|  | 31 | +        <ImageView
 |  
|  | 32 | +            android:layout_width="wrap_content"
 |  
|  | 33 | +            android:layout_height="wrap_content"
 |  
|  | 34 | +            android:contentDescription="@string/settings"
 |  
|  | 35 | +            app:layout_constraintBottom_toBottomOf="parent"
 |  
|  | 36 | +            app:layout_constraintEnd_toEndOf="parent"
 |  
|  | 37 | +            app:layout_constraintStart_toStartOf="parent"
 |  
|  | 38 | +            app:layout_constraintTop_toTopOf="parent"
 |  
|  | 39 | +            app:srcCompat="@drawable/mozac_ic_settings" />
 |  
|  | 40 | +    </androidx.constraintlayout.widget.ConstraintLayout>
 |  
|  | 41 | +
 |  
|  | 42 | +    <androidx.constraintlayout.widget.ConstraintLayout
 |  
|  | 43 | +        android:id="@+id/back_button"
 |  
|  | 44 | +        android:layout_width="48dp"
 |  
|  | 45 | +        android:layout_height="48dp"
 |  
|  | 46 | +        android:layout_marginStart="8dp"
 |  
|  | 47 | +        android:layout_marginTop="8dp"
 |  
|  | 48 | +        android:visibility="invisible"
 |  
|  | 49 | +        app:layout_constraintStart_toStartOf="parent"
 |  
|  | 50 | +        app:layout_constraintTop_toTopOf="parent">
 |  
|  | 51 | +
 |  
|  | 52 | +        <ImageView
 |  
|  | 53 | +            android:layout_width="wrap_content"
 |  
|  | 54 | +            android:layout_height="wrap_content"
 |  
|  | 55 | +            android:contentDescription="@string/settings"
 |  
|  | 56 | +            app:layout_constraintBottom_toBottomOf="parent"
 |  
|  | 57 | +            app:layout_constraintEnd_toEndOf="parent"
 |  
|  | 58 | +            app:layout_constraintStart_toStartOf="parent"
 |  
|  | 59 | +            app:layout_constraintTop_toTopOf="parent"
 |  
|  | 60 | +            app:srcCompat="@drawable/mozac_ic_back" />
 |  
|  | 61 | +    </androidx.constraintlayout.widget.ConstraintLayout>
 |  
|  | 62 | +
 |  
| 29 | 63 |  
 |  
| 30 | 64 |      <ImageView
 |  
| 31 | 65 |          android:id="@+id/tor_connect_image"
 |  
| ... | ... | @@ -45,99 +79,139 @@ |  
| 45 | 79 |          android:layout_width="match_parent"
 |  
| 46 | 80 |          android:layout_height="wrap_content"
 |  
| 47 | 81 |          android:layout_marginStart="24dp"
 |  
| 48 |  | -        android:layout_marginTop="24dp"
 |  
| 49 | 82 |          android:layout_marginEnd="24dp"
 |  
| 50 | 83 |          android:text="@string/connection_assist_tor_connect_title"
 |  
| 51 | 84 |          android:textColor="#FBFBFE"
 |  
| 52 |  | -        android:textFontWeight="400"
 |  
| 53 | 85 |          android:textSize="22sp"
 |  
|  | 86 | +        app:layout_constraintBottom_toBottomOf="parent"
 |  
| 54 | 87 |          app:layout_constraintEnd_toEndOf="parent"
 |  
| 55 | 88 |          app:layout_constraintStart_toStartOf="parent"
 |  
| 56 |  | -        app:layout_constraintTop_toBottomOf="@id/tor_connect_image" />
 |  
|  | 89 | +        app:layout_constraintTop_toBottomOf="@id/tor_connect_image"
 |  
|  | 90 | +        app:layout_constraintVertical_bias="0.03" />
 |  
| 57 | 91 |  
 |  
| 58 | 92 |      <TextView
 |  
| 59 |  | -        android:id="@+id/connect_to_tor_description"
 |  
|  | 93 | +        android:id="@+id/title_description"
 |  
| 60 | 94 |          android:layout_width="match_parent"
 |  
| 61 | 95 |          android:layout_height="wrap_content"
 |  
| 62 | 96 |          android:layout_marginStart="24dp"
 |  
| 63 |  | -        android:layout_marginTop="16dp"
 |  
| 64 | 97 |          android:layout_marginEnd="24dp"
 |  
|  | 98 | +        android:lineSpacingExtra="6dp"
 |  
| 65 | 99 |          android:text="@string/preferences_tor_network_settings_explanation"
 |  
| 66 | 100 |          android:textColor="#FBFBFE"
 |  
| 67 |  | -        android:textFontWeight="400"
 |  
| 68 | 101 |          android:textSize="14sp"
 |  
|  | 102 | +        app:layout_constraintBottom_toBottomOf="parent"
 |  
| 69 | 103 |          app:layout_constraintEnd_toEndOf="parent"
 |  
| 70 | 104 |          app:layout_constraintHorizontal_bias="0.0"
 |  
| 71 | 105 |          app:layout_constraintStart_toStartOf="parent"
 |  
| 72 |  | -        app:layout_constraintTop_toBottomOf="@id/title_large_text_view" />
 |  
|  | 106 | +        app:layout_constraintTop_toBottomOf="@id/title_large_text_view"
 |  
|  | 107 | +        app:layout_constraintVertical_bias="0.03" />
 |  
|  | 108 | +
 |  
| 73 | 109 |  
 |  
| 74 | 110 |      <TextView
 |  
| 75 |  | -        android:id="@+id/connect_automatically"
 |  
| 76 |  | -        android:layout_width="wrap_content"
 |  
|  | 111 | +        android:id="@+id/quick_start_description"
 |  
|  | 112 | +        android:layout_width="230dp"
 |  
| 77 | 113 |          android:layout_height="wrap_content"
 |  
| 78 | 114 |          android:layout_marginStart="24dp"
 |  
| 79 |  | -        android:layout_marginTop="24dp"
 |  
| 80 | 115 |          android:text="@string/connection_assist_always_connect_automatically_toggle_description"
 |  
| 81 |  | -        android:textColor="#80FBFBFE"
 |  
|  | 116 | +        android:textColor="#FBFBFE"
 |  
| 82 | 117 |          android:textSize="14sp"
 |  
| 83 |  | -        app:layout_constraintBottom_toBottomOf="@+id/quickstart_switch"
 |  
|  | 118 | +        app:layout_constraintBottom_toBottomOf="parent"
 |  
| 84 | 119 |          app:layout_constraintStart_toStartOf="parent"
 |  
| 85 |  | -        app:layout_constraintTop_toTopOf="@+id/quickstart_switch"
 |  
| 86 |  | -        app:layout_constraintVertical_bias="1.25" />
 |  
|  | 120 | +        app:layout_constraintTop_toBottomOf="@+id/title_description"
 |  
|  | 121 | +        app:layout_constraintVertical_bias=".03" />
 |  
| 87 | 122 |  
 |  
| 88 | 123 |      <androidx.appcompat.widget.SwitchCompat
 |  
| 89 | 124 |          android:id="@+id/quickstart_switch"
 |  
| 90 | 125 |          android:layout_width="wrap_content"
 |  
| 91 | 126 |          android:layout_height="wrap_content"
 |  
| 92 |  | -        android:layout_marginTop="24dp"
 |  
|  | 127 | +        android:layout_marginStart="100dp"
 |  
| 93 | 128 |          android:layout_marginEnd="24dp"
 |  
| 94 | 129 |          android:layout_marginBottom="24dp"
 |  
| 95 |  | -        android:enabled="false"
 |  
| 96 | 130 |          android:gravity="center"
 |  
| 97 |  | -        app:thumbTint="#7D6298"
 |  
|  | 131 | +        app:layout_constraintBottom_toBottomOf="parent"
 |  
| 98 | 132 |          app:layout_constraintEnd_toEndOf="parent"
 |  
| 99 |  | -        app:layout_constraintTop_toBottomOf="@id/connect_to_tor_description"
 |  
|  | 133 | +        app:layout_constraintHorizontal_bias="0"
 |  
|  | 134 | +        app:layout_constraintStart_toEndOf="@+id/quick_start_description"
 |  
|  | 135 | +        app:layout_constraintTop_toBottomOf="@id/title_description"
 |  
|  | 136 | +        app:layout_constraintVertical_bias=".023"
 |  
| 100 | 137 |          app:layout_goneMarginEnd="6dp"
 |  
| 101 | 138 |          app:layout_goneMarginTop="9dp" />
 |  
| 102 | 139 |  
 |  
| 103 |  | -    <Button
 |  
| 104 |  | -        android:id="@+id/tor_bootstrap_connect_button"
 |  
|  | 140 | +    <TextView
 |  
|  | 141 | +        android:id="@+id/unblock_the_internet_in_country_description"
 |  
|  | 142 | +        android:layout_width="match_parent"
 |  
|  | 143 | +        android:layout_height="wrap_content"
 |  
|  | 144 | +        android:layout_marginStart="24dp"
 |  
|  | 145 | +        android:layout_marginTop="24dp"
 |  
|  | 146 | +        android:layout_marginEnd="24dp"
 |  
|  | 147 | +        android:text="@string/connection_assist_unblock_the_internet_in_country_or_region"
 |  
|  | 148 | +        android:textColor="#FBFBFE"
 |  
|  | 149 | +        android:visibility="invisible"
 |  
|  | 150 | +        app:layout_constraintEnd_toEndOf="parent"
 |  
|  | 151 | +        app:layout_constraintStart_toStartOf="parent"
 |  
|  | 152 | +        app:layout_constraintTop_toBottomOf="@id/title_description" />
 |  
|  | 153 | +
 |  
|  | 154 | +    <androidx.appcompat.widget.AppCompatSpinner
 |  
|  | 155 | +        android:id="@+id/country_drop_down"
 |  
|  | 156 | +        style="@style/Widget.AppCompat.Spinner.Underlined"
 |  
| 105 | 157 |          android:layout_width="match_parent"
 |  
| 106 | 158 |          android:layout_height="wrap_content"
 |  
| 107 | 159 |          android:layout_marginStart="24dp"
 |  
|  | 160 | +        android:layout_marginTop="8dp"
 |  
|  | 161 | +        android:layout_marginEnd="24dp"
 |  
|  | 162 | +        android:textColor="#FBFBFE"
 |  
|  | 163 | +        android:tooltipText="@string/connection_assist_share_my_location_country_or_region"
 |  
|  | 164 | +        android:visibility="invisible"
 |  
|  | 165 | +        app:layout_constraintEnd_toEndOf="parent"
 |  
|  | 166 | +        app:layout_constraintStart_toStartOf="parent"
 |  
|  | 167 | +        app:layout_constraintTop_toBottomOf="@id/unblock_the_internet_in_country_description" />
 |  
|  | 168 | +
 |  
|  | 169 | +    <ImageView
 |  
|  | 170 | +        android:id="@+id/wordmarkLogo"
 |  
|  | 171 | +        android:layout_width="160dp"
 |  
|  | 172 | +        android:layout_height="160dp"
 |  
|  | 173 | +        android:src="">"@mipmap/ic_launcher_round"
 |  
|  | 174 | +
 |  
|  | 175 | +        app:layout_constraintBottom_toBottomOf="parent"
 |  
|  | 176 | +        app:layout_constraintEnd_toEndOf="parent"
 |  
|  | 177 | +        app:layout_constraintStart_toStartOf="parent"
 |  
|  | 178 | +        app:layout_constraintTop_toTopOf="parent"
 |  
|  | 179 | +        android:contentDescription="" />
 |  
|  | 180 | +
 |  
|  | 181 | +    <Button
 |  
|  | 182 | +        android:id="@+id/tor_bootstrap_button_1"
 |  
|  | 183 | +        android:layout_width="wrap_content"
 |  
|  | 184 | +        android:layout_height="wrap_content"
 |  
|  | 185 | +        android:layout_marginStart="24dp"
 |  
| 108 | 186 |          android:layout_marginEnd="24dp"
 |  
| 109 | 187 |          android:layout_marginBottom="8dp"
 |  
| 110 | 188 |          android:background="">"@drawable/rounded_corners"
 |  
| 111 | 189 |          android:backgroundTint="@color/connect_button_purple"
 |  
| 112 |  | -        android:maxWidth="312dp"
 |  
|  | 190 | +        android:minWidth="360dp"
 |  
| 113 | 191 |          android:text="@string/tor_bootstrap_connect"
 |  
| 114 | 192 |          android:textAllCaps="false"
 |  
| 115 | 193 |          android:textColor="#FBFBFE"
 |  
| 116 |  | -        android:textFontWeight="500"
 |  
| 117 | 194 |          android:textSize="14sp"
 |  
| 118 | 195 |          android:textStyle="bold"
 |  
| 119 |  | -        app:layout_constraintBottom_toTopOf="@id/tor_bootstrap_network_settings_button"
 |  
|  | 196 | +        app:layout_constraintBottom_toTopOf="@id/tor_bootstrap_button_2"
 |  
| 120 | 197 |          app:layout_constraintEnd_toEndOf="parent"
 |  
| 121 |  | -        app:layout_constraintHorizontal_bias="0.0"
 |  
| 122 | 198 |          app:layout_constraintStart_toStartOf="parent"
 |  
| 123 | 199 |          app:layout_constraintTop_toBottomOf="@+id/quickstart_switch"
 |  
| 124 | 200 |          app:layout_constraintVertical_bias="1" />
 |  
| 125 | 201 |  
 |  
| 126 |  | -
 |  
| 127 | 202 |      <Button
 |  
| 128 |  | -        android:id="@+id/tor_bootstrap_network_settings_button"
 |  
| 129 |  | -        android:layout_width="match_parent"
 |  
|  | 203 | +        android:id="@+id/tor_bootstrap_button_2"
 |  
|  | 204 | +        android:layout_width="wrap_content"
 |  
| 130 | 205 |          android:layout_height="wrap_content"
 |  
| 131 | 206 |          android:layout_marginStart="24dp"
 |  
| 132 | 207 |          android:layout_marginEnd="24dp"
 |  
| 133 |  | -        android:layout_marginBottom="24dp"
 |  
|  | 208 | +        android:layout_marginBottom="8dp"
 |  
| 134 | 209 |          android:background="">"@drawable/rounded_corners"
 |  
| 135 | 210 |          android:backgroundTint="@color/configure_connection_button_white"
 |  
| 136 |  | -        android:maxWidth="312dp"
 |  
|  | 211 | +        android:minWidth="360dp"
 |  
| 137 | 212 |          android:text="@string/connection_assist_configure_connection_button"
 |  
| 138 | 213 |          android:textAllCaps="false"
 |  
| 139 | 214 |          android:textColor="#15141A"
 |  
| 140 |  | -        android:textFontWeight="500"
 |  
| 141 | 215 |          android:textSize="14sp"
 |  
| 142 | 216 |          android:textStyle="bold"
 |  
| 143 | 217 |          app:layout_constraintBottom_toBottomOf="parent"
 |  fenix/app/src/main/res/values/colors.xml
 
 
| ... | ... | @@ -273,6 +273,8 @@ |  
| 273 | 273 |      <color name="sync_disconnected_background_private_theme">#5B5846</color>
 |  
| 274 | 274 |      <color name="onboarding_illustration_deselected_private_theme">#99FBFBFE</color>
 |  
| 275 | 275 |      <color name="prompt_login_edit_text_cursor_color_private_theme">@color/photonViolet50</color>
 |  
|  | 276 | +    <color name="backgroundGradientDark">#FF3A3274</color>
 |  
|  | 277 | +    <color name="backgroundGradientLight">#FF7329A4</color>
 |  
| 276 | 278 |  
 |  
| 277 | 279 |      <!-- Normal theme colors for light mode -->
 |  
| 278 | 280 |      <color name="accent_normal_theme">@color/photonInk20</color>
 |  
| ... | ... | @@ -344,5 +346,6 @@ |  
| 344 | 346 |      <!-- Connection Assist -->
 |  
| 345 | 347 |      <color name="connect_button_purple">#9059FF</color>
 |  
| 346 | 348 |      <color name="configure_connection_button_white">#E1E0E7</color>
 |  
|  | 349 | +    <color name="warning_yellow">#FFA436</color>
 |  
| 347 | 350 |  
 |  
| 348 | 351 |  </resources> |  fenix/app/src/main/res/values/styles.xml
 
 
| ... | ... | @@ -12,7 +12,7 @@ |  
| 12 | 12 |          <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item>
 |  
| 13 | 13 |          <item name="android:progressBarStyleHorizontal">@style/progressBarStyleHorizontal</item>
 |  
| 14 | 14 |          <item name="android:statusBarColor">@android:color/transparent</item>
 |  
| 15 |  | -        <item name="android:windowBackground">@color/fx_mobile_layer_color_1</item>
 |  
|  | 15 | +        <item name="android:windowBackground">@color/backgroundGradientDark</item>
 |  
| 16 | 16 |          <item name="android:colorEdgeEffect">@color/accent_normal_theme</item>
 |  
| 17 | 17 |          <item name="android:colorAccent">@color/fx_mobile_text_color_primary</item>
 |  
| 18 | 18 |          <item name="android:textColorPrimary">@color/state_list_text_color</item>
 |  
 |