Larger channel cards in search results

- Thumbnail larger (100dp) than the usual (92dp) throughout the app
- Description lint count is 8 (normally 3)
This commit is contained in:
Mahendran 2023-01-29 17:44:46 +05:30
parent e0cb2892b8
commit 75292e099c
8 changed files with 170 additions and 10 deletions

View File

@ -0,0 +1,19 @@
{
"data": [
{
"name": "BBC",
"additional": "12K subscribers•233 videos",
"description": "The BBC is the worlds leading public service broadcaster. Were impartial and independent, and every day we create distinctive, world-class programmes and content which inform, educate and entertain millions of people in the UK and around the world. SUBSCRIBE to our YouTube channel to get the best of BBC entertainment and comedy programmes, stories from science and nature documentaries, and much more! https://bit.ly/2IXqEIn Get ALL your fresh TV, and sofa-hugging box sets on iPlayer https://bbc.in/2J18jYJ"
},
{
"name": "Linus Tech Tips",
"additional": "1M subscribers•233 videos",
"description": "Looking for a Tech YouTuber?\n\nLinus Tech Tips is a passionate team of \"professionally curious\" experts in consumer technology and video production which aims to inform and educate people of all ages through our entertaining videos. We create product reviews, step-by-step computer build guides, and a variety of other tech-focused content.\n\nSchedule:\nNew videos every Saturday to Thursday @ 10:00am Pacific\nLive WAN Show podcasts every Friday @ ~5:00pm Pacific"
},
{
"name": "Marques Brownlee",
"additional": "13 subscribers•12K videos",
"description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality!\n\nbusiness@MKBHD.com\n\nNYC"
}
]
}

View File

