From ca16e52984f970719d0e1e4cde272dd84d39d5a8 Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Fri, 17 Apr 2026 17:58:46 +0100 Subject: [PATCH 01/11] add error screen compasable --- .../newpipe/ui/screens/ErrorReportScreen.kt | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt new file mode 100644 index 00000000000..ff6b430d082 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt @@ -0,0 +1,183 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.ui.screens + +import android.content.res.Configuration +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.schabi.newpipe.R +import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar +import org.schabi.newpipe.ui.theme.AppTheme + +@Composable +fun ErrorReportScreen( + sorryMessage: String, + errorMessage: String, + infoLabels: String, + infoValues: String, + errorDetails: String, + onBackClick: () -> Unit, + onReportViaEmail: (comment: String) -> Unit, + onCopyForGitHub: (comment: String) -> Unit, + onReportOnGitHub: () -> Unit +) { + var comment by rememberSaveable { mutableStateOf("") } + + ScaffoldWithToolbar( + title = stringResource(R.string.error_report_title), + onBackClick = onBackClick + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + // Sorry header + Text( + text = sorryMessage, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + // What happened + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.what_happened_headline), + style = MaterialTheme.typography.titleMedium + ) + Text( + text = errorMessage, + color = MaterialTheme.colorScheme.primary + ) + + // Device info + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.what_device_headline), + style = MaterialTheme.typography.titleMedium + ) + Row { + Text( + text = infoLabels, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = infoValues, + modifier = Modifier + .padding(start = 16.dp) + .horizontalScroll(rememberScrollState()) + ) + } + + // Error details + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.error_details_headline), + style = MaterialTheme.typography.titleMedium + ) + SelectionContainer { + Text( + text = errorDetails, + fontFamily = FontFamily.Monospace, + modifier = Modifier.horizontalScroll(rememberScrollState()) + ) + } + + // User comment + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.your_comment), + style = MaterialTheme.typography.titleMedium + ) + OutlinedTextField( + value = comment, + onValueChange = { comment = it }, + modifier = Modifier.fillMaxWidth() + ) + + // Report via email button + Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { onReportViaEmail(comment) }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.error_report_button_text)) + } + + // GitHub notice + Text( + text = stringResource(R.string.error_report_open_github_notice), + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = 10.dp, bottom = 5.dp) + ) + + // Copy for GitHub button + Button( + onClick = { onCopyForGitHub(comment) }, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.copy_for_github)) + } + + // Report on GitHub button + Button( + onClick = onReportOnGitHub, + modifier = Modifier.fillMaxWidth() + ) { + Text(text = stringResource(R.string.error_report_open_issue_button_text)) + } + } + } +} + +@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun ErrorReportScreenPreview() { + AppTheme { + Surface(color = MaterialTheme.colorScheme.background) { + ErrorReportScreen( + sorryMessage = "Sorry, that should not have happened.", + errorMessage = "Requested list not handled", + infoLabels = "What:\nRequest:\nContent Language:\nContent Country:\nApp Language:\nService:\nTimestamp:\nPackage:\nVersion:\nOS version:", + infoValues = "Requested list\nnone\nen\nUS\nen_US\nYouTube\n2026-04-17T12:00:00Z\norg.schabi.newpipe\n0.27.5\nAndroid 14 - 34", + errorDetails = "java.lang.IllegalArgumentException: ...\n\tat org.schabi.newpipe.SomeClass.method(SomeClass.kt:42)", + onBackClick = {}, + onReportViaEmail = {}, + onCopyForGitHub = {}, + onReportOnGitHub = {} + ) + } + } +} From f61d086dd0cc2f5371c16a7bd843ff7b0ba56be7 Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Sat, 18 Apr 2026 00:38:59 +0100 Subject: [PATCH 02/11] add error activity --- .../org/schabi/newpipe/error/ErrorActivity.kt | 147 +++++++----------- .../org/schabi/newpipe/ui/BaseActivity.kt | 44 ++++++ .../newpipe/ui/screens/ErrorReportScreen.kt | 16 +- 3 files changed, 110 insertions(+), 97 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt index c68a2cfd1e1..10235a1fa01 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt @@ -10,33 +10,29 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.util.Log -import android.view.Menu -import android.view.MenuItem import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.IntentCompat import androidx.core.net.toUri import com.grack.nanojson.JsonWriter +import dagger.hilt.android.AndroidEntryPoint import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import org.schabi.newpipe.BuildConfig import org.schabi.newpipe.R -import org.schabi.newpipe.databinding.ActivityErrorBinding +import org.schabi.newpipe.ui.BaseActivity +import org.schabi.newpipe.ui.screens.ErrorReportScreen import org.schabi.newpipe.util.Localization -import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils -import org.schabi.newpipe.util.text.setTextWithLinks /** * This activity is used to show error details and allow reporting them in various ways. * Use [ErrorUtil.openActivity] to correctly open this activity. */ -class ErrorActivity : AppCompatActivity() { +@AndroidEntryPoint +class ErrorActivity : BaseActivity() { private lateinit var errorInfo: ErrorInfo private lateinit var currentTimeStamp: String - private lateinit var binding: ActivityErrorBinding - private val contentCountryString: String get() = Localization.getPreferredContentCountry(this).countryCode @@ -49,11 +45,7 @@ class ErrorActivity : AppCompatActivity() { private val osString: String get() { val name = System.getProperty("os.name")!! - val osBase = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Build.VERSION.BASE_OS.ifEmpty { "Android" } - } else { - "Android" - } + val osBase = Build.VERSION.BASE_OS.ifEmpty { "Android" } return "$name $osBase ${Build.VERSION.RELEASE} - ${Build.VERSION.SDK_INT}" } @@ -67,73 +59,53 @@ class ErrorActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - ThemeHelper.setDayNightMode(this) - ThemeHelper.setTheme(this) - - binding = ActivityErrorBinding.inflate(layoutInflater) - setContentView(binding.getRoot()) - - setSupportActionBar(binding.toolbarLayout.toolbar) - supportActionBar?.apply { - setDisplayHomeAsUpEnabled(true) - setTitle(R.string.error_report_title) - setDisplayShowTitleEnabled(true) - } - errorInfo = IntentCompat.getParcelableExtra(intent, ERROR_INFO, ErrorInfo::class.java)!! - // important add guru meditation - addGuruMeditation() // print current time, as zoned ISO8601 timestamp currentTimeStamp = ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - binding.errorReportEmailButton.setOnClickListener { _ -> - openPrivacyPolicyDialog(this, "EMAIL") - } - - binding.errorReportCopyButton.setOnClickListener { _ -> - ShareUtils.copyToClipboard(this, buildMarkdown()) - } - - binding.errorReportGitHubButton.setOnClickListener { _ -> - openPrivacyPolicyDialog(this, "GITHUB") - } - - // normal bugreport - buildInfo(errorInfo) - binding.errorMessageView.setTextWithLinks(errorInfo.getMessage(this)) - binding.errorView.text = formErrorText(errorInfo.stackTraces) - // print stack trace once again for debugging: errorInfo.stackTraces.forEach { Log.e(TAG, it) } - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.error_menu, menu) - return true - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - android.R.id.home -> { - onBackPressed() - true - } - - R.id.menu_item_share_error -> { - ShareUtils.shareText( - applicationContext, - getString(R.string.error_report_title), - buildJson() - ) - true - } - else -> false + val sorryMessage = getString(R.string.sorry_string) + "\n" + getString(R.string.guru_meditation) + val errorMessage = errorInfo.getMessage(this).toString() + val infoLabels = getString(R.string.info_labels) + val infoValues = buildInfoString() + val errorDetails = formErrorText(errorInfo.stackTraces) + + composeSetContent { + ErrorReportScreen( + sorryMessage = sorryMessage, + errorMessage = errorMessage, + infoLabels = infoLabels, + infoValues = infoValues, + errorDetails = errorDetails, + onBackClick = { finish() }, + onReportViaEmail = { comment -> + openPrivacyPolicyDialog(this, "EMAIL", comment) + }, + onCopyForGitHub = { comment -> + ShareUtils.copyToClipboard(this, buildMarkdown(comment)) + }, + onReportOnGitHub = { + openPrivacyPolicyDialog(this, "GITHUB") + }, + onShareError = { comment -> + ShareUtils.shareText( + applicationContext, + getString(R.string.error_report_title), + buildJson(comment) + ) + } + ) } } - private fun openPrivacyPolicyDialog(context: Context, action: String) { + private fun openPrivacyPolicyDialog( + context: Context, + action: String, + comment: String = "" + ) { AlertDialog.Builder(context) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(R.string.privacy_policy_title) @@ -148,7 +120,7 @@ class ErrorActivity : AppCompatActivity() { .setData("mailto:".toUri()) // only email apps should handle this .putExtra(Intent.EXTRA_EMAIL, arrayOf(ERROR_EMAIL_ADDRESS)) .putExtra(Intent.EXTRA_SUBJECT, errorEmailSubject) - .putExtra(Intent.EXTRA_TEXT, buildJson()) + .putExtra(Intent.EXTRA_TEXT, buildJson(comment)) ShareUtils.openIntentInApp(context, intent) } else if (action == "GITHUB") { // open the NewPipe issue page on GitHub ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL) @@ -163,24 +135,20 @@ class ErrorActivity : AppCompatActivity() { return stacktrace.joinToString(separator + "\n", separator + "\n", separator) } - private fun buildInfo(info: ErrorInfo) { - binding.errorInfoLabelsView.text = getString(R.string.info_labels) - - val text = info.userAction.message + "\n" + - info.request + "\n" + + private fun buildInfoString(): String { + return errorInfo.userAction.message + "\n" + + errorInfo.request + "\n" + contentLanguageString + "\n" + contentCountryString + "\n" + appLanguage + "\n" + - info.getServiceName() + "\n" + + errorInfo.getServiceName() + "\n" + currentTimeStamp + "\n" + packageName + "\n" + BuildConfig.VERSION_NAME + "\n" + osString - - binding.errorInfosView.text = text } - private fun buildJson(): String { + private fun buildJson(comment: String): String { try { return JsonWriter.string() .`object`() @@ -195,7 +163,7 @@ class ErrorActivity : AppCompatActivity() { .value("os", osString) .value("time", currentTimeStamp) .array("exceptions", errorInfo.stackTraces.toList()) - .value("user_comment", binding.errorCommentBox.getText().toString()) + .value("user_comment", comment) .end() .done() } catch (exception: Exception) { @@ -205,12 +173,11 @@ class ErrorActivity : AppCompatActivity() { return "" } - private fun buildMarkdown(): String { + private fun buildMarkdown(comment: String): String { try { return buildString(1024) { - val userComment = binding.errorCommentBox.text.toString() - if (userComment.isNotEmpty()) { - appendLine(userComment) + if (comment.isNotEmpty()) { + appendLine(comment) } // basic error info @@ -223,7 +190,6 @@ class ErrorActivity : AppCompatActivity() { appendLine("* __Service:__ ${errorInfo.getServiceName()}") appendLine("* __Timestamp:__ $currentTimeStamp") appendLine("* __Package:__ $packageName") - appendLine("* __Service:__ ${errorInfo.getServiceName()}") appendLine("* __Version:__ ${BuildConfig.VERSION_NAME}") appendLine("* __OS:__ $osString") @@ -260,18 +226,9 @@ class ErrorActivity : AppCompatActivity() { } } - private fun addGuruMeditation() { - // just an easter egg - var text = binding.errorSorryView.text.toString() - text += "\n" + getString(R.string.guru_meditation) - binding.errorSorryView.text = text - } - companion object { - // LOG TAGS private val TAG = ErrorActivity::class.java.toString() - // BUNDLE TAGS const val ERROR_INFO = "error_info" private const val ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org" diff --git a/app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt b/app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt new file mode 100644 index 00000000000..79cfcb84f4c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.ui + +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.runtime.Composable +import org.schabi.newpipe.ui.theme.AppTheme + +/** + * Base activity for Compose-based screens. Provides edge-to-edge display and + * wraps Compose content in [AppTheme]. + * + * Subclasses should be annotated with `@AndroidEntryPoint` if they need Hilt injection. + */ +open class BaseActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge( + navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + super.onCreate(savedInstanceState) + } + + /** + * Sets the Compose content wrapped in [AppTheme]. Call this instead of [setContent] directly. + */ + fun composeSetContent(content: @Composable () -> Unit) { + setContent { + AppTheme(content = content) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt index ff6b430d082..58da4fee0d3 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt @@ -17,6 +17,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface @@ -27,6 +29,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -47,13 +50,22 @@ fun ErrorReportScreen( onBackClick: () -> Unit, onReportViaEmail: (comment: String) -> Unit, onCopyForGitHub: (comment: String) -> Unit, - onReportOnGitHub: () -> Unit + onReportOnGitHub: () -> Unit, + onShareError: (comment: String) -> Unit = {} ) { var comment by rememberSaveable { mutableStateOf("") } ScaffoldWithToolbar( title = stringResource(R.string.error_report_title), - onBackClick = onBackClick + onBackClick = onBackClick, + actions = { + IconButton(onClick = { onShareError(comment) }) { + Icon( + painter = painterResource(R.drawable.ic_share), + contentDescription = stringResource(R.string.share) + ) + } + } ) { paddingValues -> Column( modifier = Modifier From 7360d56ad3d954308f75b3fbf8565b633fd1f1ef Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Sat, 18 Apr 2026 01:30:45 +0100 Subject: [PATCH 03/11] create separate privacy policy dialog --- .../org/schabi/newpipe/error/ErrorActivity.kt | 47 +++------- .../components/common/PrivacyPolicyDialog.kt | 94 +++++++++++++++++++ .../newpipe/ui/screens/ErrorReportScreen.kt | 24 ++++- 3 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt index 10235a1fa01..b77eb76e294 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt @@ -5,12 +5,10 @@ package org.schabi.newpipe.error -import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import android.util.Log -import androidx.appcompat.app.AlertDialog import androidx.core.content.IntentCompat import androidx.core.net.toUri import com.grack.nanojson.JsonWriter @@ -67,7 +65,7 @@ class ErrorActivity : BaseActivity() { // print stack trace once again for debugging: errorInfo.stackTraces.forEach { Log.e(TAG, it) } - val sorryMessage = getString(R.string.sorry_string) + "\n" + getString(R.string.guru_meditation) + val sorryMessage = getString(R.string.sorry_string) val errorMessage = errorInfo.getMessage(this).toString() val infoLabels = getString(R.string.info_labels) val infoValues = buildInfoString() @@ -81,14 +79,15 @@ class ErrorActivity : BaseActivity() { infoValues = infoValues, errorDetails = errorDetails, onBackClick = { finish() }, - onReportViaEmail = { comment -> - openPrivacyPolicyDialog(this, "EMAIL", comment) - }, + onReportViaEmail = { comment -> sendErrorEmail(comment) }, onCopyForGitHub = { comment -> ShareUtils.copyToClipboard(this, buildMarkdown(comment)) }, onReportOnGitHub = { - openPrivacyPolicyDialog(this, "GITHUB") + ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL) + }, + onReadPrivacyPolicy = { + ShareUtils.openUrlInApp(this, getString(R.string.privacy_policy_url)) }, onShareError = { comment -> ShareUtils.shareText( @@ -101,33 +100,13 @@ class ErrorActivity : BaseActivity() { } } - private fun openPrivacyPolicyDialog( - context: Context, - action: String, - comment: String = "" - ) { - AlertDialog.Builder(context) - .setIcon(android.R.drawable.ic_dialog_alert) - .setTitle(R.string.privacy_policy_title) - .setMessage(R.string.start_accept_privacy_policy) - .setCancelable(false) - .setNeutralButton(R.string.read_privacy_policy) { _, _ -> - ShareUtils.openUrlInApp(context, context.getString(R.string.privacy_policy_url)) - } - .setPositiveButton(R.string.accept) { _, _ -> - if (action == "EMAIL") { // send on email - val intent = Intent(Intent.ACTION_SENDTO) - .setData("mailto:".toUri()) // only email apps should handle this - .putExtra(Intent.EXTRA_EMAIL, arrayOf(ERROR_EMAIL_ADDRESS)) - .putExtra(Intent.EXTRA_SUBJECT, errorEmailSubject) - .putExtra(Intent.EXTRA_TEXT, buildJson(comment)) - ShareUtils.openIntentInApp(context, intent) - } else if (action == "GITHUB") { // open the NewPipe issue page on GitHub - ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL) - } - } - .setNegativeButton(R.string.decline, null) - .show() + private fun sendErrorEmail(comment: String) { + val intent = Intent(Intent.ACTION_SENDTO) + .setData("mailto:".toUri()) + .putExtra(Intent.EXTRA_EMAIL, arrayOf(ERROR_EMAIL_ADDRESS)) + .putExtra(Intent.EXTRA_SUBJECT, errorEmailSubject) + .putExtra(Intent.EXTRA_TEXT, buildJson(comment)) + ShareUtils.openIntentInApp(this, intent) } private fun formErrorText(stacktrace: Array): String { diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt new file mode 100644 index 00000000000..2dea9c4b4b5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.ui.components.common + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Warning +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import org.schabi.newpipe.R +import org.schabi.newpipe.ui.theme.AppTheme + +@Composable +fun PrivacyPolicyDialog( + onAccept: () -> Unit, + onDecline: () -> Unit, + onReadPrivacyPolicy: () -> Unit +) { + AlertDialog( + onDismissRequest = onDecline, + icon = { + Icon( + imageVector = Icons.Default.Warning, + contentDescription = null, + tint = MaterialTheme.colorScheme.error + ) + }, + title = { + Text( + text = stringResource(R.string.privacy_policy_title), + color = MaterialTheme.colorScheme.onSurface + ) + }, + text = { + Column { + Text( + text = stringResource(R.string.start_accept_privacy_policy), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + TextButton(onClick = onReadPrivacyPolicy) { + Text( + text = stringResource(R.string.read_privacy_policy), + color = MaterialTheme.colorScheme.primary + ) + } + } + }, + confirmButton = { + TextButton(onClick = onAccept) { + Text( + text = stringResource(R.string.accept), + color = MaterialTheme.colorScheme.primary + ) + } + }, + dismissButton = { + TextButton(onClick = onDecline) { + Text( + text = stringResource(R.string.decline), + color = MaterialTheme.colorScheme.error + ) + } + }, + containerColor = MaterialTheme.colorScheme.surface, + titleContentColor = MaterialTheme.colorScheme.onSurface, + textContentColor = MaterialTheme.colorScheme.onSurfaceVariant + ) +} + +@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun PrivacyPolicyDialogPreview() { + AppTheme { + Surface(color = MaterialTheme.colorScheme.background) { + PrivacyPolicyDialog( + onAccept = {}, + onDecline = {}, + onReadPrivacyPolicy = {} + ) + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt index 58da4fee0d3..73d0ae48f81 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt @@ -37,9 +37,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.schabi.newpipe.R +import org.schabi.newpipe.ui.components.common.PrivacyPolicyDialog import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar import org.schabi.newpipe.ui.theme.AppTheme +private const val ACTION_EMAIL = "EMAIL" +private const val ACTION_GITHUB = "GITHUB" + @Composable fun ErrorReportScreen( sorryMessage: String, @@ -51,9 +55,25 @@ fun ErrorReportScreen( onReportViaEmail: (comment: String) -> Unit, onCopyForGitHub: (comment: String) -> Unit, onReportOnGitHub: () -> Unit, + onReadPrivacyPolicy: () -> Unit = {}, onShareError: (comment: String) -> Unit = {} ) { var comment by rememberSaveable { mutableStateOf("") } + var privacyDialogAction by rememberSaveable { mutableStateOf(null) } + + privacyDialogAction?.let { action -> + PrivacyPolicyDialog( + onAccept = { + privacyDialogAction = null + when (action) { + ACTION_EMAIL -> onReportViaEmail(comment) + ACTION_GITHUB -> onReportOnGitHub() + } + }, + onDecline = { privacyDialogAction = null }, + onReadPrivacyPolicy = onReadPrivacyPolicy + ) + } ScaffoldWithToolbar( title = stringResource(R.string.error_report_title), @@ -141,7 +161,7 @@ fun ErrorReportScreen( // Report via email button Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { onReportViaEmail(comment) }, + onClick = { privacyDialogAction = ACTION_EMAIL }, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.error_report_button_text)) @@ -164,7 +184,7 @@ fun ErrorReportScreen( // Report on GitHub button Button( - onClick = onReportOnGitHub, + onClick = { privacyDialogAction = ACTION_GITHUB }, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.error_report_open_issue_button_text)) From 3a059f84e79b97fc0c9fed976c945f6769bd2a82 Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Sat, 18 Apr 2026 01:51:19 +0100 Subject: [PATCH 04/11] move base activity --- .../java/org/schabi/newpipe/{ui => }/BaseActivity.kt | 11 ++++------- .../java/org/schabi/newpipe/error/ErrorActivity.kt | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) rename app/src/main/java/org/schabi/newpipe/{ui => }/BaseActivity.kt (74%) diff --git a/app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt b/app/src/main/java/org/schabi/newpipe/BaseActivity.kt similarity index 74% rename from app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt rename to app/src/main/java/org/schabi/newpipe/BaseActivity.kt index 79cfcb84f4c..a43340cf3b8 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/BaseActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/BaseActivity.kt @@ -1,9 +1,9 @@ /* - * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. + * SPDX-FileCopyrightText: 2015-2026 NewPipe contributors * SPDX-License-Identifier: GPL-3.0-or-later */ -package org.schabi.newpipe.ui +package org.schabi.newpipe import android.graphics.Color import android.os.Build @@ -17,7 +17,7 @@ import org.schabi.newpipe.ui.theme.AppTheme /** * Base activity for Compose-based screens. Provides edge-to-edge display and - * wraps Compose content in [AppTheme]. + * wraps Compose content in [org.schabi.newpipe.ui.theme.AppTheme]. * * Subclasses should be annotated with `@AndroidEntryPoint` if they need Hilt injection. */ @@ -25,7 +25,7 @@ open class BaseActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge( - navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) + navigationBarStyle = SystemBarStyle.Companion.auto(Color.TRANSPARENT, Color.TRANSPARENT) ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false @@ -33,9 +33,6 @@ open class BaseActivity : ComponentActivity() { super.onCreate(savedInstanceState) } - /** - * Sets the Compose content wrapped in [AppTheme]. Call this instead of [setContent] directly. - */ fun composeSetContent(content: @Composable () -> Unit) { setContent { AppTheme(content = content) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt index b77eb76e294..32517736679 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt @@ -15,9 +15,9 @@ import com.grack.nanojson.JsonWriter import dagger.hilt.android.AndroidEntryPoint import java.time.ZonedDateTime import java.time.format.DateTimeFormatter +import org.schabi.newpipe.BaseActivity import org.schabi.newpipe.BuildConfig import org.schabi.newpipe.R -import org.schabi.newpipe.ui.BaseActivity import org.schabi.newpipe.ui.screens.ErrorReportScreen import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.external_communication.ShareUtils From f51408d34e5b9f41a77009d4e0f54dee582285f7 Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Sun, 19 Apr 2026 14:44:11 +0100 Subject: [PATCH 05/11] refactor error activity and tiny tweaks --- .../org/schabi/newpipe/error/ErrorActivity.kt | 30 +----- .../components/common/PrivacyPolicyDialog.kt | 27 +++--- .../newpipe/ui/screens/ErrorReportScreen.kt | 91 +++++++++++++++---- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt index 32517736679..639b4b74390 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt @@ -65,19 +65,9 @@ class ErrorActivity : BaseActivity() { // print stack trace once again for debugging: errorInfo.stackTraces.forEach { Log.e(TAG, it) } - val sorryMessage = getString(R.string.sorry_string) - val errorMessage = errorInfo.getMessage(this).toString() - val infoLabels = getString(R.string.info_labels) - val infoValues = buildInfoString() - val errorDetails = formErrorText(errorInfo.stackTraces) - composeSetContent { ErrorReportScreen( - sorryMessage = sorryMessage, - errorMessage = errorMessage, - infoLabels = infoLabels, - infoValues = infoValues, - errorDetails = errorDetails, + errorInfo = errorInfo, onBackClick = { finish() }, onReportViaEmail = { comment -> sendErrorEmail(comment) }, onCopyForGitHub = { comment -> @@ -109,24 +99,6 @@ class ErrorActivity : BaseActivity() { ShareUtils.openIntentInApp(this, intent) } - private fun formErrorText(stacktrace: Array): String { - val separator = "-------------------------------------" - return stacktrace.joinToString(separator + "\n", separator + "\n", separator) - } - - private fun buildInfoString(): String { - return errorInfo.userAction.message + "\n" + - errorInfo.request + "\n" + - contentLanguageString + "\n" + - contentCountryString + "\n" + - appLanguage + "\n" + - errorInfo.getServiceName() + "\n" + - currentTimeStamp + "\n" + - packageName + "\n" + - BuildConfig.VERSION_NAME + "\n" + - osString - } - private fun buildJson(comment: String): String { try { return JsonWriter.string() diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt index 2dea9c4b4b5..cec5d18ca25 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt @@ -38,20 +38,18 @@ fun PrivacyPolicyDialog( }, title = { Text( - text = stringResource(R.string.privacy_policy_title), - color = MaterialTheme.colorScheme.onSurface + text = stringResource(R.string.privacy_policy_title) ) }, text = { Column { Text( - text = stringResource(R.string.start_accept_privacy_policy), - color = MaterialTheme.colorScheme.onSurfaceVariant + text = stringResource(R.string.start_accept_privacy_policy) ) TextButton(onClick = onReadPrivacyPolicy) { Text( text = stringResource(R.string.read_privacy_policy), - color = MaterialTheme.colorScheme.primary + color = MaterialTheme.colorScheme.secondary ) } } @@ -59,8 +57,7 @@ fun PrivacyPolicyDialog( confirmButton = { TextButton(onClick = onAccept) { Text( - text = stringResource(R.string.accept), - color = MaterialTheme.colorScheme.primary + text = stringResource(R.string.accept) ) } }, @@ -78,17 +75,15 @@ fun PrivacyPolicyDialog( ) } -@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true) +@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) @Composable private fun PrivacyPolicyDialogPreview() { AppTheme { - Surface(color = MaterialTheme.colorScheme.background) { - PrivacyPolicyDialog( - onAccept = {}, - onDecline = {}, - onReadPrivacyPolicy = {} - ) - } + PrivacyPolicyDialog( + onAccept = {}, + onDecline = {}, + onReadPrivacyPolicy = {} + ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt index 73d0ae48f81..69a48a01113 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt @@ -5,7 +5,9 @@ package org.schabi.newpipe.ui.screens +import android.content.Context import android.content.res.Configuration +import android.os.Build import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -21,7 +23,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -29,6 +30,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily @@ -36,17 +38,47 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import org.schabi.newpipe.BuildConfig import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.ui.components.common.PrivacyPolicyDialog import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.util.Localization private const val ACTION_EMAIL = "EMAIL" private const val ACTION_GITHUB = "GITHUB" @Composable fun ErrorReportScreen( - sorryMessage: String, + errorInfo: ErrorInfo, + onBackClick: () -> Unit, + onReportViaEmail: (comment: String) -> Unit, + onCopyForGitHub: (comment: String) -> Unit, + onReportOnGitHub: () -> Unit, + onReadPrivacyPolicy: () -> Unit = {}, + onShareError: (comment: String) -> Unit = {} +) { + val context = LocalContext.current + + ErrorReportContent( + errorMessage = errorInfo.getMessage(context).toString(), + infoLabels = stringResource(R.string.info_labels), + infoValues = buildInfoString(context, errorInfo), + errorDetails = formErrorText(errorInfo.stackTraces), + onBackClick = onBackClick, + onReportViaEmail = onReportViaEmail, + onCopyForGitHub = onCopyForGitHub, + onReportOnGitHub = onReportOnGitHub, + onReadPrivacyPolicy = onReadPrivacyPolicy, + onShareError = onShareError + ) +} + +@Composable +private fun ErrorReportContent( errorMessage: String, infoLabels: String, infoValues: String, @@ -95,7 +127,7 @@ fun ErrorReportScreen( ) { // Sorry header Text( - text = sorryMessage, + text = stringResource(R.string.sorry_string), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, textAlign = TextAlign.Center, @@ -193,23 +225,46 @@ fun ErrorReportScreen( } } -@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun buildInfoString(context: Context, errorInfo: ErrorInfo): String { + val contentLanguage = Localization.getPreferredLocalization(context).localizationCode + val contentCountry = Localization.getPreferredContentCountry(context).countryCode + val appLanguage = Localization.getAppLocale().toString() + val osName = System.getProperty("os.name")!! + val osBase = Build.VERSION.BASE_OS.ifEmpty { "Android" } + val osString = "$osName $osBase ${Build.VERSION.RELEASE} - ${Build.VERSION.SDK_INT}" + val timestamp = ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + + return errorInfo.userAction.message + "\n" + + errorInfo.request + "\n" + + contentLanguage + "\n" + + contentCountry + "\n" + + appLanguage + "\n" + + errorInfo.getServiceName() + "\n" + + timestamp + "\n" + + context.packageName + "\n" + + BuildConfig.VERSION_NAME + "\n" + + osString +} + +private fun formErrorText(stackTraces: Array): String { + val separator = "-------------------------------------" + return stackTraces.joinToString(separator + "\n", separator + "\n", separator) +} + +@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true) +@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) @Composable private fun ErrorReportScreenPreview() { AppTheme { - Surface(color = MaterialTheme.colorScheme.background) { - ErrorReportScreen( - sorryMessage = "Sorry, that should not have happened.", - errorMessage = "Requested list not handled", - infoLabels = "What:\nRequest:\nContent Language:\nContent Country:\nApp Language:\nService:\nTimestamp:\nPackage:\nVersion:\nOS version:", - infoValues = "Requested list\nnone\nen\nUS\nen_US\nYouTube\n2026-04-17T12:00:00Z\norg.schabi.newpipe\n0.27.5\nAndroid 14 - 34", - errorDetails = "java.lang.IllegalArgumentException: ...\n\tat org.schabi.newpipe.SomeClass.method(SomeClass.kt:42)", - onBackClick = {}, - onReportViaEmail = {}, - onCopyForGitHub = {}, - onReportOnGitHub = {} - ) - } + ErrorReportContent( + errorMessage = "Requested list not handled", + infoLabels = "What:\nRequest:\nContent Language:\nContent Country:\nApp Language:\nService:\nTimestamp:\nPackage:\nVersion:\nOS version:", + infoValues = "Requested list\nnone\nen\nUS\nen_US\nYouTube\n2026-04-17T12:00:00Z\norg.schabi.newpipe\n0.27.5\nAndroid 14 - 34", + errorDetails = "-------------------------------------\njava.lang.IllegalArgumentException: ...\n\tat org.schabi.newpipe.SomeClass.method(SomeClass.kt:42)\n-------------------------------------", + onBackClick = {}, + onReportViaEmail = {}, + onCopyForGitHub = {}, + onReportOnGitHub = {} + ) } } From 0173233783a6709a2b89858427182de5935ba967 Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Mon, 20 Apr 2026 20:13:33 +0100 Subject: [PATCH 06/11] add new compose activity and navdisplay --- app/src/main/AndroidManifest.xml | 4 + .../org/schabi/newpipe/ComposeActivity.kt | 84 ++++++++++ .../org/schabi/newpipe/error/ErrorInfo.kt | 3 + .../schabi/newpipe/error/ErrorReportHelper.kt | 152 ++++++++++++++++++ .../schabi/newpipe/navigation/NavDisplay.kt | 74 +++++++++ .../org/schabi/newpipe/navigation/Screen.kt | 4 + 6 files changed, 321 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/ComposeActivity.kt create mode 100644 app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt create mode 100644 app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3e4f7522116..b3e62ac8ee7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,6 +80,10 @@ + + + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe + +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.core.content.IntentCompat +import androidx.navigation3.runtime.NavKey +import dagger.hilt.android.AndroidEntryPoint +import org.schabi.newpipe.error.ErrorInfo +import org.schabi.newpipe.navigation.NavDisplay +import org.schabi.newpipe.navigation.Screen +import org.schabi.newpipe.ui.theme.AppTheme + +/** + * Single host activity for all Compose-based screens. + * Other parts of the app (including legacy View-based code) launch this activity + * via Intent with extras specifying which screen to display. + */ +@AndroidEntryPoint +class ComposeActivity: ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge( + navigationBarStyle = SystemBarStyle.Companion.auto(Color.TRANSPARENT, Color.TRANSPARENT) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + super.onCreate(savedInstanceState) + + val startDestination: NavKey = resolveStartDestination(intent) + + setContent { + AppTheme { + NavDisplay(startDestination) + } + } + } + + private fun resolveStartDestination(intent: Intent): NavKey { + return when (intent.getStringExtra(EXTRA_SCREEN)) { + SCREEN_ERROR -> { + val errorInfo = IntentCompat.getParcelableExtra( + intent, EXTRA_ERROR_INFO, ErrorInfo::class.java + ) ?: throw IllegalArgumentException("ErrorInfo is required for error screen") + Screen.Error(errorInfo) + } + SCREEN_SETTINGS -> Screen.Settings.Home + else -> throw IllegalArgumentException( + "Unknown screen: ${intent.getStringExtra(EXTRA_SCREEN)}" + ) + } + } + + companion object { + const val EXTRA_SCREEN = "extra_screen" + const val EXTRA_ERROR_INFO = "extra_error_info" + + const val SCREEN_ERROR = "error" + const val SCREEN_SETTINGS = "settings" + + /** + * Creates an intent to launch the error report screen. + */ + fun errorIntent(context: Context, errorInfo: ErrorInfo): Intent { + return Intent(context, ComposeActivity::class.java).apply { + putExtra(EXTRA_SCREEN, SCREEN_ERROR) + putExtra(EXTRA_ERROR_INFO, errorInfo) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 56ca7ecbbe7..960eb79d362 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -9,6 +9,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource import com.google.android.exoplayer2.upstream.Loader import java.net.UnknownHostException import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.ServiceList @@ -35,6 +36,7 @@ import org.schabi.newpipe.util.text.getText * An error has occurred in the app. This class contains plain old parcelable data that can be used * to report the error and to show it to the user along with correct action buttons. */ +@Serializable @Parcelize class ErrorInfo private constructor( val stackTraces: Array, @@ -141,6 +143,7 @@ class ErrorInfo private constructor( } companion object { + @Serializable @Parcelize class ErrorMessage( @StringRes diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt new file mode 100644 index 00000000000..e46eddf6d6e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2015-2026 NewPipe contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.error + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import androidx.core.net.toUri +import com.grack.nanojson.JsonWriter +import org.schabi.newpipe.BuildConfig +import org.schabi.newpipe.R +import org.schabi.newpipe.util.Localization +import org.schabi.newpipe.util.external_communication.ShareUtils +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter + +/** + * Pure utility functions for building and sending error reports. + * No Activity dependency — only requires a [Context]. + */ +object ErrorReportHelper { + + private val TAG = ErrorReportHelper::class.java.simpleName + + private const val ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org" + private const val ERROR_EMAIL_SUBJECT = "Exception in " + const val ERROR_GITHUB_ISSUE_URL = "https://github.com/TeamNewPipe/NewPipe/issues" + + fun buildJson(context: Context, errorInfo: ErrorInfo, comment: String): String { + try { + return JsonWriter.string() + .`object`() + .value("user_action", errorInfo.userAction.message) + .value("request", errorInfo.request) + .value("content_language", getContentLanguage(context)) + .value("content_country", getContentCountry(context)) + .value("app_language", getAppLanguage()) + .value("service", errorInfo.getServiceName()) + .value("package", context.packageName) + .value("version", BuildConfig.VERSION_NAME) + .value("os", getOsString()) + .value("time", getCurrentTimestamp()) + .array("exceptions", errorInfo.stackTraces.toList()) + .value("user_comment", comment) + .end() + .done() + } catch (e: Exception) { + Log.e(TAG, "Could not build json", e) + } + return "" + } + + fun buildMarkdown(context: Context, errorInfo: ErrorInfo, comment: String): String { + try { + return buildString(1024) { + if (comment.isNotEmpty()) { + appendLine(comment) + } + + appendLine("## Exception") + appendLine("* __User Action:__ ${errorInfo.userAction.message}") + appendLine("* __Request:__ ${errorInfo.request}") + appendLine("* __Content Country:__ ${getContentCountry(context)}") + appendLine("* __Content Language:__ ${getContentLanguage(context)}") + appendLine("* __App Language:__ ${getAppLanguage()}") + appendLine("* __Service:__ ${errorInfo.getServiceName()}") + appendLine("* __Timestamp:__ ${getCurrentTimestamp()}") + appendLine("* __Package:__ ${context.packageName}") + appendLine("* __Version:__ ${BuildConfig.VERSION_NAME}") + appendLine("* __OS:__ ${getOsString()}") + + if (errorInfo.stackTraces.size > 1) { + append("
Exceptions (") + append(errorInfo.stackTraces.size) + append(")

\n") + } + + errorInfo.stackTraces.forEachIndexed { index, stacktrace -> + append("

Crash log ") + if (errorInfo.stackTraces.size > 1) { + append(index + 1) + } + append("

\n") + append("\n```\n$stacktrace\n```\n") + append("

\n") + } + + if (errorInfo.stackTraces.size > 1) { + append("

\n") + } + + append("
\n") + } + } catch (e: Exception) { + Log.e(TAG, "Could not build markdown", e) + return "" + } + } + + fun sendErrorEmail(context: Context, errorInfo: ErrorInfo, comment: String) { + val subject = "$ERROR_EMAIL_SUBJECT${context.getString(R.string.app_name)} ${BuildConfig.VERSION_NAME}" + val intent = Intent(Intent.ACTION_SENDTO) + .setData("mailto:".toUri()) + .putExtra(Intent.EXTRA_EMAIL, arrayOf(ERROR_EMAIL_ADDRESS)) + .putExtra(Intent.EXTRA_SUBJECT, subject) + .putExtra(Intent.EXTRA_TEXT, buildJson(context, errorInfo, comment)) + ShareUtils.openIntentInApp(context, intent) + } + + fun shareError(context: Context, errorInfo: ErrorInfo, comment: String) { + ShareUtils.shareText( + context, + context.getString(R.string.error_report_title), + buildJson(context, errorInfo, comment) + ) + } + + fun copyForGitHub(context: Context, errorInfo: ErrorInfo, comment: String) { + ShareUtils.copyToClipboard(context, buildMarkdown(context, errorInfo, comment)) + } + + fun openGitHubIssues(context: Context) { + ShareUtils.openUrlInApp(context, ERROR_GITHUB_ISSUE_URL) + } + + fun openPrivacyPolicy(context: Context) { + ShareUtils.openUrlInApp(context, context.getString(R.string.privacy_policy_url)) + } + + private fun getContentLanguage(context: Context): String = + Localization.getPreferredLocalization(context).localizationCode + + private fun getContentCountry(context: Context): String = + Localization.getPreferredContentCountry(context).countryCode + + private fun getAppLanguage(): String = + Localization.getAppLocale().toString() + + private fun getOsString(): String { + val name = System.getProperty("os.name")!! + val osBase = Build.VERSION.BASE_OS.ifEmpty { "Android" } + return "$name $osBase ${Build.VERSION.RELEASE} - ${Build.VERSION.SDK_INT}" + } + + private fun getCurrentTimestamp(): String = + ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) +} + diff --git a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt new file mode 100644 index 00000000000..87d83c7a53e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package org.schabi.newpipe.navigation + +import androidx.activity.compose.LocalActivity +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.ui.NavDisplay +import org.schabi.newpipe.error.ErrorReportHelper +import org.schabi.newpipe.ui.screens.ErrorReportScreen +import org.schabi.newpipe.ui.screens.settings.navigation.SettingsNavigation +import org.schabi.newpipe.util.external_communication.ShareUtils + +/** + * Top-level navigation display for all Compose screens in the app. + * @param startDestination the initial screen to display, resolved from the launching Intent. + */ + +@Composable +fun NavDisplay(startDestination: NavKey) { + val backstack = rememberNavBackStack(startDestination) + val context = LocalContext.current + + // TODO: Drop this logic once everything is in Compose + val activity = LocalActivity.current + + fun onNavigateUp() { + if (backstack.size == 1) activity?.finish() else backstack.removeLastOrNull() + } + + NavDisplay( + backStack = backstack, + onBack = { backstack.removeLastOrNull() }, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator() + ), + entryProvider = entryProvider { + entry { screen -> + ErrorReportScreen( + errorInfo = screen.errorInfo, + onBackClick = ::onNavigateUp, + onReportViaEmail = { comment -> + ErrorReportHelper.sendErrorEmail(context, screen.errorInfo, comment) + }, + onCopyForGitHub = { comment -> + ErrorReportHelper.copyForGitHub(context, screen.errorInfo, comment) + }, + onReportOnGitHub = { + ErrorReportHelper.openGitHubIssues(context) + }, + onReadPrivacyPolicy = { + ErrorReportHelper.openPrivacyPolicy(context) + }, + onShareError = { comment -> + ErrorReportHelper.shareError(context, screen.errorInfo, comment) + } + ) + } + + entry { + SettingsNavigation(onExitSettings = ::onNavigateUp) + } + } + ) +} diff --git a/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt b/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt index b791fb80495..4570dd430a8 100644 --- a/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt +++ b/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt @@ -7,10 +7,14 @@ package org.schabi.newpipe.navigation import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable +import org.schabi.newpipe.error.ErrorInfo @Serializable sealed interface Screen : NavKey { + @Serializable + data class Error(val errorInfo: ErrorInfo) : Screen + sealed interface Settings : Screen { @Serializable data object Home : Settings From e27d0438404e5f7304c67b75359d7d32250d24e2 Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Mon, 20 Apr 2026 21:26:48 +0100 Subject: [PATCH 07/11] refactor app to utilize new compose activity --- app/src/main/AndroidManifest.xml | 8 - .../java/org/schabi/newpipe/BaseActivity.kt | 41 ---- .../org/schabi/newpipe/ComposeActivity.kt | 25 ++- .../org/schabi/newpipe/error/ErrorActivity.kt | 190 ------------------ .../org/schabi/newpipe/error/ErrorInfo.kt | 3 - .../schabi/newpipe/error/ErrorReportHelper.kt | 17 +- .../org/schabi/newpipe/error/ErrorUtil.kt | 6 +- .../schabi/newpipe/navigation/NavDisplay.kt | 106 +++++++++- .../org/schabi/newpipe/navigation/Screen.kt | 3 +- .../newpipe/settings/SettingsV2Activity.kt | 30 --- .../settings/navigation/SettingsNavigation.kt | 63 ------ .../schabi/newpipe/util/NavigationHelper.java | 11 +- 12 files changed, 122 insertions(+), 381 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/BaseActivity.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/ui/screens/settings/navigation/SettingsNavigation.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b3e62ac8ee7..b295a895c0b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,10 +95,6 @@ android:exported="false" android:label="@string/settings" /> - - - - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package org.schabi.newpipe - -import android.graphics.Color -import android.os.Build -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import androidx.compose.runtime.Composable -import org.schabi.newpipe.ui.theme.AppTheme - -/** - * Base activity for Compose-based screens. Provides edge-to-edge display and - * wraps Compose content in [org.schabi.newpipe.ui.theme.AppTheme]. - * - * Subclasses should be annotated with `@AndroidEntryPoint` if they need Hilt injection. - */ -open class BaseActivity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - enableEdgeToEdge( - navigationBarStyle = SystemBarStyle.Companion.auto(Color.TRANSPARENT, Color.TRANSPARENT) - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - window.isNavigationBarContrastEnforced = false - } - super.onCreate(savedInstanceState) - } - - fun composeSetContent(content: @Composable () -> Unit) { - setContent { - AppTheme(content = content) - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/ComposeActivity.kt b/app/src/main/java/org/schabi/newpipe/ComposeActivity.kt index 6cc1ece69d5..d9c0bc52105 100644 --- a/app/src/main/java/org/schabi/newpipe/ComposeActivity.kt +++ b/app/src/main/java/org/schabi/newpipe/ComposeActivity.kt @@ -14,7 +14,6 @@ import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.core.content.IntentCompat import androidx.navigation3.runtime.NavKey import dagger.hilt.android.AndroidEntryPoint import org.schabi.newpipe.error.ErrorInfo @@ -28,11 +27,11 @@ import org.schabi.newpipe.ui.theme.AppTheme * via Intent with extras specifying which screen to display. */ @AndroidEntryPoint -class ComposeActivity: ComponentActivity() { +class ComposeActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge( - navigationBarStyle = SystemBarStyle.Companion.auto(Color.TRANSPARENT, Color.TRANSPARENT) + navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { window.isNavigationBarContrastEnforced = false @@ -50,13 +49,10 @@ class ComposeActivity: ComponentActivity() { private fun resolveStartDestination(intent: Intent): NavKey { return when (intent.getStringExtra(EXTRA_SCREEN)) { - SCREEN_ERROR -> { - val errorInfo = IntentCompat.getParcelableExtra( - intent, EXTRA_ERROR_INFO, ErrorInfo::class.java - ) ?: throw IllegalArgumentException("ErrorInfo is required for error screen") - Screen.Error(errorInfo) - } + SCREEN_ERROR -> Screen.Error + SCREEN_SETTINGS -> Screen.Settings.Home + else -> throw IllegalArgumentException( "Unknown screen: ${intent.getStringExtra(EXTRA_SCREEN)}" ) @@ -70,9 +66,6 @@ class ComposeActivity: ComponentActivity() { const val SCREEN_ERROR = "error" const val SCREEN_SETTINGS = "settings" - /** - * Creates an intent to launch the error report screen. - */ fun errorIntent(context: Context, errorInfo: ErrorInfo): Intent { return Intent(context, ComposeActivity::class.java).apply { putExtra(EXTRA_SCREEN, SCREEN_ERROR) @@ -80,5 +73,11 @@ class ComposeActivity: ComponentActivity() { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } } + + fun settingsIntent(context: Context): Intent { + return Intent(context, ComposeActivity::class.java).apply { + putExtra(EXTRA_SCREEN, SCREEN_SETTINGS) + } + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt deleted file mode 100644 index 639b4b74390..00000000000 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.kt +++ /dev/null @@ -1,190 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2026 NewPipe contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package org.schabi.newpipe.error - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.util.Log -import androidx.core.content.IntentCompat -import androidx.core.net.toUri -import com.grack.nanojson.JsonWriter -import dagger.hilt.android.AndroidEntryPoint -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import org.schabi.newpipe.BaseActivity -import org.schabi.newpipe.BuildConfig -import org.schabi.newpipe.R -import org.schabi.newpipe.ui.screens.ErrorReportScreen -import org.schabi.newpipe.util.Localization -import org.schabi.newpipe.util.external_communication.ShareUtils - -/** - * This activity is used to show error details and allow reporting them in various ways. - * Use [ErrorUtil.openActivity] to correctly open this activity. - */ -@AndroidEntryPoint -class ErrorActivity : BaseActivity() { - private lateinit var errorInfo: ErrorInfo - private lateinit var currentTimeStamp: String - - private val contentCountryString: String - get() = Localization.getPreferredContentCountry(this).countryCode - - private val contentLanguageString: String - get() = Localization.getPreferredLocalization(this).localizationCode - - private val appLanguage: String - get() = Localization.getAppLocale().toString() - - private val osString: String - get() { - val name = System.getProperty("os.name")!! - val osBase = Build.VERSION.BASE_OS.ifEmpty { "Android" } - return "$name $osBase ${Build.VERSION.RELEASE} - ${Build.VERSION.SDK_INT}" - } - - private val errorEmailSubject: String - get() = "$ERROR_EMAIL_SUBJECT ${getString(R.string.app_name)} ${BuildConfig.VERSION_NAME}" - - // ///////////////////////////////////////////////////////////////////// - // Activity lifecycle - // ///////////////////////////////////////////////////////////////////// - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - errorInfo = IntentCompat.getParcelableExtra(intent, ERROR_INFO, ErrorInfo::class.java)!! - - // print current time, as zoned ISO8601 timestamp - currentTimeStamp = ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - - // print stack trace once again for debugging: - errorInfo.stackTraces.forEach { Log.e(TAG, it) } - - composeSetContent { - ErrorReportScreen( - errorInfo = errorInfo, - onBackClick = { finish() }, - onReportViaEmail = { comment -> sendErrorEmail(comment) }, - onCopyForGitHub = { comment -> - ShareUtils.copyToClipboard(this, buildMarkdown(comment)) - }, - onReportOnGitHub = { - ShareUtils.openUrlInApp(this, ERROR_GITHUB_ISSUE_URL) - }, - onReadPrivacyPolicy = { - ShareUtils.openUrlInApp(this, getString(R.string.privacy_policy_url)) - }, - onShareError = { comment -> - ShareUtils.shareText( - applicationContext, - getString(R.string.error_report_title), - buildJson(comment) - ) - } - ) - } - } - - private fun sendErrorEmail(comment: String) { - val intent = Intent(Intent.ACTION_SENDTO) - .setData("mailto:".toUri()) - .putExtra(Intent.EXTRA_EMAIL, arrayOf(ERROR_EMAIL_ADDRESS)) - .putExtra(Intent.EXTRA_SUBJECT, errorEmailSubject) - .putExtra(Intent.EXTRA_TEXT, buildJson(comment)) - ShareUtils.openIntentInApp(this, intent) - } - - private fun buildJson(comment: String): String { - try { - return JsonWriter.string() - .`object`() - .value("user_action", errorInfo.userAction.message) - .value("request", errorInfo.request) - .value("content_language", contentLanguageString) - .value("content_country", contentCountryString) - .value("app_language", appLanguage) - .value("service", errorInfo.getServiceName()) - .value("package", packageName) - .value("version", BuildConfig.VERSION_NAME) - .value("os", osString) - .value("time", currentTimeStamp) - .array("exceptions", errorInfo.stackTraces.toList()) - .value("user_comment", comment) - .end() - .done() - } catch (exception: Exception) { - Log.e(TAG, "Error while erroring: Could not build json", exception) - } - - return "" - } - - private fun buildMarkdown(comment: String): String { - try { - return buildString(1024) { - if (comment.isNotEmpty()) { - appendLine(comment) - } - - // basic error info - appendLine("## Exception") - appendLine("* __User Action:__ ${errorInfo.userAction.message}") - appendLine("* __Request:__ ${errorInfo.request}") - appendLine("* __Content Country:__ $contentCountryString") - appendLine("* __Content Language:__ $contentLanguageString") - appendLine("* __App Language:__ $appLanguage") - appendLine("* __Service:__ ${errorInfo.getServiceName()}") - appendLine("* __Timestamp:__ $currentTimeStamp") - appendLine("* __Package:__ $packageName") - appendLine("* __Version:__ ${BuildConfig.VERSION_NAME}") - appendLine("* __OS:__ $osString") - - // Collapse all logs to a single paragraph when there are more than one - // to keep the GitHub issue clean. - if (errorInfo.stackTraces.size > 1) { - append("
Exceptions (") - append(errorInfo.stackTraces.size) - append(")

\n") - } - - // add the logs - errorInfo.stackTraces.forEachIndexed { index, stacktrace -> - append("

Crash log ") - if (errorInfo.stackTraces.size > 1) { - append(index + 1) - } - append("") - append("

\n") - append("\n```\n${stacktrace}\n```\n") - append("

\n") - } - - // make sure to close everything - if (errorInfo.stackTraces.size > 1) { - append("

\n") - } - - append("
\n") - } - } catch (exception: Exception) { - Log.e(TAG, "Error while erroring: Could not build markdown", exception) - return "" - } - } - - companion object { - private val TAG = ErrorActivity::class.java.toString() - - const val ERROR_INFO = "error_info" - - private const val ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org" - private const val ERROR_EMAIL_SUBJECT = "Exception in " - - private const val ERROR_GITHUB_ISSUE_URL = "https://github.com/TeamNewPipe/NewPipe/issues" - } -} diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index 960eb79d362..56ca7ecbbe7 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -9,7 +9,6 @@ import com.google.android.exoplayer2.upstream.HttpDataSource import com.google.android.exoplayer2.upstream.Loader import java.net.UnknownHostException import kotlinx.parcelize.Parcelize -import kotlinx.serialization.Serializable import org.schabi.newpipe.R import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.ServiceList @@ -36,7 +35,6 @@ import org.schabi.newpipe.util.text.getText * An error has occurred in the app. This class contains plain old parcelable data that can be used * to report the error and to show it to the user along with correct action buttons. */ -@Serializable @Parcelize class ErrorInfo private constructor( val stackTraces: Array, @@ -143,7 +141,6 @@ class ErrorInfo private constructor( } companion object { - @Serializable @Parcelize class ErrorMessage( @StringRes diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt index e46eddf6d6e..c533b06dbfa 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt @@ -11,12 +11,12 @@ import android.os.Build import android.util.Log import androidx.core.net.toUri import com.grack.nanojson.JsonWriter +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter import org.schabi.newpipe.BuildConfig import org.schabi.newpipe.R import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.external_communication.ShareUtils -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter /** * Pure utility functions for building and sending error reports. @@ -131,14 +131,11 @@ object ErrorReportHelper { ShareUtils.openUrlInApp(context, context.getString(R.string.privacy_policy_url)) } - private fun getContentLanguage(context: Context): String = - Localization.getPreferredLocalization(context).localizationCode + private fun getContentLanguage(context: Context): String = Localization.getPreferredLocalization(context).localizationCode - private fun getContentCountry(context: Context): String = - Localization.getPreferredContentCountry(context).countryCode + private fun getContentCountry(context: Context): String = Localization.getPreferredContentCountry(context).countryCode - private fun getAppLanguage(): String = - Localization.getAppLocale().toString() + private fun getAppLanguage(): String = Localization.getAppLocale().toString() private fun getOsString(): String { val name = System.getProperty("os.name")!! @@ -146,7 +143,5 @@ object ErrorReportHelper { return "$name $osBase ${Build.VERSION.RELEASE} - ${Build.VERSION.SDK_INT}" } - private fun getCurrentTimestamp(): String = - ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + private fun getCurrentTimestamp(): String = ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) } - diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index 0fa302623b5..5c56497411f 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import com.google.android.material.snackbar.Snackbar +import org.schabi.newpipe.ComposeActivity import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R @@ -148,10 +149,7 @@ class ErrorUtil { } private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent { - val intent = Intent(context, ErrorActivity::class.java) - intent.putExtra(ErrorActivity.ERROR_INFO, errorInfo) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - return intent + return ComposeActivity.errorIntent(context, errorInfo) } private fun showSnackbar(context: Context, rootView: View?, errorInfo: ErrorInfo) { diff --git a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt index 87d83c7a53e..07a863b6186 100644 --- a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt +++ b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt @@ -5,25 +5,33 @@ package org.schabi.newpipe.navigation +import android.content.Intent import androidx.activity.compose.LocalActivity +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.core.content.IntentCompat import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay +import org.schabi.newpipe.ComposeActivity +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorReportHelper import org.schabi.newpipe.ui.screens.ErrorReportScreen -import org.schabi.newpipe.ui.screens.settings.navigation.SettingsNavigation -import org.schabi.newpipe.util.external_communication.ShareUtils +import org.schabi.newpipe.ui.screens.settings.debug.DebugScreen +import org.schabi.newpipe.ui.screens.settings.home.SettingsHomeScreen /** * Top-level navigation display for all Compose screens in the app. * @param startDestination the initial screen to display, resolved from the launching Intent. */ - @Composable fun NavDisplay(startDestination: NavKey) { val backstack = rememberNavBackStack(startDestination) @@ -33,26 +41,50 @@ fun NavDisplay(startDestination: NavKey) { val activity = LocalActivity.current fun onNavigateUp() { - if (backstack.size == 1) activity?.finish() else backstack.removeLastOrNull() + if (backstack.size > 1) { + backstack.removeLastOrNull() + } else { + // If this is the last screen in the backstack and there's no parent task, + // navigate to MainActivity so the user lands on the home screen. + activity?.let { + if (it.isTaskRoot) { + it.startActivity( + Intent(it, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } + ) + } + it.finish() + } + } } NavDisplay( backStack = backstack, - onBack = { backstack.removeLastOrNull() }, + onBack = ::onNavigateUp, entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator() ), entryProvider = entryProvider { - entry { screen -> + // Error Report + entry { + val errorInfo = remember { + IntentCompat.getParcelableExtra( + activity!!.intent, + ComposeActivity.EXTRA_ERROR_INFO, + ErrorInfo::class.java + )!! + } + ErrorReportScreen( - errorInfo = screen.errorInfo, + errorInfo = errorInfo, onBackClick = ::onNavigateUp, onReportViaEmail = { comment -> - ErrorReportHelper.sendErrorEmail(context, screen.errorInfo, comment) + ErrorReportHelper.sendErrorEmail(context, errorInfo, comment) }, onCopyForGitHub = { comment -> - ErrorReportHelper.copyForGitHub(context, screen.errorInfo, comment) + ErrorReportHelper.copyForGitHub(context, errorInfo, comment) }, onReportOnGitHub = { ErrorReportHelper.openGitHubIssues(context) @@ -61,13 +93,65 @@ fun NavDisplay(startDestination: NavKey) { ErrorReportHelper.openPrivacyPolicy(context) }, onShareError = { comment -> - ErrorReportHelper.shareError(context, screen.errorInfo, comment) + ErrorReportHelper.shareError(context, errorInfo, comment) } ) } + // Settings entry { - SettingsNavigation(onExitSettings = ::onNavigateUp) + SettingsHomeScreen( + onNavigate = { screen -> backstack.add(screen) }, + onBackClick = ::onNavigateUp + ) + } + + entry { + Text(stringResource(id = R.string.settings_category_player_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_player_behavior_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_downloads_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_look_and_feel_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_history_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_content_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_feed_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_services_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_language_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_backup_restore_title)) + } + + entry { + Text(stringResource(id = R.string.settings_category_updates_title)) + } + + entry { + DebugScreen(onBackClick = { backstack.removeLastOrNull() }) } } ) diff --git a/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt b/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt index 4570dd430a8..c2fe138fc87 100644 --- a/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt +++ b/app/src/main/java/org/schabi/newpipe/navigation/Screen.kt @@ -7,13 +7,12 @@ package org.schabi.newpipe.navigation import androidx.navigation3.runtime.NavKey import kotlinx.serialization.Serializable -import org.schabi.newpipe.error.ErrorInfo @Serializable sealed interface Screen : NavKey { @Serializable - data class Error(val errorInfo: ErrorInfo) : Screen + data object Error : Screen sealed interface Settings : Screen { @Serializable diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt b/app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt deleted file mode 100644 index bcbc43ef3a3..00000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2017-2025 NewPipe contributors - * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package org.schabi.newpipe.settings - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import dagger.hilt.android.AndroidEntryPoint -import org.schabi.newpipe.ui.screens.settings.navigation.SettingsNavigation -import org.schabi.newpipe.ui.theme.AppTheme - -@AndroidEntryPoint -class SettingsV2Activity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - AppTheme { - SettingsNavigation( - onExitSettings = { finish() } - ) - } - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/settings/navigation/SettingsNavigation.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/settings/navigation/SettingsNavigation.kt deleted file mode 100644 index 421d71aea1d..00000000000 --- a/app/src/main/java/org/schabi/newpipe/ui/screens/settings/navigation/SettingsNavigation.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package org.schabi.newpipe.ui.screens.settings.navigation - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator -import androidx.navigation3.runtime.entryProvider -import androidx.navigation3.runtime.rememberNavBackStack -import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator -import androidx.navigation3.ui.NavDisplay -import org.schabi.newpipe.R -import org.schabi.newpipe.navigation.Screen -import org.schabi.newpipe.ui.screens.settings.debug.DebugScreen -import org.schabi.newpipe.ui.screens.settings.home.SettingsHomeScreen - -@Composable -fun SettingsNavigation(onExitSettings: () -> Unit) { - val backStack = rememberNavBackStack(Screen.Settings.Home) - - val handleBack: () -> Unit = { - if (backStack.size > 1) { - backStack.removeLastOrNull() - } else { - onExitSettings() - } - } - - NavDisplay( - backStack = backStack, - onBack = handleBack, - entryProvider = entryProvider { - entry { - SettingsHomeScreen( - onNavigate = { screen -> backStack.add(screen) }, - onBackClick = handleBack - ) - } - entry { Text(stringResource(id = R.string.settings_category_player_title)) } - entry { Text(stringResource(id = R.string.settings_category_player_behavior_title)) } - entry { Text(stringResource(id = R.string.settings_category_downloads_title)) } - entry { Text(stringResource(id = R.string.settings_category_look_and_feel_title)) } - entry { Text(stringResource(id = R.string.settings_category_history_title)) } - entry { Text(stringResource(id = R.string.settings_category_content_title)) } - entry { Text(stringResource(id = R.string.settings_category_feed_title)) } - entry { Text(stringResource(id = R.string.settings_category_services_title)) } - entry { Text(stringResource(id = R.string.settings_category_language_title)) } - entry { Text(stringResource(id = R.string.settings_category_backup_restore_title)) } - entry { Text(stringResource(id = R.string.settings_category_updates_title)) } - entry { - DebugScreen(onBackClick = { backStack.removeLastOrNull() }) - } - }, - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator() - ) - ) -} diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 0a7906b8d75..e612e8efd49 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -66,8 +66,8 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.ComposeActivity; import org.schabi.newpipe.settings.SettingsActivity; -import org.schabi.newpipe.settings.SettingsV2Activity; import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.List; @@ -640,12 +640,13 @@ public static void openAbout(final Context context) { } public static void openSettings(final Context context) { - final Class settingsClass = PreferenceManager.getDefaultSharedPreferences(context) + final boolean useCompose = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(Localization.compatGetString(context, - R.string.settings_layout_redesign_key), false) - ? SettingsV2Activity.class : SettingsActivity.class; + R.string.settings_layout_redesign_key), false); - final Intent intent = new Intent(context, settingsClass); + final Intent intent = useCompose + ? ComposeActivity.Companion.settingsIntent(context) + : new Intent(context, SettingsActivity.class); context.startActivity(intent); } From 0504c3202248757eccf9842cdae4552bf5309e3c Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Mon, 20 Apr 2026 21:45:14 +0100 Subject: [PATCH 08/11] clean up arguments in the ErrorReportScreen --- .../schabi/newpipe/navigation/NavDisplay.kt | 36 +++++++------ .../newpipe/ui/screens/ErrorReportScreen.kt | 51 ++++++++----------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt index 07a863b6186..655068c2701 100644 --- a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt +++ b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt @@ -24,6 +24,7 @@ import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorReportHelper +import org.schabi.newpipe.ui.screens.ErrorReportEvent import org.schabi.newpipe.ui.screens.ErrorReportScreen import org.schabi.newpipe.ui.screens.settings.debug.DebugScreen import org.schabi.newpipe.ui.screens.settings.home.SettingsHomeScreen @@ -79,21 +80,26 @@ fun NavDisplay(startDestination: NavKey) { ErrorReportScreen( errorInfo = errorInfo, - onBackClick = ::onNavigateUp, - onReportViaEmail = { comment -> - ErrorReportHelper.sendErrorEmail(context, errorInfo, comment) - }, - onCopyForGitHub = { comment -> - ErrorReportHelper.copyForGitHub(context, errorInfo, comment) - }, - onReportOnGitHub = { - ErrorReportHelper.openGitHubIssues(context) - }, - onReadPrivacyPolicy = { - ErrorReportHelper.openPrivacyPolicy(context) - }, - onShareError = { comment -> - ErrorReportHelper.shareError(context, errorInfo, comment) + onEvent = { event -> + when (event) { + is ErrorReportEvent.ReportViaEmail -> + ErrorReportHelper.sendErrorEmail(context, errorInfo, event.comment) + + is ErrorReportEvent.CopyForGitHub -> + ErrorReportHelper.copyForGitHub(context, errorInfo, event.comment) + + is ErrorReportEvent.ReportOnGitHub -> + ErrorReportHelper.openGitHubIssues(context) + + is ErrorReportEvent.ReadPrivacyPolicy -> + ErrorReportHelper.openPrivacyPolicy(context) + + is ErrorReportEvent.ShareError -> + ErrorReportHelper.shareError(context, errorInfo, event.comment) + + is ErrorReportEvent.NavigateUp -> + onNavigateUp() + } } ) } diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt index 69a48a01113..8275fd92695 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt @@ -48,18 +48,19 @@ import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.util.Localization -private const val ACTION_EMAIL = "EMAIL" -private const val ACTION_GITHUB = "GITHUB" +sealed interface ErrorReportEvent { + data class ReportViaEmail(val comment: String) : ErrorReportEvent + data class CopyForGitHub(val comment: String) : ErrorReportEvent + data object ReportOnGitHub : ErrorReportEvent + data object ReadPrivacyPolicy : ErrorReportEvent + data class ShareError(val comment: String) : ErrorReportEvent + data object NavigateUp : ErrorReportEvent +} @Composable fun ErrorReportScreen( errorInfo: ErrorInfo, - onBackClick: () -> Unit, - onReportViaEmail: (comment: String) -> Unit, - onCopyForGitHub: (comment: String) -> Unit, - onReportOnGitHub: () -> Unit, - onReadPrivacyPolicy: () -> Unit = {}, - onShareError: (comment: String) -> Unit = {} + onEvent: (ErrorReportEvent) -> Unit ) { val context = LocalContext.current @@ -68,27 +69,20 @@ fun ErrorReportScreen( infoLabels = stringResource(R.string.info_labels), infoValues = buildInfoString(context, errorInfo), errorDetails = formErrorText(errorInfo.stackTraces), - onBackClick = onBackClick, - onReportViaEmail = onReportViaEmail, - onCopyForGitHub = onCopyForGitHub, - onReportOnGitHub = onReportOnGitHub, - onReadPrivacyPolicy = onReadPrivacyPolicy, - onShareError = onShareError + onEvent = onEvent ) } +private const val ACTION_EMAIL = "EMAIL" +private const val ACTION_GITHUB = "GITHUB" + @Composable private fun ErrorReportContent( errorMessage: String, infoLabels: String, infoValues: String, errorDetails: String, - onBackClick: () -> Unit, - onReportViaEmail: (comment: String) -> Unit, - onCopyForGitHub: (comment: String) -> Unit, - onReportOnGitHub: () -> Unit, - onReadPrivacyPolicy: () -> Unit = {}, - onShareError: (comment: String) -> Unit = {} + onEvent: (ErrorReportEvent) -> Unit ) { var comment by rememberSaveable { mutableStateOf("") } var privacyDialogAction by rememberSaveable { mutableStateOf(null) } @@ -98,20 +92,20 @@ private fun ErrorReportContent( onAccept = { privacyDialogAction = null when (action) { - ACTION_EMAIL -> onReportViaEmail(comment) - ACTION_GITHUB -> onReportOnGitHub() + ACTION_EMAIL -> onEvent(ErrorReportEvent.ReportViaEmail(comment)) + ACTION_GITHUB -> onEvent(ErrorReportEvent.ReportOnGitHub) } }, onDecline = { privacyDialogAction = null }, - onReadPrivacyPolicy = onReadPrivacyPolicy + onReadPrivacyPolicy = { onEvent(ErrorReportEvent.ReadPrivacyPolicy) } ) } ScaffoldWithToolbar( title = stringResource(R.string.error_report_title), - onBackClick = onBackClick, + onBackClick = { onEvent(ErrorReportEvent.NavigateUp) }, actions = { - IconButton(onClick = { onShareError(comment) }) { + IconButton(onClick = { onEvent(ErrorReportEvent.ShareError(comment)) }) { Icon( painter = painterResource(R.drawable.ic_share), contentDescription = stringResource(R.string.share) @@ -208,7 +202,7 @@ private fun ErrorReportContent( // Copy for GitHub button Button( - onClick = { onCopyForGitHub(comment) }, + onClick = { onEvent(ErrorReportEvent.CopyForGitHub(comment)) }, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.copy_for_github)) @@ -261,10 +255,7 @@ private fun ErrorReportScreenPreview() { infoLabels = "What:\nRequest:\nContent Language:\nContent Country:\nApp Language:\nService:\nTimestamp:\nPackage:\nVersion:\nOS version:", infoValues = "Requested list\nnone\nen\nUS\nen_US\nYouTube\n2026-04-17T12:00:00Z\norg.schabi.newpipe\n0.27.5\nAndroid 14 - 34", errorDetails = "-------------------------------------\njava.lang.IllegalArgumentException: ...\n\tat org.schabi.newpipe.SomeClass.method(SomeClass.kt:42)\n-------------------------------------", - onBackClick = {}, - onReportViaEmail = {}, - onCopyForGitHub = {}, - onReportOnGitHub = {} + onEvent = {} ) } } From 93eeb198c81169ea391a30a4245686c72faa777c Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Tue, 21 Apr 2026 10:21:22 +0100 Subject: [PATCH 09/11] get rid of redundant colors --- .../newpipe/ui/components/common/PrivacyPolicyDialog.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt index cec5d18ca25..7124dfa7f1d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/PrivacyPolicyDialog.kt @@ -12,7 +12,6 @@ import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -68,10 +67,7 @@ fun PrivacyPolicyDialog( color = MaterialTheme.colorScheme.error ) } - }, - containerColor = MaterialTheme.colorScheme.surface, - titleContentColor = MaterialTheme.colorScheme.onSurface, - textContentColor = MaterialTheme.colorScheme.onSurfaceVariant + } ) } From ea3c1be3168559bca0c5cf1f29a486952a530d0b Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Tue, 21 Apr 2026 10:48:04 +0100 Subject: [PATCH 10/11] tackle review issues --- .../org/schabi/newpipe/error/ErrorReportHelper.kt | 8 ++++---- .../org/schabi/newpipe/navigation/NavDisplay.kt | 15 +-------------- .../newpipe/ui/screens/ErrorReportScreen.kt | 6 +++--- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt index c533b06dbfa..03958b97137 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorReportHelper.kt @@ -48,8 +48,8 @@ object ErrorReportHelper { .value("user_comment", comment) .end() .done() - } catch (e: Exception) { - Log.e(TAG, "Could not build json", e) + } catch (exception: Exception) { + Log.e(TAG, "Could not build json", exception) } return "" } @@ -95,8 +95,8 @@ object ErrorReportHelper { append("
\n") } - } catch (e: Exception) { - Log.e(TAG, "Could not build markdown", e) + } catch (exception: Exception) { + Log.e(TAG, "Could not build markdown", exception) return "" } } diff --git a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt index 655068c2701..94d5cdf6161 100644 --- a/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt +++ b/app/src/main/java/org/schabi/newpipe/navigation/NavDisplay.kt @@ -5,7 +5,6 @@ package org.schabi.newpipe.navigation -import android.content.Intent import androidx.activity.compose.LocalActivity import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,7 +19,6 @@ import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay import org.schabi.newpipe.ComposeActivity -import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorReportHelper @@ -45,18 +43,7 @@ fun NavDisplay(startDestination: NavKey) { if (backstack.size > 1) { backstack.removeLastOrNull() } else { - // If this is the last screen in the backstack and there's no parent task, - // navigate to MainActivity so the user lands on the home screen. - activity?.let { - if (it.isTaskRoot) { - it.startActivity( - Intent(it, MainActivity::class.java).apply { - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - } - ) - } - it.finish() - } + activity?.finish() } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt index 8275fd92695..d2920905d75 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/screens/ErrorReportScreen.kt @@ -48,6 +48,9 @@ import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar import org.schabi.newpipe.ui.theme.AppTheme import org.schabi.newpipe.util.Localization +private const val ACTION_EMAIL = "EMAIL" +private const val ACTION_GITHUB = "GITHUB" + sealed interface ErrorReportEvent { data class ReportViaEmail(val comment: String) : ErrorReportEvent data class CopyForGitHub(val comment: String) : ErrorReportEvent @@ -73,9 +76,6 @@ fun ErrorReportScreen( ) } -private const val ACTION_EMAIL = "EMAIL" -private const val ACTION_GITHUB = "GITHUB" - @Composable private fun ErrorReportContent( errorMessage: String, From 560865f256e4b06f4716fb9678f87873491f0c1b Mon Sep 17 00:00:00 2001 From: Ida Delphine Date: Tue, 21 Apr 2026 10:59:17 +0100 Subject: [PATCH 11/11] rename getErrorActivityIntent --- .../java/org/schabi/newpipe/error/ErrorUtil.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index 5c56497411f..b86cc4c3dd3 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -26,7 +26,7 @@ import org.schabi.newpipe.R * is available. * - Use a notification if the exception happens inside a background service (player, subscription * import, ...) or there is no activity/fragment from which to extract a root view. - * - Finally use the error activity only as a last resort in case the exception is critical and + * - Finally use the error screen only as a last resort in case the exception is critical and * happens in an open activity (since the workflow would be interrupted anyway in that case). */ class ErrorUtil { @@ -34,7 +34,7 @@ class ErrorUtil { private const val ERROR_REPORT_NOTIFICATION_ID = 5340681 /** - * Starts a new error activity allowing the user to report the provided error. Only use this + * Starts a new error screen allowing the user to report the provided error. Only use this * method directly as a last resort in case the exception is critical and happens in an open * activity (since the workflow would be interrupted anyway in that case). So never use this * for background services. @@ -51,12 +51,12 @@ class ErrorUtil { ) { createNotification(context, errorInfo) } else { - context.startActivity(getErrorActivityIntent(context, errorInfo)) + context.startActivity(getErrorScreenIntent(context, errorInfo)) } } /** - * Show a bottom snackbar to the user, with a report button that opens the error activity. + * Show a bottom snackbar to the user, with a report button that opens the error screen. * Use this method if the exception is not critical and it happens in a place where a root * view is available. * @@ -71,7 +71,7 @@ class ErrorUtil { } /** - * Show a bottom snackbar to the user, with a report button that opens the error activity. + * Show a bottom snackbar to the user, with a report button that opens the error screen. * Use this method if the exception is not critical and it happens in a place where a root * view is available. * @@ -105,7 +105,7 @@ class ErrorUtil { } /** - * Create an error notification. Tapping on the notification opens the error activity. Use + * Create an error notification. Tapping on the notification opens the error screen. Use * this method if the exception happens inside a background service (player, subscription * import, ...) or there is no activity/fragment from which to extract a root view. * @@ -129,7 +129,7 @@ class ErrorUtil { PendingIntentCompat.getActivity( context, 0, - getErrorActivityIntent(context, errorInfo), + getErrorScreenIntent(context, errorInfo), PendingIntent.FLAG_UPDATE_CURRENT, false ) @@ -148,7 +148,7 @@ class ErrorUtil { } } - private fun getErrorActivityIntent(context: Context, errorInfo: ErrorInfo): Intent { + private fun getErrorScreenIntent(context: Context, errorInfo: ErrorInfo): Intent { return ComposeActivity.errorIntent(context, errorInfo) } @@ -160,7 +160,7 @@ class ErrorUtil { Snackbar.make(rootView, errorInfo.getMessage(context), Snackbar.LENGTH_LONG) .setActionTextColor(Color.YELLOW) .setAction(context.getString(R.string.error_snackbar_action).uppercase()) { - context.startActivity(getErrorActivityIntent(context, errorInfo)) + context.startActivity(getErrorScreenIntent(context, errorInfo)) }.show() } }