Skip to content
Snippets Groups Projects
Verified Commit b462d44f authored by Matthew Finkel's avatar Matthew Finkel Committed by aguestuser
Browse files

Add Tor integration and UI

Bug 40001: Start Tor as part of the Fenix initialization

Bug 40028: Implement Tor Service controller

Bug 40028: Integrate Tor Controller into HomeFragment

Bug 40028: Implement Tor connect and logger screens

Bug 40028: Implement Tor Onboarding

Bug 40028: Implement new home screen

Bug 40028: Define bootstrapping events and Quick Start

Bug 40041: Implement Tor Network Settings

Bug 40041: Integrate Tor Network Settings

Bug 40179: Show Snowflake bridge option on Release

Bug 40176: Re-render Home fragment on resume
parent f9aa56be
Branches
Tags
1 merge request!144Resolve #40213: Rebase Fenix patches to 99.0
Showing
with 883 additions and 28 deletions
......@@ -19,6 +19,8 @@ default:
- 32GB
before_script:
- set -e
# Prepare our Debian environment.
- apt-get update -qq
- apt-get upgrade -qy
......@@ -47,6 +49,33 @@ default:
# Create local.properties file.
- echo "sdk.dir=$PWD" > local.properties
# Fetch tor library build dependencies
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-onion-proxy-library/0.0.3/android-release.aar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-onion-proxy-library/0.0.3/universal-0.0.3.jar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-android-service/1.0/jsocksAndroid-release.aar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor-android-service/1.0/service-release.aar
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_x86.tar.gz
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_x86_64.tar.gz
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_armv7.tar.gz
- wget --quiet https://people.torproject.org/~sysrqb/mirrors/tor/0.4.4.4-rc/tor_aarch64.tar.gz
- echo "4e2bd087df6128a31c598d297367c106762f539c25d71cc343aae25386e4bee9 android-release.aar" | sha256sum -c
- echo "0082d1d035a0808d03d7e1009398ed2c3e3ea4c51811198f6ff83ccda456036f universal-0.0.3.jar" | sha256sum -c
- echo "3dca44a48fdbd3f6c44f7ea335ae85fe542676d8e48c6438f84a2c852daf4f54 jsocksAndroid-release.aar" | sha256sum -c
- echo "d38967569af56d809f09f08b888962971149411832c3bc2b7b0b64a43ceb0dcd service-release.aar" | sha256sum -c
- echo "0faa344f658d2e38c7cc881f51c1d8658aac56801f7def1b75be72a8d3f90c54 tor_x86.tar.gz" | sha256sum -c
- echo "acf1d403ca12e3302d1150d2b5fba6585aa83d46db16f15ad33fae39645c5b5d tor_x86_64.tar.gz" | sha256sum -c
- echo "132cb40bbc15bd8f72abd15324705ab720f8b316a2fca84baaeb5db37f602c32 tor_armv7.tar.gz" | sha256sum -c
- echo "6c453c5f7566c87d422ef60750cba794d9616e9197114be5dc1194f75b55b04e tor_aarch64.tar.gz" | sha256sum -c
- mv android-release.aar universal-0.0.3.jar jsocksAndroid-release.aar service-release.aar app/
- tar -C app/src/main -xf tor_x86.tar.gz
- tar -C app/src/main -xf tor_x86_64.tar.gz
- tar -C app/src/main -xf tor_armv7.tar.gz
- tar -C app/src/main -xf tor_aarch64.tar.gz
- rm tor_x86.tar.gz tor_x86_64.tar.gz tor_armv7.tar.gz tor_aarch64.tar.gz
stages:
- build
- buildFenixProduction
......@@ -57,7 +86,7 @@ stages:
buildDebug:
stage: build
script:
- ./gradlew clean app:assembleDebug --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleDebug --stacktrace
# Disable for now.
#artifacts:
......@@ -71,7 +100,7 @@ testLight:
# from config/pre-push-recommended.sh
# `tee` into a log file because resulting output is larger than 4 MB
# (4 MB is max log size)
- ./gradlew ktlint detekt assembleDebug assembleDebugAndroidTest testDebug | tee testLight_gradle.log
- ./gradlew -PdisableTor=true ktlint detekt assembleDebug assembleDebugAndroidTest testDebug | tee testLight_gradle.log
artifacts:
paths:
- testLight_gradle.log
......@@ -85,25 +114,25 @@ buildFenixProduction:
only:
- schedules
script:
- ./gradlew clean app:assembleNightly --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleNightly --stacktrace
buildFennecBeta:
stage: buildFennecBeta
only:
- schedules
script:
- ./gradlew clean app:assembleBeta --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleBeta --stacktrace
buildFennecProduction:
stage: buildFennecProduction
only:
- schedules
script:
- ./gradlew clean app:assembleRelease --stacktrace
- ./gradlew -PdisableTor=true clean app:assembleRelease --stacktrace
testAll:
stage: test
only:
- schedules
script:
- ./gradlew clean test
- ./gradlew -PdisableTor=true clean test
......@@ -260,12 +260,20 @@ android.applicationVariants.all { variant ->
def useReleaseVersioning = variant.buildType.buildConfigFields['USE_RELEASE_VERSIONING']?.value ?: false
def versionName = variant.buildType.name == 'nightly' ? Config.nightlyVersionName() : Config.releaseVersionName(project)
def disableTor = false
if (project.hasProperty("disableTor")) {
disableTor = project.getProperty("disableTor")
}
println("----------------------------------------------")
println("Variant name: " + variant.name)
println("Application ID: " + [variant.mergedFlavor.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
println("Build type: " + variant.buildType.name)
println("Flavor: " + variant.flavorName)
println("Telemetry enabled: " + !isDebugOrDCD)
println("Tor is disabled: " + disableTor)
buildConfigField "boolean", "DISABLE_TOR", "$disableTor"
if (useReleaseVersioning) {
// The Google Play Store does not allow multiple APKs for the same app that all have the
......@@ -640,6 +648,18 @@ dependencies {
testImplementation "org.mozilla.telemetry:glean-native-forUnitTests:${project.ext.glean_version}"
lintChecks project(":mozilla-lint-rules")
// Tor Android Services Dependencies
implementation 'net.freehaven.tor.control:jtorctl:0.2'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'org.slf4j:slf4j-android:1.7.25'
// Tor Android Services.
implementation files('service-release.aar')
// Tor Onion Proxy Library.
implementation files('universal-0.0.3.jar')
implementation files('android-release.aar')
}
protobuf {
......
......
......@@ -335,6 +335,13 @@
android:value="androidx.startup"
tools:node="remove" />
</provider>
<!-- Define Orbotservice's TorService -->
<service
android:name="org.torproject.android.service.TorService"
android:enabled="true"
android:exported="false"
android:stopWithTask="true">
</service>
</application>
</manifest>
......@@ -87,6 +87,7 @@ import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
import java.util.UUID
import java.util.concurrent.TimeUnit
import org.torproject.android.service.util.Prefs
/**
*The main application class for Fenix. Records data to measure initialization performance.
......@@ -100,6 +101,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
private val logger = Logger("FenixApplication")
var terminating = false
open val components by lazy { Components(this) }
var visibilityLifecycleCallback: VisibilityLifecycleCallback? = null
......@@ -136,6 +139,21 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
PerfStartup.applicationOnCreate.stopAndAccumulate(completeMethodDurationTimerId)
}
fun isTerminating() = terminating
fun terminate() {
onTerminate()
System.exit(0)
}
override fun onTerminate() {
terminating = true
super.onTerminate()
components.torController.stop()
components.torController.stopTor()
}
@OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
protected open fun initializeGlean() {
val telemetryEnabled = settings().isTelemetryEnabled
......@@ -170,6 +188,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
@CallSuper
@OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
open fun setupInMainProcessOnly() {
ProfilerMarkerFactProcessor.create { components.core.engine.profiler }.register()
......@@ -195,6 +214,11 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
if (!megazordSetup.isCompleted) {
runBlockingIncrement { megazordSetup.await() }
}
GlobalScope.launch(Dispatchers.IO) {
// Give TAS the base Context
Prefs.setContext(applicationContext)
}
}
setupLeakCanary()
......@@ -216,6 +240,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
initVisualCompletenessQueueAndQueueTasks()
ProcessLifecycleOwner.get().lifecycle.addObserver(TelemetryLifecycleObserver(components.core.store))
components.torController.start()
}
@OptIn(DelicateCoroutinesApi::class) // GlobalScope usage
......
......
......@@ -153,6 +153,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
private var isToolbarInflated = false
private var isBeingRecreated = false
private val webExtensionPopupFeature by lazy {
WebExtensionPopupFeature(components.core.store, ::openPopup)
}
......@@ -456,6 +458,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.core.contileTopSitesUpdater.stopPeriodicWork()
components.core.pocketStoriesService.stopPeriodicStoriesRefresh()
privateNotificationObserver?.stop()
if (!isBeingRecreated && !(application as FenixApplication).isTerminating()) {
// We assume the Activity is being destroyed because the user
// swiped away the app on the Recent screen. When this happens,
// we assume the user expects the entire Application is destroyed
// and not only the top Activity/Task. Therefore we kill the
// underlying Application, as well.
(application as FenixApplication).terminate()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
......@@ -475,6 +485,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
message = "recreate()"
)
isBeingRecreated = true
super.recreate()
}
......
......
......@@ -33,6 +33,7 @@ import org.mozilla.fenix.perf.StartupActivityLog
import org.mozilla.fenix.perf.StartupStateProvider
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.tor.TorController
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wallpapers.WallpaperFileManager
......@@ -187,6 +188,7 @@ class Components(private val context: Context) {
val startupActivityLog by lazyMonitored { StartupActivityLog() }
val startupStateProvider by lazyMonitored { StartupStateProvider(startupActivityLog, appStartReasonProvider) }
val appStore by lazyMonitored { AppStore() }
val torController by lazyMonitored { TorController(context) }
}
/**
......
......
......@@ -31,6 +31,8 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.doOnLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
......@@ -74,6 +76,7 @@ import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
......@@ -128,6 +131,7 @@ import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.SupportUtils.SumoTopic.HELP
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.tor.bootstrap.TorQuickStart
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
import org.mozilla.fenix.utils.ToolbarPopupWindow
......@@ -187,6 +191,7 @@ class HomeFragment : Fragment() {
private val recentTabsListFeature = ViewBoundFeatureWrapper<RecentTabsListFeature>()
private val recentBookmarksFeature = ViewBoundFeatureWrapper<RecentBookmarksFeature>()
private val historyMetadataFeature = ViewBoundFeatureWrapper<RecentVisitsFeature>()
private val torQuickStart by lazy { TorQuickStart(requireContext()) }
@VisibleForTesting
internal var getMenuButton: () -> MenuButton? = { binding.menuButton }
......@@ -230,9 +235,22 @@ class HomeFragment : Fragment() {
val activity = activity as HomeActivity
val components = requireComponents
// Splits by full stops or commas and puts the parts in different lines.
// Ignoring separators at the end of the string, it is expected
// that there are at most two parts (e.g. "Explore. Privately.").
val localBinding = binding;
binding.exploreprivately.text = localBinding
.exploreprivately
.text
?.replace(" *([.,。।]) *".toRegex(), "$1\n")
?.trim()
currentMode = CurrentMode(
requireContext(),
onboarding,
torQuickStart,
!BuildConfig.DISABLE_TOR,
components.torController,
browsingModeManager,
::dispatchModeChanges
)
......@@ -357,7 +375,11 @@ class HomeFragment : Fragment() {
hideOnboarding = ::hideOnboardingAndOpenSearch,
registerCollectionStorageObserver = ::registerCollectionStorageObserver,
removeCollectionWithUndo = ::removeCollectionWithUndo,
showTabTray = ::openTabsTray
showTabTray = ::openTabsTray,
handleTorBootstrapConnect = ::handleTorBootstrapConnect,
cancelTorBootstrap = ::cancelTorBootstrap,
initiateTorBootstrap = ::initiateTorBootstrap,
openTorNetworkSettings = ::openTorNetworkSettings
),
recentTabController = DefaultRecentTabsController(
selectTabUseCase = components.useCases.tabsUseCases.selectTab,
......@@ -406,6 +428,9 @@ class HomeFragment : Fragment() {
displayWallpaperIfEnabled()
adjustHomeFragmentView(currentMode.getCurrentMode())
showSessionControlView()
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
requireComponents.core.engine.profiler?.addMarker(
MarkersFragmentLifecycleCallbacks.MARKER_NAME, profilerStartTime, "HomeFragment.onCreateView",
......@@ -497,6 +522,108 @@ class HomeFragment : Fragment() {
}
}
// This function should be paired with showSessionControlView()
@SuppressWarnings("ComplexMethod", "NestedBlockDepth", "LongMethod")
private fun adjustHomeFragmentView(mode: Mode) {
binding.sessionControlRecyclerView.apply {
visibility = View.INVISIBLE
}
if (mode == Mode.Bootstrap) {
binding.sessionControlRecyclerView.apply {
setPadding(0, 0, 0, 0)
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(0, 0, 0, 0)
}
binding.homeAppBar.apply {
visibility = View.GONE
// Reset this as SCROLL in case it was previously set as NO_SCROLL after bootstrap
children.forEach {
(it.layoutParams as AppBarLayout.LayoutParams).scrollFlags =
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
}
}
binding.onionPatternImage.apply {
visibility = View.GONE
}
binding.toolbarLayout.apply {
visibility = View.GONE
}
} else {
// Keep synchronized with xml layout (somehow).
binding.sessionControlRecyclerView.apply {
setPadding(
SESSION_CONTROL_VIEW_PADDING,
SESSION_CONTROL_VIEW_PADDING,
SESSION_CONTROL_VIEW_PADDING,
SESSION_CONTROL_VIEW_PADDING
)
// Default margin until it is re-set below (either set immediately or after Layout)
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(
0,
0,
0,
DEFAULT_ONBOARDING_FINISH_MARGIN
)
}
binding.toolbarLayout.apply {
visibility = View.VISIBLE
// If the Layout rendering pass was completed, then we have a |height| value,
// if it wasn't completed then we have 0.
if (height == 0) {
// Set the bottom margin after the toolbar height is defined during Layout
doOnLayout {
val toolbarLayoutHeight = binding.toolbarLayout.height
binding.sessionControlRecyclerView.apply {
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(
0,
0,
0,
toolbarLayoutHeight - SESSION_CONTROL_VIEW_PADDING
)
}
}
} else {
binding.sessionControlRecyclerView.apply {
(layoutParams as ViewGroup.MarginLayoutParams).setMargins(
0,
0,
0,
height - SESSION_CONTROL_VIEW_PADDING
)
}
}
}
// Hide the onion pattern during Onboarding, too.
binding.onionPatternImage.apply {
visibility = if (onboarding.userHasBeenOnboarded()) {
View.VISIBLE
} else {
View.GONE
}
}
binding.homeAppBar.apply {
visibility = View.VISIBLE
children.forEach {
(it.layoutParams as AppBarLayout.LayoutParams).scrollFlags =
if (onboarding.userHasBeenOnboarded()) {
AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
} else {
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
}
}
}
}
}
// This function should be paired with adjustHomeFragmentView()
private fun showSessionControlView() {
binding.sessionControlRecyclerView.apply {
visibility = View.VISIBLE
}
}
@Suppress("LongMethod", "ComplexMethod")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// DO NOT ADD ANYTHING ABOVE THIS getProfilerTime CALL!
......@@ -794,9 +921,11 @@ class HomeFragment : Fragment() {
}
private fun dispatchModeChanges(mode: Mode) {
if (mode != Mode.fromBrowsingMode(browsingModeManager.mode)) {
homeFragmentStore.dispatch(HomeFragmentAction.ModeChange(mode))
}
adjustHomeFragmentView(mode)
updateSessionControlView()
showSessionControlView()
}
@VisibleForTesting
......@@ -820,12 +949,23 @@ class HomeFragment : Fragment() {
}
}
override fun onStop() {
super.onStop()
currentMode.unregisterTorListener()
}
override fun onResume() {
super.onResume()
if (browsingModeManager.mode == BrowsingMode.Private) {
activity?.window?.setBackgroundDrawableResource(R.drawable.private_home_background_gradient)
}
// fenix#40176: Ensure the Home fragment is rendered correctly when we resume.
val mode = currentMode.getCurrentMode()
adjustHomeFragmentView(mode)
updateSessionControlView()
showSessionControlView()
hideToolbar()
// Whenever a tab is selected its last access timestamp is automatically updated by A-C.
......@@ -921,6 +1061,10 @@ class HomeFragment : Fragment() {
mode = currentMode.getCurrentMode()
)
)
adjustHomeFragmentView(
currentMode.getCurrentMode()
)
showSessionControlView()
}
}
......@@ -1265,6 +1409,25 @@ class HomeFragment : Fragment() {
FeatureFlags.showWallpapers &&
(activity as? HomeActivity)?.themeManager?.currentTheme?.isPrivate?.not() ?: false
private fun handleTorBootstrapConnect() {
requireComponents.torController.onTorConnecting()
}
private fun cancelTorBootstrap() {
requireComponents.torController.stopTor()
}
private fun initiateTorBootstrap(withDebugLogging: Boolean = false) {
requireComponents.torController.initiateTorBootstrap(lifecycleScope, withDebugLogging)
}
private fun openTorNetworkSettings() {
val directions =
HomeFragmentDirections
.actionHomeFragmentToTorNetworkSettingsFragment()
findNavController().navigate(directions)
}
companion object {
const val ALL_NORMAL_TABS = "all_normal"
const val ALL_PRIVATE_TABS = "all_private"
......@@ -1286,5 +1449,9 @@ class HomeFragment : Fragment() {
// Elevation for undo toasts
internal const val TOAST_ELEVATION = 80f
// Layout
private const val DEFAULT_ONBOARDING_FINISH_MARGIN = 60
private const val SESSION_CONTROL_VIEW_PADDING = 16
}
}
......@@ -9,6 +9,9 @@ import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import org.mozilla.fenix.tor.TorController
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.tor.bootstrap.TorQuickStart
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.ext.components
......@@ -21,6 +24,7 @@ sealed class Mode {
object Normal : Mode()
object Private : Mode()
data class Onboarding(val state: OnboardingState) : Mode()
object Bootstrap : Mode()
companion object {
fun fromBrowsingMode(browsingMode: BrowsingMode) = when (browsingMode) {
......@@ -40,16 +44,26 @@ sealed class OnboardingState {
object SignedIn : OnboardingState()
}
@SuppressWarnings("LongParameterList", "TooManyFunctions")
class CurrentMode(
private val context: Context,
private val onboarding: FenixOnboarding,
private val torQuickStart: TorQuickStart,
private val shouldStartTor: Boolean,
private val torController: TorController,
private val browsingModeManager: BrowsingModeManager,
private val dispatchModeChanges: (mode: Mode) -> Unit
) : AccountObserver {
) : AccountObserver, TorEvents {
private val accountManager by lazy { context.components.backgroundServices.accountManager }
fun getCurrentMode() = if (onboarding.userHasBeenOnboarded()) {
init {
torController.registerTorListener(this)
}
fun getCurrentMode() = if (shouldStartTor && (!torQuickStart.quickStartTor() && !torController.isBootstrapped)) {
Mode.Bootstrap
} else if (onboarding.userHasBeenOnboarded()) {
Mode.fromBrowsingMode(browsingModeManager.mode)
} else {
val account = accountManager.authenticatedAccount()
......@@ -64,6 +78,26 @@ class CurrentMode(
dispatchModeChanges(getCurrentMode())
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorConnecting() {
}
override fun onTorConnected() {
dispatchModeChanges(getCurrentMode())
}
override fun onTorStopped() {
dispatchModeChanges(getCurrentMode())
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorStatusUpdate(entry: String?, status: String?) {
}
fun unregisterTorListener() {
torController.unregisterTorListener(this)
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) = emitModeChanges()
override fun onAuthenticationProblems() = emitModeChanges()
override fun onLoggedOut() = emitModeChanges()
......
......
......@@ -35,6 +35,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.CustomizeHomeButtonViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TorBootstrapPagerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.ExperimentDefaultBrowserCardViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFinishViewHolder
......@@ -45,6 +46,8 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSe
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingThemePickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingToolbarPositionPickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.TorOnboardingSecurityLevelViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.TorOnboardingDonateViewHolder
import org.mozilla.fenix.home.tips.ButtonTipViewHolder
import org.mozilla.fenix.home.topsites.TopSitePagerViewHolder
import mozilla.components.feature.tab.collections.Tab as ComponentTab
......@@ -113,6 +116,8 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID)
object NoCollectionsMessage : AdapterItem(NoCollectionsMessageViewHolder.LAYOUT_ID)
object TorBootstrap : AdapterItem(TorBootstrapPagerViewHolder.LAYOUT_ID)
object CollectionHeader : AdapterItem(CollectionHeaderViewHolder.LAYOUT_ID)
data class CollectionItem(
val collection: TabCollection,
......@@ -160,6 +165,9 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
object CustomizeHomeButton : AdapterItem(CustomizeHomeButtonViewHolder.LAYOUT_ID)
object TorOnboardingSecurityLevel : AdapterItem(TorOnboardingSecurityLevelViewHolder.LAYOUT_ID)
object TorOnboardingDonate : AdapterItem(TorOnboardingDonateViewHolder.LAYOUT_ID)
object RecentTabsHeader : AdapterItem(RecentTabsHeaderViewHolder.LAYOUT_ID)
object RecentTabItem : AdapterItem(RecentTabViewHolder.LAYOUT_ID)
......@@ -266,6 +274,11 @@ class SessionControlAdapter(
view,
interactor
)
TorBootstrapPagerViewHolder.LAYOUT_ID -> TorBootstrapPagerViewHolder(
view,
components,
interactor
)
NoCollectionsMessageViewHolder.LAYOUT_ID ->
NoCollectionsMessageViewHolder(
view,
......@@ -302,6 +315,15 @@ class SessionControlAdapter(
interactor
)
BottomSpacerViewHolder.LAYOUT_ID -> BottomSpacerViewHolder(view)
TorOnboardingSecurityLevelViewHolder.LAYOUT_ID -> TorOnboardingSecurityLevelViewHolder(
view,
interactor
)
TorOnboardingDonateViewHolder.LAYOUT_ID -> TorOnboardingDonateViewHolder(
view,
interactor
)
else -> throw IllegalStateException()
}
}
......
......
......@@ -138,11 +138,26 @@ interface SessionControlController {
*/
fun handleStartBrowsingClicked()
/**
* @see [OnboardingInteractor.onOpenSettingsClicked]
*/
fun handleOpenSettingsClicked()
/**
* @see [OnboardingInteractor.onOpenSecurityLevelSettingsClicked]
*/
fun handleOpenSecurityLevelSettingsClicked()
/**
* @see [OnboardingInteractor.onReadPrivacyNoticeClicked]
*/
fun handleReadPrivacyNoticeClicked()
/**
* @see [OnboardingInteractor.onDonateClicked]
*/
fun handleDonateClicked()
/**
* @see [CollectionInteractor.onToggleCollectionExpanded]
*/
......@@ -207,6 +222,31 @@ interface SessionControlController {
* @see [SessionControlInteractor.reportSessionMetrics]
*/
fun handleReportSessionMetrics(state: HomeFragmentState)
/**
* @see [TorBootstrapInteractor.onTorBootstrapConnectClicked]
*/
fun handleTorBootstrapConnectClicked()
/**
* @see [TorBootstrapInteractor.onTorStopBootstrapping]
*/
fun handleTorStopBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorStartBootstrapping]
*/
fun handleTorStartBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorStartDebugBootstrapping]
*/
fun handleTorStartDebugBootstrapping()
/**
* @see [TorBootstrapInteractor.onTorBootstrapNetworkSettingsClicked]
*/
fun handleTorNetworkSettingsClicked()
}
@Suppress("TooManyFunctions", "LargeClass")
......@@ -227,7 +267,11 @@ class DefaultSessionControlController(
private val hideOnboarding: () -> Unit,
private val registerCollectionStorageObserver: () -> Unit,
private val removeCollectionWithUndo: (tabCollection: TabCollection) -> Unit,
private val showTabTray: () -> Unit
private val showTabTray: () -> Unit,
private val handleTorBootstrapConnect: () -> Unit,
private val initiateTorBootstrap: (Boolean) -> Unit,
private val cancelTorBootstrap: () -> Unit,
private val openTorNetworkSettings: () -> Unit
) : SessionControlController {
override fun handleCollectionAddTabTapped(collection: TabCollection) {
......@@ -518,6 +562,14 @@ class DefaultSessionControlController(
}
}
override fun handleOpenSettingsClicked() {
val directions = HomeFragmentDirections.actionGlobalPrivateBrowsingFragment()
navController.nav(R.id.homeFragment, directions)
}
override fun handleOpenSecurityLevelSettingsClicked() {
}
override fun handleReadPrivacyNoticeClicked() {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
......@@ -526,6 +578,14 @@ class DefaultSessionControlController(
)
}
override fun handleDonateClicked() {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.DONATE_URL,
newTab = true,
from = BrowserDirection.FromHome
)
}
override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
fragmentStore.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
}
......@@ -659,4 +719,24 @@ class DefaultSessionControlController(
track(Event.RecentBookmarkCount(state.recentBookmarks.size))
}
}
override fun handleTorBootstrapConnectClicked() {
handleTorBootstrapConnect()
}
override fun handleTorStopBootstrapping() {
cancelTorBootstrap()
}
override fun handleTorStartBootstrapping() {
initiateTorBootstrap(false)
}
override fun handleTorStartDebugBootstrapping() {
initiateTorBootstrap(true)
}
override fun handleTorNetworkSettingsClicked() {
openTorNetworkSettings()
}
}
......@@ -155,6 +155,16 @@ interface OnboardingInteractor {
*/
fun onStartBrowsingClicked()
/**
* Hides the onboarding and navigates to Settings. Called when a user clicks on the "Open settings" button.
*/
fun onOpenSettingsClicked()
/**
* Hides the onboarding and navigates to Settings. Called when a user clicks on the "Open Security Settings" button.
*/
fun onOpenSecurityLevelSettingsClicked()
/**
* Opens a custom tab to privacy notice url. Called when a user clicks on the "read our privacy notice" button.
*/
......@@ -165,6 +175,11 @@ interface OnboardingInteractor {
* historyMetadata and pocketArticles sections.
*/
fun showOnboardingDialog()
/**
* Opens a custom tab to privacy notice url. Called when a user clicks on the "read our privacy notice" button.
*/
fun onDonateClicked()
}
interface TipInteractor {
......@@ -181,6 +196,34 @@ interface CustomizeHomeIteractor {
fun openCustomizeHomePage()
}
interface TorBootstrapInteractor {
/**
* Initiates Tor bootstrapping. Called when a user clicks on the "Connect" button.
*/
fun onTorBootstrapConnectClicked()
/**
* Initiates Tor bootstrapping. Called when a user clicks on the "Connect" button.
*/
fun onTorStartBootstrapping()
/**
* Stop Tor bootstrapping. Called when a user clicks on the "settings" cog/button.
*/
fun onTorStopBootstrapping()
/**
* Initiates Tor bootstrapping with debug logging. Called when bootstrapping fails with
* the control.txt file not existing.
*/
fun onTorStartDebugBootstrapping()
/**
* Open Tor Network Settings preference screen
*/
fun onTorBootstrapNetworkSettingsClicked()
}
/**
* Interface for top site related actions in the [SessionControlInteractor].
*/
......@@ -269,6 +312,7 @@ class SessionControlInteractor(
RecentBookmarksInteractor,
RecentVisitsInteractor,
CustomizeHomeIteractor,
TorBootstrapInteractor,
PocketStoriesInteractor {
override fun onCollectionAddTabTapped(collection: TabCollection) {
......@@ -327,6 +371,14 @@ class SessionControlInteractor(
controller.handleStartBrowsingClicked()
}
override fun onOpenSettingsClicked() {
controller.handleOpenSettingsClicked()
}
override fun onOpenSecurityLevelSettingsClicked() {
controller.handleOpenSecurityLevelSettingsClicked()
}
override fun onReadPrivacyNoticeClicked() {
controller.handleReadPrivacyNoticeClicked()
}
......@@ -335,6 +387,10 @@ class SessionControlInteractor(
controller.handleShowOnboardingDialog()
}
override fun onDonateClicked() {
controller.handleDonateClicked()
}
override fun onToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
controller.handleToggleCollectionExpanded(collection, expand)
}
......@@ -460,4 +516,24 @@ class SessionControlInteractor(
override fun reportSessionMetrics(state: HomeFragmentState) {
controller.handleReportSessionMetrics(state)
}
override fun onTorBootstrapConnectClicked() {
controller.handleTorBootstrapConnectClicked()
}
override fun onTorStopBootstrapping() {
controller.handleTorStopBootstrapping()
}
override fun onTorStartBootstrapping() {
controller.handleTorStartBootstrapping()
}
override fun onTorStartDebugBootstrapping() {
controller.handleTorStartDebugBootstrapping()
}
override fun onTorBootstrapNetworkSettingsClicked() {
controller.handleTorNetworkSettingsClicked()
}
}
......@@ -119,6 +119,8 @@ private fun showCollections(
private fun privateModeAdapterItems() = listOf(AdapterItem.PrivateBrowsingDescription)
private fun bootstrapAdapterItems() = listOf(AdapterItem.TorBootstrap)
private fun onboardingAdapterItems(onboardingState: OnboardingState): List<AdapterItem> {
val items: MutableList<AdapterItem> = mutableListOf(AdapterItem.OnboardingHeader)
......@@ -152,6 +154,13 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List<Adapt
return items
}
private fun torOnboardingAdapterItems() =
listOf(
AdapterItem.TorOnboardingSecurityLevel,
AdapterItem.TorOnboardingDonate,
AdapterItem.OnboardingFinish
)
private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
is Mode.Normal -> normalModeAdapterItems(
topSites,
......@@ -166,7 +175,8 @@ private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
pocketStories
)
is Mode.Private -> privateModeAdapterItems()
is Mode.Onboarding -> onboardingAdapterItems(mode.state)
is Mode.Onboarding -> torOnboardingAdapterItems()
is Mode.Bootstrap -> bootstrapAdapterItems()
}
@VisibleForTesting
......
......
......@@ -4,7 +4,6 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
......@@ -18,19 +17,19 @@ class PrivateBrowsingDescriptionViewHolder(
) : RecyclerView.ViewHolder(view) {
init {
val resources = view.resources
val appName = resources.getString(R.string.app_name)
val binding = PrivateBrowsingDescriptionBinding.bind(view)
binding.privateSessionDescription.text = resources.getString(
R.string.private_browsing_placeholder_description_2, appName
)
with(binding.privateSessionCommonMyths) {
movementMethod = LinkMovementMethod.getInstance()
addUnderline()
setOnClickListener {
interactor.onPrivateBrowsingLearnMoreClicked()
}
}
//val resources = view.resources
//val appName = resources.getString(R.string.app_name)
//val binding = PrivateBrowsingDescriptionBinding.bind(view)
//binding.privateSessionDescription.text = resources.getString(
// R.string.private_browsing_placeholder_description_2, appName
//)
//with(binding.privateSessionCommonMyths) {
// movementMethod = LinkMovementMethod.getInstance()
// addUnderline()
// setOnClickListener {
// interactor.onPrivateBrowsingLearnMoreClicked()
// }
//}
}
companion object {
......
......
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.view.View
import androidx.appcompat.widget.SwitchCompat
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.TorBootstrapConnectBinding
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.tor.bootstrap.TorQuickStart
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.home.sessioncontrol.TorBootstrapInteractor
class TorBootstrapConnectViewHolder(
private val view: View,
private val components: Components,
private val interactor: TorBootstrapInteractor
) : RecyclerView.ViewHolder(view), TorEvents {
var binding: TorBootstrapConnectBinding
init {
binding = TorBootstrapConnectBinding.bind(view)
val torQuickStart = TorQuickStart(view.context)
setQuickStartDescription(view, torQuickStart)
with(binding.quickStartToggle) {
setOnCheckedChangeListener { _, isChecked ->
torQuickStart.setQuickStartTor(isChecked)
setQuickStartDescription(view, torQuickStart)
}
isChecked = torQuickStart.quickStartTor()
}
with(binding.torBootstrapNetworkSettingsButton) {
setOnClickListener {
interactor.onTorStopBootstrapping()
interactor.onTorBootstrapNetworkSettingsClicked()
with(binding.torBootstrapProgress) {
visibility = View.INVISIBLE
}
with(binding.torBootstrapConnectButton) {
visibility = View.VISIBLE
}
}
}
with(binding.torBootstrapConnectButton) {
setOnClickListener {
interactor.onTorBootstrapConnectClicked()
interactor.onTorStartBootstrapping()
visibility = View.INVISIBLE
with(binding.torBootstrapProgress) {
visibility = View.VISIBLE
}
}
}
components.torController.registerTorListener(this)
}
private fun setQuickStartDescription(view: View, torQuickStart: TorQuickStart) {
val resources = view.context.resources
val appName = resources.getString(R.string.app_name)
if (torQuickStart.quickStartTor()) {
binding.torBootstrapQuickStartDescription.text = resources.getString(
R.string.tor_bootstrap_quick_start_enabled, appName
)
} else {
binding.torBootstrapQuickStartDescription.text = resources.getString(
R.string.tor_bootstrap_quick_start_disabled
)
}
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorConnecting() {
}
override fun onTorConnected() {
components.torController.unregisterTorListener(this)
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorStopped() {
}
override fun onTorStatusUpdate(entry: String?, status: String?) {
if (entry == null) return
binding.torBootstrapStatusMessage.text = entry
if (entry.startsWith(BOOTSTRAPPED_PREFIX)) {
val percentIdx = entry.indexOf("%")
val percent = entry.substring(
BOOTSTRAPPED_PREFIX.length,
percentIdx
)
with(binding.torBootstrapProgress) {
progress = percent.toInt()
}
}
}
companion object {
const val LAYOUT_ID = R.layout.tor_bootstrap_connect
const val BOOTSTRAPPED_PREFIX = "NOTICE: Bootstrapped "
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.text.method.ScrollingMovementMethod
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.TorBootstrapLoggerBinding
import org.mozilla.fenix.tor.TorEvents
import org.mozilla.fenix.components.Components
class TorBootstrapLoggerViewHolder(
private val view: View,
private val components: Components
) : RecyclerView.ViewHolder(view), TorEvents {
private var entries = mutableListOf<String>()
private var binding: TorBootstrapLoggerBinding
init {
binding = TorBootstrapLoggerBinding.bind(view)
components.torController.registerTorListener(this)
val currentEntries = components.torController.logEntries
.filter { it.first != null }
.filter { !(it.first!!.startsWith("Circuit") && it.second == "ON") }
// Keep synchronized with format in onTorStatusUpdate
.flatMap { listOf("(${it.second}) '${it.first}'") }
val entriesLen = currentEntries.size
val subListOffset = if (entriesLen > MAX_NEW_ENTRIES) MAX_NEW_ENTRIES else entriesLen
entries = currentEntries.subList((entriesLen - subListOffset), entriesLen) as MutableList<String>
val initLog = "---------------" + view.resources.getString(R.string.tor_initializing_log) + "---------------"
entries.add(0, initLog)
with(binding.torBootstrapLogEntries) {
movementMethod = ScrollingMovementMethod()
text = formatLogEntries(entries)
}
}
private fun formatLogEntries(entries: List<String>) = entries.joinToString("\n")
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorConnecting() {
}
override fun onTorConnected() {
components.torController.unregisterTorListener(this)
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onTorStopped() {
}
override fun onTorStatusUpdate(entry: String?, status: String?) {
if (status == null || entry == null) return
if (status == "ON" && entry.startsWith("Circuit")) return
if (entries.size > MAX_LINES) {
entries = entries.drop(1) as MutableList<String>
}
entries.add("($status) '$entry'")
binding.torBootstrapLogEntries.text = formatLogEntries(entries)
}
companion object {
const val LAYOUT_ID = R.layout.tor_bootstrap_logger
const val MAX_NEW_ENTRIES = 24
const val MAX_LINES = 25
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.TorBootstrapPagerBinding
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.home.sessioncontrol.TorBootstrapInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.torbootstrap.BootstrapPagerAdapter
class TorBootstrapPagerViewHolder(
view: View,
components: Components,
interactor: TorBootstrapInteractor
) : RecyclerView.ViewHolder(view) {
private val bootstrapPagerAdapter = BootstrapPagerAdapter(components, interactor)
init {
val binding = TorBootstrapPagerBinding.bind(view)
binding.bootstrapPager.apply {
adapter = bootstrapPagerAdapter
}
}
companion object {
const val LAYOUT_ID = R.layout.tor_bootstrap_pager
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.TorOnboardingDonateBinding
import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor
class TorOnboardingDonateViewHolder(
view: View,
private val interactor: OnboardingInteractor
) : RecyclerView.ViewHolder(view) {
init {
val binding = TorOnboardingDonateBinding.bind(view)
binding.donateNowButton.setOnClickListener {
interactor.onDonateClicked()
}
}
companion object {
const val LAYOUT_ID = R.layout.tor_onboarding_donate
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.TorOnboardingSecurityLevelBinding
import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor
import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class TorOnboardingSecurityLevelViewHolder(
view: View,
private val interactor: OnboardingInteractor
) : RecyclerView.ViewHolder(view) {
private var standardSecurityLevel: OnboardingRadioButton
private var saferSecurityLevel: OnboardingRadioButton
private var safestSecurityLevel: OnboardingRadioButton
init {
val binding = TorOnboardingSecurityLevelBinding.bind(view)
binding.headerText.setOnboardingIcon(R.drawable.ic_onboarding_tracking_protection)
standardSecurityLevel = binding.securityLevelStandardOption
saferSecurityLevel = binding.securityLevelSaferOption
safestSecurityLevel = binding.securityLevelSafestOption
binding.descriptionText.text = view.context.getString(
R.string.tor_onboarding_security_level_description
)
binding.openSettingsButton.setOnClickListener {
interactor.onOpenSettingsClicked()
}
setupRadioGroup()
}
private fun setupRadioGroup() {
addToRadioGroup(standardSecurityLevel, saferSecurityLevel, safestSecurityLevel)
standardSecurityLevel.isChecked = true
safestSecurityLevel.isChecked = false
saferSecurityLevel.isChecked = false
standardSecurityLevel.onClickListener {
updateSecurityLevel()
}
saferSecurityLevel.onClickListener {
updateSecurityLevel()
}
safestSecurityLevel.onClickListener {
updateSecurityLevel()
}
}
private fun updateSecurityLevel() {
}
companion object {
const val LAYOUT_ID = R.layout.tor_onboarding_security_level
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.sessioncontrol.viewholders.torbootstrap
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.home.sessioncontrol.TorBootstrapInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.TorBootstrapConnectViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TorBootstrapLoggerViewHolder
class BootstrapPagerAdapter(
private val components: Components,
private val interactor: TorBootstrapInteractor
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == BOOTSTRAP_UI_PAGE_TYPE) {
val viewDVH = LayoutInflater.from(parent.context)
.inflate(TorBootstrapConnectViewHolder.LAYOUT_ID, parent, false)
return TorBootstrapConnectViewHolder(viewDVH, components, interactor)
} else {
val viewLVH = LayoutInflater.from(parent.context)
.inflate(TorBootstrapLoggerViewHolder.LAYOUT_ID, parent, false)
return TorBootstrapLoggerViewHolder(viewLVH, components)
}
}
@SuppressWarnings("EmptyFunctionBlock")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
}
override fun getItemViewType(position: Int): Int = position
override fun getItemCount(): Int = BOOTSTRAP_PAGE_COUNT
companion object {
const val BOOTSTRAP_UI_PAGE_TYPE = 0
const val BOOTSTRAP_LOG_PAGE_TYPE = 1
const val BOOTSTRAP_PAGE_COUNT = 2
}
}
......@@ -247,6 +247,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
resources.getString(R.string.pref_key_search_settings) -> {
SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
}
resources.getString(R.string.pref_key_tor_network_settings) -> {
SettingsFragmentDirections.actionSettingsFragmentToTorNetworkSettingsFragment()
}
resources.getString(R.string.pref_key_tracking_protection_settings) -> {
requireContext().metrics.track(Event.TrackingProtectionSettings)
SettingsFragmentDirections.actionSettingsFragmentToTrackingProtectionFragment()
......
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment