Dan Ballard pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits: 2095a229 by clairehurst at 2024-04-17T00:27:30+00:00 fixup! Add Tor integration and UI
- - - - -
4 changed files:
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsComposeFragment.kt - − fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsFragment.kt - + fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt - fenix/app/src/main/res/navigation/nav_graph.xml
Changes:
===================================== fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsComposeFragment.kt ===================================== @@ -0,0 +1,82 @@ +/* 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.tor + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.DisableSelection +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.unit.dp +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import mozilla.components.ui.colors.PhotonColors + +class TorLogsComposeFragment : Fragment() { + private val viewModel: TorLogsViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return ComposeView(requireContext()).apply { + setContent { + SelectionContainer { + Column( + // Column instead of LazyColumn so that you can select all the logs, and not just one "screen" at a time + // The logs won't be too big so loading them all instead of just whats visible shouldn't be a big deal + modifier = Modifier + .fillMaxSize() + .verticalScroll(state = rememberScrollState(), reverseScrolling = true), + ) { + for (log in viewModel.torLogs) { + LogRow(log = log) + } + } + } + } + } + } +} + +@Composable +@Stable +fun LogRow(log: TorLog, modifier: Modifier = Modifier) { + Column( + modifier + .fillMaxWidth() + .padding( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ), + ) { + DisableSelection { + Text( + text = log.timestamp.toString(), + color = PhotonColors.LightGrey40, + modifier = modifier + .padding(bottom = 4.dp), + ) + } + Text( + text = log.text, + color = PhotonColors.LightGrey05, + ) + } +}
===================================== fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsFragment.kt deleted ===================================== @@ -1,81 +0,0 @@ -/* 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.tor - -import android.os.Bundle -import android.text.method.ScrollingMovementMethod -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import org.mozilla.fenix.R -import org.mozilla.fenix.components.Components -import org.mozilla.fenix.databinding.TorBootstrapLoggerBinding -import org.mozilla.fenix.ext.requireComponents -import org.mozilla.fenix.tor.view.TorBootstrapLoggerViewHolder - -class TorLogsFragment : Fragment(), TorLogs { - - private var entries = mutableListOf<String>() - internal var _binding: TorBootstrapLoggerBinding? = null - private val binding get() = _binding!! - - private var _components: Components? = null - private val components get() = _components!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - _binding = TorBootstrapLoggerBinding.inflate(inflater) - _components = requireComponents - - components.torController.registerTorLogListener(this) - - val currentEntries = components.torController.logEntries.filter { it.second != null } - .filter { !(it.second!!.startsWith("Circuit") && it.first == "ON") } - // Keep synchronized with format in onTorStatusUpdate - .flatMap { listOf("(${it.first}) '${it.second}'") } - val entriesLen = currentEntries.size - val subListOffset = - if (entriesLen > TorBootstrapLoggerViewHolder.MAX_NEW_ENTRIES) TorBootstrapLoggerViewHolder.MAX_NEW_ENTRIES else entriesLen - entries = - currentEntries.subList((entriesLen - subListOffset), entriesLen) as MutableList<String> - val initLog = - "---------------" + getString(R.string.tor_initializing_log) + "---------------" - entries.add(0, initLog) - - with(binding.torBootstrapLogEntries) { - movementMethod = ScrollingMovementMethod() - text = formatLogEntries(entries) - } - - - return binding.root - } - - // TODO on destroy unregiuster - - private fun formatLogEntries(entries: List<String>) = entries.joinToString("\n") - - override fun onLog(type: String?, message: String?) { - if (message == null || type == null) return - if (type == "ON" && type.startsWith("Circuit")) return - - if (entries.size > TorBootstrapLoggerViewHolder.MAX_LINES) { - entries = entries.drop(1) as MutableList<String> - } - entries.add("($type) '$message'") - - binding.torBootstrapLogEntries.text = formatLogEntries(entries) - } - - override fun onStop() { - super.onStop() - components.torController.unregisterTorLogListener(this) - } - -}
===================================== fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt ===================================== @@ -0,0 +1,88 @@ +/* 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.tor + +import android.app.Application +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import android.widget.Toast +import androidx.compose.runtime.Stable +import androidx.lifecycle.AndroidViewModel +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.components +import java.sql.Timestamp + +class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogs { + private val torController = application.components.torController + private val clipboardManager = + application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + val torLogs: MutableList<TorLog> = mutableListOf( + TorLog( + "---------------" + application.getString(R.string.tor_initializing_log) + "---------------", + ), + ) + + init { + setupClipboardListener() + torController.registerTorLogListener(this) + val currentEntries = torController.logEntries.filter { it.second != null } + .filter { !(it.second!!.startsWith("Circuit") && it.first == "ON") } + // Keep synchronized with format in onTorStatusUpdate + .flatMap { listOf(TorLog("[${it.first}] ${it.second}")) } + torLogs.addAll(currentEntries) + } + + override fun onLog(type: String?, message: String?) { + if (message == null || type == null) return + if (type == "ON" && type.startsWith("Circuit")) return + + torLogs.add(TorLog("[$type] $message")) + } + + override fun onCleared() { + super.onCleared() + torController.unregisterTorLogListener(this) + } + + private fun setupClipboardListener() { + clipboardManager.addPrimaryClipChangedListener { + // Only show a toast for Android 12 and lower. + // https://developer.android.com/develop/ui/views/touch-and-input/copy-paste#du... + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText( + getApplication<Application>().applicationContext, + getApplication<Application>().getString(R.string.toast_copy_link_to_clipboard), // "Copied to clipboard" already translated + Toast.LENGTH_SHORT, + ).show() + } + } + } + + fun copyAllLogsToClipboard() { // TODO add kebab menu in top right corner which includes option to "Copy all logs" + clipboardManager.setPrimaryClip( + ClipData.newPlainText( + "Copied Text", + getAllTorLogs(), + ), + ) + } + + private fun getAllTorLogs(): String { + var ret = "" + for (log in torLogs) { + ret += log.text + '\n' + } + return ret + } +} + +@Stable +data class TorLog( + val text: String, + val timestamp: Timestamp = Timestamp(System.currentTimeMillis()), +)
===================================== fenix/app/src/main/res/navigation/nav_graph.xml ===================================== @@ -976,8 +976,7 @@ <fragment android:id="@+id/torBridgeConfigFragment" android:name="org.mozilla.fenix.settings.TorBridgeConfigFragment" - android:label="@string/preferences_tor_network_settings_bridge_config" - tools:layout="@layout/fragment_tor_bridge_config" /> + android:label="@string/preferences_tor_network_settings_bridge_config" /> <fragment android:id="@+id/torBetaConnectionFeaturesFragment" android:name="org.mozilla.fenix.tor.TorBetaConnectionFeaturesFragment" @@ -985,9 +984,8 @@ tools:layout="@layout/tor_network_settings_beta_connection_features" /> <fragment android:id="@+id/torLogsFragment" - android:name="org.mozilla.fenix.tor.TorLogsFragment" - android:label="Tor Logs" - tools:layout="@layout/tor_bootstrap_logger" /> + android:name="org.mozilla.fenix.tor.TorLogsComposeFragment" + android:label="@string/preferences_tor_logs" />
<fragment android:id="@+id/trackingProtectionFragment"
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/2095...