From 8b79d0ee2944eb47232ed5eba4d44b48865ed57a Mon Sep 17 00:00:00 2001 From: toliuweijing Date: Mon, 19 Aug 2024 15:11:51 +0800 Subject: [PATCH 01/11] Migrate empty_state_view to Jetpack Compose --- .../newpipe/fragments/EmptyFragment.java | 8 +- .../list/channel/ChannelFragment.java | 19 +- .../list/channel/ChannelTabFragment.java | 7 + .../fragments/list/search/SearchFragment.java | 6 + .../local/bookmark/BookmarkFragment.java | 2 + .../schabi/newpipe/local/feed/FeedFragment.kt | 2 + .../subscription/SubscriptionFragment.kt | 3 + .../settings/SelectPlaylistFragment.java | 5 +- .../PreferenceSearchFragment.java | 5 + .../ui/emptystate/EmptyStateComposable.kt | 168 ++++++++++++++++++ .../newpipe/ui/emptystate/EmptyStateUtil.kt | 87 +++++++++ .../newpipe/ui/theme/ColorExtensions.kt | 21 +++ .../main/res/layout/fragment_bookmarks.xml | 6 +- app/src/main/res/layout/fragment_channel.xml | 29 +-- .../main/res/layout/fragment_channel_tab.xml | 6 +- .../res/layout/fragment_channel_videos.xml | 28 +-- app/src/main/res/layout/fragment_empty.xml | 7 +- app/src/main/res/layout/fragment_feed.xml | 6 +- app/src/main/res/layout/fragment_search.xml | 25 +-- .../main/res/layout/fragment_subscription.xml | 6 +- .../res/layout/select_playlist_fragment.xml | 6 +- .../settings_preferencesearch_fragment.xml | 25 +-- 22 files changed, 356 insertions(+), 121 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt create mode 100644 app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt create mode 100644 app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt diff --git a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java index d4e73bcac78..8c939a3e8a1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java @@ -6,9 +6,11 @@ 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 View onCreateView(final LayoutInflater inflater, @Nullable final ViewGrou 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; } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 55e3ae52a55..394d97f125e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -10,7 +10,6 @@ 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; @@ -20,6 +19,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.compose.runtime.MutableState; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.core.view.MenuProvider; @@ -45,6 +45,9 @@ 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.EmptyStateSpecBuilder; +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; @@ -102,6 +105,8 @@ public class ChannelFragment extends BaseStateFragment private SubscriptionEntity channelSubscription; private MenuProvider menuProvider; + private MutableState emptyStateSpec; + public static ChannelFragment getInstance(final int serviceId, final String url, final String name) { final ChannelFragment instance = new ChannelFragment(); @@ -199,6 +204,10 @@ public View onCreateView(@NonNull final LayoutInflater inflater, protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); + emptyStateSpec = EmptyStateUtil.mutableStateOf( + EmptyStateSpec.Companion.getContentNotSupported()); + EmptyStateUtil.setEmptyStateComposable(binding.emptyStateView, emptyStateSpec); + tabAdapter = new TabAdapter(getChildFragmentManager()); binding.viewPager.setAdapter(tabAdapter); binding.tabLayout.setupWithViewPager(binding.viewPager); @@ -645,8 +654,10 @@ private void showContentNotSupportedIfNeeded() { return; } - binding.errorContentNotSupported.setVisibility(View.VISIBLE); - binding.channelKaomoji.setText("(︶︹︺)"); - binding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f); + emptyStateSpec.setValue( + new EmptyStateSpecBuilder(emptyStateSpec.getValue()) + .descriptionVisibility(true) + .build() + ); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java index 5d398821a3a..feb23b6ac9f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java @@ -26,6 +26,7 @@ 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 View onCreateView(@NonNull final LayoutInflater inflater, 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(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 18c60400b47..06293ccee35 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -64,6 +64,8 @@ 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 void onActivityResult(final int requestCode, final int resultCode, final 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); diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index a5e1594d1b5..bff6f63de9c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -38,6 +38,7 @@ 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.EmptyStateUtil; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.debounce.DebounceSavable; @@ -123,6 +124,7 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); itemListAdapter.setUseItemHandle(true); + EmptyStateUtil.setEmptyStateComposable(rootView.findViewById(R.id.empty_state_view)); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index a4e53aab1db..f976f44aa84 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -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() { 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) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 5583a2c4a6b..e4a9b79a238 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -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() { 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) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java index c340dca2231..efe3c4f6637 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -11,6 +11,7 @@ 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,7 @@ 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.EmptyStateUtil; import org.schabi.newpipe.util.image.CoilHelper; import java.util.List; @@ -40,7 +42,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 +64,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup recyclerView = v.findViewById(R.id.items_list); emptyView = v.findViewById(R.id.empty_state_view); + EmptyStateUtil.setEmptyStateText(emptyView, R.string.no_playlist_bookmarked_yet); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter(); recyclerView.setAdapter(playlistAdapter); diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java index 9d169d660ad..f667bb90039 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java @@ -11,6 +11,8 @@ 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 View onCreateView( 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); diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt new file mode 100644 index 00000000000..3af217a9c0e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -0,0 +1,168 @@ +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.LocalContentColor +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.schabi.newpipe.R +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.errorHint + +@Composable +fun EmptyStateComposable( + spec: EmptyStateSpec, + modifier: Modifier = Modifier, +) = EmptyStateComposable( + modifier = spec.modifier(modifier), + emojiModifier = spec.emojiModifier(), + emojiText = spec.emojiText(), + emojiTextStyle = spec.emojiTextStyle(), + descriptionModifier = spec.descriptionModifier(), + descriptionText = spec.descriptionText(), + descriptionTextStyle = spec.descriptionTextStyle(), + descriptionTextVisibility = spec.descriptionVisibility(), +) + +@Composable +private fun EmptyStateComposable( + modifier: Modifier, + emojiModifier: Modifier, + emojiText: String, + emojiTextStyle: TextStyle, + descriptionModifier: Modifier, + descriptionText: String, + descriptionTextStyle: TextStyle, + descriptionTextVisibility: Boolean, +) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.errorHint + ) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + modifier = emojiModifier, + text = emojiText, + style = emojiTextStyle, + ) + + if (descriptionTextVisibility) { + Text( + modifier = descriptionModifier, + text = descriptionText, + style = descriptionTextStyle, + ) + } + } + } +} + +@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.NoComment) + } +} + +data class EmptyStateSpec( + val modifier: (Modifier) -> Modifier, + val emojiModifier: () -> Modifier, + val emojiText: @Composable () -> String, + val emojiTextStyle: @Composable () -> TextStyle, + val descriptionText: @Composable () -> String, + val descriptionModifier: () -> Modifier, + val descriptionTextStyle: @Composable () -> TextStyle, + val descriptionVisibility: () -> Boolean = { true }, +) { + + companion object { + + val GenericError = + EmptyStateSpec( + modifier = { + it + .fillMaxWidth() + .heightIn(min = 128.dp) + }, + emojiModifier = { Modifier }, + emojiText = { "¯\\_(ツ)_/¯" }, + emojiTextStyle = { MaterialTheme.typography.titleLarge }, + descriptionModifier = { + Modifier + .padding(top = 6.dp) + .padding(horizontal = 16.dp) + }, + descriptionText = { stringResource(id = R.string.empty_list_subtitle) }, + descriptionTextStyle = { MaterialTheme.typography.bodyMedium } + ) + + val NoComment = + EmptyStateSpec( + modifier = { it.padding(top = 85.dp) }, + emojiModifier = { Modifier.padding(bottom = 10.dp) }, + emojiText = { "(╯°-°)╯" }, + emojiTextStyle = { + LocalTextStyle.current.merge( + fontFamily = FontFamily.Monospace, + fontSize = 35.sp, + ) + }, + descriptionModifier = { Modifier }, + descriptionText = { stringResource(id = R.string.no_comments) }, + descriptionTextStyle = { + LocalTextStyle.current.merge(fontSize = 24.sp) + } + ) + + val NoSearchResult = + NoComment.copy( + modifier = { it }, + emojiText = { "╰(°●°╰)" }, + descriptionText = { stringResource(id = R.string.search_no_results) } + ) + + val NoSearchMaxSizeResult = + NoSearchResult.copy( + modifier = { it.fillMaxSize() }, + ) + + val ContentNotSupported = + NoComment.copy( + modifier = { it.padding(top = 90.dp) }, + emojiText = { "(︶︹︺)" }, + emojiTextStyle = { LocalTextStyle.current.merge(fontSize = 45.sp) }, + descriptionModifier = { Modifier.padding(top = 20.dp) }, + descriptionText = { stringResource(id = R.string.content_not_supported) }, + descriptionTextStyle = { LocalTextStyle.current.merge(fontSize = 15.sp) }, + descriptionVisibility = { false }, + ) + } +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt new file mode 100644 index 00000000000..b025bdc5ba5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -0,0 +1,87 @@ +@file:JvmName("EmptyStateUtil") + +package org.schabi.newpipe.ui.emptystate + +import androidx.annotation.StringRes +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import org.schabi.newpipe.ui.theme.AppTheme +import org.schabi.newpipe.ui.theme.errorHint +import androidx.compose.runtime.mutableStateOf as composeRuntimeMutableStateOf + +@JvmOverloads +fun ComposeView.setEmptyStateText( + @StringRes stringRes: Int, + strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, +) = apply { + setViewCompositionStrategy(strategy) + setContent { + AppTheme { + Text( + text = stringResource(id = stringRes), + color = MaterialTheme.colorScheme.errorHint, + ) + } + } +} + +@JvmOverloads +fun ComposeView.setEmptyStateComposable( + spec: EmptyStateSpec = EmptyStateSpec.GenericError, + strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, +) = apply { + setViewCompositionStrategy(strategy) + setContent { + AppTheme { + EmptyStateComposable( + spec = spec + ) + } + } +} + +@JvmOverloads +fun ComposeView.setEmptyStateComposable( + spec: State, + strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, +) = apply { + setViewCompositionStrategy(strategy) + setContent { + AppTheme { + EmptyStateComposable( + spec = spec.value, + ) + } + } +} + +/** + * Used in Java land to bridge the [MutableState] API. + */ +fun mutableStateOf(param: T): MutableState { + return composeRuntimeMutableStateOf(param) +} + +/** + * Used in Java land to modify [EmptyStateSpec] properties. + * TODO: remove after Kotlin migration + */ +class EmptyStateSpecBuilder(var spec: EmptyStateSpec) { + + fun descriptionText(@StringRes stringRes: Int) = apply { + spec = spec.copy( + descriptionText = { stringResource(id = stringRes) } + ) + } + + fun descriptionVisibility(descriptionTextVisibility: Boolean) = apply { + spec = spec.copy(descriptionVisibility = { descriptionTextVisibility }) + } + + fun build() = spec +} diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt new file mode 100644 index 00000000000..8e9118c6ec9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt @@ -0,0 +1,21 @@ +package org.schabi.newpipe.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.ColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +/** + * Extended color for error hint. + * See [specification](https://m1.material.io/patterns/errors.html#errors-user-input-errors) + */ +val md_theme_light_error_hint = Color(0x61000000) + +val md_theme_dark_error_hint = Color(0x80FFFFFF) + +val ColorScheme.errorHint: Color + @Composable get() = if (isSystemInDarkTheme()) { + Color(0x80FFFFFF) + } else { + Color(0x61000000) + } diff --git a/app/src/main/res/layout/fragment_bookmarks.xml b/app/src/main/res/layout/fragment_bookmarks.xml index 418c119644e..9767a10811e 100644 --- a/app/src/main/res/layout/fragment_bookmarks.xml +++ b/app/src/main/res/layout/fragment_bookmarks.xml @@ -24,15 +24,15 @@ android:visibility="gone" tools:visibility="visible" /> - + tools:visibility="visible" + /> - - - - - - - + tools:visibility="visible" + /> - + tools:visibility="visible" + /> - - - - - - - + tools:visibility="visible" + /> - + android:layout_marginTop="90dp" + /> + diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index de2096605d7..3c61c824fd5 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -140,15 +140,15 @@ android:visibility="gone" tools:visibility="visible" /> - + tools:visibility="visible" + /> - - - - - - - + tools:visibility="visible" + /> - + tools:visibility="visible" + /> - - + /> - - - - - - - + tools:visibility="gone" + /> Date: Mon, 19 Aug 2024 16:11:31 +0800 Subject: [PATCH 02/11] boost error hint color --- .../org/schabi/newpipe/ui/theme/ColorExtensions.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt index 8e9118c6ec9..3c057b16aef 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt @@ -7,15 +7,13 @@ import androidx.compose.ui.graphics.Color /** * Extended color for error hint. - * See [specification](https://m1.material.io/patterns/errors.html#errors-user-input-errors) */ -val md_theme_light_error_hint = Color(0x61000000) - -val md_theme_dark_error_hint = Color(0x80FFFFFF) +val md_theme_light_error_hint = Color(0xCC000000) +val md_theme_dark_error_hint = Color(0xCCFFFFFF) val ColorScheme.errorHint: Color @Composable get() = if (isSystemInDarkTheme()) { - Color(0x80FFFFFF) + md_theme_dark_error_hint } else { - Color(0x61000000) + md_theme_light_error_hint } From 46b9243661a6dcbafd59a4a91f6d562cdb4cbe2a Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 11:53:01 +0100 Subject: [PATCH 03/11] Remove unneeded empty state changes in ChannelFragment --- .../fragments/list/channel/ChannelFragment.java | 17 +++++------------ .../ui/emptystate/EmptyStateComposable.kt | 17 +++++------------ .../newpipe/ui/emptystate/EmptyStateUtil.kt | 6 +----- 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 394d97f125e..2d5873e3f41 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -19,7 +19,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.compose.runtime.MutableState; import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.core.view.MenuProvider; @@ -46,7 +45,6 @@ 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.EmptyStateSpecBuilder; import org.schabi.newpipe.ui.emptystate.EmptyStateUtil; import org.schabi.newpipe.util.ChannelTabHelper; import org.schabi.newpipe.util.Constants; @@ -105,8 +103,6 @@ public class ChannelFragment extends BaseStateFragment private SubscriptionEntity channelSubscription; private MenuProvider menuProvider; - private MutableState emptyStateSpec; - public static ChannelFragment getInstance(final int serviceId, final String url, final String name) { final ChannelFragment instance = new ChannelFragment(); @@ -204,9 +200,10 @@ public View onCreateView(@NonNull final LayoutInflater inflater, protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - emptyStateSpec = EmptyStateUtil.mutableStateOf( - EmptyStateSpec.Companion.getContentNotSupported()); - EmptyStateUtil.setEmptyStateComposable(binding.emptyStateView, emptyStateSpec); + EmptyStateUtil.setEmptyStateComposable( + binding.emptyStateView, + EmptyStateSpec.Companion.getContentNotSupported() + ); tabAdapter = new TabAdapter(getChildFragmentManager()); binding.viewPager.setAdapter(tabAdapter); @@ -654,10 +651,6 @@ private void showContentNotSupportedIfNeeded() { return; } - emptyStateSpec.setValue( - new EmptyStateSpecBuilder(emptyStateSpec.getValue()) - .descriptionVisibility(true) - .build() - ); + binding.emptyStateView.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index 3af217a9c0e..d67aed898d4 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -37,7 +37,6 @@ fun EmptyStateComposable( descriptionModifier = spec.descriptionModifier(), descriptionText = spec.descriptionText(), descriptionTextStyle = spec.descriptionTextStyle(), - descriptionTextVisibility = spec.descriptionVisibility(), ) @Composable @@ -49,7 +48,6 @@ private fun EmptyStateComposable( descriptionModifier: Modifier, descriptionText: String, descriptionTextStyle: TextStyle, - descriptionTextVisibility: Boolean, ) { CompositionLocalProvider( LocalContentColor provides MaterialTheme.colorScheme.errorHint @@ -65,13 +63,11 @@ private fun EmptyStateComposable( style = emojiTextStyle, ) - if (descriptionTextVisibility) { - Text( - modifier = descriptionModifier, - text = descriptionText, - style = descriptionTextStyle, - ) - } + Text( + modifier = descriptionModifier, + text = descriptionText, + style = descriptionTextStyle, + ) } } } @@ -100,9 +96,7 @@ data class EmptyStateSpec( val descriptionText: @Composable () -> String, val descriptionModifier: () -> Modifier, val descriptionTextStyle: @Composable () -> TextStyle, - val descriptionVisibility: () -> Boolean = { true }, ) { - companion object { val GenericError = @@ -162,7 +156,6 @@ data class EmptyStateSpec( descriptionModifier = { Modifier.padding(top = 20.dp) }, descriptionText = { stringResource(id = R.string.content_not_supported) }, descriptionTextStyle = { LocalTextStyle.current.merge(fontSize = 15.sp) }, - descriptionVisibility = { false }, ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt index b025bdc5ba5..526a33850ee 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -71,7 +71,7 @@ fun mutableStateOf(param: T): MutableState { * Used in Java land to modify [EmptyStateSpec] properties. * TODO: remove after Kotlin migration */ -class EmptyStateSpecBuilder(var spec: EmptyStateSpec) { +class EmptyStateSpecBuilder(private var spec: EmptyStateSpec) { fun descriptionText(@StringRes stringRes: Int) = apply { spec = spec.copy( @@ -79,9 +79,5 @@ class EmptyStateSpecBuilder(var spec: EmptyStateSpec) { ) } - fun descriptionVisibility(descriptionTextVisibility: Boolean) = apply { - spec = spec.copy(descriptionVisibility = { descriptionTextVisibility }) - } - fun build() = spec } From 1abced992bc09deef0f1c82c9a02e518a521984d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 12:07:03 +0100 Subject: [PATCH 04/11] Use normal colors for empty state view --- .../ui/emptystate/EmptyStateComposable.kt | 35 ++++++++----------- .../newpipe/ui/emptystate/EmptyStateUtil.kt | 3 -- .../newpipe/ui/theme/ColorExtensions.kt | 19 ---------- 3 files changed, 14 insertions(+), 43 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index d67aed898d4..797b2cd66e4 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -7,12 +7,10 @@ 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.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -23,7 +21,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.schabi.newpipe.R import org.schabi.newpipe.ui.theme.AppTheme -import org.schabi.newpipe.ui.theme.errorHint @Composable fun EmptyStateComposable( @@ -49,26 +46,22 @@ private fun EmptyStateComposable( descriptionText: String, descriptionTextStyle: TextStyle, ) { - CompositionLocalProvider( - LocalContentColor provides MaterialTheme.colorScheme.errorHint + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - modifier = emojiModifier, - text = emojiText, - style = emojiTextStyle, - ) + Text( + modifier = emojiModifier, + text = emojiText, + style = emojiTextStyle, + ) - Text( - modifier = descriptionModifier, - text = descriptionText, - style = descriptionTextStyle, - ) - } + Text( + modifier = descriptionModifier, + text = descriptionText, + style = descriptionTextStyle, + ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt index 526a33850ee..85ed3697e6e 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -3,7 +3,6 @@ package org.schabi.newpipe.ui.emptystate import androidx.annotation.StringRes -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.MutableState import androidx.compose.runtime.State @@ -11,7 +10,6 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.stringResource import org.schabi.newpipe.ui.theme.AppTheme -import org.schabi.newpipe.ui.theme.errorHint import androidx.compose.runtime.mutableStateOf as composeRuntimeMutableStateOf @JvmOverloads @@ -24,7 +22,6 @@ fun ComposeView.setEmptyStateText( AppTheme { Text( text = stringResource(id = stringRes), - color = MaterialTheme.colorScheme.errorHint, ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt b/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt deleted file mode 100644 index 3c057b16aef..00000000000 --- a/app/src/main/java/org/schabi/newpipe/ui/theme/ColorExtensions.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.schabi.newpipe.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.ColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color - -/** - * Extended color for error hint. - */ -val md_theme_light_error_hint = Color(0xCC000000) -val md_theme_dark_error_hint = Color(0xCCFFFFFF) - -val ColorScheme.errorHint: Color - @Composable get() = if (isSystemInDarkTheme()) { - md_theme_dark_error_hint - } else { - md_theme_light_error_hint - } From 1cd5563b271d8d8ff6281153d4cfe06e59c89419 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 12:14:40 +0100 Subject: [PATCH 05/11] All empty states now have the same style --- .../ui/emptystate/EmptyStateComposable.kt | 49 +++---------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index 797b2cd66e4..ff0f888ed8d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -7,18 +7,14 @@ 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.LocalTextStyle 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.TextStyle -import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import org.schabi.newpipe.R import org.schabi.newpipe.ui.theme.AppTheme @@ -28,23 +24,15 @@ fun EmptyStateComposable( modifier: Modifier = Modifier, ) = EmptyStateComposable( modifier = spec.modifier(modifier), - emojiModifier = spec.emojiModifier(), emojiText = spec.emojiText(), - emojiTextStyle = spec.emojiTextStyle(), - descriptionModifier = spec.descriptionModifier(), descriptionText = spec.descriptionText(), - descriptionTextStyle = spec.descriptionTextStyle(), ) @Composable private fun EmptyStateComposable( - modifier: Modifier, - emojiModifier: Modifier, emojiText: String, - emojiTextStyle: TextStyle, - descriptionModifier: Modifier, descriptionText: String, - descriptionTextStyle: TextStyle, + modifier: Modifier = Modifier, ) { Column( modifier = modifier, @@ -52,15 +40,16 @@ private fun EmptyStateComposable( verticalArrangement = Arrangement.Center ) { Text( - modifier = emojiModifier, text = emojiText, - style = emojiTextStyle, + style = MaterialTheme.typography.titleLarge, ) Text( - modifier = descriptionModifier, + modifier = Modifier + .padding(top = 6.dp) + .padding(horizontal = 16.dp), text = descriptionText, - style = descriptionTextStyle, + style = MaterialTheme.typography.bodyMedium, ) } } @@ -83,12 +72,8 @@ fun EmptyStateComposableNoCommentPreview() { data class EmptyStateSpec( val modifier: (Modifier) -> Modifier, - val emojiModifier: () -> Modifier, val emojiText: @Composable () -> String, - val emojiTextStyle: @Composable () -> TextStyle, val descriptionText: @Composable () -> String, - val descriptionModifier: () -> Modifier, - val descriptionTextStyle: @Composable () -> TextStyle, ) { companion object { @@ -99,34 +84,15 @@ data class EmptyStateSpec( .fillMaxWidth() .heightIn(min = 128.dp) }, - emojiModifier = { Modifier }, emojiText = { "¯\\_(ツ)_/¯" }, - emojiTextStyle = { MaterialTheme.typography.titleLarge }, - descriptionModifier = { - Modifier - .padding(top = 6.dp) - .padding(horizontal = 16.dp) - }, descriptionText = { stringResource(id = R.string.empty_list_subtitle) }, - descriptionTextStyle = { MaterialTheme.typography.bodyMedium } ) val NoComment = EmptyStateSpec( modifier = { it.padding(top = 85.dp) }, - emojiModifier = { Modifier.padding(bottom = 10.dp) }, emojiText = { "(╯°-°)╯" }, - emojiTextStyle = { - LocalTextStyle.current.merge( - fontFamily = FontFamily.Monospace, - fontSize = 35.sp, - ) - }, - descriptionModifier = { Modifier }, descriptionText = { stringResource(id = R.string.no_comments) }, - descriptionTextStyle = { - LocalTextStyle.current.merge(fontSize = 24.sp) - } ) val NoSearchResult = @@ -145,10 +111,7 @@ data class EmptyStateSpec( NoComment.copy( modifier = { it.padding(top = 90.dp) }, emojiText = { "(︶︹︺)" }, - emojiTextStyle = { LocalTextStyle.current.merge(fontSize = 45.sp) }, - descriptionModifier = { Modifier.padding(top = 20.dp) }, descriptionText = { stringResource(id = R.string.content_not_supported) }, - descriptionTextStyle = { LocalTextStyle.current.merge(fontSize = 15.sp) }, ) } } From 55e4014036a559f50a941bb6cc5505d20a6fa127 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 12:24:11 +0100 Subject: [PATCH 06/11] Use custom EmptyStateSpec for bookmark fragment --- .../newpipe/local/bookmark/BookmarkFragment.java | 6 +++++- .../newpipe/settings/SelectPlaylistFragment.java | 4 +++- .../ui/emptystate/EmptyStateComposable.kt | 7 +++++++ .../newpipe/ui/emptystate/EmptyStateUtil.kt | 16 ---------------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index bff6f63de9c..4f60e36aec0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -38,6 +38,7 @@ 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; @@ -124,7 +125,10 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); itemListAdapter.setUseItemHandle(true); - EmptyStateUtil.setEmptyStateComposable(rootView.findViewById(R.id.empty_state_view)); + EmptyStateUtil.setEmptyStateComposable( + rootView.findViewById(R.id.empty_state_view), + EmptyStateSpec.Companion.getNoBookmarkedPlaylist() + ); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java index efe3c4f6637..6227d95a95e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java @@ -28,6 +28,7 @@ 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; @@ -64,7 +65,8 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup recyclerView = v.findViewById(R.id.items_list); emptyView = v.findViewById(R.id.empty_state_view); - EmptyStateUtil.setEmptyStateText(emptyView, R.string.no_playlist_bookmarked_yet); + EmptyStateUtil.setEmptyStateComposable(emptyView, + EmptyStateSpec.Companion.getNoBookmarkedPlaylist()); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter(); recyclerView.setAdapter(playlistAdapter); diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index ff0f888ed8d..c76dac38c6f 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -113,5 +113,12 @@ data class EmptyStateSpec( emojiText = { "(︶︹︺)" }, descriptionText = { stringResource(id = R.string.content_not_supported) }, ) + + val NoBookmarkedPlaylist = + EmptyStateSpec( + modifier = { it }, + emojiText = { "(╥﹏╥)" }, + descriptionText = { stringResource(id = R.string.no_playlist_bookmarked_yet) }, + ) } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt index 85ed3697e6e..765ef2e6bd6 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -3,7 +3,6 @@ package org.schabi.newpipe.ui.emptystate import androidx.annotation.StringRes -import androidx.compose.material3.Text import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.ui.platform.ComposeView @@ -12,21 +11,6 @@ import androidx.compose.ui.res.stringResource import org.schabi.newpipe.ui.theme.AppTheme import androidx.compose.runtime.mutableStateOf as composeRuntimeMutableStateOf -@JvmOverloads -fun ComposeView.setEmptyStateText( - @StringRes stringRes: Int, - strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, -) = apply { - setViewCompositionStrategy(strategy) - setContent { - AppTheme { - Text( - text = stringResource(id = stringRes), - ) - } - } -} - @JvmOverloads fun ComposeView.setEmptyStateComposable( spec: EmptyStateSpec = EmptyStateSpec.GenericError, From 404d9f3fac1c8dba5f1536cc474c76a2e56d33b2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 12:42:58 +0100 Subject: [PATCH 07/11] Use empty state view in a few more places --- .../ImportSubscriptionsHintPlaceholderItem.kt | 12 ++++++--- .../settings/SelectChannelFragment.java | 8 +++++- .../ui/emptystate/EmptyStateComposable.kt | 15 +++++++++++ .../giga/ui/fragment/MissionsFragment.java | 5 +++- app/src/main/res/layout/list_empty_view.xml | 25 ------------------- .../layout/list_empty_view_subscriptions.xml | 23 ++--------------- app/src/main/res/layout/missions.xml | 9 ++++--- .../res/layout/select_channel_fragment.xml | 7 ++---- .../res/layout/select_playlist_fragment.xml | 3 +-- 9 files changed, 44 insertions(+), 63 deletions(-) delete mode 100644 app/src/main/res/layout/list_empty_view.xml diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ImportSubscriptionsHintPlaceholderItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ImportSubscriptionsHintPlaceholderItem.kt index 93b551895c5..cf0b8c3ff73 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ImportSubscriptionsHintPlaceholderItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ImportSubscriptionsHintPlaceholderItem.kt @@ -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() { +class ImportSubscriptionsHintPlaceholderItem : BindableItem() { 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) } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index c566313e37a..cbd6b0656ea 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -11,6 +11,7 @@ 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.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 subscriptions = new Vector<>(); @@ -91,6 +94,9 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup 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); diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index c76dac38c6f..3e101d7607a 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -13,6 +13,7 @@ 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 @@ -42,6 +43,7 @@ private fun EmptyStateComposable( Text( text = emojiText, style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center, ) Text( @@ -50,6 +52,7 @@ private fun EmptyStateComposable( .padding(horizontal = 16.dp), text = descriptionText, style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, ) } } @@ -120,5 +123,17 @@ data class EmptyStateSpec( 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) }, + ) } } diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 690ed4a9735..ad9a3b7cdb9 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -22,6 +22,7 @@ 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.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 View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 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 diff --git a/app/src/main/res/layout/list_empty_view.xml b/app/src/main/res/layout/list_empty_view.xml deleted file mode 100644 index a25042aed5a..00000000000 --- a/app/src/main/res/layout/list_empty_view.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/list_empty_view_subscriptions.xml b/app/src/main/res/layout/list_empty_view_subscriptions.xml index 74a5eced44b..ad182019967 100644 --- a/app/src/main/res/layout/list_empty_view_subscriptions.xml +++ b/app/src/main/res/layout/list_empty_view_subscriptions.xml @@ -1,25 +1,6 @@ - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android" /> diff --git a/app/src/main/res/layout/missions.xml b/app/src/main/res/layout/missions.xml index 641e286936c..291d19306ae 100644 --- a/app/src/main/res/layout/missions.xml +++ b/app/src/main/res/layout/missions.xml @@ -3,10 +3,11 @@ android:layout_height="match_parent" android:orientation="vertical"> - + - - + android:layout_margin="10dp" /> + android:layout_margin="10dp" /> Date: Thu, 21 Nov 2024 13:14:19 +0100 Subject: [PATCH 08/11] Remove unused methods in EmptyStateUtil --- .../newpipe/ui/emptystate/EmptyStateUtil.kt | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt index 765ef2e6bd6..422fb69fcc8 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -2,14 +2,9 @@ package org.schabi.newpipe.ui.emptystate -import androidx.annotation.StringRes -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.stringResource import org.schabi.newpipe.ui.theme.AppTheme -import androidx.compose.runtime.mutableStateOf as composeRuntimeMutableStateOf @JvmOverloads fun ComposeView.setEmptyStateComposable( @@ -25,40 +20,3 @@ fun ComposeView.setEmptyStateComposable( } } } - -@JvmOverloads -fun ComposeView.setEmptyStateComposable( - spec: State, - strategy: ViewCompositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed, -) = apply { - setViewCompositionStrategy(strategy) - setContent { - AppTheme { - EmptyStateComposable( - spec = spec.value, - ) - } - } -} - -/** - * Used in Java land to bridge the [MutableState] API. - */ -fun mutableStateOf(param: T): MutableState { - return composeRuntimeMutableStateOf(param) -} - -/** - * Used in Java land to modify [EmptyStateSpec] properties. - * TODO: remove after Kotlin migration - */ -class EmptyStateSpecBuilder(private var spec: EmptyStateSpec) { - - fun descriptionText(@StringRes stringRes: Int) = apply { - spec = spec.copy( - descriptionText = { stringResource(id = stringRes) } - ) - } - - fun build() = spec -} From c8b01a06b0caa92907b94b12248e415159caad57 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 13:14:23 +0100 Subject: [PATCH 09/11] Use empty state view in compose --- .../ui/components/common/NoItemsMessage.kt | 42 ------------ .../ui/components/video/RelatedItems.kt | 66 ++++++++++--------- .../video/comment/CommentRepliesDialog.kt | 20 ++++-- .../video/comment/CommentSection.kt | 26 ++++++-- .../ui/emptystate/EmptyStateComposable.kt | 30 +++++++-- 5 files changed, 93 insertions(+), 91 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/ui/components/common/NoItemsMessage.kt diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/NoItemsMessage.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/NoItemsMessage.kt deleted file mode 100644 index afb53fdf422..00000000000 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/NoItemsMessage.kt +++ /dev/null @@ -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) - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt index 6eee01bc031..3c6c49d356d 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt @@ -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 { + 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( - modifier = Modifier - .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + 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) - } + 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) diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt index 17ea900a544..d6d00b28cd3 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentRepliesDialog.kt @@ -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 { diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt index 33c4e21395f..d603c4a6ffe 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/video/comment/CommentSection.kt @@ -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) + } + ) + ) } } } diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt index 3e101d7607a..ab9bf6336ba 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateComposable.kt @@ -69,7 +69,7 @@ fun EmptyStateComposableGenericErrorPreview() { @Composable fun EmptyStateComposableNoCommentPreview() { AppTheme { - EmptyStateComposable(EmptyStateSpec.NoComment) + EmptyStateComposable(EmptyStateSpec.NoComments) } } @@ -91,15 +91,35 @@ data class EmptyStateSpec( descriptionText = { stringResource(id = R.string.empty_list_subtitle) }, ) - val NoComment = + val NoVideos = EmptyStateSpec( - modifier = { it.padding(top = 85.dp) }, + 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 = - NoComment.copy( + NoComments.copy( modifier = { it }, emojiText = { "╰(°●°╰)" }, descriptionText = { stringResource(id = R.string.search_no_results) } @@ -111,7 +131,7 @@ data class EmptyStateSpec( ) val ContentNotSupported = - NoComment.copy( + NoComments.copy( modifier = { it.padding(top = 90.dp) }, emojiText = { "(︶︹︺)" }, descriptionText = { stringResource(id = R.string.content_not_supported) }, From cff3834fded2a6043cc5ab87159dda413b204ac1 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Nov 2024 13:17:33 +0100 Subject: [PATCH 10/11] Fix setEmptyStateComposable dark theme --- .../schabi/newpipe/ui/emptystate/EmptyStateUtil.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt index 422fb69fcc8..2fced431fa9 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/emptystate/EmptyStateUtil.kt @@ -2,6 +2,10 @@ 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 @@ -14,9 +18,13 @@ fun ComposeView.setEmptyStateComposable( setViewCompositionStrategy(strategy) setContent { AppTheme { - EmptyStateComposable( - spec = spec - ) + CompositionLocalProvider( + LocalContentColor provides contentColorFor(MaterialTheme.colorScheme.background) + ) { + EmptyStateComposable( + spec = spec + ) + } } } } From de5d45849f1d39cb764f812db7af836717915ef8 Mon Sep 17 00:00:00 2001 From: Jie Li Date: Fri, 22 Nov 2024 20:59:39 +0000 Subject: [PATCH 11/11] migrated to version catalogs --- app/build.gradle | 201 +++++++++++++++++--------------------- build.gradle | 7 +- gradle/libs.versions.toml | 152 ++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 117 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle b/app/build.gradle index 98270fa7004..9d919c1f623 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,14 +3,14 @@ import com.android.tools.profgen.ArtProfileSerializer import com.android.tools.profgen.DexFile 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' + 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 } android { @@ -109,25 +109,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 @@ -137,7 +118,7 @@ checkstyle { getConfigDirectory().set(rootProject.file("checkstyle")) ignoreFailures false showViolations true - toolVersion = checkstyleVersion + toolVersion = libs.versions.checkstyle.get() } tasks.register('runCheckstyle', Checkstyle) { @@ -200,146 +181,140 @@ kapt { 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 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 'androidx.viewpager2:viewpager2:1.1.0-beta02' - 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.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.10.01')) - 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 // 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() { diff --git a/build.gradle b/build.gradle index 25568c22082..ec8bb05c356 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '2.0.21' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1' + classpath libs.android.tools.build.gradle + classpath libs.kotlin.gradle.plugin + classpath libs.hilt.android.gradle.plugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000000..d2ff9c058a0 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,152 @@ +[versions] +acraCore = "5.11.3" +androidState = "1.4.1" +androidx-junit = "1.1.5" +appcompat = "1.6.1" +assertjCore = "3.24.2" +auto-service = "1.1.1" +bridge = "2.0.2" +cardview = "1.0.0" +checkstyle = "10.12.1" +coil = "3.0.3" +constraintlayout = "2.1.4" +core-ktx = "1.12.0" +desugar-jdk-libs-nio = "2.0.4" +documentFile = "1.0.1" +exoplayer = "2.18.7" +fragment-compose = "1.8.2" +gradle = "8.7.1" +groupie = "2.10.1" +hilt = "2.51.1" +jetpack-compose = "2024.10.01" +jsoup = "1.17.2" +junit = "4.13.2" +kotlin = "2.0.21" +kotlinxCoroutinesRx3 = "1.8.1" +ktlint = "0.45.2" +lazycolumnscrollbar = "2.2.0" +leakcanary = "2.12" +lifecycle = "2.6.2" +localbroadcastmanager = "1.1.0" +markwon = "4.6.2" +material = "1.11.0" +media = "1.7.0" +mockitoCore = "5.6.0" +navigationCompose = "2.8.3" +okhttp = "4.12.0" +pagingCompose = "3.3.2" +preference = "1.2.1" +prettytime = "5.0.8.Final" +processPhoenix = "2.1.2" +recyclerview = "1.3.2" +room = "2.6.1" +runner = "1.5.2" +rxandroid = "3.0.2" +rxbinding = "4.0.0" +rxjava = "3.1.8" +sonarqube = "4.0.0.2929" +stetho = "1.6.0" +swiperefreshlayout = "1.1.0" +# 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/ +teamnewpipe-filepicker = "5.0.0" +teamnewpipe-newpipe-extractor = "d3d5f2b3f03a5f2b479b9f6fdf1c2555cbb9de0e" +teamnewpipe-nanojson = "1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" +viewpager2 = "1.1.0-beta02" +work = "2.8.1" + +[plugins] +android-application = { id = "com.android.application" } +checkstyle = { id = "checkstyle" } +hilt = { id = "com.google.dagger.hilt.android" } +kotlin-android = { id = "kotlin-android" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-kapt = { id = "kotlin-kapt" } +kotlin-parcelize = { id = "kotlin-parcelize" } +sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } + +[libraries] +acra-core = { group = "ch.acra", name = "acra-core", version.ref = "acraCore" } +android-state = { group = "com.evernote", name = "android-state", version.ref = "androidState" } +android-state-processor = { group = "com.evernote", name = "android-state-processor", version.ref = "androidState" } +android-tools-build-gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "gradle" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" } +androidx-compose-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "jetpack-compose" } +androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentFile" } +androidx-fragment-compose = { group = "androidx.fragment", name = "fragment-compose", version.ref = "fragment-compose" } +androidx-lifecycle-livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose" } +androidx-localbroadcastmanager = { group = "androidx.localbroadcastmanager", name = "localbroadcastmanager", version.ref = "localbroadcastmanager" } +androidx-material = { group = "com.google.android.material", name = "material", version.ref = "material" } +androidx-media = { group = "androidx.media", name = "media", version.ref = "media" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-junit" } +androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCompose" } +androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" } +androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" } +androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +androidx-room-rxjava3 = { group = "androidx.room", name = "room-rxjava3", version.ref = "room" } +androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" } +androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } +androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } +androidx-viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2" } +androidx-work-runtime = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" } +androidx-work-rxjava3 = { group = "androidx.work", name = "work-rxjava3", version.ref = "work" } +assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjCore" } +auto-service = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "auto-service" } +auto-service-kapt = { group = "com.google.auto.service", name = "auto-service", version.ref = "auto-service" } +coil-compose = { group = "io.coil-kt.coil3", name = 'coil-compose', version.ref = "coil" } +desugar-jdk-libs-nio = { group = "com.android.tools", name = "desugar_jdk_libs_nio", version.ref = "desugar-jdk-libs-nio" } +exoplayer-core = { group = "com.google.android.exoplayer", name = "exoplayer-core", version.ref = "exoplayer" } +exoplayer-database = { group = "com.google.android.exoplayer", name = "exoplayer-database", version.ref = "exoplayer" } +exoplayer-datasource = { group = "com.google.android.exoplayer", name = "exoplayer-datasource", version.ref = "exoplayer" } +exoplayer-dash = { module = "com.google.android.exoplayer:exoplayer-dash", version.ref = "exoplayer" } +exoplayer-hls = { group = "com.google.android.exoplayer", name = "exoplayer-hls", version.ref = "exoplayer" } +exoplayer-smoothstreaming = { group = "com.google.android.exoplayer", name = "exoplayer-smoothstreaming", version.ref = "exoplayer" } +exoplayer-ui = { group = "com.google.android.exoplayer", name = "exoplayer-ui", version.ref = "exoplayer" } +extension-mediasession = { group = "com.google.android.exoplayer", name = "extension-mediasession", version.ref = "exoplayer" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-android-gradle-plugin = { group = "com.google.dagger", name = "hilt-android-gradle-plugin", version.ref = "hilt" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } +jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } +kotlinx-coroutines-rx3 = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-rx3", version.ref = "kotlinxCoroutinesRx3" } +lazycolumnscrollbar = { group = "com.github.nanihadesuka", name = "LazyColumnScrollbar", version.ref = "lazycolumnscrollbar" } +leakcanary-android-core = { module = "com.squareup.leakcanary:leakcanary-android-core", version.ref = "leakcanary" } +leakcanary-object-watcher = { group = "com.squareup.leakcanary", name = "leakcanary-object-watcher-android", version.ref = "leakcanary" } +leakcanary-plumber-android = { group = "com.squareup.leakcanary", name = "plumber-android", version.ref = "leakcanary" } +lisawray-groupie = { group = "com.github.lisawray.groupie", name = "groupie", version.ref = "groupie" } +lisawray-groupie-viewbinding = { group = "com.github.lisawray.groupie", name = "groupie-viewbinding", version.ref = "groupie" } +livefront-bridge = { group = "com.github.livefront", name = "bridge", version.ref = "bridge" } +markwon-core = { group = "io.noties.markwon", name = "core", version.ref = "markwon" } +markwon-linkify = { group = "io.noties.markwon", name = "linkify", version.ref = "markwon" } +mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockitoCore" } +okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } +prettytime = { group = "org.ocpsoft.prettytime", name = "prettytime", version.ref = "prettytime" } +process-phoenix = { group = "com.jakewharton", name = "process-phoenix", version.ref = "processPhoenix" } +rxjava3-rxandroid = { group = "io.reactivex.rxjava3", name = "rxandroid", version.ref = "rxandroid" } +rxbinding4-rxbinding = { group = "com.jakewharton.rxbinding4", name = "rxbinding", version.ref = "rxbinding" } +rxjava3-rxjava = { group = "io.reactivex.rxjava3", name = "rxjava", version.ref = "rxjava" } +stetho = { group = "com.facebook.stetho", name = "stetho", version.ref = "stetho" } +stetho-okhttp3 = { group = "com.facebook.stetho", name = "stetho-okhttp3", version.ref = "stetho" } +teamnewpipe-newpipe-extractor = { group = "com.github.TeamNewPipe", name = "NewPipeExtractor", version.ref = "teamnewpipe-newpipe-extractor" } +teamnewpipe-nononsense-filepicker = { group = "com.github.TeamNewPipe", name = "NoNonsense-FilePicker", version.ref = "teamnewpipe-filepicker" } +teamnewpipe-nanojson = { group = "com.github.TeamNewPipe", name = "nanojson", version.ref = "teamnewpipe-nanojson" } +tools-checkstyle = { group = "com.puppycrawl.tools", name = "checkstyle", version.ref = "checkstyle" } +tools-ktlint = { group = "com.pinterest", name = "ktlint", version.ref = "ktlint" }