From 41a1a458bbd72218ba3a1953dfe4010aa25fbdee Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Thu, 7 May 2026 12:23:49 +0800 Subject: [PATCH] Switch to compose implementation of AboutActivity Signed-off-by: Aayush Gupta --- app/build.gradle.kts | 4 +- app/src/main/AndroidManifest.xml | 5 - .../org/schabi/newpipe/about/AboutActivity.kt | 260 ------------------ .../java/org/schabi/newpipe/about/License.kt | 11 - .../schabi/newpipe/about/LicenseFragment.kt | 142 ---------- .../newpipe/about/LicenseFragmentHelper.kt | 55 ---- .../schabi/newpipe/about/SoftwareComponent.kt | 17 -- .../schabi/newpipe/about/StandardLicenses.kt | 21 -- .../schabi/newpipe/util/NavigationHelper.java | 7 +- app/src/main/res/layout/activity_about.xml | 39 --- app/src/main/res/layout/fragment_licenses.xml | 55 ---- composeApp/build.gradle.kts | 6 +- gradle/libs.versions.toml | 2 - 13 files changed, 11 insertions(+), 613 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/about/License.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/about/StandardLicenses.kt delete mode 100644 app/src/main/res/layout/activity_about.xml delete mode 100644 app/src/main/res/layout/fragment_licenses.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 390977147..609c6e0aa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -199,6 +199,9 @@ sonar { } dependencies { + // Compose UI + implementation(project(":composeApp")) + /** Desugaring **/ coreLibraryDesugaring(libs.android.desugar) @@ -228,7 +231,6 @@ dependencies { implementation(libs.androidx.room.rxjava3) ksp(libs.androidx.room.compiler) implementation(libs.androidx.swiperefreshlayout) - implementation(libs.androidx.viewpager2) implementation(libs.androidx.work.runtime) implementation(libs.androidx.work.rxjava3) implementation(libs.google.android.material) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 67a33742b..3ff502277 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -91,11 +91,6 @@ android:exported="false" android:label="@string/settings" /> - - diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt deleted file mode 100644 index ed5951f04..000000000 --- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt +++ /dev/null @@ -1,260 +0,0 @@ -package org.schabi.newpipe.about - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import androidx.annotation.StringRes -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity -import androidx.viewpager2.adapter.FragmentStateAdapter -import com.google.android.material.tabs.TabLayoutMediator -import org.schabi.newpipe.BuildConfig -import org.schabi.newpipe.R -import org.schabi.newpipe.databinding.ActivityAboutBinding -import org.schabi.newpipe.databinding.FragmentAboutBinding -import org.schabi.newpipe.util.ThemeHelper -import org.schabi.newpipe.util.external_communication.ShareUtils - -class AboutActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ThemeHelper.setTheme(this) - title = getString(R.string.title_activity_about) - - val aboutBinding = ActivityAboutBinding.inflate(layoutInflater) - setContentView(aboutBinding.root) - setSupportActionBar(aboutBinding.aboutToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - - // Create the adapter that will return a fragment for each of the three - // primary sections of the activity. - val mAboutStateAdapter = AboutStateAdapter(this) - // Set up the ViewPager with the sections adapter. - aboutBinding.aboutViewPager2.adapter = mAboutStateAdapter - TabLayoutMediator( - aboutBinding.aboutTabLayout, - aboutBinding.aboutViewPager2 - ) { tab, position -> - tab.setText(mAboutStateAdapter.getPageTitle(position)) - }.attach() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - finish() - return true - } - return super.onOptionsItemSelected(item) - } - - /** - * A placeholder fragment containing a simple view. - */ - class AboutFragment : Fragment() { - private fun Button.openLink(@StringRes url: Int) { - setOnClickListener { - ShareUtils.openUrlInApp(context, requireContext().getString(url)) - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - FragmentAboutBinding.inflate(inflater, container, false).apply { - aboutAppVersion.text = BuildConfig.VERSION_NAME - aboutGithubLink.openLink(R.string.github_url) - aboutDonationLink.openLink(R.string.donation_url) - aboutWebsiteLink.openLink(R.string.website_url) - aboutPrivacyPolicyLink.openLink(R.string.privacy_policy_url) - faqLink.openLink(R.string.faq_url) - return root - } - } - } - - /** - * A [FragmentStateAdapter] that returns a fragment corresponding to - * one of the sections/tabs/pages. - */ - private class AboutStateAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { - private val posAbout = 0 - private val posLicense = 1 - private val totalCount = 2 - - override fun createFragment(position: Int): Fragment { - return when (position) { - posAbout -> AboutFragment() - posLicense -> LicenseFragment.newInstance(SOFTWARE_COMPONENTS) - else -> error("Unknown position for ViewPager2") - } - } - - override fun getItemCount(): Int { - // Show 2 total pages. - return totalCount - } - - fun getPageTitle(position: Int): Int { - return when (position) { - posAbout -> R.string.tab_about - posLicense -> R.string.tab_licenses - else -> error("Unknown position for ViewPager2") - } - } - } - - companion object { - /** - * List of all software components. - */ - private val SOFTWARE_COMPONENTS = arrayListOf( - SoftwareComponent( - "ACRA", - "2013", - "Kevin Gaudin", - "https://github.com/ACRA/acra", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "AndroidX", - "2005 - 2011", - "The Android Open Source Project", - "https://developer.android.com/jetpack", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "ExoPlayer", - "2014 - 2020", - "Google, Inc.", - "https://github.com/google/ExoPlayer", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "GigaGet", - "2014 - 2015", - "Peter Cai", - "https://github.com/PaperAirplane-Dev-Team/GigaGet", - StandardLicenses.GPL3 - ), - SoftwareComponent( - "Groupie", - "2016", - "Lisa Wray", - "https://github.com/lisawray/groupie", - StandardLicenses.MIT - ), - SoftwareComponent( - "Android-State", - "2018", - "Evernote", - "https://github.com/Evernote/android-state", - StandardLicenses.EPL1 - ), - SoftwareComponent( - "Bridge", - "2021", - "Livefront", - "https://github.com/livefront/bridge", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "Jsoup", - "2009 - 2020", - "Jonathan Hedley", - "https://github.com/jhy/jsoup", - StandardLicenses.MIT - ), - SoftwareComponent( - "Markwon", - "2019", - "Dimitry Ivanov", - "https://github.com/noties/Markwon", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "Material Components for Android", - "2016 - 2020", - "Google, Inc.", - "https://github.com/material-components/material-components-android", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "NewPipe Extractor", - "2017 - 2020", - "Christian Schabesberger", - "https://github.com/TeamNewPipe/NewPipeExtractor", - StandardLicenses.GPL3 - ), - SoftwareComponent( - "NoNonsense-FilePicker", - "2016", - "Jonas Kalderstam", - "https://github.com/spacecowboy/NoNonsense-FilePicker", - StandardLicenses.MPL2 - ), - SoftwareComponent( - "OkHttp", - "2019", - "Square, Inc.", - "https://square.github.io/okhttp/", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "Coil", - "2023", - "Coil Contributors", - "https://coil-kt.github.io/coil/", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "PrettyTime", - "2012 - 2020", - "Lincoln Baxter, III", - "https://github.com/ocpsoft/prettytime", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "ProcessPhoenix", - "2015", - "Jake Wharton", - "https://github.com/JakeWharton/ProcessPhoenix", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "RxAndroid", - "2015", - "The RxAndroid authors", - "https://github.com/ReactiveX/RxAndroid", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "RxBinding", - "2015", - "Jake Wharton", - "https://github.com/JakeWharton/RxBinding", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "RxJava", - "2016 - 2020", - "RxJava Contributors", - "https://github.com/ReactiveX/RxJava", - StandardLicenses.APACHE2 - ), - SoftwareComponent( - "SearchPreference", - "2018", - "ByteHamster", - "https://github.com/ByteHamster/SearchPreference", - StandardLicenses.MIT - ) - ) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/about/License.kt b/app/src/main/java/org/schabi/newpipe/about/License.kt deleted file mode 100644 index fc50c646d..000000000 --- a/app/src/main/java/org/schabi/newpipe/about/License.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.schabi.newpipe.about - -import android.os.Parcelable -import java.io.Serializable -import kotlinx.parcelize.Parcelize - -/** - * Class for storing information about a software license. - */ -@Parcelize -class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt deleted file mode 100644 index bd0632c13..000000000 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt +++ /dev/null @@ -1,142 +0,0 @@ -package org.schabi.newpipe.about - -import android.os.Bundle -import android.util.Base64 -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.WebView -import androidx.appcompat.app.AlertDialog -import androidx.core.os.BundleCompat -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.schedulers.Schedulers -import org.schabi.newpipe.BuildConfig -import org.schabi.newpipe.R -import org.schabi.newpipe.databinding.FragmentLicensesBinding -import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding -import org.schabi.newpipe.ktx.parcelableArrayList -import org.schabi.newpipe.util.external_communication.ShareUtils - -/** - * Fragment containing the software licenses. - */ -class LicenseFragment : Fragment() { - private lateinit var softwareComponents: List - private var activeSoftwareComponent: SoftwareComponent? = null - private val compositeDisposable = CompositeDisposable() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - softwareComponents = arguments?.parcelableArrayList(ARG_COMPONENTS)!! - .sortedBy { it.name } // Sort components by name - activeSoftwareComponent = savedInstanceState?.let { - BundleCompat.getSerializable(it, SOFTWARE_COMPONENT_KEY, SoftwareComponent::class.java) - } - } - - override fun onDestroy() { - compositeDisposable.dispose() - super.onDestroy() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val binding = FragmentLicensesBinding.inflate(inflater, container, false) - binding.licensesAppReadLicense.setOnClickListener { - compositeDisposable.add( - showLicense(NEWPIPE_SOFTWARE_COMPONENT) - ) - } - for (component in softwareComponents) { - val componentBinding = ItemSoftwareComponentBinding - .inflate(inflater, container, false) - componentBinding.name.text = component.name - componentBinding.copyright.text = getString( - R.string.copyright, - component.years, - component.copyrightOwner, - component.license.abbreviation - ) - val root: View = componentBinding.root - root.tag = component - root.setOnClickListener { - compositeDisposable.add( - showLicense(component) - ) - } - binding.licensesSoftwareComponents.addView(root) - registerForContextMenu(root) - } - activeSoftwareComponent?.let { compositeDisposable.add(showLicense(it)) } - return binding.root - } - - override fun onSaveInstanceState(savedInstanceState: Bundle) { - super.onSaveInstanceState(savedInstanceState) - activeSoftwareComponent?.let { savedInstanceState.putSerializable(SOFTWARE_COMPONENT_KEY, it) } - } - - private fun showLicense( - softwareComponent: SoftwareComponent - ): Disposable { - return if (context == null) { - Disposable.empty() - } else { - val context = requireContext() - activeSoftwareComponent = softwareComponent - Observable.fromCallable { getFormattedLicense(context, softwareComponent.license) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString( - formattedLicense.toByteArray(), - Base64.NO_PADDING - ) - val webView = WebView(context) - webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - - val builder = AlertDialog.Builder(requireContext()) - .setTitle(softwareComponent.name) - .setView(webView) - .setOnCancelListener { activeSoftwareComponent = null } - .setOnDismissListener { activeSoftwareComponent = null } - .setPositiveButton(R.string.done) { dialog, _ -> dialog.dismiss() } - - if (softwareComponent != NEWPIPE_SOFTWARE_COMPONENT) { - builder.setNeutralButton(R.string.open_website_license) { _, _ -> - ShareUtils.openUrlInApp(requireContext(), softwareComponent.link) - } - } - - builder.show() - } - } - } - - companion object { - private const val ARG_COMPONENTS = "components" - private const val SOFTWARE_COMPONENT_KEY = "ACTIVE_SOFTWARE_COMPONENT" - private val NEWPIPE_SOFTWARE_COMPONENT = SoftwareComponent( - "NewPipe", - "2014-2023", - "Team NewPipe", - "https://newpipe.net/", - StandardLicenses.GPL3, - BuildConfig.VERSION_NAME - ) - - fun newInstance(softwareComponents: ArrayList): LicenseFragment { - val fragment = LicenseFragment() - fragment.arguments = bundleOf(ARG_COMPONENTS to softwareComponents) - return fragment - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt deleted file mode 100644 index 32e4f812f..000000000 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.schabi.newpipe.about - -import android.content.Context -import java.io.IOException -import org.schabi.newpipe.R -import org.schabi.newpipe.util.ThemeHelper - -/** - * @param context the context to use - * @param license the license - * @return String which contains a HTML formatted license page - * styled according to the context's theme - */ -fun getFormattedLicense(context: Context, license: License): String { - try { - return context.assets.open(license.filename).bufferedReader().use { it.readText() } - // split the HTML file and insert the stylesheet into the HEAD of the file - .replace("", "") - } catch (e: IOException) { - throw IllegalArgumentException("Could not get license file: ${license.filename}", e) - } -} - -/** - * @param context the Android context - * @return String which is a CSS stylesheet according to the context's theme - */ -fun getLicenseStylesheet(context: Context): String { - val isLightTheme = ThemeHelper.isLightThemeSelected(context) - val licenseBackgroundColor = getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color - ) - val licenseTextColor = getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color - ) - val youtubePrimaryColor = getHexRGBColor( - context, - if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color - ) - return "body{padding:12px 15px;margin:0;background:#$licenseBackgroundColor;color:#$licenseTextColor}" + - "a[href]{color:#$youtubePrimaryColor}pre{white-space:pre-wrap}" -} - -/** - * Cast R.color to a hexadecimal color value. - * - * @param context the context to use - * @param color the color number from R.color - * @return a six characters long String with hexadecimal RGB values - */ -fun getHexRGBColor(context: Context, color: Int): String { - return context.getString(color).substring(3) -} diff --git a/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt b/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt deleted file mode 100644 index a43ddfd5e..000000000 --- a/app/src/main/java/org/schabi/newpipe/about/SoftwareComponent.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.schabi.newpipe.about - -import android.os.Parcelable -import java.io.Serializable -import kotlinx.parcelize.Parcelize - -@Parcelize -class SoftwareComponent -@JvmOverloads -constructor( - val name: String, - val years: String, - val copyrightOwner: String, - val link: String, - val license: License, - val version: String? = null -) : Parcelable, Serializable diff --git a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.kt b/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.kt deleted file mode 100644 index c5b9618fe..000000000 --- a/app/src/main/java/org/schabi/newpipe/about/StandardLicenses.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.schabi.newpipe.about - -/** - * Class containing information about standard software licenses. - */ -object StandardLicenses { - @JvmField - val GPL3 = License("GNU General Public License, Version 3.0", "GPLv3", "gpl_3.html") - - @JvmField - val APACHE2 = License("Apache License, Version 2.0", "ALv2", "apache2.html") - - @JvmField - val MPL2 = License("Mozilla Public License, Version 2.0", "MPL 2.0", "mpl2.html") - - @JvmField - val MIT = License("MIT License", "MIT", "mit.html") - - @JvmField - val EPL1 = License("Eclipse Public License, Version 1.0", "EPL 1.0", "epl1.html") -} 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 9632b9bee..59cd71d02 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -25,11 +25,13 @@ import androidx.fragment.app.FragmentTransaction; import com.jakewharton.processphoenix.ProcessPhoenix; +import net.newpipe.app.extensions.ContextKt; +import net.newpipe.app.navigation.Screen; + import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.RouterActivity; -import org.schabi.newpipe.about.AboutActivity; import org.schabi.newpipe.database.feed.model.FeedGroupEntity; import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.error.ErrorUtil; @@ -682,8 +684,7 @@ public final class NavigationHelper { } public static void openAbout(final Context context) { - final Intent intent = new Intent(context, AboutActivity.class); - context.startActivity(intent); + ContextKt.navigateTo(context, Screen.About.INSTANCE); } public static void openSettings(final Context context) { diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml deleted file mode 100644 index 661c4affc..000000000 --- a/app/src/main/res/layout/activity_about.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_licenses.xml b/app/src/main/res/layout/fragment_licenses.xml deleted file mode 100644 index c46d888ba..000000000 --- a/app/src/main/res/layout/fragment_licenses.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - -