diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 9d57617d3..0c5b002e3 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.fragments.list.playlist; -import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; @@ -24,10 +23,7 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; -import org.schabi.newpipe.player.BackgroundPlayer; -import org.schabi.newpipe.player.MainVideoPlayer; -import org.schabi.newpipe.player.PopupVideoPlayer; -import org.schabi.newpipe.playlist.ExternalPlayQueue; +import org.schabi.newpipe.playlist.PlaylistPlayQueue; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; @@ -185,7 +181,7 @@ public class PlaylistFragment extends BaseListInfoFragment { } private PlayQueue getPlayQueue() { - return new ExternalPlayQueue( + return new PlaylistPlayQueue( currentInfo.service_id, currentInfo.url, currentInfo.next_streams_url, diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 9aef6dbd2..30dc3d8bc 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -1,7 +1,11 @@ package org.schabi.newpipe.info_list.holder; +import android.content.Context; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.PopupMenu; import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -10,7 +14,9 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.playlist.ChannelPlayQueue; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.NavigationHelper; import de.hdodenhof.circleimageview.CircleImageView; @@ -18,6 +24,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { public final CircleImageView itemThumbnailView; public final TextView itemTitleView; public final TextView itemAdditionalDetailView; + public final ImageButton itemActionDropdown; ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { super(infoItemBuilder, layoutId, parent); @@ -25,6 +32,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); itemTitleView = itemView.findViewById(R.id.itemTitleView); itemAdditionalDetailView = itemView.findViewById(R.id.itemAdditionalDetails); + itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown); } public ChannelMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { @@ -50,6 +58,55 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { } } }); + + enableActionDropdown(item); + } + + private void enableActionDropdown(final ChannelInfoItem item) { + itemActionDropdown.setVisibility(View.VISIBLE); + itemActionDropdown.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item); + if (itemBuilder.getOnChannelSelectedListener() != null) { + itemBuilder.getOnChannelSelectedListener().dropdownClicked(item, actionMenu); + } + actionMenu.show(); + } + }); + } + + private PopupMenu getStreamDropdown(final Context context, final View anchor, final ChannelInfoItem infoItem) { + PopupMenu actionMenu = new PopupMenu(context, anchor); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); + mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnMainPlayer(context, new ChannelPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title); + popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnPopupPlayer(context, new ChannelPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title); + backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnBackgroundPlayer(context, new ChannelPlayQueue(infoItem)); + return true; + } + }); + + return actionMenu; } protected String getDetailLine(final ChannelInfoItem item) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java index 3c29a4b76..5e0f44939 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistInfoItemHolder.java @@ -1,8 +1,12 @@ package org.schabi.newpipe.info_list.holder; +import android.content.Context; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.PopupMenu; import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -11,12 +15,15 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.playlist.PlaylistPlayQueue; +import org.schabi.newpipe.util.NavigationHelper; public class PlaylistInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; public final TextView itemStreamCountView; public final TextView itemTitleView; public final TextView itemUploaderView; + public final ImageButton itemActionDropdown; public PlaylistInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { super(infoItemBuilder, R.layout.list_playlist_item, parent); @@ -25,6 +32,7 @@ public class PlaylistInfoItemHolder extends InfoItemHolder { itemTitleView = itemView.findViewById(R.id.itemTitleView); itemStreamCountView = itemView.findViewById(R.id.itemStreamCountView); itemUploaderView = itemView.findViewById(R.id.itemUploaderView); + itemActionDropdown = itemView.findViewById(R.id.itemActionDropdown); } @Override @@ -47,8 +55,56 @@ public class PlaylistInfoItemHolder extends InfoItemHolder { } } }); + + enableActionDropdown(item); } + private void enableActionDropdown(final PlaylistInfoItem item) { + itemActionDropdown.setVisibility(View.VISIBLE); + itemActionDropdown.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final PopupMenu actionMenu = getStreamDropdown(itemBuilder.getContext(), itemActionDropdown, item); + if (itemBuilder.getOnPlaylistSelectedListener() != null) { + itemBuilder.getOnPlaylistSelectedListener().dropdownClicked(item, actionMenu); + } + actionMenu.show(); + } + }); + } + + private PopupMenu getStreamDropdown(final Context context, final View anchor, final PlaylistInfoItem infoItem) { + PopupMenu actionMenu = new PopupMenu(context, anchor); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); + mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnMainPlayer(context, new PlaylistPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupPlay = actionMenu.getMenu().add(R.string.controls_popup_title); + popupPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnPopupPlayer(context, new PlaylistPlayQueue(infoItem)); + return true; + } + }); + + final MenuItem backgroundPlay = actionMenu.getMenu().add(R.string.controls_background_title); + backgroundPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.playOnBackgroundPlayer(context, new PlaylistPlayQueue(infoItem)); + return true; + } + }); + + return actionMenu; + } /** * Display options for playlist thumbnails */ diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 1dea9633b..6c399e481 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.info_list.holder; import android.content.Context; import android.support.v4.content.ContextCompat; -import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -10,7 +9,6 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; -import android.widget.Toast; import com.nostra13.universalimageloader.core.DisplayImageOptions; @@ -19,9 +17,6 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.info_list.InfoItemBuilder; -import org.schabi.newpipe.player.BackgroundPlayer; -import org.schabi.newpipe.player.PopupVideoPlayer; -import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -91,11 +86,13 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { case AUDIO_LIVE_STREAM: case NONE: default: + disableActionDropdown(); break; } } private void enableActionDropdown(final StreamInfoItem item) { + itemActionDropdown.setClickable(true); itemActionDropdown.setVisibility(View.VISIBLE); itemActionDropdown.setOnClickListener(new View.OnClickListener() { @Override @@ -109,10 +106,34 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { }); } + private void disableActionDropdown() { + itemActionDropdown.setVisibility(View.GONE); + itemActionDropdown.setClickable(false); + itemActionDropdown.setOnClickListener(null); + } + private PopupMenu getStreamDropdown(final Context context, final View anchor, final StreamInfoItem infoItem) { PopupMenu actionMenu = new PopupMenu(context, anchor); - final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_btn_text); + final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background); + backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup); + popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem)); + return true; + } + }); + + final MenuItem mainPlay = actionMenu.getMenu().add(R.string.play_all); mainPlay.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { @@ -139,24 +160,6 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { } }); - final MenuItem backgroundEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_background); - backgroundEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); - return true; - } - }); - - final MenuItem popupEnqueue = actionMenu.getMenu().add(R.string.enqueue_on_popup); - popupEnqueue.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem menuItem) { - NavigationHelper.enqueueOnPopupPlayer(context, new SinglePlayQueue(infoItem)); - return true; - } - }); - return actionMenu; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 2e4e4af5c..04f1606fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -157,7 +157,7 @@ public class MediaSourceManager { } private void onPlayQueueChanged(final PlayQueueEvent event) { - if (playQueue.isEmpty()) { + if (playQueue.isEmpty() && playQueue.isComplete()) { playbackListener.shutdown(); return; } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java new file mode 100644 index 000000000..c48f76127 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java @@ -0,0 +1,151 @@ +package org.schabi.newpipe.playlist; + +import android.util.Log; + +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.util.ExtractorHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.reactivex.SingleObserver; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.annotations.NonNull; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public final class ChannelPlayQueue extends PlayQueue { + private final String TAG = "ChannelPlayQueue@" + Integer.toHexString(hashCode()); + + private boolean isInitial; + private boolean isComplete; + + private int serviceId; + private String baseUrl; + private String nextUrl; + + private transient Disposable fetchReactor; + + public ChannelPlayQueue(final ChannelInfoItem item) { + this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + } + + public ChannelPlayQueue(final int serviceId, + final String url, + final String nextPageUrl, + final List streams, + final int index) { + super(index, extractChannelItems(streams)); + + this.baseUrl = url; + this.nextUrl = nextPageUrl; + this.serviceId = serviceId; + + this.isInitial = streams.isEmpty(); + this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); + } + + @Override + public boolean isComplete() { + return isComplete; + } + + @Override + public void fetch() { + if (isInitial) { + ExtractorHelper.getChannelInfo(this.serviceId, this.baseUrl, false) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getChannelInitialObserver()); + } else { + ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getChannelNextItemsObserver()); + } + } + + private SingleObserver getChannelInitialObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull ChannelInfo result) { + if (!result.has_more_streams) isComplete = true; + nextUrl = result.next_streams_url; + + append(extractChannelItems(result.related_streams)); + + isInitial = false; + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + private SingleObserver getChannelNextItemsObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull ListExtractor.NextItemsResult result) { + if (!result.hasMoreStreams()) isComplete = true; + nextUrl = result.nextItemsUrl; + + append(extractChannelItems(result.nextItemsList)); + + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + @Override + public void dispose() { + super.dispose(); + if (fetchReactor != null) fetchReactor.dispose(); + } + + private static List extractChannelItems(final List infos) { + List result = new ArrayList<>(); + for (final InfoItem stream : infos) { + if (stream instanceof StreamInfoItem) { + result.add(new PlayQueueItem((StreamInfoItem) stream)); + } + } + return result; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java similarity index 55% rename from app/src/main/java/org/schabi/newpipe/playlist/ExternalPlayQueue.java rename to app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java index 019b684d4..39ac6517d 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/ExternalPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java @@ -4,6 +4,8 @@ import android.util.Log; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.playlist.PlaylistInfo; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.util.ExtractorHelper; @@ -17,9 +19,10 @@ import io.reactivex.annotations.NonNull; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -public final class ExternalPlayQueue extends PlayQueue { - private final String TAG = "ExternalPlayQueue@" + Integer.toHexString(hashCode()); +public final class PlaylistPlayQueue extends PlayQueue { + private final String TAG = "PlaylistPlayQueue@" + Integer.toHexString(hashCode()); + private boolean isInitial; private boolean isComplete; private int serviceId; @@ -28,7 +31,11 @@ public final class ExternalPlayQueue extends PlayQueue { private transient Disposable fetchReactor; - public ExternalPlayQueue(final int serviceId, + public PlaylistPlayQueue(final PlaylistInfoItem item) { + this(item.service_id, item.url, item.url, Collections.emptyList(), 0); + } + + public PlaylistPlayQueue(final int serviceId, final String url, final String nextPageUrl, final List streams, @@ -39,7 +46,8 @@ public final class ExternalPlayQueue extends PlayQueue { this.nextUrl = nextPageUrl; this.serviceId = serviceId; - this.isComplete = nextPageUrl == null || nextPageUrl.isEmpty(); + this.isInitial = streams.isEmpty(); + this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty()); } @Override @@ -49,13 +57,51 @@ public final class ExternalPlayQueue extends PlayQueue { @Override public void fetch() { - ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(getPlaylistObserver()); + if (isInitial) { + ExtractorHelper.getPlaylistInfo(this.serviceId, this.baseUrl, false) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getInitialPlaylistObserver()); + } else { + ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getPlaylistNextItemsObserver()); + } } - private SingleObserver getPlaylistObserver() { + private SingleObserver getInitialPlaylistObserver() { + return new SingleObserver() { + @Override + public void onSubscribe(@NonNull Disposable d) { + if (isComplete || (fetchReactor != null && !fetchReactor.isDisposed())) { + d.dispose(); + } else { + fetchReactor = d; + } + } + + @Override + public void onSuccess(@NonNull PlaylistInfo result) { + if (!result.has_more_streams) isComplete = true; + nextUrl = result.next_streams_url; + + append(extractPlaylistItems(result.related_streams)); + + fetchReactor.dispose(); + fetchReactor = null; + } + + @Override + public void onError(@NonNull Throwable e) { + Log.e(TAG, "Error fetching more playlist, marking playlist as complete.", e); + isComplete = true; + append(); // Notify change + } + }; + } + + private SingleObserver getPlaylistNextItemsObserver() { return new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index 3b4b71dc9..90f88b26f 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -7,7 +7,9 @@ android:layout_height="@dimen/video_item_search_height" android:background="?attr/selectableItemBackground" android:clickable="true" - android:padding="@dimen/video_item_search_padding"> + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + + tools:text="Channel Title, Lorem ipsum" /> + tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" /> + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + + + android:focusable="true" + android:paddingTop="@dimen/video_item_search_padding" + android:paddingBottom="@dimen/video_item_search_padding"> + +