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:
parent
e0cb2892b8
commit
75292e099c
8 changed files with 170 additions and 10 deletions
19
app/sampledata/channels.json
Normal file
19
app/sampledata/channels.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"name": "BBC",
|
||||
"additional": "12K subscribers•233 videos",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -17,6 +17,7 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
|||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
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.ChannelInfoItemHolder;
|
||||
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 CHANNEL_HOLDER_TYPE = 0x201;
|
||||
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 PLAYLIST_HOLDER_TYPE = 0x301;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
} else if (useMiniVariant) {
|
||||
return MINI_CHANNEL_HOLDER_TYPE;
|
||||
|
@ -304,6 +308,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
|
|||
return new ChannelMiniInfoItemHolder(infoItemBuilder, parent);
|
||||
case CHANNEL_HOLDER_TYPE:
|
||||
return new ChannelInfoItemHolder(infoItemBuilder, parent);
|
||||
case CARD_CHANNEL_HOLDER_TYPE:
|
||||
return new ChannelCardInfoItemHolder(infoItemBuilder, parent);
|
||||
case GRID_CHANNEL_HOLDER_TYPE:
|
||||
return new ChannelGridInfoItemHolder(infoItemBuilder, parent);
|
||||
case MINI_PLAYLIST_HOLDER_TYPE:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
|||
final ChannelInfoItem item = (ChannelInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.getName());
|
||||
itemTitleView.setSelected(true);
|
||||
|
||||
final String detailLine = getDetailLine(item);
|
||||
if (detailLine == null) {
|
||||
|
@ -77,11 +78,24 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
|||
} else {
|
||||
itemChannelDescriptionView.setVisibility(View.VISIBLE);
|
||||
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
|
||||
private String getDetailLine(final ChannelInfoItem item) {
|
||||
if (item.getStreamCount() >= 0 && item.getSubscriberCount() >= 0) {
|
||||
|
|
|
@ -60,7 +60,6 @@ import org.schabi.newpipe.util.NavigationHelper
|
|||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.ServiceHelper
|
||||
import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountChannels
|
||||
import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
@ -245,7 +244,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
super.initViews(rootView, savedInstanceState)
|
||||
_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 {
|
||||
spanSizeLookup = groupAdapter.spanSizeLookup
|
||||
}
|
||||
|
@ -380,15 +379,15 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
|||
override fun handleResult(result: SubscriptionState) {
|
||||
super.handleResult(result)
|
||||
|
||||
val shouldUseGridLayout = shouldUseGridLayout(context)
|
||||
when (result) {
|
||||
is SubscriptionState.LoadedState -> {
|
||||
result.subscriptions.forEach {
|
||||
if (it is ChannelItem) {
|
||||
it.gesturesListener = listenerChannelItem
|
||||
it.itemVersion = when {
|
||||
shouldUseGridLayout -> ChannelItem.ItemVersion.GRID
|
||||
else -> ChannelItem.ItemVersion.MINI
|
||||
it.itemVersion = if (SubscriptionViewModel.shouldUseGridForSubscription(requireContext())) {
|
||||
ChannelItem.ItemVersion.GRID
|
||||
} else {
|
||||
ChannelItem.ItemVersion.MINI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
@ -8,12 +9,13 @@ import com.xwray.groupie.Group
|
|||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.processors.BehaviorProcessor
|
||||
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.subscription.item.ChannelItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardGridItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||
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
|
||||
|
||||
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
@ -22,7 +24,7 @@ class SubscriptionViewModel(application: Application) : AndroidViewModel(applica
|
|||
|
||||
// true -> list view, false -> grid view
|
||||
private val listViewMode = BehaviorProcessor.createDefault(
|
||||
!ThemeHelper.shouldUseGridLayout(application)
|
||||
!shouldUseGridForSubscription(application)
|
||||
)
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
74
app/src/main/res/layout/list_channel_card_item.xml
Normal file
74
app/src/main/res/layout/list_channel_card_item.xml
Normal 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>
|
|
@ -4,6 +4,7 @@
|
|||
<dimen name="margin_normal">16dp</dimen>
|
||||
<dimen name="margin_small">8dp</dimen>
|
||||
<dimen name="margin_large">32dp</dimen>
|
||||
<dimen name="spacing_mid">12dp</dimen>
|
||||
<dimen name="spacing_normal">8dp</dimen>
|
||||
<dimen name="spacing_micro">4dp</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_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_min_width">128dp</dimen>
|
||||
<!-- Calculated: 2*video_item_search_padding + video_item_search_thumbnail_image_height -->
|
||||
|
|
Loading…
Reference in a new issue