@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.info_list.holder.ChannelCardInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder; import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder; import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
@ -73,6 +74,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200; private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200;
private static final int CHANNEL_HOLDER_TYPE = 0x201; private static final int CHANNEL_HOLDER_TYPE = 0x201;
private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202; private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202;
private static final int CARD_CHANNEL_HOLDER_TYPE = 0x203;
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300; private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
private static final int PLAYLIST_HOLDER_TYPE = 0x301; private static final int PLAYLIST_HOLDER_TYPE = 0x301;
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302; private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
@ -249,7 +251,9 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return STREAM_HOLDER_TYPE; return STREAM_HOLDER_TYPE;
} }
case CHANNEL: case CHANNEL:
if (itemMode == ItemViewMode.GRID) { if (itemMode == ItemViewMode.CARD) {
return CARD_CHANNEL_HOLDER_TYPE;
} else if (itemMode == ItemViewMode.GRID) {
return GRID_CHANNEL_HOLDER_TYPE; return GRID_CHANNEL_HOLDER_TYPE;
} else if (useMiniVariant) { } else if (useMiniVariant) {
return MINI_CHANNEL_HOLDER_TYPE; return MINI_CHANNEL_HOLDER_TYPE;
@ -304,6 +308,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
return new ChannelMiniInfoItemHolder(infoItemBuilder, parent); return new ChannelMiniInfoItemHolder(infoItemBuilder, parent);
case CHANNEL_HOLDER_TYPE: case CHANNEL_HOLDER_TYPE:
return new ChannelInfoItemHolder(infoItemBuilder, parent); return new ChannelInfoItemHolder(infoItemBuilder, parent);
case CARD_CHANNEL_HOLDER_TYPE:
return new ChannelCardInfoItemHolder(infoItemBuilder, parent);
case GRID_CHANNEL_HOLDER_TYPE: case GRID_CHANNEL_HOLDER_TYPE:
return new ChannelGridInfoItemHolder(infoItemBuilder, parent); return new ChannelGridInfoItemHolder(infoItemBuilder, parent);
case MINI_PLAYLIST_HOLDER_TYPE: case MINI_PLAYLIST_HOLDER_TYPE:

View File

@ -0,0 +1,22 @@
package org.schabi.newpipe.info_list.holder;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import org.schabi.newpipe.info_list.InfoItemBuilder;
public class ChannelCardInfoItemHolder extends ChannelMiniInfoItemHolder {
public ChannelCardInfoItemHolder(final InfoItemBuilder infoItemBuilder,
final ViewGroup parent) {
super(infoItemBuilder, R.layout.list_channel_card_item, parent);
}
@Override
protected int getDescriptionMaxLineCount(@Nullable final String content) {
// Based on `list_channel_card_item` left side content (thumbnail 100dp
// + additional details), Right side description can grow up to 8 lines.
return 8;
}
}

View File

@ -46,6 +46,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
final ChannelInfoItem item = (ChannelInfoItem) infoItem; final ChannelInfoItem item = (ChannelInfoItem) infoItem;
itemTitleView.setText(item.getName()); itemTitleView.setText(item.getName());
itemTitleView.setSelected(true);
final String detailLine = getDetailLine(item); final String detailLine = getDetailLine(item);
if (detailLine == null) { if (detailLine == null) {
@ -77,11 +78,24 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
} else { } else {
itemChannelDescriptionView.setVisibility(View.VISIBLE); itemChannelDescriptionView.setVisibility(View.VISIBLE);
itemChannelDescriptionView.setText(item.getDescription()); itemChannelDescriptionView.setText(item.getDescription());
itemChannelDescriptionView.setMaxLines(detailLine == null ? 3 : 2); // setMaxLines utilize the line space for description if the additional details
// (sub / video count) are not present.
// Case1: 2 lines of description + 1 line additional details
// Case2: 3 lines of description (additionalDetails is GONE)
itemChannelDescriptionView.setMaxLines(getDescriptionMaxLineCount(detailLine));
} }
} }
} }
/**
* Returns max number of allowed lines for the description field.
* @param content additional detail content (video / sub count)
* @return max line count
*/
protected int getDescriptionMaxLineCount(@Nullable final String content) {
return content == null ? 3 : 2;
}
@Nullable @Nullable
private String getDetailLine(final ChannelInfoItem item) { private String getDetailLine(final ChannelInfoItem item) {
if (item.getStreamCount() >= 0 && item.getSubscriberCount() >= 0) { if (item.getStreamCount() >= 0 && item.getSubscriberCount() >= 0) {

View File

@ -60,7 +60,6 @@ import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ServiceHelper import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
import org.schabi.newpipe.util.external_communication.ShareUtils import org.schabi.newpipe.util.external_communication.ShareUtils
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -245,7 +244,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
super.initViews(rootView, savedInstanceState) super.initViews(rootView, savedInstanceState)
_binding = FragmentSubscriptionBinding.bind(rootView) _binding = FragmentSubscriptionBinding.bind(rootView)
groupAdapter.spanCount = if (shouldUseGridLayout(context)) getGridSpanCountChannels(context) else 1 groupAdapter.spanCount = if (SubscriptionViewModel.shouldUseGridForSubscription(requireContext())) getGridSpanCountChannels(context) else 1
binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply { binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
spanSizeLookup = groupAdapter.spanSizeLookup spanSizeLookup = groupAdapter.spanSizeLookup
} }
@ -380,15 +379,15 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
override fun handleResult(result: SubscriptionState) { override fun handleResult(result: SubscriptionState) {
super.handleResult(result) super.handleResult(result)
val shouldUseGridLayout = shouldUseGridLayout(context)
when (result) { when (result) {
is SubscriptionState.LoadedState -> { is SubscriptionState.LoadedState -> {
result.subscriptions.forEach { result.subscriptions.forEach {
if (it is ChannelItem) { if (it is ChannelItem) {
it.gesturesListener = listenerChannelItem it.gesturesListener = listenerChannelItem
it.itemVersion = when { it.itemVersion = if (SubscriptionViewModel.shouldUseGridForSubscription(requireContext())) {
shouldUseGridLayout -> ChannelItem.ItemVersion.GRID ChannelItem.ItemVersion.GRID
else -> ChannelItem.ItemVersion.MINI } else {
ChannelItem.ItemVersion.MINI
} }
} }
} }

View File

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.subscription package org.schabi.newpipe.local.subscription
import android.app.Application import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -8,12 +9,13 @@ import com.xwray.groupie.Group
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.processors.BehaviorProcessor
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.local.feed.FeedDatabaseManager import org.schabi.newpipe.local.feed.FeedDatabaseManager
import org.schabi.newpipe.local.subscription.item.ChannelItem import org.schabi.newpipe.local.subscription.item.ChannelItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.ThemeHelper.getItemViewMode
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) { class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
@ -22,7 +24,7 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
// true -> list view, false -> grid view // true -> list view, false -> grid view
private val listViewMode = BehaviorProcessor.createDefault( private val listViewMode = BehaviorProcessor.createDefault(
!ThemeHelper.shouldUseGridLayout(application) !shouldUseGridForSubscription(application)
) )
private val listViewModeFlowable = listViewMode.distinctUntilChanged() private val listViewModeFlowable = listViewMode.distinctUntilChanged()
@ -77,4 +79,26 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
data class LoadedState(val subscriptions: List<Group>) : SubscriptionState() data class LoadedState(val subscriptions: List<Group>) : SubscriptionState()
data class ErrorState(val error: Throwable? = null) : SubscriptionState() data class ErrorState(val error: Throwable? = null) : SubscriptionState()
} }
companion object {
/**
* Returns whether to use GridLayout mode for Subscription Fragment.
*
* ### Current mapping:
*
* | ItemViewMode | ItemVersion | Span count |
* |---|---|---|
* | AUTO | MINI | 1 |
* | LIST | MINI | 1 |
* | CARD | GRID | > 1 (ThemeHelper defined) |
* | GRID | GRID | > 1 (ThemeHelper defined) |
*
* @see [SubscriptionViewModel.shouldUseGridForSubscription] to modify Layout Manager
*/
fun shouldUseGridForSubscription(context: Context): Boolean {
val itemViewMode = getItemViewMode(context)
return itemViewMode == ItemViewMode.GRID || itemViewMode == ItemViewMode.CARD
}
}
} }

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/channel_item_grid_padding">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/itemThumbnailView"
android:layout_width="@dimen/channel_item_card_thumbnail_image_size"
android:layout_height="@dimen/channel_item_card_thumbnail_image_size"
android:layout_centerHorizontal="true"
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:src="@drawable/placeholder_person"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearance="@style/CircularImageView"
tools:ignore="RtlHardcoded"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/itemTitleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_mid"
android:ellipsize="end"
android:lines="1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="@dimen/video_item_search_title_text_size"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/itemThumbnailView"
app:layout_constraintTop_toTopOf="@id/itemThumbnailView"
tools:ignore="RtlHardcoded"
tools:text="@sample/channels.json/data/name" />
<TextView
android:id="@+id/itemChannelDescriptionView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/itemTitleView"
android:layout_centerHorizontal="true"
android:ellipsize="end"
android:maxLines="8"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/itemTitleView"
app:layout_constraintTop_toBottomOf="@id/itemTitleView"
tools:ignore="RtlHardcoded"
tools:text="@sample/channels.json/data/description" />
<TextView
android:id="@+id/itemAdditionalDetails"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_micro"
android:gravity="center"
android:lines="2"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="@dimen/video_item_search_upload_date_text_size"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="@id/itemThumbnailView"
app:layout_constraintStart_toStartOf="@id/itemThumbnailView"
app:layout_constraintTop_toBottomOf="@id/itemThumbnailView"
tools:ignore="RtlHardcoded"
tools:text="@sample/channels.json/data/additional" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,6 +4,7 @@
<dimen name="margin_normal">16dp</dimen> <dimen name="margin_normal">16dp</dimen>
<dimen name="margin_small">8dp</dimen> <dimen name="margin_small">8dp</dimen>
<dimen name="margin_large">32dp</dimen> <dimen name="margin_large">32dp</dimen>
<dimen name="spacing_mid">12dp</dimen>
<dimen name="spacing_normal">8dp</dimen> <dimen name="spacing_normal">8dp</dimen>
<dimen name="spacing_micro">4dp</dimen> <dimen name="spacing_micro">4dp</dimen>
<dimen name="spacing_nano">2dp</dimen> <dimen name="spacing_nano">2dp</dimen>
@ -38,6 +39,7 @@
<dimen name="video_item_grid_thumbnail_image_width">164dp</dimen> <dimen name="video_item_grid_thumbnail_image_width">164dp</dimen>
<dimen name="video_item_grid_thumbnail_image_height">92dp</dimen> <dimen name="video_item_grid_thumbnail_image_height">92dp</dimen>
<dimen name="channel_item_card_thumbnail_image_size">100dp</dimen>
<dimen name="channel_item_grid_thumbnail_image_size">92dp</dimen> <dimen name="channel_item_grid_thumbnail_image_size">92dp</dimen>
<dimen name="channel_item_grid_min_width">128dp</dimen> <dimen name="channel_item_grid_min_width">128dp</dimen>
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height --> <!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->