mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	Merge branch 'refactor' into pr11282
This commit is contained in:
		
							
								
								
									
										210
									
								
								app/build.gradle
									
									
									
									
									
								
							
							
						
						
									
										210
									
								
								app/build.gradle
									
									
									
									
									
								
							| @@ -4,15 +4,15 @@ import com.android.tools.profgen.DexFile | ||||
| import com.mikepenz.aboutlibraries.plugin.DuplicateMode | ||||
|  | ||||
| plugins { | ||||
|     id "com.android.application" | ||||
|     id "kotlin-android" | ||||
|     id "kotlin-kapt" | ||||
|     id "kotlin-parcelize" | ||||
|     id "checkstyle" | ||||
|     id "org.sonarqube" version "4.0.0.2929" | ||||
|     id "org.jetbrains.kotlin.plugin.compose" version "${kotlin_version}" | ||||
|     id 'com.google.dagger.hilt.android' | ||||
|     id 'com.mikepenz.aboutlibraries.plugin' | ||||
|     alias libs.plugins.android.application | ||||
|     alias libs.plugins.kotlin.android | ||||
|     alias libs.plugins.kotlin.compose | ||||
|     alias libs.plugins.kotlin.kapt | ||||
|     alias libs.plugins.kotlin.parcelize | ||||
|     alias libs.plugins.checkstyle | ||||
|     alias libs.plugins.sonarqube | ||||
|     alias libs.plugins.hilt | ||||
|     alias libs.plugins.aboutlibraries | ||||
| } | ||||
|  | ||||
| android { | ||||
| @@ -111,25 +111,6 @@ android { | ||||
|     } | ||||
| } | ||||
|  | ||||
| ext { | ||||
|     checkstyleVersion = '10.12.1' | ||||
|  | ||||
|     androidxLifecycleVersion = '2.6.2' | ||||
|     androidxRoomVersion = '2.6.1' | ||||
|     androidxWorkVersion = '2.8.1' | ||||
|  | ||||
|     stateSaverVersion = '1.4.1' | ||||
|     exoPlayerVersion = '2.18.7' | ||||
|     googleAutoServiceVersion = '1.1.1' | ||||
|     groupieVersion = '2.10.1' | ||||
|     markwonVersion = '4.6.2' | ||||
|  | ||||
|     leakCanaryVersion = '2.12' | ||||
|     stethoVersion = '1.6.0' | ||||
|  | ||||
|     coilVersion = '3.0.3' | ||||
| } | ||||
|  | ||||
| configurations { | ||||
|     checkstyle | ||||
|     ktlint | ||||
| @@ -139,7 +120,7 @@ checkstyle { | ||||
|     getConfigDirectory().set(rootProject.file("checkstyle")) | ||||
|     ignoreFailures false | ||||
|     showViolations true | ||||
|     toolVersion = checkstyleVersion | ||||
|     toolVersion = libs.versions.checkstyle.get() | ||||
| } | ||||
|  | ||||
| tasks.register('runCheckstyle', Checkstyle) { | ||||
| @@ -181,11 +162,13 @@ tasks.register('formatKtlint', JavaExec) { | ||||
|     jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") | ||||
| } | ||||
|  | ||||
| apply from: 'check-dependencies.gradle' | ||||
|  | ||||
| afterEvaluate { | ||||
|     if (!System.properties.containsKey('skipFormatKtlint')) { | ||||
|         preDebugBuild.dependsOn formatKtlint | ||||
|     } | ||||
|     preDebugBuild.dependsOn runCheckstyle, runKtlint | ||||
|     preDebugBuild.dependsOn runCheckstyle, runKtlint, checkDependenciesOrder | ||||
| } | ||||
|  | ||||
| sonar { | ||||
| @@ -209,146 +192,143 @@ aboutLibraries { | ||||
|  | ||||
| dependencies { | ||||
| /** Desugaring **/ | ||||
|     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4' | ||||
|     coreLibraryDesugaring libs.desugar.jdk.libs.nio | ||||
|  | ||||
| /** NewPipe libraries **/ | ||||
|     // You can use a local version by uncommenting a few lines in settings.gradle | ||||
|     // Or you can use a commit you pushed to GitHub by just replacing TeamNewPipe with your GitHub | ||||
|     // name and the commit hash with the commit hash of the (pushed) commit you want to test | ||||
|     // This works thanks to JitPack: https://jitpack.io/ | ||||
|     implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' | ||||
|     // WORKAROUND: v0.24.2 can't be resolved by jitpack -> use git commit hash instead | ||||
|     implementation 'com.github.TeamNewPipe:NewPipeExtractor:d3d5f2b3f03a5f2b479b9f6fdf1c2555cbb9de0e' | ||||
|     implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' | ||||
|     implementation libs.teamnewpipe.nanojson | ||||
|     implementation libs.teamnewpipe.newpipe.extractor | ||||
|     implementation libs.teamnewpipe.nononsense.filepicker | ||||
|  | ||||
| /** Checkstyle **/ | ||||
|     checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" | ||||
|     ktlint 'com.pinterest:ktlint:0.45.2' | ||||
|     checkstyle libs.tools.checkstyle | ||||
|     ktlint libs.tools.ktlint | ||||
|  | ||||
| /** Kotlin **/ | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" | ||||
|     implementation libs.kotlin.stdlib | ||||
|  | ||||
| /** AndroidX **/ | ||||
|     implementation 'androidx.appcompat:appcompat:1.6.1' | ||||
|     implementation 'androidx.cardview:cardview:1.0.0' | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.4' | ||||
|     implementation 'androidx.core:core-ktx:1.12.0' | ||||
|     implementation 'androidx.documentfile:documentfile:1.0.1' | ||||
|     implementation 'androidx.fragment:fragment-compose:1.8.2' | ||||
|     implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}" | ||||
|     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}" | ||||
|     implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' | ||||
|     implementation 'androidx.media:media:1.7.0' | ||||
|     implementation 'androidx.preference:preference:1.2.1' | ||||
|     implementation 'androidx.recyclerview:recyclerview:1.3.2' | ||||
|     implementation "androidx.room:room-runtime:${androidxRoomVersion}" | ||||
|     implementation "androidx.room:room-rxjava3:${androidxRoomVersion}" | ||||
|     kapt "androidx.room:room-compiler:${androidxRoomVersion}" | ||||
|     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' | ||||
|     implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" | ||||
|     implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" | ||||
|     implementation 'com.google.android.material:material:1.11.0' | ||||
|     implementation libs.androidx.appcompat | ||||
|     implementation libs.androidx.cardview | ||||
|     implementation libs.androidx.constraintlayout | ||||
|     implementation libs.androidx.core.ktx | ||||
|     implementation libs.androidx.documentfile | ||||
|     implementation libs.androidx.fragment.compose | ||||
|     implementation libs.androidx.lifecycle.livedata | ||||
|     implementation libs.androidx.lifecycle.viewmodel | ||||
|     implementation libs.androidx.localbroadcastmanager | ||||
|     implementation libs.androidx.media | ||||
|     implementation libs.androidx.preference | ||||
|     implementation libs.androidx.recyclerview | ||||
|     implementation libs.androidx.room.runtime | ||||
|     implementation libs.androidx.room.rxjava3 | ||||
|     kapt libs.androidx.room.compiler | ||||
|     implementation libs.androidx.swiperefreshlayout | ||||
|     // Newer version specified to prevent accessibility regressions with RecyclerView, see: | ||||
|     // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 | ||||
|     implementation libs.androidx.viewpager2 | ||||
|     implementation libs.androidx.work.runtime | ||||
|     implementation libs.androidx.work.rxjava3 | ||||
|     implementation libs.androidx.material | ||||
|  | ||||
| /** Third-party libraries **/ | ||||
|     // Instance state boilerplate elimination | ||||
|     implementation 'com.github.livefront:bridge:v2.0.2' | ||||
|     implementation "com.evernote:android-state:$stateSaverVersion" | ||||
|     kapt "com.evernote:android-state-processor:$stateSaverVersion" | ||||
|     implementation libs.livefront.bridge | ||||
|     implementation libs.android.state | ||||
|     kapt libs.android.state.processor | ||||
|  | ||||
|     // HTML parser | ||||
|     implementation "org.jsoup:jsoup:1.17.2" | ||||
|     implementation libs.jsoup | ||||
|  | ||||
|     // HTTP client | ||||
|     implementation "com.squareup.okhttp3:okhttp:4.12.0" | ||||
|     implementation libs.okhttp | ||||
|  | ||||
|     // Media player | ||||
|     implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-dash:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-database:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-datasource:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-hls:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-ui:${exoPlayerVersion}" | ||||
|     implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}" | ||||
|     implementation libs.exoplayer.core | ||||
|     implementation libs.exoplayer.dash | ||||
|     implementation libs.exoplayer.database | ||||
|     implementation libs.exoplayer.datasource | ||||
|     implementation libs.exoplayer.hls | ||||
|     implementation libs.exoplayer.smoothstreaming | ||||
|     implementation libs.exoplayer.ui | ||||
|     implementation libs.extension.mediasession | ||||
|  | ||||
|     // Metadata generator for service descriptors | ||||
|     compileOnly "com.google.auto.service:auto-service-annotations:${googleAutoServiceVersion}" | ||||
|     kapt "com.google.auto.service:auto-service:${googleAutoServiceVersion}" | ||||
|     compileOnly libs.auto.service | ||||
|     kapt libs.auto.service.kapt | ||||
|  | ||||
|     // Manager for complex RecyclerView layouts | ||||
|     implementation "com.github.lisawray.groupie:groupie:${groupieVersion}" | ||||
|     implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}" | ||||
|     implementation libs.lisawray.groupie | ||||
|     implementation libs.lisawray.groupie.viewbinding | ||||
|  | ||||
|     // Image loading | ||||
|     implementation "io.coil-kt.coil3:coil-compose:${coilVersion}" | ||||
|     implementation "io.coil-kt.coil3:coil-network-okhttp:${coilVersion}" | ||||
|     implementation libs.coil.compose | ||||
|  | ||||
|     // Markdown library for Android | ||||
|     implementation "io.noties.markwon:core:${markwonVersion}" | ||||
|     implementation "io.noties.markwon:linkify:${markwonVersion}" | ||||
|     implementation libs.markwon.core | ||||
|     implementation libs.markwon.linkify | ||||
|  | ||||
|     // Crash reporting | ||||
|     implementation "ch.acra:acra-core:5.11.3" | ||||
|     implementation libs.acra.core | ||||
|  | ||||
|     // Properly restarting | ||||
|     implementation 'com.jakewharton:process-phoenix:2.1.2' | ||||
|     implementation libs.process.phoenix | ||||
|  | ||||
|     // Reactive extensions for Java VM | ||||
|     implementation "io.reactivex.rxjava3:rxjava:3.1.8" | ||||
|     implementation "io.reactivex.rxjava3:rxandroid:3.0.2" | ||||
|     implementation libs.rxjava3.rxjava | ||||
|     implementation libs.rxjava3.rxandroid | ||||
|     // RxJava binding APIs for Android UI widgets | ||||
|     implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" | ||||
|     implementation libs.rxbinding4.rxbinding | ||||
|  | ||||
|     // Date and time formatting | ||||
|     implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final" | ||||
|     implementation libs.prettytime | ||||
|  | ||||
|     // Jetpack Compose | ||||
|     implementation(platform('androidx.compose:compose-bom:2024.11.00')) | ||||
|     implementation 'androidx.compose.material3:material3' | ||||
|     implementation 'androidx.compose.material3.adaptive:adaptive' | ||||
|     implementation 'androidx.activity:activity-compose' | ||||
|     implementation 'androidx.compose.ui:ui-tooling-preview' | ||||
|     implementation 'androidx.lifecycle:lifecycle-viewmodel-compose' | ||||
|     implementation 'androidx.compose.ui:ui-text' // Needed for parsing HTML to AnnotatedString | ||||
|     implementation 'androidx.compose.material:material-icons-extended' | ||||
|     implementation(platform(libs.androidx.compose.bom)) | ||||
|     implementation libs.androidx.compose.material3 | ||||
|     implementation libs.androidx.compose.adaptive | ||||
|     implementation libs.androidx.activity.compose | ||||
|     implementation libs.androidx.compose.ui.tooling.preview | ||||
|     implementation libs.androidx.lifecycle.viewmodel.compose | ||||
|     implementation libs.androidx.compose.ui.text // Needed for parsing HTML to AnnotatedString | ||||
|     implementation libs.androidx.compose.material.icons.extended | ||||
|  | ||||
|     // Jetpack Compose related dependencies | ||||
|     implementation 'androidx.paging:paging-compose:3.3.2' | ||||
|     implementation "androidx.navigation:navigation-compose:2.8.3" | ||||
|     implementation libs.androidx.paging.compose | ||||
|     implementation libs.androidx.navigation.compose | ||||
|  | ||||
|     // Coroutines interop | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.8.1' | ||||
|     implementation libs.kotlinx.coroutines.rx3 | ||||
|  | ||||
|     // Library loading for About screen | ||||
|     implementation "com.mikepenz:aboutlibraries-compose-m3:$about_libs" | ||||
|     implementation libs.aboutlibraries.compose.m3 | ||||
|  | ||||
|     // Hilt | ||||
|     implementation("com.google.dagger:hilt-android:2.51.1") | ||||
|     kapt("com.google.dagger:hilt-compiler:2.51.1") | ||||
|     implementation libs.hilt.android | ||||
|     kapt(libs.hilt.compiler) | ||||
|  | ||||
|     // Scroll | ||||
|     implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0' | ||||
|     implementation libs.lazycolumnscrollbar | ||||
|  | ||||
| /** Debugging **/ | ||||
|     // Memory leak detection | ||||
|     debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}" | ||||
|     debugImplementation "com.squareup.leakcanary:plumber-android:${leakCanaryVersion}" | ||||
|     debugImplementation "com.squareup.leakcanary:leakcanary-android-core:${leakCanaryVersion}" | ||||
|     debugImplementation libs.leakcanary.object.watcher | ||||
|     debugImplementation libs.leakcanary.plumber.android | ||||
|     debugImplementation libs.leakcanary.android.core | ||||
|     // Debug bridge for Android | ||||
|     debugImplementation "com.facebook.stetho:stetho:${stethoVersion}" | ||||
|     debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}" | ||||
|     debugImplementation libs.stetho | ||||
|     debugImplementation libs.stetho.okhttp3 | ||||
|  | ||||
|     // Jetpack Compose | ||||
|     debugImplementation 'androidx.compose.ui:ui-tooling' | ||||
|     debugImplementation libs.androidx.compose.ui.tooling | ||||
|  | ||||
| /** Testing **/ | ||||
|     testImplementation 'junit:junit:4.13.2' | ||||
|     testImplementation 'org.mockito:mockito-core:5.6.0' | ||||
|     testImplementation libs.junit | ||||
|     testImplementation libs.mockito.core | ||||
|  | ||||
|     androidTestImplementation "androidx.test.ext:junit:1.1.5" | ||||
|     androidTestImplementation "androidx.test:runner:1.5.2" | ||||
|     androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}" | ||||
|     androidTestImplementation "org.assertj:assertj-core:3.24.2" | ||||
|     androidTestImplementation libs.androidx.junit | ||||
|     androidTestImplementation libs.androidx.runner | ||||
|     androidTestImplementation libs.androidx.room.testing | ||||
|     androidTestImplementation libs.assertj.core | ||||
| } | ||||
|  | ||||
| static String getGitWorkingBranch() { | ||||
|   | ||||
							
								
								
									
										48
									
								
								app/check-dependencies.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								app/check-dependencies.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| tasks.register('checkDependenciesOrder') { | ||||
|     group = 'verification' | ||||
|     description = 'Checks that each section in libs.versions.toml is sorted alphabetically' | ||||
|  | ||||
|     def tomlFile = file('../gradle/libs.versions.toml') | ||||
|  | ||||
|     doLast { | ||||
|         if (!tomlFile.exists()) { | ||||
|             throw new GradleException('TOML file not found') | ||||
|         } | ||||
|  | ||||
|         def lines = tomlFile.readLines() | ||||
|         def nonSortedBlocks = [] | ||||
|         def currentBlock = [] | ||||
|         def prevLine = '' | ||||
|         def prevIndex = 0 | ||||
|  | ||||
|         lines.eachWithIndex { line, lineIndex -> | ||||
|             if (line.trim() && !line.startsWith('#')) { | ||||
|                 if (line.startsWith('[')) { | ||||
|                     prevLine = '' | ||||
|                 } else { | ||||
|                     def currIndex = lineIndex + 1 | ||||
|                     if (prevLine > line) { | ||||
|                         if (currentBlock && currentBlock[-1] == "${prevIndex}: ${prevLine}") { | ||||
|                             currentBlock.add("${currIndex}: ${line}") | ||||
|                         } else { | ||||
|                             if (!currentBlock.isEmpty()) { | ||||
|                                 nonSortedBlocks.add(currentBlock) | ||||
|                                 currentBlock = [] | ||||
|                             } | ||||
|                             currentBlock.add("${prevIndex}: ${prevLine}") | ||||
|                             currentBlock.add("${currIndex}: ${line}") | ||||
|                         } | ||||
|                     } | ||||
|                     prevLine = line | ||||
|                     prevIndex = lineIndex + 1 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!currentBlock.isEmpty()) { | ||||
|             nonSortedBlocks.add(currentBlock) | ||||
|             throw new GradleException("The following lines were not sorted:\n" + | ||||
|                     nonSortedBlocks.collect { it.join("\n") }.join("\n\n")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -6,9 +6,11 @@ import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
|  | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.compose.ui.platform.ComposeView; | ||||
|  | ||||
| import org.schabi.newpipe.BaseFragment; | ||||
| import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
|  | ||||
| public class EmptyFragment extends BaseFragment { | ||||
|     private static final String SHOW_MESSAGE = "SHOW_MESSAGE"; | ||||
| @@ -26,8 +28,10 @@ public class EmptyFragment extends BaseFragment { | ||||
|                              final Bundle savedInstanceState) { | ||||
|         final boolean showMessage = getArguments().getBoolean(SHOW_MESSAGE); | ||||
|         final View view = inflater.inflate(R.layout.fragment_empty, container, false); | ||||
|         view.findViewById(R.id.empty_state_view).setVisibility( | ||||
|                 showMessage ? View.VISIBLE : View.GONE); | ||||
|  | ||||
|         final ComposeView composeView = view.findViewById(R.id.empty_state_view); | ||||
|         EmptyStateUtil.setEmptyStateComposable(composeView); | ||||
|         composeView.setVisibility(showMessage ? View.VISIBLE : View.GONE); | ||||
|         return view; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import android.graphics.Color; | ||||
| import android.os.Bundle; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| import android.util.TypedValue; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| @@ -45,6 +44,8 @@ import org.schabi.newpipe.fragments.detail.TabAdapter; | ||||
| import org.schabi.newpipe.ktx.AnimationType; | ||||
| import org.schabi.newpipe.local.feed.notifications.NotificationHelper; | ||||
| import org.schabi.newpipe.local.subscription.SubscriptionManager; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.ChannelTabHelper; | ||||
| import org.schabi.newpipe.util.Constants; | ||||
| import org.schabi.newpipe.util.ExtractorHelper; | ||||
| @@ -199,6 +200,11 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo> | ||||
|     protected void initViews(final View rootView, final Bundle savedInstanceState) { | ||||
|         super.initViews(rootView, savedInstanceState); | ||||
|  | ||||
|         EmptyStateUtil.setEmptyStateComposable( | ||||
|                 binding.emptyStateView, | ||||
|                 EmptyStateSpec.Companion.getContentNotSupported() | ||||
|         ); | ||||
|  | ||||
|         tabAdapter = new TabAdapter(getChildFragmentManager()); | ||||
|         binding.viewPager.setAdapter(tabAdapter); | ||||
|         binding.tabLayout.setupWithViewPager(binding.viewPager); | ||||
| @@ -645,8 +651,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo> | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         binding.errorContentNotSupported.setVisibility(View.VISIBLE); | ||||
|         binding.channelKaomoji.setText("(︶︹︺)"); | ||||
|         binding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f); | ||||
|         binding.emptyStateView.setVisibility(View.VISIBLE); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment; | ||||
| import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder; | ||||
| import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue; | ||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.ChannelTabHelper; | ||||
| import org.schabi.newpipe.util.ExtractorHelper; | ||||
| import org.schabi.newpipe.util.PlayButtonHelper; | ||||
| @@ -79,6 +80,12 @@ public class ChannelTabFragment extends BaseListInfoFragment<InfoItem, ChannelTa | ||||
|         return inflater.inflate(R.layout.fragment_channel_tab, container, false); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { | ||||
|         super.onViewCreated(rootView, savedInstanceState); | ||||
|         EmptyStateUtil.setEmptyStateComposable(rootView.findViewById(R.id.empty_state_view)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onDestroyView() { | ||||
|         super.onDestroyView(); | ||||
|   | ||||
| @@ -64,6 +64,8 @@ import org.schabi.newpipe.ktx.AnimationType; | ||||
| import org.schabi.newpipe.ktx.ExceptionUtils; | ||||
| import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||
| import org.schabi.newpipe.settings.NewPipeSettings; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.Constants; | ||||
| import org.schabi.newpipe.util.DeviceUtils; | ||||
| import org.schabi.newpipe.util.ExtractorHelper; | ||||
| @@ -344,6 +346,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I | ||||
|     protected void initViews(final View rootView, final Bundle savedInstanceState) { | ||||
|         super.initViews(rootView, savedInstanceState); | ||||
|  | ||||
|         EmptyStateUtil.setEmptyStateComposable( | ||||
|                 searchBinding.emptyStateView, | ||||
|                 EmptyStateSpec.Companion.getNoSearchResult()); | ||||
|  | ||||
|         searchBinding.suggestionsList.setAdapter(suggestionListAdapter); | ||||
|         // animations are just strange and useless, since the suggestions keep changing too much | ||||
|         searchBinding.suggestionsList.setItemAnimator(null); | ||||
|   | ||||
| @@ -38,6 +38,8 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder; | ||||
| import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder; | ||||
| import org.schabi.newpipe.local.playlist.LocalPlaylistManager; | ||||
| import org.schabi.newpipe.local.playlist.RemotePlaylistManager; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.NavigationHelper; | ||||
| import org.schabi.newpipe.util.OnClickGesture; | ||||
| import org.schabi.newpipe.util.debounce.DebounceSavable; | ||||
| @@ -123,6 +125,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL | ||||
|         super.initViews(rootView, savedInstanceState); | ||||
|  | ||||
|         itemListAdapter.setUseItemHandle(true); | ||||
|         EmptyStateUtil.setEmptyStateComposable( | ||||
|                 rootView.findViewById(R.id.empty_state_view), | ||||
|                 EmptyStateSpec.Companion.getNoBookmarkedPlaylist() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -74,6 +74,7 @@ import org.schabi.newpipe.ktx.slideUp | ||||
| import org.schabi.newpipe.local.feed.item.StreamItem | ||||
| import org.schabi.newpipe.local.feed.service.FeedLoadService | ||||
| import org.schabi.newpipe.local.subscription.SubscriptionManager | ||||
| import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable | ||||
| import org.schabi.newpipe.util.DeviceUtils | ||||
| import org.schabi.newpipe.util.Localization | ||||
| import org.schabi.newpipe.util.NavigationHelper | ||||
| @@ -132,6 +133,7 @@ class FeedFragment : BaseStateFragment<FeedState>() { | ||||
|     override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) { | ||||
|         // super.onViewCreated() calls initListeners() which require the binding to be initialized | ||||
|         _feedBinding = FragmentFeedBinding.bind(rootView) | ||||
|         feedBinding.emptyStateView.setEmptyStateComposable() | ||||
|         super.onViewCreated(rootView, savedInstanceState) | ||||
|  | ||||
|         val factory = FeedViewModel.getFactory(requireContext(), groupId) | ||||
|   | ||||
| @@ -56,6 +56,7 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService | ||||
| import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE | ||||
| import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard | ||||
| import org.schabi.newpipe.streams.io.StoredFileHelper | ||||
| import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable | ||||
| import org.schabi.newpipe.util.NavigationHelper | ||||
| import org.schabi.newpipe.util.OnClickGesture | ||||
| import org.schabi.newpipe.util.ServiceHelper | ||||
| @@ -257,6 +258,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() { | ||||
|         binding.itemsList.adapter = groupAdapter | ||||
|         binding.itemsList.itemAnimator = null | ||||
|  | ||||
|         binding.emptyStateView.setEmptyStateComposable() | ||||
|  | ||||
|         viewModel = ViewModelProvider(this)[SubscriptionViewModel::class.java] | ||||
|         viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) } | ||||
|         viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) { | ||||
|   | ||||
| @@ -3,14 +3,18 @@ package org.schabi.newpipe.local.subscription.item | ||||
| import android.view.View | ||||
| import com.xwray.groupie.viewbinding.BindableItem | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.databinding.ListEmptyViewBinding | ||||
| import org.schabi.newpipe.databinding.ListEmptyViewSubscriptionsBinding | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec | ||||
| import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable | ||||
|  | ||||
| /** | ||||
|  * When there are no subscriptions, show a hint to the user about how to import subscriptions | ||||
|  */ | ||||
| class ImportSubscriptionsHintPlaceholderItem : BindableItem<ListEmptyViewBinding>() { | ||||
| class ImportSubscriptionsHintPlaceholderItem : BindableItem<ListEmptyViewSubscriptionsBinding>() { | ||||
|     override fun getLayout(): Int = R.layout.list_empty_view_subscriptions | ||||
|     override fun bind(viewBinding: ListEmptyViewBinding, position: Int) {} | ||||
|     override fun bind(viewBinding: ListEmptyViewSubscriptionsBinding, position: Int) { | ||||
|         viewBinding.root.setEmptyStateComposable(EmptyStateSpec.NoSubscriptionsHint) | ||||
|     } | ||||
|     override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount | ||||
|     override fun initializeViewBinding(view: View) = ListEmptyViewBinding.bind(view) | ||||
|     override fun initializeViewBinding(view: View) = ListEmptyViewSubscriptionsBinding.bind(view) | ||||
| } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import android.widget.TextView; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.compose.ui.platform.ComposeView; | ||||
| import androidx.fragment.app.DialogFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| @@ -19,6 +20,8 @@ import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.database.subscription.SubscriptionEntity; | ||||
| import org.schabi.newpipe.error.ErrorUtil; | ||||
| import org.schabi.newpipe.local.subscription.SubscriptionManager; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.ThemeHelper; | ||||
| import org.schabi.newpipe.util.image.CoilHelper; | ||||
|  | ||||
| @@ -57,7 +60,7 @@ public class SelectChannelFragment extends DialogFragment { | ||||
|     private OnCancelListener onCancelListener = null; | ||||
|  | ||||
|     private ProgressBar progressBar; | ||||
|     private TextView emptyView; | ||||
|     private ComposeView emptyView; | ||||
|     private RecyclerView recyclerView; | ||||
|  | ||||
|     private List<SubscriptionEntity> subscriptions = new Vector<>(); | ||||
| @@ -91,6 +94,9 @@ public class SelectChannelFragment extends DialogFragment { | ||||
|  | ||||
|         progressBar = v.findViewById(R.id.progressBar); | ||||
|         emptyView = v.findViewById(R.id.empty_state_view); | ||||
|  | ||||
|         EmptyStateUtil.setEmptyStateComposable(emptyView, | ||||
|                 EmptyStateSpec.Companion.getNoSubscriptions()); | ||||
|         progressBar.setVisibility(View.VISIBLE); | ||||
|         recyclerView.setVisibility(View.GONE); | ||||
|         emptyView.setVisibility(View.GONE); | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import android.widget.ProgressBar; | ||||
| import android.widget.TextView; | ||||
|  | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.compose.ui.platform.ComposeView; | ||||
| import androidx.fragment.app.DialogFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| @@ -27,6 +28,8 @@ import org.schabi.newpipe.error.ErrorUtil; | ||||
| import org.schabi.newpipe.error.UserAction; | ||||
| import org.schabi.newpipe.local.playlist.LocalPlaylistManager; | ||||
| import org.schabi.newpipe.local.playlist.RemotePlaylistManager; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.image.CoilHelper; | ||||
|  | ||||
| import java.util.List; | ||||
| @@ -40,7 +43,7 @@ public class SelectPlaylistFragment extends DialogFragment { | ||||
|     private OnSelectedListener onSelectedListener = null; | ||||
|  | ||||
|     private ProgressBar progressBar; | ||||
|     private TextView emptyView; | ||||
|     private ComposeView emptyView; | ||||
|     private RecyclerView recyclerView; | ||||
|     private Disposable disposable = null; | ||||
|  | ||||
| @@ -62,6 +65,8 @@ public class SelectPlaylistFragment extends DialogFragment { | ||||
|         recyclerView = v.findViewById(R.id.items_list); | ||||
|         emptyView = v.findViewById(R.id.empty_state_view); | ||||
|  | ||||
|         EmptyStateUtil.setEmptyStateComposable(emptyView, | ||||
|                 EmptyStateSpec.Companion.getNoBookmarkedPlaylist()); | ||||
|         recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
|         final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter(); | ||||
|         recyclerView.setAdapter(playlistAdapter); | ||||
|   | ||||
| @@ -11,6 +11,8 @@ import androidx.fragment.app.Fragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
|  | ||||
| import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| @@ -39,6 +41,9 @@ public class PreferenceSearchFragment extends Fragment { | ||||
|         binding = SettingsPreferencesearchFragmentBinding.inflate(inflater, container, false); | ||||
|  | ||||
|         binding.searchResults.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
|         EmptyStateUtil.setEmptyStateComposable( | ||||
|                 binding.emptyStateView, | ||||
|                 EmptyStateSpec.Companion.getNoSearchMaxSizeResult()); | ||||
|  | ||||
|         adapter = new PreferenceSearchAdapter(); | ||||
|         adapter.setOnItemClickListener(this::onItemClicked); | ||||
|   | ||||
| @@ -1,42 +0,0 @@ | ||||
| package org.schabi.newpipe.ui.components.common | ||||
|  | ||||
| import android.content.res.Configuration | ||||
| import androidx.annotation.StringRes | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.wrapContentSize | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.Surface | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.sp | ||||
| import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.ui.theme.AppTheme | ||||
|  | ||||
| @Composable | ||||
| fun NoItemsMessage(@StringRes message: Int) { | ||||
|     Column( | ||||
|         modifier = Modifier | ||||
|             .fillMaxWidth() | ||||
|             .wrapContentSize(Alignment.Center), | ||||
|         horizontalAlignment = Alignment.CenterHorizontally | ||||
|     ) { | ||||
|         Text(text = "(╯°-°)╯", fontSize = 35.sp) | ||||
|         Text(text = stringResource(id = message), fontSize = 24.sp) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||||
| @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||||
| @Composable | ||||
| private fun NoItemsMessagePreview() { | ||||
|     AppTheme { | ||||
|         Surface(color = MaterialTheme.colorScheme.background) { | ||||
|             NoItemsMessage(message = R.string.no_videos) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -26,9 +26,10 @@ import org.schabi.newpipe.R | ||||
| import org.schabi.newpipe.extractor.stream.StreamInfo | ||||
| import org.schabi.newpipe.extractor.stream.StreamType | ||||
| import org.schabi.newpipe.info_list.ItemViewMode | ||||
| import org.schabi.newpipe.ui.components.common.NoItemsMessage | ||||
| import org.schabi.newpipe.ui.components.items.ItemList | ||||
| import org.schabi.newpipe.ui.components.items.stream.StreamInfoItem | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateComposable | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec | ||||
| import org.schabi.newpipe.ui.theme.AppTheme | ||||
| import org.schabi.newpipe.util.NO_SERVICE_ID | ||||
|  | ||||
| @@ -41,43 +42,44 @@ fun RelatedItems(info: StreamInfo) { | ||||
|         mutableStateOf(sharedPreferences.getBoolean(key, false)) | ||||
|     } | ||||
|  | ||||
|     if (info.relatedItems.isEmpty()) { | ||||
|         NoItemsMessage(message = R.string.no_videos) | ||||
|     } else { | ||||
|         ItemList( | ||||
|             items = info.relatedItems, | ||||
|             mode = ItemViewMode.LIST, | ||||
|             listHeader = { | ||||
|                 item { | ||||
|                     Row( | ||||
|                         modifier = Modifier | ||||
|                             .fillMaxWidth() | ||||
|                             .padding(start = 12.dp, end = 12.dp), | ||||
|                         horizontalArrangement = Arrangement.SpaceBetween, | ||||
|                         verticalAlignment = Alignment.CenterVertically, | ||||
|                     ) { | ||||
|                         Text(text = stringResource(R.string.auto_queue_description)) | ||||
|     ItemList( | ||||
|         items = info.relatedItems, | ||||
|         mode = ItemViewMode.LIST, | ||||
|         listHeader = { | ||||
|             item { | ||||
|                 Row( | ||||
|                     modifier = Modifier | ||||
|                         .fillMaxWidth() | ||||
|                         .padding(start = 12.dp, end = 12.dp), | ||||
|                     horizontalArrangement = Arrangement.SpaceBetween, | ||||
|                     verticalAlignment = Alignment.CenterVertically, | ||||
|                 ) { | ||||
|                     Text(text = stringResource(R.string.auto_queue_description)) | ||||
|  | ||||
|                         Row( | ||||
|                             horizontalArrangement = Arrangement.spacedBy(4.dp), | ||||
|                             verticalAlignment = Alignment.CenterVertically | ||||
|                         ) { | ||||
|                             Text(text = stringResource(R.string.auto_queue_toggle)) | ||||
|                             Switch( | ||||
|                                 checked = isAutoQueueEnabled, | ||||
|                                 onCheckedChange = { | ||||
|                                     isAutoQueueEnabled = it | ||||
|                                     sharedPreferences.edit { | ||||
|                                         putBoolean(key, it) | ||||
|                                     } | ||||
|                     Row( | ||||
|                         horizontalArrangement = Arrangement.spacedBy(4.dp), | ||||
|                         verticalAlignment = Alignment.CenterVertically | ||||
|                     ) { | ||||
|                         Text(text = stringResource(R.string.auto_queue_toggle)) | ||||
|                         Switch( | ||||
|                             checked = isAutoQueueEnabled, | ||||
|                             onCheckedChange = { | ||||
|                                 isAutoQueueEnabled = it | ||||
|                                 sharedPreferences.edit { | ||||
|                                     putBoolean(key, it) | ||||
|                                 } | ||||
|                             ) | ||||
|                         } | ||||
|                             } | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|             if (info.relatedItems.isEmpty()) { | ||||
|                 item { | ||||
|                     EmptyStateComposable(EmptyStateSpec.NoVideos) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.input.nestedscroll.nestedScroll | ||||
| import androidx.compose.ui.platform.rememberNestedScrollInteropConnection | ||||
| import androidx.compose.ui.res.pluralStringResource | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.tooling.preview.datasource.LoremIpsum | ||||
| import androidx.compose.ui.unit.dp | ||||
| @@ -38,7 +39,8 @@ import org.schabi.newpipe.extractor.stream.Description | ||||
| import org.schabi.newpipe.paging.CommentRepliesSource | ||||
| import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar | ||||
| import org.schabi.newpipe.ui.components.common.LoadingIndicator | ||||
| import org.schabi.newpipe.ui.components.common.NoItemsMessage | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateComposable | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec | ||||
| import org.schabi.newpipe.ui.theme.AppTheme | ||||
|  | ||||
| @Composable | ||||
| @@ -130,13 +132,17 @@ private fun CommentRepliesDialog( | ||||
|                             val refresh = comments.loadState.refresh | ||||
|                             if (refresh is LoadState.Loading) { | ||||
|                                 LoadingIndicator(modifier = Modifier.padding(top = 8.dp)) | ||||
|                             } else if (refresh is LoadState.Error) { | ||||
|                                 // TODO use error panel instead | ||||
|                                 EmptyStateComposable( | ||||
|                                     EmptyStateSpec.DisabledComments.copy( | ||||
|                                         descriptionText = { | ||||
|                                             stringResource(R.string.error_unable_to_load_comments) | ||||
|                                         } | ||||
|                                     ) | ||||
|                                 ) | ||||
|                             } else { | ||||
|                                 val message = if (refresh is LoadState.Error) { | ||||
|                                     R.string.error_unable_to_load_comments | ||||
|                                 } else { | ||||
|                                     R.string.no_comments | ||||
|                                 } | ||||
|                                 NoItemsMessage(message) | ||||
|                                 EmptyStateComposable(EmptyStateSpec.NoComments) | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.input.nestedscroll.nestedScroll | ||||
| import androidx.compose.ui.platform.rememberNestedScrollInteropConnection | ||||
| import androidx.compose.ui.res.pluralStringResource | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.lifecycle.compose.collectAsStateWithLifecycle | ||||
| @@ -28,7 +29,8 @@ import org.schabi.newpipe.extractor.comments.CommentsInfoItem | ||||
| import org.schabi.newpipe.extractor.stream.Description | ||||
| import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar | ||||
| import org.schabi.newpipe.ui.components.common.LoadingIndicator | ||||
| import org.schabi.newpipe.ui.components.common.NoItemsMessage | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateComposable | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateSpec | ||||
| import org.schabi.newpipe.ui.theme.AppTheme | ||||
| import org.schabi.newpipe.viewmodels.CommentsViewModel | ||||
| import org.schabi.newpipe.viewmodels.util.Resource | ||||
| @@ -66,11 +68,11 @@ private fun CommentSection( | ||||
|  | ||||
|                     if (commentInfo.isCommentsDisabled) { | ||||
|                         item { | ||||
|                             NoItemsMessage(R.string.comments_are_disabled) | ||||
|                             EmptyStateComposable(EmptyStateSpec.DisabledComments) | ||||
|                         } | ||||
|                     } else if (count == 0) { | ||||
|                         item { | ||||
|                             NoItemsMessage(R.string.no_comments) | ||||
|                             EmptyStateComposable(EmptyStateSpec.NoComments) | ||||
|                         } | ||||
|                     } else { | ||||
|                         // do not show anything if the comment count is unknown | ||||
| @@ -95,7 +97,14 @@ private fun CommentSection( | ||||
|  | ||||
|                             is LoadState.Error -> { | ||||
|                                 item { | ||||
|                                     NoItemsMessage(R.string.error_unable_to_load_comments) | ||||
|                                     // TODO use error panel instead | ||||
|                                     EmptyStateComposable( | ||||
|                                         EmptyStateSpec.DisabledComments.copy( | ||||
|                                             descriptionText = { | ||||
|                                                 stringResource(R.string.error_unable_to_load_comments) | ||||
|                                             } | ||||
|                                         ) | ||||
|                                     ) | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
| @@ -110,7 +119,14 @@ private fun CommentSection( | ||||
|  | ||||
|                 is Resource.Error -> { | ||||
|                     item { | ||||
|                         NoItemsMessage(R.string.error_unable_to_load_comments) | ||||
|                         // TODO use error panel instead | ||||
|                         EmptyStateComposable( | ||||
|                             EmptyStateSpec.DisabledComments.copy( | ||||
|                                 descriptionText = { | ||||
|                                     stringResource(R.string.error_unable_to_load_comments) | ||||
|                                 } | ||||
|                             ) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -0,0 +1,159 @@ | ||||
| package org.schabi.newpipe.ui.emptystate | ||||
|  | ||||
| import android.graphics.Color | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.heightIn | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.res.stringResource | ||||
| 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.theme.AppTheme | ||||
|  | ||||
| @Composable | ||||
| fun EmptyStateComposable( | ||||
|     spec: EmptyStateSpec, | ||||
|     modifier: Modifier = Modifier, | ||||
| ) = EmptyStateComposable( | ||||
|     modifier = spec.modifier(modifier), | ||||
|     emojiText = spec.emojiText(), | ||||
|     descriptionText = spec.descriptionText(), | ||||
| ) | ||||
|  | ||||
| @Composable | ||||
| private fun EmptyStateComposable( | ||||
|     emojiText: String, | ||||
|     descriptionText: String, | ||||
|     modifier: Modifier = Modifier, | ||||
| ) { | ||||
|     Column( | ||||
|         modifier = modifier, | ||||
|         horizontalAlignment = Alignment.CenterHorizontally, | ||||
|         verticalArrangement = Arrangement.Center | ||||
|     ) { | ||||
|         Text( | ||||
|             text = emojiText, | ||||
|             style = MaterialTheme.typography.titleLarge, | ||||
|             textAlign = TextAlign.Center, | ||||
|         ) | ||||
|  | ||||
|         Text( | ||||
|             modifier = Modifier | ||||
|                 .padding(top = 6.dp) | ||||
|                 .padding(horizontal = 16.dp), | ||||
|             text = descriptionText, | ||||
|             style = MaterialTheme.typography.bodyMedium, | ||||
|             textAlign = TextAlign.Center, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview(showBackground = true, backgroundColor = Color.WHITE.toLong()) | ||||
| @Composable | ||||
| fun EmptyStateComposableGenericErrorPreview() { | ||||
|     AppTheme { | ||||
|         EmptyStateComposable(EmptyStateSpec.GenericError) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview(showBackground = true, backgroundColor = Color.WHITE.toLong()) | ||||
| @Composable | ||||
| fun EmptyStateComposableNoCommentPreview() { | ||||
|     AppTheme { | ||||
|         EmptyStateComposable(EmptyStateSpec.NoComments) | ||||
|     } | ||||
| } | ||||
|  | ||||
| data class EmptyStateSpec( | ||||
|     val modifier: (Modifier) -> Modifier, | ||||
|     val emojiText: @Composable () -> String, | ||||
|     val descriptionText: @Composable () -> String, | ||||
| ) { | ||||
|     companion object { | ||||
|  | ||||
|         val GenericError = | ||||
|             EmptyStateSpec( | ||||
|                 modifier = { | ||||
|                     it | ||||
|                         .fillMaxWidth() | ||||
|                         .heightIn(min = 128.dp) | ||||
|                 }, | ||||
|                 emojiText = { "¯\\_(ツ)_/¯" }, | ||||
|                 descriptionText = { stringResource(id = R.string.empty_list_subtitle) }, | ||||
|             ) | ||||
|  | ||||
|         val NoVideos = | ||||
|             EmptyStateSpec( | ||||
|                 modifier = { | ||||
|                     it | ||||
|                         .fillMaxWidth() | ||||
|                         .heightIn(min = 128.dp) | ||||
|                 }, | ||||
|                 emojiText = { "(╯°-°)╯" }, | ||||
|                 descriptionText = { stringResource(id = R.string.no_videos) }, | ||||
|             ) | ||||
|  | ||||
|         val NoComments = | ||||
|             EmptyStateSpec( | ||||
|                 modifier = { | ||||
|                     it | ||||
|                         .fillMaxWidth() | ||||
|                         .heightIn(min = 128.dp) | ||||
|                 }, | ||||
|                 emojiText = { "¯\\_(╹x╹)_/¯" }, | ||||
|                 descriptionText = { stringResource(id = R.string.no_comments) }, | ||||
|             ) | ||||
|  | ||||
|         val DisabledComments = | ||||
|             NoComments.copy( | ||||
|                 descriptionText = { stringResource(id = R.string.comments_are_disabled) }, | ||||
|             ) | ||||
|  | ||||
|         val NoSearchResult = | ||||
|             NoComments.copy( | ||||
|                 modifier = { it }, | ||||
|                 emojiText = { "╰(°●°╰)" }, | ||||
|                 descriptionText = { stringResource(id = R.string.search_no_results) } | ||||
|             ) | ||||
|  | ||||
|         val NoSearchMaxSizeResult = | ||||
|             NoSearchResult.copy( | ||||
|                 modifier = { it.fillMaxSize() }, | ||||
|             ) | ||||
|  | ||||
|         val ContentNotSupported = | ||||
|             NoComments.copy( | ||||
|                 modifier = { it.padding(top = 90.dp) }, | ||||
|                 emojiText = { "(︶︹︺)" }, | ||||
|                 descriptionText = { stringResource(id = R.string.content_not_supported) }, | ||||
|             ) | ||||
|  | ||||
|         val NoBookmarkedPlaylist = | ||||
|             EmptyStateSpec( | ||||
|                 modifier = { it }, | ||||
|                 emojiText = { "(╥﹏╥)" }, | ||||
|                 descriptionText = { stringResource(id = R.string.no_playlist_bookmarked_yet) }, | ||||
|             ) | ||||
|  | ||||
|         val NoSubscriptionsHint = | ||||
|             EmptyStateSpec( | ||||
|                 modifier = { it }, | ||||
|                 emojiText = { "(꩜ᯅ꩜)" }, | ||||
|                 descriptionText = { stringResource(id = R.string.import_subscriptions_hint) }, | ||||
|             ) | ||||
|  | ||||
|         val NoSubscriptions = | ||||
|             NoSubscriptionsHint.copy( | ||||
|                 descriptionText = { stringResource(id = R.string.no_channel_subscribed_yet) }, | ||||
|             ) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,30 @@ | ||||
| @file:JvmName("EmptyStateUtil") | ||||
|  | ||||
| package org.schabi.newpipe.ui.emptystate | ||||
|  | ||||
| import androidx.compose.material3.LocalContentColor | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.contentColorFor | ||||
| import androidx.compose.runtime.CompositionLocalProvider | ||||
| import androidx.compose.ui.platform.ComposeView | ||||
| import androidx.compose.ui.platform.ViewCompositionStrategy | ||||
| import org.schabi.newpipe.ui.theme.AppTheme | ||||
|  | ||||
| @JvmOverloads | ||||
| fun ComposeView.setEmptyStateComposable( | ||||
|     spec: EmptyStateSpec = EmptyStateSpec.GenericError, | ||||
|     strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, | ||||
| ) = apply { | ||||
|     setViewCompositionStrategy(strategy) | ||||
|     setContent { | ||||
|         AppTheme { | ||||
|             CompositionLocalProvider( | ||||
|                 LocalContentColor provides contentColorFor(MaterialTheme.colorScheme.background) | ||||
|             ) { | ||||
|                 EmptyStateComposable( | ||||
|                     spec = spec | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -22,6 +22,7 @@ import androidx.activity.result.ActivityResultLauncher; | ||||
| import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.compose.ui.platform.ComposeView; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.preference.PreferenceManager; | ||||
| import androidx.recyclerview.widget.GridLayoutManager; | ||||
| @@ -34,6 +35,7 @@ import org.schabi.newpipe.R; | ||||
| import org.schabi.newpipe.settings.NewPipeSettings; | ||||
| import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard; | ||||
| import org.schabi.newpipe.streams.io.StoredFileHelper; | ||||
| import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; | ||||
| import org.schabi.newpipe.util.FilePickerActivityHelper; | ||||
|  | ||||
| import java.io.File; | ||||
| @@ -108,7 +110,8 @@ public class MissionsFragment extends Fragment { | ||||
|         mContext.bindService(new Intent(mContext, DownloadManagerService.class), mConnection, Context.BIND_AUTO_CREATE); | ||||
|  | ||||
|         // Views | ||||
|         mEmpty = v.findViewById(R.id.list_empty_view); | ||||
|         mEmpty = v.findViewById(R.id.empty_state_view); | ||||
|         EmptyStateUtil.setEmptyStateComposable((ComposeView) mEmpty); | ||||
|         mList = v.findViewById(R.id.mission_recycler); | ||||
|  | ||||
|         // Init layouts managers | ||||
|   | ||||
| @@ -24,15 +24,15 @@ | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <include | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:layout_marginTop="50dp" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|         tools:visibility="visible" | ||||
|         /> | ||||
|  | ||||
|     <View | ||||
|         android:layout_width="match_parent" | ||||
|   | ||||
| @@ -168,37 +168,14 @@ | ||||
|             android:visibility="gone" | ||||
|             tools:visibility="visible" /> | ||||
|  | ||||
|         <LinearLayout | ||||
|         <androidx.compose.ui.platform.ComposeView | ||||
|             android:id="@+id/empty_state_view" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_centerInParent="true" | ||||
|             android:orientation="vertical" | ||||
|             android:paddingTop="90dp" | ||||
|             android:visibility="gone" | ||||
|             tools:visibility="visible"> | ||||
|  | ||||
|             <org.schabi.newpipe.views.NewPipeTextView | ||||
|                 android:id="@+id/channel_kaomoji" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_gravity="center" | ||||
|                 android:layout_marginBottom="10dp" | ||||
|                 android:fontFamily="monospace" | ||||
|                 android:text="(︶︹︺)" | ||||
|                 android:textSize="35sp" | ||||
|                 tools:ignore="HardcodedText" /> | ||||
|  | ||||
|             <org.schabi.newpipe.views.NewPipeTextView | ||||
|                 android:id="@+id/error_content_not_supported" | ||||
|                 android:layout_width="wrap_content" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="20dp" | ||||
|                 android:text="@string/content_not_supported" | ||||
|                 android:textSize="15sp" | ||||
|                 android:visibility="gone" /> | ||||
|  | ||||
|         </LinearLayout> | ||||
|             tools:visibility="visible" | ||||
|             /> | ||||
|  | ||||
|         <!--ERROR PANEL--> | ||||
|         <include | ||||
|   | ||||
| @@ -20,15 +20,15 @@ | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <include | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:gravity="center" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|         tools:visibility="visible" | ||||
|         /> | ||||
|  | ||||
|     <!--ERROR PANEL--> | ||||
|     <include | ||||
|   | ||||
| @@ -20,36 +20,14 @@ | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <LinearLayout | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:orientation="vertical" | ||||
|         android:paddingTop="90dp" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible"> | ||||
|  | ||||
|         <org.schabi.newpipe.views.NewPipeTextView | ||||
|             android:id="@+id/channel_kaomoji" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:layout_marginBottom="10dp" | ||||
|             android:fontFamily="monospace" | ||||
|             android:text="(╯°-°)╯" | ||||
|             android:textSize="35sp" | ||||
|             tools:ignore="HardcodedText,UnusedAttribute" /> | ||||
|  | ||||
|         <org.schabi.newpipe.views.NewPipeTextView | ||||
|             android:id="@+id/channel_no_videos" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:text="@string/empty_view_no_videos" | ||||
|             android:textSize="24sp" /> | ||||
|  | ||||
|     </LinearLayout> | ||||
|         tools:visibility="visible" | ||||
|         /> | ||||
|  | ||||
|     <!--ERROR PANEL--> | ||||
|     <include | ||||
|   | ||||
| @@ -7,12 +7,13 @@ | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent"> | ||||
|  | ||||
|         <include | ||||
|         <androidx.compose.ui.platform.ComposeView | ||||
|             android:id="@+id/empty_state_view" | ||||
|             layout="@layout/list_empty_view" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:layout_marginTop="90dp" /> | ||||
|             android:layout_marginTop="90dp" | ||||
|             /> | ||||
|  | ||||
|     </androidx.core.widget.NestedScrollView> | ||||
| </RelativeLayout> | ||||
|   | ||||
| @@ -140,15 +140,15 @@ | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <include | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:layout_marginTop="50dp" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|         tools:visibility="visible" | ||||
|         /> | ||||
|  | ||||
|     <View | ||||
|         android:layout_width="match_parent" | ||||
|   | ||||
| @@ -52,33 +52,14 @@ | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <LinearLayout | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:orientation="vertical" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible"> | ||||
|  | ||||
|         <org.schabi.newpipe.views.NewPipeTextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:layout_marginBottom="10dp" | ||||
|             android:fontFamily="monospace" | ||||
|             android:text="╰(°●°╰)" | ||||
|             android:textSize="35sp" | ||||
|             tools:ignore="HardcodedText,UnusedAttribute" /> | ||||
|  | ||||
|         <org.schabi.newpipe.views.NewPipeTextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:text="@string/search_no_results" | ||||
|             android:textSize="24sp" /> | ||||
|  | ||||
|     </LinearLayout> | ||||
|         tools:visibility="visible" | ||||
|         /> | ||||
|  | ||||
|     <LinearLayout | ||||
|         android:id="@+id/suggestions_panel" | ||||
|   | ||||
| @@ -24,16 +24,16 @@ | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|  | ||||
|     <include | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_below="@id/items_list" | ||||
|         android:layout_centerInParent="true" | ||||
|         android:layout_marginTop="50dp" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="visible" /> | ||||
|         tools:visibility="visible" | ||||
|         /> | ||||
|  | ||||
|     <View | ||||
|         android:layout_width="match_parent" | ||||
|   | ||||
| @@ -1,25 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout 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:gravity="center" | ||||
|     android:minHeight="128dp" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|     <org.schabi.newpipe.views.NewPipeTextView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="¯\\_(ツ)_/¯" | ||||
|         android:textAppearance="?android:attr/textAppearanceLarge" | ||||
|         tools:ignore="HardcodedText" /> | ||||
|  | ||||
|     <org.schabi.newpipe.views.NewPipeTextView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="center" | ||||
|         android:layout_marginTop="6dp" | ||||
|         android:gravity="center" | ||||
|         android:paddingHorizontal="16dp" | ||||
|         android:text="@string/empty_list_subtitle" /> | ||||
| </LinearLayout> | ||||
| @@ -1,25 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
| <androidx.compose.ui.platform.ComposeView | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:gravity="center" | ||||
|     android:minHeight="128dp" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|     <org.schabi.newpipe.views.NewPipeTextView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="¯\\_(ツ)_/¯" | ||||
|         android:textAppearance="?android:attr/textAppearanceLarge" | ||||
|         tools:ignore="HardcodedText" /> | ||||
|  | ||||
|     <org.schabi.newpipe.views.NewPipeTextView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="center" | ||||
|         android:layout_marginTop="6dp" | ||||
|         android:gravity="center" | ||||
|         android:paddingHorizontal="16dp" | ||||
|         android:text="@string/import_subscriptions_hint" /> | ||||
| </LinearLayout> | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" /> | ||||
|   | ||||
| @@ -3,10 +3,11 @@ | ||||
|     android:layout_height="match_parent" | ||||
|     android:orientation="vertical"> | ||||
|  | ||||
|     <include | ||||
|         android:id="@+id/list_empty_view" | ||||
|         layout="@layout/list_empty_view" | ||||
|         android:visibility="gone" /> | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:minHeight="128dp" /> | ||||
|  | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|         android:id="@+id/mission_recycler" | ||||
|   | ||||
| @@ -24,14 +24,11 @@ | ||||
|         android:layout_height="wrap_content" | ||||
|         tools:listitem="@layout/select_channel_item" /> | ||||
|  | ||||
|  | ||||
|     <org.schabi.newpipe.views.NewPipeTextView | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_margin="10dp" | ||||
|         android:text="@string/no_channel_subscribed_yet" | ||||
|         android:textAppearance="?android:attr/textAppearanceListItem" /> | ||||
|         android:layout_margin="10dp" /> | ||||
|  | ||||
|     <ProgressBar | ||||
|         android:id="@+id/progressBar" | ||||
|   | ||||
| @@ -26,14 +26,11 @@ | ||||
|  | ||||
|     </androidx.recyclerview.widget.RecyclerView> | ||||
|  | ||||
|  | ||||
|     <org.schabi.newpipe.views.NewPipeTextView | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_margin="10dp" | ||||
|         android:text="@string/no_playlist_bookmarked_yet" | ||||
|         android:textAppearance="?android:attr/textAppearanceListItem" /> | ||||
|         android:layout_margin="10dp" /> | ||||
|  | ||||
|     <ProgressBar | ||||
|         android:id="@+id/progressBar" | ||||
|   | ||||
| @@ -12,33 +12,14 @@ | ||||
|         android:layout_height="4dp" | ||||
|         android:background="?attr/toolbar_shadow" /> | ||||
|  | ||||
|     <LinearLayout | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/empty_state_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:gravity="center" | ||||
|         android:orientation="vertical" | ||||
|         android:visibility="gone" | ||||
|         tools:visibility="gone"> | ||||
|  | ||||
|         <org.schabi.newpipe.views.NewPipeTextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:layout_marginBottom="10dp" | ||||
|             android:fontFamily="monospace" | ||||
|             android:text="╰(°●°╰)" | ||||
|             android:textSize="35sp" | ||||
|             tools:ignore="HardcodedText,UnusedAttribute" /> | ||||
|  | ||||
|         <org.schabi.newpipe.views.NewPipeTextView | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center" | ||||
|             android:text="@string/search_no_results" | ||||
|             android:textSize="24sp" /> | ||||
|  | ||||
|     </LinearLayout> | ||||
|         tools:visibility="gone" | ||||
|         /> | ||||
|  | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|         android:id="@+id/searchResults" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stypox
					Stypox