Skip to content

Commit 05271d9

Browse files
Migrate about activity to Jetpack Compose
1 parent 9d04a73 commit 05271d9

21 files changed

Lines changed: 467 additions & 695 deletions

app/build.gradle

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ android {
106106
}
107107

108108
composeOptions {
109-
kotlinCompilerExtensionVersion = "1.5.3"
109+
kotlinCompilerExtensionVersion = "1.5.14"
110110
}
111111
}
112112

@@ -230,9 +230,6 @@ dependencies {
230230
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
231231
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
232232
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
233-
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
234-
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
235-
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
236233
implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}"
237234
implementation "androidx.work:work-rxjava3:${androidxWorkVersion}"
238235
implementation 'com.google.android.material:material:1.11.0'
@@ -289,10 +286,12 @@ dependencies {
289286
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
290287

291288
// Jetpack Compose
292-
implementation(platform('androidx.compose:compose-bom:2024.02.01'))
293-
implementation 'androidx.compose.material3:material3'
289+
implementation(platform('androidx.compose:compose-bom:2024.06.00'))
290+
implementation 'androidx.compose.material3:material3:1.3.0-beta04'
294291
implementation 'androidx.activity:activity-compose'
295292
implementation 'androidx.compose.ui:ui-tooling-preview'
293+
implementation 'androidx.compose.ui:ui-text:1.7.0-beta05' // Needed for parsing HTML to AnnotatedString
294+
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
296295

297296
/** Debugging **/
298297
// Memory leak detection
Lines changed: 12 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -1,199 +1,28 @@
11
package org.schabi.newpipe.about
22

33
import android.os.Bundle
4-
import android.view.LayoutInflater
5-
import android.view.MenuItem
6-
import android.view.View
7-
import android.view.ViewGroup
8-
import android.widget.Button
9-
import androidx.annotation.StringRes
4+
import androidx.activity.compose.setContent
105
import androidx.appcompat.app.AppCompatActivity
11-
import androidx.fragment.app.Fragment
12-
import androidx.fragment.app.FragmentActivity
13-
import androidx.viewpager2.adapter.FragmentStateAdapter
14-
import com.google.android.material.tabs.TabLayoutMediator
15-
import org.schabi.newpipe.BuildConfig
6+
import androidx.compose.ui.res.stringResource
167
import org.schabi.newpipe.R
17-
import org.schabi.newpipe.databinding.ActivityAboutBinding
18-
import org.schabi.newpipe.databinding.FragmentAboutBinding
8+
import org.schabi.newpipe.compose.screen.ScaffoldWithToolbar
9+
import org.schabi.newpipe.compose.theme.AppTheme
1910
import org.schabi.newpipe.util.Localization
20-
import org.schabi.newpipe.util.ThemeHelper
21-
import org.schabi.newpipe.util.external_communication.ShareUtils
2211

2312
class AboutActivity : AppCompatActivity() {
24-
2513
override fun onCreate(savedInstanceState: Bundle?) {
2614
Localization.assureCorrectAppLanguage(this)
2715
super.onCreate(savedInstanceState)
28-
ThemeHelper.setTheme(this)
29-
title = getString(R.string.title_activity_about)
30-
31-
val aboutBinding = ActivityAboutBinding.inflate(layoutInflater)
32-
setContentView(aboutBinding.root)
33-
setSupportActionBar(aboutBinding.aboutToolbar)
34-
supportActionBar?.setDisplayHomeAsUpEnabled(true)
35-
36-
// Create the adapter that will return a fragment for each of the three
37-
// primary sections of the activity.
38-
val mAboutStateAdapter = AboutStateAdapter(this)
39-
// Set up the ViewPager with the sections adapter.
40-
aboutBinding.aboutViewPager2.adapter = mAboutStateAdapter
41-
TabLayoutMediator(
42-
aboutBinding.aboutTabLayout,
43-
aboutBinding.aboutViewPager2
44-
) { tab, position ->
45-
tab.setText(mAboutStateAdapter.getPageTitle(position))
46-
}.attach()
47-
}
48-
49-
override fun onOptionsItemSelected(item: MenuItem): Boolean {
50-
if (item.itemId == android.R.id.home) {
51-
finish()
52-
return true
53-
}
54-
return super.onOptionsItemSelected(item)
55-
}
56-
57-
/**
58-
* A placeholder fragment containing a simple view.
59-
*/
60-
class AboutFragment : Fragment() {
61-
private fun Button.openLink(@StringRes url: Int) {
62-
setOnClickListener {
63-
ShareUtils.openUrlInApp(context, requireContext().getString(url))
64-
}
65-
}
66-
67-
override fun onCreateView(
68-
inflater: LayoutInflater,
69-
container: ViewGroup?,
70-
savedInstanceState: Bundle?
71-
): View {
72-
FragmentAboutBinding.inflate(inflater, container, false).apply {
73-
aboutAppVersion.text = BuildConfig.VERSION_NAME
74-
aboutGithubLink.openLink(R.string.github_url)
75-
aboutDonationLink.openLink(R.string.donation_url)
76-
aboutWebsiteLink.openLink(R.string.website_url)
77-
aboutPrivacyPolicyLink.openLink(R.string.privacy_policy_url)
78-
faqLink.openLink(R.string.faq_url)
79-
return root
80-
}
81-
}
82-
}
8316

84-
/**
85-
* A [FragmentStateAdapter] that returns a fragment corresponding to
86-
* one of the sections/tabs/pages.
87-
*/
88-
private class AboutStateAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
89-
private val posAbout = 0
90-
private val posLicense = 1
91-
private val totalCount = 2
92-
93-
override fun createFragment(position: Int): Fragment {
94-
return when (position) {
95-
posAbout -> AboutFragment()
96-
posLicense -> LicenseFragment.newInstance(SOFTWARE_COMPONENTS)
97-
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
17+
setContent {
18+
AppTheme {
19+
ScaffoldWithToolbar(
20+
title = stringResource(R.string.title_activity_about),
21+
onBackClick = { onBackPressedDispatcher.onBackPressed() }
22+
) { padding ->
23+
AboutScreen(padding)
24+
}
9825
}
9926
}
100-
101-
override fun getItemCount(): Int {
102-
// Show 2 total pages.
103-
return totalCount
104-
}
105-
106-
fun getPageTitle(position: Int): Int {
107-
return when (position) {
108-
posAbout -> R.string.tab_about
109-
posLicense -> R.string.tab_licenses
110-
else -> throw IllegalArgumentException("Unknown position for ViewPager2")
111-
}
112-
}
113-
}
114-
115-
companion object {
116-
/**
117-
* List of all software components.
118-
*/
119-
private val SOFTWARE_COMPONENTS = arrayListOf(
120-
SoftwareComponent(
121-
"ACRA", "2013", "Kevin Gaudin",
122-
"https://github.com/ACRA/acra", StandardLicenses.APACHE2
123-
),
124-
SoftwareComponent(
125-
"AndroidX", "2005 - 2011", "The Android Open Source Project",
126-
"https://developer.android.com/jetpack", StandardLicenses.APACHE2
127-
),
128-
SoftwareComponent(
129-
"ExoPlayer", "2014 - 2020", "Google, Inc.",
130-
"https://github.com/google/ExoPlayer", StandardLicenses.APACHE2
131-
),
132-
SoftwareComponent(
133-
"GigaGet", "2014 - 2015", "Peter Cai",
134-
"https://github.com/PaperAirplane-Dev-Team/GigaGet", StandardLicenses.GPL3
135-
),
136-
SoftwareComponent(
137-
"Groupie", "2016", "Lisa Wray",
138-
"https://github.com/lisawray/groupie", StandardLicenses.MIT
139-
),
140-
SoftwareComponent(
141-
"Icepick", "2015", "Frankie Sardo",
142-
"https://github.com/frankiesardo/icepick", StandardLicenses.EPL1
143-
),
144-
SoftwareComponent(
145-
"Jsoup", "2009 - 2020", "Jonathan Hedley",
146-
"https://github.com/jhy/jsoup", StandardLicenses.MIT
147-
),
148-
SoftwareComponent(
149-
"Markwon", "2019", "Dimitry Ivanov",
150-
"https://github.com/noties/Markwon", StandardLicenses.APACHE2
151-
),
152-
SoftwareComponent(
153-
"Material Components for Android", "2016 - 2020", "Google, Inc.",
154-
"https://github.com/material-components/material-components-android",
155-
StandardLicenses.APACHE2
156-
),
157-
SoftwareComponent(
158-
"NewPipe Extractor", "2017 - 2020", "Christian Schabesberger",
159-
"https://github.com/TeamNewPipe/NewPipeExtractor", StandardLicenses.GPL3
160-
),
161-
SoftwareComponent(
162-
"NoNonsense-FilePicker", "2016", "Jonas Kalderstam",
163-
"https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2
164-
),
165-
SoftwareComponent(
166-
"OkHttp", "2019", "Square, Inc.",
167-
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
168-
),
169-
SoftwareComponent(
170-
"Coil", "2023", "Coil Contributors",
171-
"https://coil-kt.github.io/coil/", StandardLicenses.APACHE2
172-
),
173-
SoftwareComponent(
174-
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",
175-
"https://github.com/ocpsoft/prettytime", StandardLicenses.APACHE2
176-
),
177-
SoftwareComponent(
178-
"ProcessPhoenix", "2015", "Jake Wharton",
179-
"https://github.com/JakeWharton/ProcessPhoenix", StandardLicenses.APACHE2
180-
),
181-
SoftwareComponent(
182-
"RxAndroid", "2015", "The RxAndroid authors",
183-
"https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2
184-
),
185-
SoftwareComponent(
186-
"RxBinding", "2015", "Jake Wharton",
187-
"https://github.com/JakeWharton/RxBinding", StandardLicenses.APACHE2
188-
),
189-
SoftwareComponent(
190-
"RxJava", "2016 - 2020", "RxJava Contributors",
191-
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2
192-
),
193-
SoftwareComponent(
194-
"SearchPreference", "2018", "ByteHamster",
195-
"https://github.com/ByteHamster/SearchPreference", StandardLicenses.MIT
196-
),
197-
)
19827
}
19928
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.schabi.newpipe.about
2+
3+
import android.content.Context
4+
import androidx.annotation.StringRes
5+
6+
class AboutData(
7+
@StringRes val title: Int,
8+
@StringRes val description: Int,
9+
@StringRes val buttonText: Int,
10+
@StringRes val url: Int
11+
)
12+
13+
/**
14+
* Class for storing information about a software license.
15+
*/
16+
class License(val name: String, val abbreviation: String, val filename: String) {
17+
fun getFormattedLicense(context: Context): String {
18+
return context.assets.open(filename).bufferedReader().use { it.readText() }
19+
}
20+
}
21+
22+
class SoftwareComponent(
23+
val name: String,
24+
val years: String,
25+
val copyrightOwner: String,
26+
val link: String,
27+
val license: License
28+
)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package org.schabi.newpipe.about
2+
3+
import android.content.res.Configuration
4+
import androidx.collection.intListOf
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.pager.HorizontalPager
11+
import androidx.compose.foundation.pager.rememberPagerState
12+
import androidx.compose.foundation.rememberScrollState
13+
import androidx.compose.foundation.verticalScroll
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.Surface
16+
import androidx.compose.material3.Tab
17+
import androidx.compose.material3.TabRow
18+
import androidx.compose.material3.Text
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.runtime.NonRestartableComposable
22+
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.mutableIntStateOf
24+
import androidx.compose.runtime.saveable.rememberSaveable
25+
import androidx.compose.runtime.setValue
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import androidx.compose.ui.unit.dp
30+
import my.nanihadesuka.compose.ColumnScrollbar
31+
import org.schabi.newpipe.R
32+
import org.schabi.newpipe.compose.theme.AppTheme
33+
34+
private val TITLES = intListOf(R.string.tab_about, R.string.tab_licenses)
35+
36+
@Composable
37+
@NonRestartableComposable
38+
fun AboutScreen(padding: PaddingValues) {
39+
Column(modifier = Modifier.padding(padding)) {
40+
var tabIndex by rememberSaveable { mutableIntStateOf(0) }
41+
val pagerState = rememberPagerState { TITLES.size }
42+
43+
LaunchedEffect(tabIndex) {
44+
pagerState.animateScrollToPage(tabIndex)
45+
}
46+
LaunchedEffect(pagerState.currentPage) {
47+
tabIndex = pagerState.currentPage
48+
}
49+
50+
TabRow(selectedTabIndex = tabIndex) {
51+
TITLES.forEachIndexed { index, titleId ->
52+
Tab(
53+
text = { Text(text = stringResource(titleId)) },
54+
selected = tabIndex == index,
55+
onClick = { tabIndex = index }
56+
)
57+
}
58+
}
59+
60+
HorizontalPager(
61+
state = pagerState,
62+
modifier = Modifier
63+
.fillMaxWidth()
64+
.weight(1f)
65+
) { page ->
66+
val scrollState = rememberScrollState()
67+
68+
ColumnScrollbar(state = scrollState) {
69+
Column(
70+
modifier = Modifier
71+
.fillMaxWidth()
72+
.padding(horizontal = 20.dp, vertical = 10.dp)
73+
.verticalScroll(scrollState),
74+
verticalArrangement = Arrangement.spacedBy(8.dp)
75+
) {
76+
if (page == 0) {
77+
AboutTab()
78+
} else {
79+
LicenseTab()
80+
}
81+
}
82+
}
83+
}
84+
}
85+
}
86+
87+
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
88+
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
89+
@Composable
90+
private fun AboutScreenPreview() {
91+
AppTheme {
92+
Surface(color = MaterialTheme.colorScheme.background) {
93+
AboutScreen(PaddingValues(8.dp))
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)