mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-02-08 07:00:07 +00:00
Migrate about activity to Jetpack Compose
This commit is contained in:
parent
9d04a73c85
commit
05271d95a9
@ -106,7 +106,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.5.3"
|
kotlinCompilerExtensionVersion = "1.5.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,9 +230,6 @@ dependencies {
|
|||||||
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
||||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
|
|
||||||
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
|
|
||||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
|
|
||||||
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
|
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
|
||||||
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
|
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
|
||||||
implementation 'com.google.android.material:material:1.11.0'
|
implementation 'com.google.android.material:material:1.11.0'
|
||||||
@ -289,10 +286,12 @@ dependencies {
|
|||||||
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
|
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
|
||||||
|
|
||||||
// Jetpack Compose
|
// Jetpack Compose
|
||||||
implementation(platform('androidx.compose:compose-bom:2024.02.01'))
|
implementation(platform('androidx.compose:compose-bom:2024.06.00'))
|
||||||
implementation 'androidx.compose.material3:material3'
|
implementation 'androidx.compose.material3:material3:1.3.0-beta04'
|
||||||
implementation 'androidx.activity:activity-compose'
|
implementation 'androidx.activity:activity-compose'
|
||||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||||
|
implementation 'androidx.compose.ui:ui-text:1.7.0-beta05' // Needed for parsing HTML to AnnotatedString
|
||||||
|
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
|
||||||
|
|
||||||
/** Debugging **/
|
/** Debugging **/
|
||||||
// Memory leak detection
|
// Memory leak detection
|
||||||
|
@ -1,199 +1,28 @@
|
|||||||
package org.schabi.newpipe.about
|
package org.schabi.newpipe.about
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import androidx.activity.compose.setContent
|
||||||
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.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.compose.ui.res.stringResource
|
||||||
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.R
|
||||||
import org.schabi.newpipe.databinding.ActivityAboutBinding
|
import org.schabi.newpipe.compose.screen.ScaffoldWithToolbar
|
||||||
import org.schabi.newpipe.databinding.FragmentAboutBinding
|
import org.schabi.newpipe.compose.theme.AppTheme
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils
|
|
||||||
|
|
||||||
class AboutActivity : AppCompatActivity() {
|
class AboutActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Localization.assureCorrectAppLanguage(this)
|
Localization.assureCorrectAppLanguage(this)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ThemeHelper.setTheme(this)
|
|
||||||
title = getString(R.string.title_activity_about)
|
|
||||||
|
|
||||||
val aboutBinding = ActivityAboutBinding.inflate(layoutInflater)
|
setContent {
|
||||||
setContentView(aboutBinding.root)
|
AppTheme {
|
||||||
setSupportActionBar(aboutBinding.aboutToolbar)
|
ScaffoldWithToolbar(
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
title = stringResource(R.string.title_activity_about),
|
||||||
|
onBackClick = { onBackPressedDispatcher.onBackPressed() }
|
||||||
// Create the adapter that will return a fragment for each of the three
|
) { padding ->
|
||||||
// primary sections of the activity.
|
AboutScreen(padding)
|
||||||
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 -> throw IllegalArgumentException("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 -> throw IllegalArgumentException("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(
|
|
||||||
"Icepick", "2015", "Frankie Sardo",
|
|
||||||
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1
|
|
||||||
),
|
|
||||||
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
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
app/src/main/java/org/schabi/newpipe/about/AboutClasses.kt
Normal file
28
app/src/main/java/org/schabi/newpipe/about/AboutClasses.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package org.schabi.newpipe.about
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
|
||||||
|
class AboutData(
|
||||||
|
@StringRes val title: Int,
|
||||||
|
@StringRes val description: Int,
|
||||||
|
@StringRes val buttonText: Int,
|
||||||
|
@StringRes val url: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for storing information about a software license.
|
||||||
|
*/
|
||||||
|
class License(val name: String, val abbreviation: String, val filename: String) {
|
||||||
|
fun getFormattedLicense(context: Context): String {
|
||||||
|
return context.assets.open(filename).bufferedReader().use { it.readText() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SoftwareComponent(
|
||||||
|
val name: String,
|
||||||
|
val years: String,
|
||||||
|
val copyrightOwner: String,
|
||||||
|
val link: String,
|
||||||
|
val license: License
|
||||||
|
)
|
96
app/src/main/java/org/schabi/newpipe/about/AboutScreen.kt
Normal file
96
app/src/main/java/org/schabi/newpipe/about/AboutScreen.kt
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package org.schabi.newpipe.about
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.collection.intListOf
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.NonRestartableComposable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
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.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import my.nanihadesuka.compose.ColumnScrollbar
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.compose.theme.AppTheme
|
||||||
|
|
||||||
|
private val TITLES = intListOf(R.string.tab_about, R.string.tab_licenses)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
fun AboutScreen(padding: PaddingValues) {
|
||||||
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
|
var tabIndex by rememberSaveable { mutableIntStateOf(0) }
|
||||||
|
val pagerState = rememberPagerState { TITLES.size }
|
||||||
|
|
||||||
|
LaunchedEffect(tabIndex) {
|
||||||
|
pagerState.animateScrollToPage(tabIndex)
|
||||||
|
}
|
||||||
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
tabIndex = pagerState.currentPage
|
||||||
|
}
|
||||||
|
|
||||||
|
TabRow(selectedTabIndex = tabIndex) {
|
||||||
|
TITLES.forEachIndexed { index, titleId ->
|
||||||
|
Tab(
|
||||||
|
text = { Text(text = stringResource(titleId)) },
|
||||||
|
selected = tabIndex == index,
|
||||||
|
onClick = { tabIndex = index }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) { page ->
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
ColumnScrollbar(state = scrollState) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 20.dp, vertical = 10.dp)
|
||||||
|
.verticalScroll(scrollState),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
if (page == 0) {
|
||||||
|
AboutTab()
|
||||||
|
} else {
|
||||||
|
LicenseTab()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun AboutScreenPreview() {
|
||||||
|
AppTheme {
|
||||||
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
|
AboutScreen(PaddingValues(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
app/src/main/java/org/schabi/newpipe/about/AboutTab.kt
Normal file
95
app/src/main/java/org/schabi/newpipe/about/AboutTab.kt
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package org.schabi.newpipe.about
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.NonRestartableComposable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import org.schabi.newpipe.BuildConfig
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.util.external_communication.ShareUtils
|
||||||
|
|
||||||
|
private val ABOUT_ITEMS = listOf(
|
||||||
|
AboutData(R.string.faq_title, R.string.faq_description, R.string.faq, R.string.faq_url),
|
||||||
|
AboutData(
|
||||||
|
R.string.contribution_title, R.string.contribution_encouragement,
|
||||||
|
R.string.view_on_github, R.string.github_url
|
||||||
|
),
|
||||||
|
AboutData(
|
||||||
|
R.string.donation_title, R.string.donation_encouragement, R.string.give_back,
|
||||||
|
R.string.donation_url
|
||||||
|
),
|
||||||
|
AboutData(
|
||||||
|
R.string.website_title, R.string.website_encouragement, R.string.open_in_browser,
|
||||||
|
R.string.website_url
|
||||||
|
),
|
||||||
|
AboutData(
|
||||||
|
R.string.privacy_policy_title, R.string.privacy_policy_encouragement,
|
||||||
|
R.string.read_privacy_policy, R.string.privacy_policy_url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
fun AboutTab() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentSize(Alignment.Center),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.ic_launcher_foreground),
|
||||||
|
contentDescription = stringResource(R.string.app_name),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(Color.Red)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
text = stringResource(R.string.app_name)
|
||||||
|
)
|
||||||
|
Text(text = BuildConfig.VERSION_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = stringResource(R.string.app_description))
|
||||||
|
|
||||||
|
for (item in ABOUT_ITEMS) {
|
||||||
|
AboutItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
private fun AboutItem(aboutData: AboutData) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(aboutData.title),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Text(text = stringResource(aboutData.description))
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
.wrapContentWidth(Alignment.End),
|
||||||
|
onClick = { ShareUtils.openUrlInApp(context, context.getString(aboutData.url)) }
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(aboutData.buttonText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package org.schabi.newpipe.about
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class for storing information about a software license.
|
|
||||||
*/
|
|
||||||
@Parcelize
|
|
||||||
class License(val name: String, val abbreviation: String, val filename: String) : Parcelable, Serializable
|
|
@ -1,140 +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.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.Localization
|
|
||||||
import org.schabi.newpipe.util.external_communication.ShareUtils
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment containing the software licenses.
|
|
||||||
*/
|
|
||||||
class LicenseFragment : Fragment() {
|
|
||||||
private lateinit var softwareComponents: List<SoftwareComponent>
|
|
||||||
private var activeSoftwareComponent: SoftwareComponent? = null
|
|
||||||
private val compositeDisposable = CompositeDisposable()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
softwareComponents = arguments?.parcelableArrayList<SoftwareComponent>(ARG_COMPONENTS)!!
|
|
||||||
.sortedBy { it.name } // Sort components by name
|
|
||||||
activeSoftwareComponent = savedInstanceState?.getSerializable(SOFTWARE_COMPONENT_KEY) as? SoftwareComponent
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
Localization.assureCorrectAppLanguage(context)
|
|
||||||
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<SoftwareComponent>): LicenseFragment {
|
|
||||||
val fragment = LicenseFragment()
|
|
||||||
fragment.arguments = bundleOf(ARG_COMPONENTS to softwareComponents)
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package org.schabi.newpipe.about
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.schabi.newpipe.R
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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("</head>", "<style>${getLicenseStylesheet(context)}</style></head>")
|
|
||||||
} 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)
|
|
||||||
}
|
|
184
app/src/main/java/org/schabi/newpipe/about/LicenseTab.kt
Normal file
184
app/src/main/java/org/schabi/newpipe/about/LicenseTab.kt
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package org.schabi.newpipe.about
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.NonRestartableComposable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextLinkStyles
|
||||||
|
import androidx.compose.ui.text.fromHtml
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
|
||||||
|
private val SOFTWARE_COMPONENTS = listOf(
|
||||||
|
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(
|
||||||
|
"Icepick", "2015", "Frankie Sardo",
|
||||||
|
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1
|
||||||
|
),
|
||||||
|
SoftwareComponent(
|
||||||
|
"Jsoup", "2009 - 2020", "Jonathan Hedley",
|
||||||
|
"https://github.com/jhy/jsoup", StandardLicenses.MIT
|
||||||
|
),
|
||||||
|
SoftwareComponent(
|
||||||
|
"LazyColumnScrollbar", "2024", "nani",
|
||||||
|
"https://github.com/nanihadesuka/LazyColumnScrollbar", 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(
|
||||||
|
"Picasso", "2013", "Square, Inc.",
|
||||||
|
"https://square.github.io/picasso/", 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
fun LicenseTab() {
|
||||||
|
var selectedLicense by remember { mutableStateOf<SoftwareComponent?>(null) }
|
||||||
|
val onClick = remember {
|
||||||
|
{ it: SoftwareComponent -> selectedLicense = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.app_license_title),
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.app_license),
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.title_licenses),
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
)
|
||||||
|
for (component in SOFTWARE_COMPONENTS) {
|
||||||
|
LicenseItem(component, onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedLicense?.let {
|
||||||
|
var formattedLicense by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
LaunchedEffect(key1 = it) {
|
||||||
|
formattedLicense = withContext(Dispatchers.IO) {
|
||||||
|
it.license.getFormattedLicense(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { selectedLicense = null },
|
||||||
|
confirmButton = {},
|
||||||
|
title = { Text(text = it.name) },
|
||||||
|
text = {
|
||||||
|
val styles = TextLinkStyles(SpanStyle(textDecoration = TextDecoration.Underline))
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
|
text = AnnotatedString.fromHtml(formattedLicense, styles)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
private fun LicenseItem(
|
||||||
|
softwareComponent: SoftwareComponent,
|
||||||
|
onClick: (SoftwareComponent) -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick(softwareComponent) }
|
||||||
|
) {
|
||||||
|
Text(text = softwareComponent.name)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = stringResource(
|
||||||
|
R.string.copyright, softwareComponent.years,
|
||||||
|
softwareComponent.copyrightOwner, softwareComponent.license.abbreviation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
package org.schabi.newpipe.about
|
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
@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
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.ui
|
package org.schabi.newpipe.compose
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -27,8 +27,8 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.ui.theme.AppTheme
|
import org.schabi.newpipe.compose.theme.AppTheme
|
||||||
import org.schabi.newpipe.ui.theme.SizeTokens
|
import org.schabi.newpipe.compose.theme.SizeTokens
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextAction(text: String, modifier: Modifier = Modifier) {
|
fun TextAction(text: String, modifier: Modifier = Modifier) {
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.schabi.newpipe.compose.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ScaffoldWithToolbar(
|
||||||
|
title: String,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
|
content: @Composable (PaddingValues) -> Unit
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(text = title) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBackClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = actions
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.ui.theme
|
package org.schabi.newpipe.compose.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.ui.theme
|
package org.schabi.newpipe.compose.theme
|
||||||
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.schabi.newpipe.ui.theme
|
package org.schabi.newpipe.compose.theme
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
@ -1,9 +0,0 @@
|
|||||||
package org.schabi.newpipe.ktx
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Parcelable
|
|
||||||
import androidx.core.os.BundleCompat
|
|
||||||
|
|
||||||
inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? {
|
|
||||||
return BundleCompat.getParcelableArrayList(this, key, T::class.java)
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context="org.schabi.newpipe.about.AboutActivity">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="@dimen/appbar_padding_top"
|
|
||||||
android:theme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar"
|
|
||||||
app:popupTheme="@style/ThemeOverlay.AppCompat.DayNight.ActionBar">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/about_toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:background="?attr/colorPrimary"
|
|
||||||
android:theme="@style/ToolbarTheme"
|
|
||||||
app:layout_scrollFlags="scroll|enterAlways" />
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/about_tabLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:tabTextColor="@color/white"
|
|
||||||
app:tabIndicatorColor="@color/white" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/about_viewPager2"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,148 +0,0 @@
|
|||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context="org.schabi.newpipe.about.AboutActivity$AboutFragment">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:contentDescription="@string/app_name"
|
|
||||||
android:src="@mipmap/ic_launcher" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:text="@string/app_name"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Large" />
|
|
||||||
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:id="@+id/about_app_version"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium"
|
|
||||||
tools:text="0.9.9" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingBottom="5dp"
|
|
||||||
android:text="@string/app_description" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:text="@string/faq_title"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/faq_description" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/faq_link"
|
|
||||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:text="@string/faq" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:text="@string/contribution_title"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/contribution_encouragement" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/about_github_link"
|
|
||||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:text="@string/view_on_github" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:text="@string/donation_title"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/donation_encouragement" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/about_donation_link"
|
|
||||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:text="@string/give_back" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:text="@string/website_title"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/website_encouragement" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/about_website_link"
|
|
||||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:text="@string/open_in_browser" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:text="@string/privacy_policy_title"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/privacy_policy_encouragement" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/about_privacy_policy_link"
|
|
||||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:text="@string/read_privacy_policy" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
@ -1,55 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:layout_marginRight="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:text="@string/app_license_title"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Large" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:text="@string/app_license" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/licenses_app_read_license"
|
|
||||||
style="@style/Base.Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginRight="@dimen/activity_vertical_margin"
|
|
||||||
android:text="@string/read_full_license" />
|
|
||||||
|
|
||||||
<org.schabi.newpipe.views.NewPipeTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:text="@string/title_licenses"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/licenses_software_components"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical" />
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium"
|
|
||||||
tools:text="Software Name" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/copyright"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/name"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
tools:text="@string/copyright" />
|
|
||||||
</RelativeLayout>
|
|
@ -1,7 +1,7 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.9.10'
|
ext.kotlin_version = '1.9.24'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user