Skip to content

Commit 2ee4c6e

Browse files
authored
Merge pull request TeamNewPipe#9728 from mahendranv/channel_card
Larger channel cards in search results
2 parents 097c236 + 75292e0 commit 2ee4c6e

8 files changed

Lines changed: 170 additions & 10 deletions

File tree

app/sampledata/channels.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"data": [
3+
{
4+
"name": "BBC",
5+
"additional": "12K subscribers•233 videos",
6+
"description": "The BBC is the world’s leading public service broadcaster. We’re 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"
7+
},
8+
{
9+
"name": "Linus Tech Tips",
10+
"additional": "1M subscribers•233 videos",
11+
"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"
12+
},
13+
{
14+
"name": "Marques Brownlee",
15+
"additional": "13 subscribers•12K videos",
16+
"description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality!\n\nbusiness@MKBHD.com\n\nNYC"
17+
}
18+
]
19+
}

app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
1818
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
1919
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
20+
import org.schabi.newpipe.info_list.holder.ChannelCardInfoItemHolder;
2021
import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder;
2122
import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder;
2223
import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder;
@@ -73,6 +74,7 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
7374
private static final int MINI_CHANNEL_HOLDER_TYPE = 0x200;
7475
private static final int CHANNEL_HOLDER_TYPE = 0x201;
7576
private static final int GRID_CHANNEL_HOLDER_TYPE = 0x202;
77+
private static final int CARD_CHANNEL_HOLDER_TYPE = 0x203;
7678
private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300;
7779
private static final int PLAYLIST_HOLDER_TYPE = 0x301;
7880
private static final int GRID_PLAYLIST_HOLDER_TYPE = 0x302;
@@ -249,7 +251,9 @@ public int getItemViewType(int position) {
249251
return STREAM_HOLDER_TYPE;
250252
}
251253
case CHANNEL:
252-
if (itemMode == ItemViewMode.GRID) {
254+
if (itemMode == ItemViewMode.CARD) {
255+
return CARD_CHANNEL_HOLDER_TYPE;
256+
} else if (itemMode == ItemViewMode.GRID) {
253257
return GRID_CHANNEL_HOLDER_TYPE;
254258
} else if (useMiniVariant) {
255259
return MINI_CHANNEL_HOLDER_TYPE;
@@ -304,6 +308,8 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup paren
304308
return new ChannelMiniInfoItemHolder(infoItemBuilder, parent);
305309
case CHANNEL_HOLDER_TYPE:
306310
return new ChannelInfoItemHolder(infoItemBuilder, parent);
311+
case CARD_CHANNEL_HOLDER_TYPE:
312+
return new ChannelCardInfoItemHolder(infoItemBuilder, parent);
307313
case GRID_CHANNEL_HOLDER_TYPE:
308314
return new ChannelGridInfoItemHolder(infoItemBuilder, parent);
309315
case MINI_PLAYLIST_HOLDER_TYPE:
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.schabi.newpipe.info_list.holder;
2+
3+
import android.view.ViewGroup;
4+
5+
import androidx.annotation.Nullable;
6+
7+
import org.schabi.newpipe.R;
8+
import org.schabi.newpipe.info_list.InfoItemBuilder;
9+
10+
public class ChannelCardInfoItemHolder extends ChannelMiniInfoItemHolder {
11+
public ChannelCardInfoItemHolder(final InfoItemBuilder infoItemBuilder,
12+
final ViewGroup parent) {
13+
super(infoItemBuilder, R.layout.list_channel_card_item, parent);
14+
}
15+
16+
@Override
17+
protected int getDescriptionMaxLineCount(@Nullable final String content) {
18+
// Based on `list_channel_card_item` left side content (thumbnail 100dp
19+
// + additional details), Right side description can grow up to 8 lines.
20+
return 8;
21+
}
22+
}

app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public void updateFromItem(final InfoItem infoItem,
4646
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
4747

4848
itemTitleView.setText(item.getName());
49+
itemTitleView.setSelected(true);
4950

5051
final String detailLine = getDetailLine(item);
5152
if (detailLine == null) {
@@ -77,11 +78,24 @@ public void updateFromItem(final InfoItem infoItem,
7778
} else {
7879
itemChannelDescriptionView.setVisibility(View.VISIBLE);
7980
itemChannelDescriptionView.setText(item.getDescription());
80-
itemChannelDescriptionView.setMaxLines(detailLine == null ? 3 : 2);
81+
// setMaxLines utilize the line space for description if the additional details
82+
// (sub / video count) are not present.
83+
// Case1: 2 lines of description + 1 line additional details
84+
// Case2: 3 lines of description (additionalDetails is GONE)
85+
itemChannelDescriptionView.setMaxLines(getDescriptionMaxLineCount(detailLine));
8186
}
8287
}
8388
}
8489

90+
/**
91+
* Returns max number of allowed lines for the description field.
92+
* @param content additional detail content (video / sub count)
93+
* @return max line count
94+
*/
95+
protected int getDescriptionMaxLineCount(@Nullable final String content) {
96+
return content == null ? 3 : 2;
97+
}
98+
8599
@Nullable
86100
private String getDetailLine(final ChannelInfoItem item) {
87101
if (item.getStreamCount() >= 0 && item.getSubscriberCount() >= 0) {

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ import org.schabi.newpipe.util.NavigationHelper
6060
import org.schabi.newpipe.util.OnClickGesture
6161
import org.schabi.newpipe.util.ServiceHelper
6262
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels
63-
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
6463
import org.schabi.newpipe.util.external_communication.ShareUtils
6564
import java.text.SimpleDateFormat
6665
import java.util.Date
@@ -245,7 +244,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
245244
super.initViews(rootView, savedInstanceState)
246245
_binding = FragmentSubscriptionBinding.bind(rootView)
247246

248-
groupAdapter.spanCount = if (shouldUseGridLayout(context)) getGridSpanCountChannels(context) else 1
247+
groupAdapter.spanCount = if (SubscriptionViewModel.shouldUseGridForSubscription(requireContext())) getGridSpanCountChannels(context) else 1
249248
binding.itemsList.layoutManager = GridLayoutManager(requireContext(), groupAdapter.spanCount).apply {
250249
spanSizeLookup = groupAdapter.spanSizeLookup
251250
}
@@ -380,15 +379,15 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
380379
override fun handleResult(result: SubscriptionState) {
381380
super.handleResult(result)
382381

383-
val shouldUseGridLayout = shouldUseGridLayout(context)
384382
when (result) {
385383
is SubscriptionState.LoadedState -> {
386384
result.subscriptions.forEach {
387385
if (it is ChannelItem) {
388386
it.gesturesListener = listenerChannelItem
389-
it.itemVersion = when {
390-
shouldUseGridLayout -> ChannelItem.ItemVersion.GRID
391-
else -> ChannelItem.ItemVersion.MINI
387+
it.itemVersion = if (SubscriptionViewModel.shouldUseGridForSubscription(requireContext())) {
388+
ChannelItem.ItemVersion.GRID
389+
} else {
390+
ChannelItem.ItemVersion.MINI
392391
}
393392
}
394393
}

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionViewModel.kt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
package org.schabi.newpipe.local.subscription
22

33
import android.app.Application
4+
import android.content.Context
45
import androidx.lifecycle.AndroidViewModel
56
import androidx.lifecycle.LiveData
67
import androidx.lifecycle.MutableLiveData
78
import com.xwray.groupie.Group
89
import io.reactivex.rxjava3.core.Flowable
910
import io.reactivex.rxjava3.processors.BehaviorProcessor
1011
import io.reactivex.rxjava3.schedulers.Schedulers
12+
import org.schabi.newpipe.info_list.ItemViewMode
1113
import org.schabi.newpipe.local.feed.FeedDatabaseManager
1214
import org.schabi.newpipe.local.subscription.item.ChannelItem
1315
import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem
1416
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
1517
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
16-
import org.schabi.newpipe.util.ThemeHelper
18+
import org.schabi.newpipe.util.ThemeHelper.getItemViewMode
1719
import java.util.concurrent.TimeUnit
1820

1921
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
@@ -22,7 +24,7 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
2224

2325
// true -> list view, false -> grid view
2426
private val listViewMode = BehaviorProcessor.createDefault(
25-
!ThemeHelper.shouldUseGridLayout(application)
27+
!shouldUseGridForSubscription(application)
2628
)
2729
private val listViewModeFlowable = listViewMode.distinctUntilChanged()
2830

@@ -77,4 +79,26 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
7779
data class LoadedState(val subscriptions: List<Group>) : SubscriptionState()
7880
data class ErrorState(val error: Throwable? = null) : SubscriptionState()
7981
}
82+
83+
companion object {
84+
85+
/**
86+
* Returns whether to use GridLayout mode for Subscription Fragment.
87+
*
88+
* ### Current mapping:
89+
*
90+
* | ItemViewMode | ItemVersion | Span count |
91+
* |---|---|---|
92+
* | AUTO | MINI | 1 |
93+
* | LIST | MINI | 1 |
94+
* | CARD | GRID | > 1 (ThemeHelper defined) |
95+
* | GRID | GRID | > 1 (ThemeHelper defined) |
96+
*
97+
* @see [SubscriptionViewModel.shouldUseGridForSubscription] to modify Layout Manager
98+
*/
99+
fun shouldUseGridForSubscription(context: Context): Boolean {
100+
val itemViewMode = getItemViewMode(context)
101+
return itemViewMode == ItemViewMode.GRID || itemViewMode == ItemViewMode.CARD
102+
}
103+
}
80104
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:id="@+id/itemRoot"
6+
android:layout_width="match_parent"
7+
android:layout_height="wrap_content"
8+
android:background="?attr/selectableItemBackground"
9+
android:clickable="true"
10+
android:focusable="true"
11+
android:padding="@dimen/channel_item_grid_padding">
12+
13+
<com.google.android.material.imageview.ShapeableImageView
14+
android:id="@+id/itemThumbnailView"
15+
android:layout_width="@dimen/channel_item_card_thumbnail_image_size"
16+
android:layout_height="@dimen/channel_item_card_thumbnail_image_size"
17+
android:layout_centerHorizontal="true"
18+
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
19+
android:src="@drawable/placeholder_person"
20+
app:layout_constraintStart_toStartOf="parent"
21+
app:layout_constraintTop_toTopOf="parent"
22+
app:shapeAppearance="@style/CircularImageView"
23+
tools:ignore="RtlHardcoded"
24+
tools:src="@tools:sample/avatars" />
25+
26+
<TextView
27+
android:id="@+id/itemTitleView"
28+
android:layout_width="0dp"
29+
android:layout_height="wrap_content"
30+
android:layout_marginStart="@dimen/spacing_mid"
31+
android:ellipsize="end"
32+
android:lines="1"
33+
android:textAppearance="?android:attr/textAppearanceLarge"
34+
android:textSize="@dimen/video_item_search_title_text_size"
35+
android:textStyle="normal"
36+
app:layout_constraintEnd_toEndOf="parent"
37+
app:layout_constraintStart_toEndOf="@id/itemThumbnailView"
38+
app:layout_constraintTop_toTopOf="@id/itemThumbnailView"
39+
tools:ignore="RtlHardcoded"
40+
tools:text="@sample/channels.json/data/name" />
41+
42+
<TextView
43+
android:id="@+id/itemChannelDescriptionView"
44+
android:layout_width="0dp"
45+
android:layout_height="wrap_content"
46+
android:layout_below="@id/itemTitleView"
47+
android:layout_centerHorizontal="true"
48+
android:ellipsize="end"
49+
android:maxLines="8"
50+
android:textAppearance="?android:attr/textAppearanceSmall"
51+
android:textSize="@dimen/video_item_search_upload_date_text_size"
52+
app:layout_constraintEnd_toEndOf="parent"
53+
app:layout_constraintStart_toStartOf="@id/itemTitleView"
54+
app:layout_constraintTop_toBottomOf="@id/itemTitleView"
55+
tools:ignore="RtlHardcoded"
56+
tools:text="@sample/channels.json/data/description" />
57+
58+
<TextView
59+
android:id="@+id/itemAdditionalDetails"
60+
android:layout_width="0dp"
61+
android:layout_height="wrap_content"
62+
android:layout_marginTop="@dimen/spacing_micro"
63+
android:gravity="center"
64+
android:lines="2"
65+
android:textAppearance="?android:attr/textAppearanceSmall"
66+
android:textSize="@dimen/video_item_search_upload_date_text_size"
67+
android:textStyle="normal"
68+
app:layout_constraintEnd_toEndOf="@id/itemThumbnailView"
69+
app:layout_constraintStart_toStartOf="@id/itemThumbnailView"
70+
app:layout_constraintTop_toBottomOf="@id/itemThumbnailView"
71+
tools:ignore="RtlHardcoded"
72+
tools:text="@sample/channels.json/data/additional" />
73+
74+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/values/dimens.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<dimen name="margin_normal">16dp</dimen>
55
<dimen name="margin_small">8dp</dimen>
66
<dimen name="margin_large">32dp</dimen>
7+
<dimen name="spacing_mid">12dp</dimen>
78
<dimen name="spacing_normal">8dp</dimen>
89
<dimen name="spacing_micro">4dp</dimen>
910
<dimen name="spacing_nano">2dp</dimen>
@@ -38,6 +39,7 @@
3839
<dimen name="video_item_grid_thumbnail_image_width">164dp</dimen>
3940
<dimen name="video_item_grid_thumbnail_image_height">92dp</dimen>
4041

42+
<dimen name="channel_item_card_thumbnail_image_size">100dp</dimen>
4143
<dimen name="channel_item_grid_thumbnail_image_size">92dp</dimen>
4244
<dimen name="channel_item_grid_min_width">128dp</dimen>
4345
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->

0 commit comments

Comments
 (0)