diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f9311621f..bc3a1c216 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -13,6 +13,9 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras * Check if this issue/feature is already fixed/implemented in the repository * If you are an android/java developer you are always welcome to fix/implement an issue/a feature yourself +## Bugfixing +* If you want to help NewPipe getting bug free, you can send me a mail to tnp@newpipe.schabi.org to let me know that you intent to help, and than register at our [sentry](https://support.schabi.org) setup. + ## Translation * NewPipe can be translated on [weblate](https://hosted.weblate.org/projects/newpipe/strings/) @@ -31,6 +34,6 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras ## Communication -* I hereby declare our Slack channel as dead!!! There are no plans on building a new chat, but if there is interest on creating one and keeping it alive, I'd be pleased to create one again. +* WE DO NOW HAVE A MAILING LIST: [newpipe@list.schabi.org](https://list.schabi.org/cgi-bin/mailman/listinfo/newpipe). * If you want to get in contact with me or one of our other contributors you can send me an email at tnp(at)schabi.org * Feel free to post suggestions, changes, ideas etc! diff --git a/README.md b/README.md index e058ffc6b..c2b3ad168 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Search YouTube in a specific language * Watch age restricted material * Display general information about channels +* Search channels ### Coming Features @@ -49,7 +50,6 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Bookmarks * View history * Search history -* Search channels * Subscribe to channels * Watch videos from a channel * Search/Watch Playlists diff --git a/app/build.gradle b/app/build.gradle index 488012afc..e068acff2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,7 +45,8 @@ dependencies { compile 'com.google.android.exoplayer:exoplayer:r1.5.5' compile 'com.google.code.gson:gson:2.4' compile 'com.nononsenseapps:filepicker:3.0.0' + compile 'ch.acra:acra:4.9.0' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' - compile 'ch.acra:acra:4.9.0' + testCompile 'org.json:json:20160810' } diff --git a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorLiveStreamTest.java b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorLiveStreamTest.java deleted file mode 100644 index 9150ba93f..000000000 --- a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorLiveStreamTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.schabi.newpipe.extractor.youtube; - -import android.test.AndroidTestCase; - - -import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.stream_info.StreamExtractor; - -import java.io.IOException; - -/** - * Created by Christian Schabesberger on 11.03.16. - * - * Copyright (C) Christian Schabesberger 2016 - * YoutubeStreamExtractorLiveStreamTest.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - - -public class YoutubeStreamExtractorLiveStreamTest extends AndroidTestCase { - - private StreamExtractor extractor; - - public void setUp() throws IOException, ExtractionException { - //todo: make the extractor not throw over a livestream - /* - - NewPipe.init(Downloader.getInstance()); - extractor = NewPipe.getService("Youtube") - .getExtractorInstance("https://www.youtube.com/watch?v=J0s6NjqdjLE", Downloader.getInstance()); - */ - } - - public void testStreamType() throws ParsingException { - assertTrue(true); - // assertTrue(extractor.getStreamType() == AbstractVideoInfo.StreamType.LIVE_STREAM); - } -} - diff --git a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java index e64605218..a5f4245bb 100644 --- a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java @@ -1,18 +1,24 @@ package org.schabi.newpipe; +import android.annotation.TargetApi; +import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.FloatingActionButton; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.View; +import android.view.Window; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.Toast; @@ -34,6 +40,9 @@ import org.schabi.newpipe.report.ErrorActivity; import java.io.IOException; import java.util.Objects; +import static android.os.Build.VERSION.SDK; +import static android.os.Build.VERSION.SDK_INT; + /** * Copyright (C) Christian Schabesberger 2016 * ChannelActivity.java is part of NewPipe. @@ -87,14 +96,17 @@ public class ChannelActivity extends AppCompatActivity { channelUrl = i.getStringExtra(CHANNEL_URL); serviceId = i.getIntExtra(SERVICE_ID, -1); + setTranslucentStatusBar(getWindow()); + infoListAdapter = new InfoListAdapter(this, rootView); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.channel_streams_view); final LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(infoListAdapter); - infoListAdapter.setOnItemSelectedListener(new InfoItemBuilder.OnItemSelectedListener() { + infoListAdapter.setOnStreamInfoItemSelectedListener( + new InfoItemBuilder.OnInfoItemSelectedListener() { @Override - public void selected(String url) { + public void selected(String url, int serviceId) { Intent detailIntent = new Intent(ChannelActivity.this, VideoItemDetailActivity.class); detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url); detailIntent.putExtra( @@ -172,7 +184,7 @@ public class ChannelActivity extends AppCompatActivity { } private void addVideos(final ChannelInfo info) { - infoListAdapter.addStreamItemList(info.related_streams); + infoListAdapter.addInfoItemList(info.related_streams); } private void postNewErrorToast(Handler h, final int stringResource) { @@ -244,9 +256,13 @@ public class ChannelActivity extends AppCompatActivity { }); pe.printStackTrace(); } catch(ExtractionException ex) { + String name = "none"; + if(service != null) { + name = service.getServiceInfo().name; + } ErrorActivity.reportError(h, ChannelActivity.this, ex, VideoItemDetailFragment.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, - service.getServiceInfo().name, channelUrl, R.string.parsing_error)); + name, channelUrl, R.string.parsing_error)); h.post(new Runnable() { @Override public void run() { @@ -272,4 +288,28 @@ public class ChannelActivity extends AppCompatActivity { channelExtractorThread.start(); } + + // fix transparent statusbar fuckup (fuck google why can't they just leave something that worked + // as it is, and everyone gets happy) + public static void setTranslucentStatusBar(Window window) { + if (window == null) return; + int sdkInt = Build.VERSION.SDK_INT; + if (sdkInt >= Build.VERSION_CODES.LOLLIPOP) { + setTranslucentStatusBarLollipop(window); + } else if (sdkInt >= Build.VERSION_CODES.KITKAT) { + setTranslucentStatusBarKiKat(window); + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static void setTranslucentStatusBarLollipop(Window window) { + window.setStatusBarColor( + ContextCompat.getColor(window.getContext(), android.R.color.transparent)); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static void setTranslucentStatusBarKiKat(Window window) { + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 3621603de..15395290a 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -5,10 +5,14 @@ import android.media.AudioManager; import android.support.v4.app.Fragment; import android.support.v4.app.NavUtils; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.PermissionHelper; @@ -33,8 +37,8 @@ import org.schabi.newpipe.util.PermissionHelper; */ public class MainActivity extends ThemableActivity { - private Fragment mainFragment = null; + private static final String TAG = MainActivity.class.toString(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -58,7 +62,7 @@ public class MainActivity extends ThemableActivity { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); - switch(id) { + switch (id) { case android.R.id.home: { Intent intent = new Intent(this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -71,10 +75,10 @@ public class MainActivity extends ThemableActivity { return true; } case R.id.action_show_downloads: { - if(!PermissionHelper.checkStoragePermissions(this)) { + if (!PermissionHelper.checkStoragePermissions(this)) { return false; } - Intent intent = new Intent(this, org.schabi.newpipe.download.DownloadActivity.class); + Intent intent = new Intent(this, DownloadActivity.class); startActivity(intent); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/ThemableActivity.java b/app/src/main/java/org/schabi/newpipe/ThemableActivity.java index 81789ec1c..8b0d126be 100644 --- a/app/src/main/java/org/schabi/newpipe/ThemableActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ThemableActivity.java @@ -1,6 +1,5 @@ package org.schabi.newpipe; -import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.v7.app.AppCompatActivity; @@ -16,4 +15,14 @@ public class ThemableActivity extends AppCompatActivity { setTheme(R.style.DarkTheme); } } + + @Override + protected void onResume() { + super.onResume(); + if (PreferenceManager.getDefaultSharedPreferences(this) + .getString("theme", getResources().getString(R.string.light_theme_title)). + equals(getResources().getString(R.string.dark_theme_title))) { + setTheme(R.style.DarkTheme); + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java index eb75dfa6d..19df2d84f 100644 --- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java @@ -46,11 +46,12 @@ import org.schabi.newpipe.Localization; import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.download.DownloadDialog; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.stream_info.AudioStream; import org.schabi.newpipe.extractor.stream_info.StreamInfo; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; import org.schabi.newpipe.extractor.stream_info.VideoStream; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.player.BackgroundPlayer; @@ -536,12 +537,13 @@ public class VideoItemDetailFragment extends Fragment { private void initSimilarVideos(final StreamInfo info) { LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similar_streams_view); - for (final StreamPreviewInfo item : info.related_streams) { + for (final InfoItem item : info.related_streams) { similarLayout.addView(infoItemBuilder.buildView(similarLayout, item)); } - infoItemBuilder.setOnItemSelectedListener(new InfoItemBuilder.OnItemSelectedListener() { + infoItemBuilder.setOnStreamInfoItemSelectedListener( + new InfoItemBuilder.OnInfoItemSelectedListener() { @Override - public void selected(String url) { + public void selected(String url, int serviceId) { openStreamUrl(url); } }); diff --git a/app/src/main/java/org/schabi/newpipe/extractor/AbstractStreamInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/AbstractStreamInfo.java index 308f5127d..bfa86b3f7 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/AbstractStreamInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/AbstractStreamInfo.java @@ -18,9 +18,9 @@ package org.schabi.newpipe.extractor; * along with NewPipe. If not, see . */ -/**Common properties between StreamInfo and StreamPreviewInfo.*/ +/**Common properties between StreamInfo and StreamInfoItem.*/ public abstract class AbstractStreamInfo { - public static enum StreamType { + public enum StreamType { NONE, // placeholder to check if stream type was checked or not VIDEO_STREAM, AUDIO_STREAM, diff --git a/app/src/main/java/org/schabi/newpipe/extractor/InfoItem.java b/app/src/main/java/org/schabi/newpipe/extractor/InfoItem.java new file mode 100644 index 000000000..a3395e8e4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/InfoItem.java @@ -0,0 +1,33 @@ +package org.schabi.newpipe.extractor; + +/** + * Created by the-scrabi on 11.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * InfoItem.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public interface InfoItem { + public enum InfoType { + STREAM, + PLAYLIST, + CHANNEL + } + + InfoType infoType(); + String getTitle(); + String getLink(); +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java new file mode 100644 index 000000000..d3b0927a1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java @@ -0,0 +1,61 @@ +package org.schabi.newpipe.extractor; + +import org.schabi.newpipe.extractor.exceptions.ExtractionException; + +import java.util.List; +import java.util.Vector; + +/** + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * InfoItemCollector.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class InfoItemCollector { + private List itemList = new Vector<>(); + private List errors = new Vector<>(); + private int serviceId = -1; + + public InfoItemCollector(int serviceId) { + this.serviceId = serviceId; + } + + public List getItemList() { + return itemList; + } + public List getErrors() { + return errors; + } + protected void addFromCollector(InfoItemCollector otherC) throws ExtractionException { + if(serviceId != otherC.serviceId) { + throw new ExtractionException("Service Id does not equal: " + + NewPipe.getNameOfService(serviceId) + + " and " + NewPipe.getNameOfService(otherC.serviceId)); + } + errors.addAll(otherC.errors); + itemList.addAll(otherC.itemList); + } + protected void addError(Exception e) { + errors.add(e); + } + protected void addItem(InfoItem item) { + itemList.add(item); + } + protected int getServiceId() { + return serviceId; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java index 8ed6f9234..7252cea49 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.extractor; import org.schabi.newpipe.extractor.channel.ChannelExtractor; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.search.SearchEngine; -import org.schabi.newpipe.extractor.search.SuggestionExtractor; import org.schabi.newpipe.extractor.stream_info.StreamExtractor; import java.io.IOException; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/search/SuggestionExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java similarity index 96% rename from app/src/main/java/org/schabi/newpipe/extractor/search/SuggestionExtractor.java rename to app/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java index f77b1912d..f198bc5e0 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/search/SuggestionExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.extractor.search; +package org.schabi.newpipe.extractor; import org.schabi.newpipe.extractor.exceptions.ExtractionException; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java index 35d3604d9..18bf67550 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java @@ -3,7 +3,7 @@ package org.schabi.newpipe.extractor.channel; import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; import java.io.IOException; @@ -31,7 +31,7 @@ public abstract class ChannelExtractor { private int serviceId; private String url; private UrlIdHandler urlIdHandler; - private StreamPreviewInfoCollector previewInfoCollector; + private StreamInfoItemCollector previewInfoCollector; private int page = -1; public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, int serviceId) @@ -40,12 +40,12 @@ public abstract class ChannelExtractor { this.page = page; this.serviceId = serviceId; this.urlIdHandler = urlIdHandler; - previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId); + previewInfoCollector = new StreamInfoItemCollector(urlIdHandler, serviceId); } public String getUrl() { return url; } public UrlIdHandler getUrlIdHandler() { return urlIdHandler; } - public StreamPreviewInfoCollector getStreamPreviewInfoCollector() { + public StreamInfoItemCollector getStreamPreviewInfoCollector() { return previewInfoCollector; } @@ -53,7 +53,7 @@ public abstract class ChannelExtractor { public abstract String getAvatarUrl() throws ParsingException; public abstract String getBannerUrl() throws ParsingException; public abstract String getFeedUrl() throws ParsingException; - public abstract StreamPreviewInfoCollector getStreams() throws ParsingException; + public abstract StreamInfoItemCollector getStreams() throws ParsingException; public abstract boolean hasNextPage() throws ParsingException; public int getServiceId() { return serviceId; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java index 7dc4b00e0..68862ec32 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java @@ -1,8 +1,9 @@ package org.schabi.newpipe.extractor.channel; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; import java.util.List; import java.util.Vector; @@ -59,7 +60,7 @@ public class ChannelInfo { info.errors.add(e); } try { - StreamPreviewInfoCollector c = extractor.getStreams(); + StreamInfoItemCollector c = extractor.getStreams(); info.related_streams = c.getItemList(); info.errors.addAll(c.getErrors()); } catch(Exception e) { @@ -74,7 +75,7 @@ public class ChannelInfo { public String avatar_url = ""; public String banner_url = ""; public String feed_url = ""; - public List related_streams = null; + public List related_streams = null; public boolean hasNextPage = false; public List errors = new Vector<>(); diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java new file mode 100644 index 000000000..b834d28e6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java @@ -0,0 +1,44 @@ +package org.schabi.newpipe.extractor.channel; + +import org.schabi.newpipe.extractor.InfoItem; + +/** + * Created by Christian Schabesberger on 11.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * ChannelInfoItem.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class ChannelInfoItem implements InfoItem { + + public int serviceId = -1; + public String channelName = ""; + public String thumbnailUrl = ""; + public String webPageUrl = ""; + public String description = ""; + public long subscriberCount = -1; + public int videoAmount = -1; + + public InfoType infoType() { + return InfoType.CHANNEL; + } + public String getTitle() { + return channelName; + } + public String getLink() { + return webPageUrl; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java new file mode 100644 index 000000000..525820d44 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java @@ -0,0 +1,74 @@ +package org.schabi.newpipe.extractor.channel; + +import org.schabi.newpipe.extractor.InfoItemCollector; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.FoundAdException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemExtractor; + +/** + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * ChannelInfoItemCollector.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class ChannelInfoItemCollector extends InfoItemCollector { + public ChannelInfoItemCollector(int serviceId) { + super(serviceId); + } + + public ChannelInfoItem extract(ChannelInfoItemExtractor extractor) throws ParsingException { + ChannelInfoItem resultItem = new ChannelInfoItem(); + // importand information + resultItem.channelName = extractor.getChannelName(); + + resultItem.serviceId = getServiceId(); + resultItem.webPageUrl = extractor.getWebPageUrl(); + + // optional information + try { + resultItem.subscriberCount = extractor.getSubscriberCount(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.videoAmount = extractor.getVideoAmount(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.thumbnailUrl = extractor.getThumbnailUrl(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.description = extractor.getDescription(); + } catch (Exception e) { + addError(e); + } + return resultItem; + } + + public void commit(ChannelInfoItemExtractor extractor) throws ParsingException { + try { + addItem(extract(extractor)); + } catch (Exception e) { + addError(e); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java new file mode 100644 index 000000000..864415cdd --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java @@ -0,0 +1,32 @@ +package org.schabi.newpipe.extractor.channel; + +import org.schabi.newpipe.extractor.exceptions.ParsingException; + +/** + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * ChannelInfoItemExtractor.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public interface ChannelInfoItemExtractor { + String getThumbnailUrl() throws ParsingException; + String getChannelName() throws ParsingException; + String getWebPageUrl() throws ParsingException; + String getDescription() throws ParsingException; + long getSubscriberCount() throws ParsingException; + int getVideoAmount() throws ParsingException; +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java new file mode 100644 index 000000000..b7ec0e3c1 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java @@ -0,0 +1,79 @@ +package org.schabi.newpipe.extractor.search; + +import org.schabi.newpipe.extractor.InfoItemCollector; +import org.schabi.newpipe.extractor.UrlIdHandler; +import org.schabi.newpipe.extractor.channel.ChannelInfoItemCollector; +import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.FoundAdException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemExtractor; + +/** + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * InfoItemSearchCollector.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class InfoItemSearchCollector extends InfoItemCollector { + private String suggestion = ""; + private StreamInfoItemCollector streamCollector; + private ChannelInfoItemCollector channelCollector; + + SearchResult result = new SearchResult(); + + InfoItemSearchCollector(UrlIdHandler handler, int serviceId) { + super(serviceId); + streamCollector = new StreamInfoItemCollector(handler, serviceId); + channelCollector = new ChannelInfoItemCollector(serviceId); + } + + public void setSuggestion(String suggestion) { + this.suggestion = suggestion; + } + + public SearchResult getSearchResult() throws ExtractionException { + + addFromCollector(channelCollector); + addFromCollector(streamCollector); + + result.suggestion = suggestion; + result.errors = getErrors(); + return result; + } + + public void commit(StreamInfoItemExtractor extractor) { + try { + result.resultList.add(streamCollector.extract(extractor)); + } catch(FoundAdException ae) { + System.err.println("Found add"); + } catch (Exception e) { + addError(e); + } + } + + public void commit(ChannelInfoItemExtractor extractor) { + try { + result.resultList.add(channelCollector.extract(extractor)); + } catch(FoundAdException ae) { + System.err.println("Found add"); + } catch (Exception e) { + addError(e); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java b/app/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java index b0dfbfc8a..9cef050eb 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java @@ -2,8 +2,10 @@ package org.schabi.newpipe.extractor.search; import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; import java.io.IOException; +import java.util.EnumSet; /** * Created by Christian Schabesberger on 10.08.15. @@ -26,24 +28,26 @@ import java.io.IOException; */ public abstract class SearchEngine { + public enum Filter { + STREAM, CHANNEL, PLAY_LIST + } + public static class NothingFoundException extends ExtractionException { public NothingFoundException(String message) { super(message); } } - - private StreamPreviewInfoSearchCollector collector; + private InfoItemSearchCollector collector; public SearchEngine(UrlIdHandler urlIdHandler, int serviceId) { - collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId); + collector = new InfoItemSearchCollector(urlIdHandler, serviceId); } - protected StreamPreviewInfoSearchCollector getStreamPreviewInfoSearchCollector() { + protected InfoItemSearchCollector getInfoItemSearchCollector() { return collector; } - //Result search(String query, int page); - public abstract StreamPreviewInfoSearchCollector search( - String query, int page, String contentCountry) + public abstract InfoItemSearchCollector search( + String query, int page, String contentCountry, EnumSet filter) throws ExtractionException, IOException; } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java b/app/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java index 97ceb46fb..155b43747 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java @@ -1,9 +1,11 @@ package org.schabi.newpipe.extractor.search; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; import java.io.IOException; +import java.util.EnumSet; import java.util.List; import java.util.Vector; @@ -29,13 +31,17 @@ import java.util.Vector; public class SearchResult { public static SearchResult getSearchResult(SearchEngine engine, String query, - int page, String languageCode) + int page, String languageCode, EnumSet filter) throws ExtractionException, IOException { - SearchResult result = engine.search(query, page, languageCode).getSearchResult(); + SearchResult result = engine + .search(query, page, languageCode, filter) + .getSearchResult(); if(result.resultList.isEmpty()) { if(result.suggestion.isEmpty()) { - throw new ExtractionException("Empty result despite no error"); + if(result.errors.isEmpty()) { + throw new ExtractionException("Empty result despite no error"); + } } else { // This is used as a fallback. Do not relay on it !!! throw new SearchEngine.NothingFoundException(result.suggestion); @@ -45,6 +51,6 @@ public class SearchResult { } public String suggestion = ""; - public List resultList = new Vector<>(); + public List resultList = new Vector<>(); public List errors = new Vector<>(); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/search/StreamPreviewInfoSearchCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/search/StreamPreviewInfoSearchCollector.java deleted file mode 100644 index 40874f717..000000000 --- a/app/src/main/java/org/schabi/newpipe/extractor/search/StreamPreviewInfoSearchCollector.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.schabi.newpipe.extractor.search; - -import org.schabi.newpipe.extractor.UrlIdHandler; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector; - -/** - * Created by Christian Schabesberger on 11.05.16. - * - * Copyright (C) Christian Schabesberger 2016 - * StreamPreviewInfoSearchCollector.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class StreamPreviewInfoSearchCollector extends StreamPreviewInfoCollector { - - private String suggestion = ""; - - public StreamPreviewInfoSearchCollector(UrlIdHandler handler, int serviceId) { - super(handler, serviceId); - } - - public void setSuggestion(String suggestion) { - this.suggestion = suggestion; - } - - public SearchResult getSearchResult() { - SearchResult result = new SearchResult(); - result.suggestion = suggestion; - result.errors = getErrors(); - result.resultList = getItemList(); - return result; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java index 62e63ab1b..413471eda 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java @@ -15,8 +15,8 @@ import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.channel.ChannelExtractor; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemExtractor; import java.io.IOException; @@ -137,7 +137,8 @@ public class YoutubeChannelExtractor extends ChannelExtractor { Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first(); String cssContent = el.html(); String url = "https:" + Parser.matchGroup1("url\\(([^)]+)\\)", cssContent); - if (url.contains("s.ytimg.com")) { + + if (url.contains("s.ytimg.com") || url.contains("default_banner")) { bannerUrl = null; } else { bannerUrl = url; @@ -150,8 +151,8 @@ public class YoutubeChannelExtractor extends ChannelExtractor { } @Override - public StreamPreviewInfoCollector getStreams() throws ParsingException { - StreamPreviewInfoCollector collector = getStreamPreviewInfoCollector(); + public StreamInfoItemCollector getStreams() throws ParsingException { + StreamInfoItemCollector collector = getStreamPreviewInfoCollector(); Element ul = null; if(isAjaxPage) { ul = doc.select("body").first(); @@ -161,7 +162,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor { for(final Element li : ul.children()) { if (li.select("div[class=\"feed-item-dismissable\"]").first() != null) { - collector.commit(new StreamPreviewInfoExtractor() { + collector.commit(new StreamInfoItemExtractor() { @Override public AbstractStreamInfo.StreamType getStreamType() throws ParsingException { return AbstractStreamInfo.StreamType.VIDEO_STREAM; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java new file mode 100644 index 000000000..7dafb63e8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java @@ -0,0 +1,83 @@ +package org.schabi.newpipe.extractor.services.youtube; + +import org.schabi.newpipe.extractor.Parser; +import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.jsoup.nodes.Element; + +/** + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2017 + * YoutubeChannelInfoItemExtractor.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class YoutubeChannelInfoItemExtractor implements ChannelInfoItemExtractor { + private Element el; + + public YoutubeChannelInfoItemExtractor(Element el) { + this.el = el; + } + + public String getThumbnailUrl() throws ParsingException { + Element img = el.select("span[class*=\"yt-thumb-simple\"]").first() + .select("img").first(); + + String url = img.attr("abs:src"); + + if(url.contains("gif")) { + url = img.attr("abs:data-thumb"); + } + return url; + } + + public String getChannelName() throws ParsingException { + return el.select("a[class*=\"yt-uix-tile-link\"]").first() + .text(); + } + + public String getWebPageUrl() throws ParsingException { + return el.select("a[class*=\"yt-uix-tile-link\"]").first() + .attr("abs:href"); + } + + public long getSubscriberCount() throws ParsingException { + Element subsEl = el.select("span[class*=\"yt-subscriber-count\"]").first(); + if(subsEl == null) { + return 0; + } else { + return Integer.parseInt(subsEl.text().replaceAll("\\D+","")); + } + } + + public int getVideoAmount() throws ParsingException { + Element metaEl = el.select("ul[class*=\"yt-lockup-meta-info\"]").first(); + if(metaEl == null) { + return 0; + } else { + return Integer.parseInt(metaEl.text().replaceAll("\\D+","")); + } + } + + public String getDescription() throws ParsingException { + Element desEl = el.select("div[class*=\"yt-lockup-description\"]").first(); + if(desEl == null) { + return ""; + } else { + return desEl.text(); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java index 55e05bb76..d650535f7 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java @@ -7,12 +7,12 @@ import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.search.InfoItemSearchCollector; import org.schabi.newpipe.extractor.search.SearchEngine; -import org.schabi.newpipe.extractor.search.StreamPreviewInfoSearchCollector; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor; import java.net.URLEncoder; import java.io.IOException; +import java.util.EnumSet; /** @@ -45,16 +45,24 @@ public class YoutubeSearchEngine extends SearchEngine { } @Override - public StreamPreviewInfoSearchCollector search(String query, int page, String languageCode) + public InfoItemSearchCollector search(String query, + int page, + String languageCode, + EnumSet filter) throws IOException, ExtractionException { - StreamPreviewInfoSearchCollector collector = getStreamPreviewInfoSearchCollector(); + InfoItemSearchCollector collector = getInfoItemSearchCollector(); + Downloader downloader = NewPipe.getDownloader(); String url = "https://www.youtube.com/results" - + "?search_query=" + URLEncoder.encode(query, CHARSET_UTF_8) - + "&page=" + Integer.toString(page + 1) - + "&filters=" + "video"; + + "?q=" + URLEncoder.encode(query, CHARSET_UTF_8) + + "&page=" + Integer.toString(page + 1); + if(filter.contains(Filter.STREAM) && !filter.contains(Filter.CHANNEL)) { + url += "&sp=EgIQAQ%253D%253D"; + } else if(!filter.contains(Filter.STREAM) && filter.contains(Filter.CHANNEL)) { + url += "&sp=EgIQAg%253D%253D"; + } String site; //String url = builder.build().toString(); @@ -92,22 +100,20 @@ public class YoutubeSearchEngine extends SearchEngine { } // search message item } else if ((el = item.select("div[class*=\"search-message\"]").first()) != null) { - //result.errorMessage = el.text(); throw new NothingFoundException(el.text()); // video item type - } else if ((el = item.select("div[class*=\"yt-lockup-video\"").first()) != null) { - collector.commit(extractPreviewInfo(el)); + } else if ((el = item.select("div[class*=\"yt-lockup-video\"]").first()) != null) { + collector.commit(new YoutubeStreamInfoItemExtractor(el)); + } else if((el = item.select("div[class*=\"yt-lockup-channel\"]").first()) != null) { + collector.commit(new YoutubeChannelInfoItemExtractor(el)); } else { - //noinspection ConstantConditions - collector.addError(new Exception("unexpected element found:\"" + el + "\"")); + // noinspection ConstantConditions + // simply ignore not known items + // throw new ExtractionException("unexpected element found: \"" + item + "\""); } } return collector; } - - private StreamPreviewInfoExtractor extractPreviewInfo(final Element item) { - return new YoutubeStreamPreviewInfoExtractor(item); - } } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java index 030878b03..595513ace 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java @@ -5,7 +5,7 @@ import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.channel.ChannelExtractor; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.search.SearchEngine; -import org.schabi.newpipe.extractor.search.SuggestionExtractor; +import org.schabi.newpipe.extractor.SuggestionExtractor; import org.schabi.newpipe.extractor.stream_info.StreamExtractor; import java.io.IOException; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java index 6dd0daa7c..30846fedc 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java @@ -20,8 +20,8 @@ import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream_info.StreamExtractor; import org.schabi.newpipe.extractor.stream_info.StreamInfo; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoCollector; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemCollector; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemExtractor; import org.schabi.newpipe.extractor.stream_info.VideoStream; import java.io.IOException; @@ -657,7 +657,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { } @Override - public StreamPreviewInfoExtractor getNextVideo() throws ParsingException { + public StreamInfoItemExtractor getNextVideo() throws ParsingException { try { return extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first() .select("li").first()); @@ -667,9 +667,9 @@ public class YoutubeStreamExtractor extends StreamExtractor { } @Override - public StreamPreviewInfoCollector getRelatedVideos() throws ParsingException { + public StreamInfoItemCollector getRelatedVideos() throws ParsingException { try { - StreamPreviewInfoCollector collector = getStreamPreviewInfoCollector(); + StreamInfoItemCollector collector = getStreamPreviewInfoCollector(); Element ul = doc.select("ul[id=\"watch-related\"]").first(); if(ul != null) { for (Element li : ul.children()) { @@ -707,10 +707,10 @@ public class YoutubeStreamExtractor extends StreamExtractor { } /**Provides information about links to other videos on the video page, such as related videos. - * This is encapsulated in a StreamPreviewInfo object, + * This is encapsulated in a StreamInfoItem object, * which is a subset of the fields in a full StreamInfo.*/ - private StreamPreviewInfoExtractor extractVideoPreviewInfo(final Element li) { - return new StreamPreviewInfoExtractor() { + private StreamInfoItemExtractor extractVideoPreviewInfo(final Element li) { + return new StreamInfoItemExtractor() { @Override public AbstractStreamInfo.StreamType getStreamType() throws ParsingException { return null; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamPreviewInfoExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java similarity index 87% rename from app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamPreviewInfoExtractor.java rename to app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java index e7420deda..1afb8f57d 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamPreviewInfoExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java @@ -4,11 +4,11 @@ import org.jsoup.nodes.Element; import org.schabi.newpipe.extractor.AbstractStreamInfo; import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItemExtractor; /** * Copyright (C) Christian Schabesberger 2016 - * YoutubeStreamPreviewInfoExtractor.java is part of NewPipe. + * YoutubeStreamInfoItemExtractor.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,11 +24,11 @@ import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfoExtractor; * along with NewPipe. If not, see . */ -public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtractor { +public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor { private final Element item; - public YoutubeStreamPreviewInfoExtractor(Element item) { + public YoutubeStreamInfoItemExtractor(Element item) { this.item = item; } @@ -83,9 +83,12 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra @Override public String getUploadDate() throws ParsingException { try { - return item.select("div[class=\"yt-lockup-meta\"]").first() - .select("li").first() - .text(); + Element div = item.select("div[class=\"yt-lockup-meta\"]").first(); + if(div == null) { + return null; + } else { + return div.select("li").first().text(); + } } catch(Exception e) { throw new ParsingException("Could not get uplaod date", e); } @@ -96,9 +99,13 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra String output; String input; try { - input = item.select("div[class=\"yt-lockup-meta\"]").first() - .select("li").get(1) - .text(); + Element div = item.select("div[class=\"yt-lockup-meta\"]").first(); + if(div == null) { + return -1; + } else { + input = div.select("li").get(1) + .text(); + } } catch (IndexOutOfBoundsException e) { if(isLiveStream(item)) { // -1 for no view count diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractor.java index 8cd1651b8..0535961e8 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractor.java @@ -4,7 +4,7 @@ import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.search.SuggestionExtractor; +import org.schabi.newpipe.extractor.SuggestionExtractor; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; @@ -15,7 +15,6 @@ import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; -import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamExtractor.java index 0f4eb7502..1b7c1b5ab 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamExtractor.java @@ -35,7 +35,7 @@ public abstract class StreamExtractor { private int serviceId; private String url; private UrlIdHandler urlIdHandler; - private StreamPreviewInfoCollector previewInfoCollector; + private StreamInfoItemCollector previewInfoCollector; public class ExtractorInitException extends ExtractionException { public ExtractorInitException(String message) { @@ -61,10 +61,10 @@ public abstract class StreamExtractor { public StreamExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) { this.serviceId = serviceId; this.urlIdHandler = urlIdHandler; - previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId); + previewInfoCollector = new StreamInfoItemCollector(urlIdHandler, serviceId); } - protected StreamPreviewInfoCollector getStreamPreviewInfoCollector() { + protected StreamInfoItemCollector getStreamPreviewInfoCollector() { return previewInfoCollector; } @@ -94,8 +94,8 @@ public abstract class StreamExtractor { public abstract String getAverageRating() throws ParsingException; public abstract int getLikeCount() throws ParsingException; public abstract int getDislikeCount() throws ParsingException; - public abstract StreamPreviewInfoExtractor getNextVideo() throws ParsingException; - public abstract StreamPreviewInfoCollector getRelatedVideos() throws ParsingException; + public abstract StreamInfoItemExtractor getNextVideo() throws ParsingException; + public abstract StreamInfoItemCollector getRelatedVideos() throws ParsingException; public abstract String getPageUrl(); public abstract StreamInfo.StreamType getStreamType() throws ParsingException; public int getServiceId() { diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfo.java index 5a1c0b740..cf3df49d8 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfo.java @@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.stream_info; import org.schabi.newpipe.extractor.AbstractStreamInfo; import org.schabi.newpipe.extractor.DashMpdParser; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.UrlIdHandler; import org.schabi.newpipe.extractor.exceptions.ExtractionException; @@ -55,14 +56,14 @@ public class StreamInfo extends AbstractStreamInfo { this.view_count = avi.view_count; //todo: better than this - if(avi instanceof StreamPreviewInfo) { + if(avi instanceof StreamInfoItem) { //shitty String to convert code /* - String dur = ((StreamPreviewInfo)avi).duration; + String dur = ((StreamInfoItem)avi).duration; int minutes = Integer.parseInt(dur.substring(0, dur.indexOf(":"))); int seconds = Integer.parseInt(dur.substring(dur.indexOf(":")+1, dur.length())); */ - this.duration = ((StreamPreviewInfo)avi).duration; + this.duration = ((StreamInfoItem)avi).duration; } } @@ -241,12 +242,12 @@ public class StreamInfo extends AbstractStreamInfo { // get next video if(streamInfo.next_video != null) { - StreamPreviewInfoCollector c = new StreamPreviewInfoCollector( + StreamInfoItemCollector c = new StreamInfoItemCollector( extractor.getUrlIdHandler(), extractor.getServiceId()); - StreamPreviewInfoExtractor nextVideo = extractor.getNextVideo(); + StreamInfoItemExtractor nextVideo = extractor.getNextVideo(); c.commit(nextVideo); if(c.getItemList().size() != 0) { - streamInfo.next_video = c.getItemList().get(0); + streamInfo.next_video = (StreamInfoItem) c.getItemList().get(0); } streamInfo.errors.addAll(c.getErrors()); } @@ -256,7 +257,7 @@ public class StreamInfo extends AbstractStreamInfo { } try { // get related videos - StreamPreviewInfoCollector c = extractor.getRelatedVideos(); + StreamInfoItemCollector c = extractor.getRelatedVideos(); streamInfo.related_streams = c.getItemList(); streamInfo.errors.addAll(c.getErrors()); } catch(Exception e) { @@ -284,8 +285,8 @@ public class StreamInfo extends AbstractStreamInfo { public int like_count = -1; public int dislike_count = -1; public String average_rating = ""; - public StreamPreviewInfo next_video = null; - public List related_streams = null; + public StreamInfoItem next_video = null; + public List related_streams = null; //in seconds. some metadata is not passed using a StreamInfo object! public int start_position = 0; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItem.java similarity index 73% rename from app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfo.java rename to app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItem.java index 77de2db95..8a6db5bc0 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItem.java @@ -4,7 +4,7 @@ package org.schabi.newpipe.extractor.stream_info; * Created by Christian Schabesberger on 26.08.15. * * Copyright (C) Christian Schabesberger 2016 - * StreamPreviewInfo.java is part of NewPipe. + * StreamInfoItem.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,21 @@ package org.schabi.newpipe.extractor.stream_info; */ import org.schabi.newpipe.extractor.AbstractStreamInfo; +import org.schabi.newpipe.extractor.InfoItem; /**Info object for previews of unopened videos, eg search results, related videos*/ -public class StreamPreviewInfo extends AbstractStreamInfo { +public class StreamInfoItem extends AbstractStreamInfo implements InfoItem { public int duration; + + public InfoType infoType() { + return InfoType.STREAM; + } + + public String getTitle() { + return title; + } + + public String getLink() { + return webpage_url; + } } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java new file mode 100644 index 000000000..15c11b850 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java @@ -0,0 +1,99 @@ +package org.schabi.newpipe.extractor.stream_info; + +import org.schabi.newpipe.extractor.InfoItemCollector; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.UrlIdHandler; +import org.schabi.newpipe.extractor.exceptions.FoundAdException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; + +import java.util.List; +import java.util.Vector; + +/** + * Created by Christian Schabesberger on 28.02.16. + * + * Copyright (C) Christian Schabesberger 2016 + * StreamInfoItemCollector.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class StreamInfoItemCollector extends InfoItemCollector { + + private UrlIdHandler urlIdHandler; + + public StreamInfoItemCollector(UrlIdHandler handler, int serviceId) { + super(serviceId); + urlIdHandler = handler; + } + + private UrlIdHandler getUrlIdHandler() { + return urlIdHandler; + } + + public StreamInfoItem extract(StreamInfoItemExtractor extractor) throws Exception { + + StreamInfoItem resultItem = new StreamInfoItem(); + // importand information + resultItem.service_id = getServiceId(); + resultItem.webpage_url = extractor.getWebPageUrl(); + if (getUrlIdHandler() == null) { + throw new ParsingException("Error: UrlIdHandler not set"); + } else if (!resultItem.webpage_url.isEmpty()) { + resultItem.id = NewPipe.getService(getServiceId()) + .getUrlIdHandlerInstance() + .getId(resultItem.webpage_url); + } + resultItem.title = extractor.getTitle(); + resultItem.stream_type = extractor.getStreamType(); + + // optional information + try { + resultItem.duration = extractor.getDuration(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.uploader = extractor.getUploader(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.upload_date = extractor.getUploadDate(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.view_count = extractor.getViewCount(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.thumbnail_url = extractor.getThumbnailUrl(); + } catch (Exception e) { + addError(e); + } + return resultItem; + } + + public void commit(StreamInfoItemExtractor extractor) throws ParsingException { + try { + addItem(extract(extractor)); + } catch(FoundAdException ae) { + System.out.println("AD_WARNING: " + ae.getMessage()); + } catch (Exception e) { + addError(e); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfoExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java similarity index 93% rename from app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfoExtractor.java rename to app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java index 3e35fc8da..4a4080a88 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfoExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java @@ -7,7 +7,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException; * Created by Christian Schabesberger on 28.02.16. * * Copyright (C) Christian Schabesberger 2016 - * StreamPreviewInfoExtractor.java is part of NewPipe. + * StreamInfoItemExtractor.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException; * along with NewPipe. If not, see . */ -public interface StreamPreviewInfoExtractor { +public interface StreamInfoItemExtractor { AbstractStreamInfo.StreamType getStreamType() throws ParsingException; String getWebPageUrl() throws ParsingException; String getTitle() throws ParsingException; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfoCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfoCollector.java deleted file mode 100644 index 20120fa2d..000000000 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamPreviewInfoCollector.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.schabi.newpipe.extractor.stream_info; - -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.UrlIdHandler; -import org.schabi.newpipe.extractor.exceptions.FoundAdException; -import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamUrlIdHandler; - -import java.util.List; -import java.util.Vector; - -/** - * Created by Christian Schabesberger on 28.02.16. - * - * Copyright (C) Christian Schabesberger 2016 - * StreamPreviewInfoCollector.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class StreamPreviewInfoCollector { - private List itemList = new Vector<>(); - private List errors = new Vector<>(); - private UrlIdHandler urlIdHandler; - private int serviceId = -1; - - public StreamPreviewInfoCollector(UrlIdHandler handler, int serviceId) { - urlIdHandler = handler; - this.serviceId = serviceId; - } - - public List getItemList() { - return itemList; - } - - public List getErrors() { - return errors; - } - - public void addError(Exception e) { - errors.add(e); - } - - public void commit(StreamPreviewInfoExtractor extractor) throws ParsingException { - try { - StreamPreviewInfo resultItem = new StreamPreviewInfo(); - // importand information - resultItem.service_id = serviceId; - resultItem.webpage_url = extractor.getWebPageUrl(); - if (urlIdHandler == null) { - throw new ParsingException("Error: UrlIdHandler not set"); - } else if(!resultItem.webpage_url.isEmpty()) { - resultItem.id = NewPipe.getService(serviceId).getUrlIdHandlerInstance().getId(resultItem.webpage_url); - } - resultItem.title = extractor.getTitle(); - resultItem.stream_type = extractor.getStreamType(); - - // optional information - try { - resultItem.duration = extractor.getDuration(); - } catch (Exception e) { - addError(e); - } - try { - resultItem.uploader = extractor.getUploader(); - } catch (Exception e) { - addError(e); - } - try { - resultItem.upload_date = extractor.getUploadDate(); - } catch (Exception e) { - addError(e); - } - try { - resultItem.view_count = extractor.getViewCount(); - } catch (Exception e) { - addError(e); - } - try { - resultItem.thumbnail_url = extractor.getThumbnailUrl(); - } catch (Exception e) { - addError(e); - } - itemList.add(resultItem); - } catch(FoundAdException ae) { - System.out.println("AD_WARNING: " + ae.getMessage()); - } catch (Exception e) { - addError(e); - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java new file mode 100644 index 000000000..d47c63cdb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java @@ -0,0 +1,54 @@ +package org.schabi.newpipe.info_list; + +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.InfoItem; + +import de.hdodenhof.circleimageview.CircleImageView; + +/** + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2016 + * ChannelInfoItemHolder .java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class ChannelInfoItemHolder extends InfoItemHolder { + public final CircleImageView itemThumbnailView; + public final TextView itemChannelTitleView; + public final TextView itemSubscriberCountView; + public final TextView itemVideoCountView; + public final TextView itemChannelDescriptionView; + public final Button itemButton; + + ChannelInfoItemHolder(View v) { + super(v); + itemThumbnailView = (CircleImageView) v.findViewById(R.id.itemThumbnailView); + itemChannelTitleView = (TextView) v.findViewById(R.id.itemChannelTitleView); + itemSubscriberCountView = (TextView) v.findViewById(R.id.itemSubscriberCountView); + itemVideoCountView = (TextView) v.findViewById(R.id.itemVideoCountView); + itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView); + itemButton = (Button) v.findViewById(R.id.item_button); + } + + @Override + public InfoItem.InfoType infoType() { + return InfoItem.InfoType.CHANNEL; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 30836053e..67b19520a 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -1,9 +1,11 @@ package org.schabi.newpipe.info_list; import android.app.Activity; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; @@ -11,7 +13,9 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.ImageErrorLoadingListener; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.AbstractStreamInfo; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.stream_info.StreamInfoItem; /** * Created by Christian Schabesberger on 26.09.16. @@ -35,8 +39,17 @@ import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo; public class InfoItemBuilder { - public interface OnItemSelectedListener { - void selected(String url); + final String viewsS; + final String videosS; + final String subsS; + + final String thousand; + final String million; + final String billion; + + private static final String TAG = InfoItemBuilder.class.toString(); + public interface OnInfoItemSelectedListener { + void selected(String url, int serviceId); } private Activity activity = null; @@ -44,18 +57,75 @@ public class InfoItemBuilder { private ImageLoader imageLoader = ImageLoader.getInstance(); private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build(); - private OnItemSelectedListener onItemSelectedListener; + private OnInfoItemSelectedListener onStreamInfoItemSelectedListener; + private OnInfoItemSelectedListener onChannelInfoItemSelectedListener; public InfoItemBuilder(Activity a, View rootView) { activity = a; this.rootView = rootView; + viewsS = a.getString(R.string.views); + videosS = a.getString(R.string.videos); + subsS = a.getString(R.string.subscriber); + thousand = a.getString(R.string.short_thousand); + million = a.getString(R.string.short_million); + billion = a.getString(R.string.short_billion); } - public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) { - this.onItemSelectedListener = onItemSelectedListener; + public void setOnStreamInfoItemSelectedListener( + OnInfoItemSelectedListener listener) { + this.onStreamInfoItemSelectedListener = listener; } - public void buildByHolder(InfoItemHolder holder, final StreamPreviewInfo info) { + public void setOnChannelInfoItemSelectedListener( + OnInfoItemSelectedListener listener) { + this.onChannelInfoItemSelectedListener = listener; + } + + public void buildByHolder(InfoItemHolder holder, final InfoItem i) { + if(i.infoType() != holder.infoType()) + return; + switch(i.infoType()) { + case STREAM: + buildStreamInfoItem((StreamInfoItemHolder) holder, (StreamInfoItem) i); + break; + case CHANNEL: + buildChannelInfoItem((ChannelInfoItemHolder) holder, (ChannelInfoItem) i); + break; + case PLAYLIST: + Log.e(TAG, "Not yet implemented"); + break; + default: + Log.e(TAG, "Trollolo"); + } + } + + public View buildView(ViewGroup parent, final InfoItem info) { + View itemView = null; + InfoItemHolder holder = null; + switch(info.infoType()) { + case STREAM: + itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.stream_item, parent, false); + holder = new StreamInfoItemHolder(itemView); + break; + case CHANNEL: + itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.channel_item, parent, false); + holder = new ChannelInfoItemHolder(itemView); + break; + case PLAYLIST: + Log.e(TAG, "Not yet implemented"); + default: + Log.e(TAG, "Trollolo"); + } + buildByHolder(holder, info); + return itemView; + } + + private void buildStreamInfoItem(StreamInfoItemHolder holder, final StreamInfoItem info) { + if(info.infoType() != InfoItem.InfoType.STREAM) { + Log.e("InfoItemBuilder", "Info type not yet supported"); + } // fill holder with information holder.itemVideoTitleView.setText(info.title); if(info.uploader != null && !info.uploader.isEmpty()) { @@ -92,29 +162,55 @@ public class InfoItemBuilder { holder.itemButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - onItemSelectedListener.selected(info.webpage_url); + onStreamInfoItemSelectedListener.selected(info.webpage_url, info.service_id); } }); } - public View buildView(ViewGroup parent, final StreamPreviewInfo info) { - View streamPreviewView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.video_item, parent, false); - InfoItemHolder holder = new InfoItemHolder(streamPreviewView); - buildByHolder(holder, info); - return streamPreviewView; + private void buildChannelInfoItem(ChannelInfoItemHolder holder, final ChannelInfoItem info) { + holder.itemChannelTitleView.setText(info.getTitle()); + holder.itemSubscriberCountView.setText(shortSubscriber(info.subscriberCount) + " • "); + holder.itemVideoCountView.setText(info.videoAmount + " " + videosS); + holder.itemChannelDescriptionView.setText(info.description); + + holder.itemThumbnailView.setImageResource(R.drawable.buddy_channel_item); + if(info.thumbnailUrl != null && !info.thumbnailUrl.isEmpty()) { + imageLoader.displayImage(info.thumbnailUrl, + holder.itemThumbnailView, + displayImageOptions, + new ImageErrorLoadingListener(activity, rootView, info.serviceId)); + } + + holder.itemButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onChannelInfoItemSelectedListener.selected(info.getLink(), info.serviceId); + } + }); } - public static String shortViewCount(Long viewCount){ + public String shortViewCount(Long viewCount){ if(viewCount >= 1000000000){ - return Long.toString(viewCount/1000000000)+"B views"; + return Long.toString(viewCount/1000000000)+ billion + " " + viewsS; }else if(viewCount>=1000000){ - return Long.toString(viewCount/1000000)+"M views"; + return Long.toString(viewCount/1000000)+ million + " " + viewsS; }else if(viewCount>=1000){ - return Long.toString(viewCount/1000)+"K views"; + return Long.toString(viewCount/1000)+ thousand + " " + viewsS; }else { - return Long.toString(viewCount)+" views"; + return Long.toString(viewCount)+ " " + viewsS; + } + } + + public String shortSubscriber(Long viewCount){ + if(viewCount >= 1000000000){ + return Long.toString(viewCount/1000000000)+ billion + " " + subsS; + }else if(viewCount>=1000000){ + return Long.toString(viewCount/1000000)+ million + " " + subsS; + }else if(viewCount>=1000){ + return Long.toString(viewCount/1000)+ thousand + " " + subsS; + }else { + return Long.toString(viewCount)+ " " + subsS; } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java index 690376465..c1fab069b 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java @@ -2,14 +2,11 @@ package org.schabi.newpipe.info_list; import android.support.v7.widget.RecyclerView; import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.InfoItem; /** - * Created by Christian Schabesberger on 01.08.16. + * Created by Christian Schabesberger on 12.02.17. * * Copyright (C) Christian Schabesberger 2016 * InfoItemHolder.java is part of NewPipe. @@ -28,25 +25,9 @@ import org.schabi.newpipe.R; * along with NewPipe. If not, see . */ -public class InfoItemHolder extends RecyclerView.ViewHolder { - - public final ImageView itemThumbnailView; - public final TextView itemVideoTitleView, - itemUploaderView, - itemDurationView, - itemUploadDateView, - itemViewCountView; - public final Button itemButton; - +public abstract class InfoItemHolder extends RecyclerView.ViewHolder { public InfoItemHolder(View v) { super(v); - itemThumbnailView = (ImageView) v.findViewById(R.id.itemThumbnailView); - itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView); - itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView); - itemDurationView = (TextView) v.findViewById(R.id.itemDurationView); - itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView); - itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView); - itemButton = (Button) v.findViewById(R.id.item_button); } - + public abstract InfoItem.InfoType infoType(); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index cc5a0f4bb..adbdc22e7 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -2,12 +2,13 @@ package org.schabi.newpipe.info_list; import android.app.Activity; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream_info.StreamPreviewInfo; +import org.schabi.newpipe.extractor.InfoItem; import java.util.List; import java.util.Vector; @@ -33,47 +34,79 @@ import java.util.Vector; */ public class InfoListAdapter extends RecyclerView.Adapter { + private static final String TAG = InfoListAdapter.class.toString(); private final InfoItemBuilder infoItemBuilder; - private final List streamList; + private final List infoItemList; public InfoListAdapter(Activity a, View rootView) { infoItemBuilder = new InfoItemBuilder(a, rootView); - streamList = new Vector<>(); + infoItemList = new Vector<>(); } - public void setOnItemSelectedListener - (InfoItemBuilder.OnItemSelectedListener onItemSelectedListener) { - infoItemBuilder.setOnItemSelectedListener(onItemSelectedListener); + public void setOnStreamInfoItemSelectedListener + (InfoItemBuilder.OnInfoItemSelectedListener listener) { + infoItemBuilder.setOnStreamInfoItemSelectedListener(listener); } - public void addStreamItemList(List videos) { + public void setOnChannelInfoItemSelectedListener + (InfoItemBuilder.OnInfoItemSelectedListener listener) { + infoItemBuilder.setOnChannelInfoItemSelectedListener(listener); + } + + public void addInfoItemList(List videos) { if(videos!= null) { - streamList.addAll(videos); + infoItemList.addAll(videos); notifyDataSetChanged(); } } public void clearSteamItemList() { - streamList.clear(); + infoItemList.clear(); notifyDataSetChanged(); } @Override public int getItemCount() { - return streamList.size(); + return infoItemList.size(); + } + + // don't ask why we have to do that this way... it's android accept it -.- + @Override + public int getItemViewType(int position) { + switch(infoItemList.get(position).infoType()) { + case STREAM: + return 0; + case CHANNEL: + return 1; + case PLAYLIST: + return 2; + default: + Log.e(TAG, "Trollolo"); + return -1; + } } @Override - public InfoItemHolder onCreateViewHolder(ViewGroup parent, int i) { - View itemView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.video_item, parent, false); - - return new InfoItemHolder(itemView); + public InfoItemHolder onCreateViewHolder(ViewGroup parent, int type) { + switch(type) { + case 0: + return new StreamInfoItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.stream_item, parent, false)); + case 1: + return new ChannelInfoItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.channel_item, parent, false)); + case 2: + Log.e(TAG, "Playlist is not yet implemented"); + return null; + default: + Log.e(TAG, "Trollolo"); + return null; + } } @Override public void onBindViewHolder(InfoItemHolder holder, int i) { - infoItemBuilder.buildByHolder(holder, streamList.get(i)); + infoItemBuilder.buildByHolder(holder, infoItemList.get(i)); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java new file mode 100644 index 000000000..81981fd82 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java @@ -0,0 +1,58 @@ +package org.schabi.newpipe.info_list; + +import android.icu.text.IDNA; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.InfoItem; + +/** + * Created by Christian Schabesberger on 01.08.16. + * + * Copyright (C) Christian Schabesberger 2016 + * StreamInfoItemHolder.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class StreamInfoItemHolder extends InfoItemHolder { + + public final ImageView itemThumbnailView; + public final TextView itemVideoTitleView, + itemUploaderView, + itemDurationView, + itemUploadDateView, + itemViewCountView; + public final Button itemButton; + + public StreamInfoItemHolder(View v) { + super(v); + itemThumbnailView = (ImageView) v.findViewById(R.id.itemThumbnailView); + itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView); + itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView); + itemDurationView = (TextView) v.findViewById(R.id.itemDurationView); + itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView); + itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView); + itemButton = (Button) v.findViewById(R.id.item_button); + } + + @Override + public InfoItem.InfoType infoType() { + return InfoItem.InfoType.STREAM; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index 039ed8eaa..932d91584 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -297,6 +297,11 @@ public class ErrorActivity extends ThemableActivity { } errorView.setText(formErrorText(errorList)); + + //print stack trace once again for debugging: + for(String e : errorList) { + Log.e(TAG, e); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java index 09e7c0903..ee20990d4 100644 --- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java +++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java @@ -19,8 +19,10 @@ import android.view.inputmethod.InputMethodManager; import android.widget.ProgressBar; import android.widget.Toast; +import org.schabi.newpipe.ChannelActivity; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchResult; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.report.ErrorActivity; @@ -29,6 +31,8 @@ import org.schabi.newpipe.detail.VideoItemDetailActivity; import org.schabi.newpipe.detail.VideoItemDetailFragment; import org.schabi.newpipe.info_list.InfoListAdapter; +import java.util.EnumSet; + import static android.app.Activity.RESULT_OK; import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST; @@ -56,6 +60,9 @@ public class SearchInfoItemFragment extends Fragment { private static final String TAG = SearchInfoItemFragment.class.toString(); + private EnumSet filter = + EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM); + /** * Listener for search queries */ @@ -166,7 +173,7 @@ public class SearchInfoItemFragment extends Fragment { sw.setSearchWorkerResultListener(new SearchWorker.SearchWorkerResultListener() { @Override public void onResult(SearchResult result) { - infoListAdapter.addStreamItemList(result.resultList); + infoListAdapter.addInfoItemList(result.resultList); setDoneLoading(); } @@ -213,12 +220,19 @@ public class SearchInfoItemFragment extends Fragment { infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content)); - infoListAdapter.setOnItemSelectedListener(new InfoItemBuilder.OnItemSelectedListener() { + infoListAdapter.setOnStreamInfoItemSelectedListener( + new InfoItemBuilder.OnInfoItemSelectedListener() { @Override - public void selected(String url) { + public void selected(String url, int serviceId) { startDetailActivity(url); } }); + infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { + @Override + public void selected(String url, int serviceId) { + startChannelActivity(url, serviceId); + } + }); recyclerView.setAdapter(infoListAdapter); recyclerView.clearOnScrollListeners(); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @@ -250,6 +264,13 @@ public class SearchInfoItemFragment extends Fragment { getActivity().startActivity(i); } + private void startChannelActivity(String url, int serviceId) { + Intent i = new Intent(getActivity(), ChannelActivity.class); + i.putExtra(ChannelActivity.CHANNEL_URL, url); + i.putExtra(ChannelActivity.SERVICE_ID, serviceId); + startActivity(i); + } + @Override public void onStart() { super.onStart(); @@ -275,6 +296,32 @@ public class SearchInfoItemFragment extends Fragment { setupSearchView(searchView); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_filter_all: + changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL)); + return true; + case R.id.menu_filter_video: + changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM)); + return true; + case R.id.menu_filter_channel: + changeFilter(item, EnumSet.of(SearchEngine.Filter.CHANNEL)); + return true; + default: + return false; + } + } + + private void changeFilter(MenuItem item, EnumSet filter) { + this.filter = filter; + item.setChecked(true); + if(searchQuery != null && !searchQuery.isEmpty()) { + Log.d(TAG, "Fuck+ " + searchQuery); + search(searchQuery); + } + } + private void setupSearchView(SearchView searchView) { suggestionListAdapter = new SuggestionListAdapter(getActivity()); searchView.setSuggestionsAdapter(suggestionListAdapter); @@ -298,7 +345,11 @@ public class SearchInfoItemFragment extends Fragment { private void search(String query, int page) { isLoading = true; SearchWorker sw = SearchWorker.getInstance(); - sw.search(streamingServiceId, query, page, getActivity()); + sw.search(streamingServiceId, + query, + page, + getActivity(), + filter); } private void setDoneLoading() { diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java index 9b1e8d86e..6a18f42dd 100644 --- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java +++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java @@ -16,6 +16,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import java.io.IOException; +import java.util.EnumSet; /** * Created by Christian Schabesberger on 02.08.16. @@ -67,14 +68,21 @@ public class SearchWorker { public static final String YOUTUBE = "Youtube"; private final String query; private final int page; + private final EnumSet filter; final Handler h = new Handler(); private volatile boolean runs = true; private Activity a = null; private int serviceId = -1; - public SearchRunnable(int serviceId, String query, int page, Activity activity, int requestId) { + public SearchRunnable(int serviceId, + String query, + int page, + EnumSet filter, + Activity activity, + int requestId) { this.serviceId = serviceId; this.query = query; this.page = page; + this.filter = filter; this.a = activity; } void terminate() { @@ -102,13 +110,14 @@ public class SearchWorker { String searchLanguage = sp.getString(searchLanguageKey, a.getString(R.string.default_language_value)); result = SearchResult - .getSearchResult(engine, query, page, searchLanguage); + .getSearchResult(engine, query, page, searchLanguage, filter); if(runs) { h.post(new ResultRunnable(result, requestId)); } // look for errors during extraction // soft errors: + View rootView = a.findViewById(android.R.id.content); if(result != null && !result.errors.isEmpty()) { Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:"); @@ -117,11 +126,17 @@ public class SearchWorker { Log.e(TAG, "------"); } - View rootView = a.findViewById(android.R.id.content); - ErrorActivity.reportError(h, a, result.errors, null, rootView, - ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, - serviceName, query, R.string.light_parsing_error)); - + if(result.resultList.isEmpty()&& !result.errors.isEmpty()) { + // if it compleatly failes dont show snackbar, instead show error directlry + ErrorActivity.reportError(h, a, result.errors, null, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + serviceName, query, R.string.parsing_error)); + } else { + // if it partly show snackbar + ErrorActivity.reportError(h, a, result.errors, null, rootView, + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + serviceName, query, R.string.light_parsing_error)); + } } // hard errors: } catch (ReCaptchaException e) { @@ -180,11 +195,15 @@ public class SearchWorker { } - public void search(int serviceId, String query, int page, Activity a) { + public void search(int serviceId, + String query, + int page, + Activity a, + EnumSet filter) { if(runnable != null) { terminate(); } - runnable = new SearchRunnable(serviceId, query, page, a, requestId); + runnable = new SearchRunnable(serviceId, query, page, filter, a, requestId); Thread thread = new Thread(runnable); thread.start(); } diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java index 0110052ec..8b8f75e7e 100644 --- a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java +++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java @@ -8,7 +8,7 @@ import android.widget.Toast; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.search.SuggestionExtractor; +import org.schabi.newpipe.extractor.SuggestionExtractor; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.R; diff --git a/app/src/main/res/drawable-hdpi/ic_headset_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_headset_black_24dp.png new file mode 100644 index 000000000..38eb219ef Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_headset_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_screen_rotation_white_24dp.png new file mode 100644 index 000000000..fa2c881e0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_screen_rotation_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png new file mode 100644 index 000000000..20ba48063 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_screen_rotation_white_24dp.png new file mode 100644 index 000000000..b6acecf34 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_screen_rotation_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png new file mode 100644 index 000000000..f02d360aa Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png differ diff --git a/app/src/main/res/drawable-nodpi/buddy_channel_item.png b/app/src/main/res/drawable-nodpi/buddy_channel_item.png new file mode 100644 index 000000000..d43c2cbd9 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/buddy_channel_item.png differ diff --git a/app/src/main/res/drawable-nodpi/dummi_thumbnail_playlist.png b/app/src/main/res/drawable-nodpi/dummi_thumbnail_playlist.png new file mode 100644 index 000000000..c70e4bf14 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/dummi_thumbnail_playlist.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_cast_black.png b/app/src/main/res/drawable-nodpi/ic_cast_black.png deleted file mode 100644 index 9dbfcd941..000000000 Binary files a/app/src/main/res/drawable-nodpi/ic_cast_black.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/ic_file_download_black.png b/app/src/main/res/drawable-nodpi/ic_file_download_black.png deleted file mode 100644 index 8c83bffa7..000000000 Binary files a/app/src/main/res/drawable-nodpi/ic_file_download_black.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/ic_headset_black.png b/app/src/main/res/drawable-nodpi/ic_headset_black.png deleted file mode 100644 index 974457ee1..000000000 Binary files a/app/src/main/res/drawable-nodpi/ic_headset_black.png and /dev/null differ diff --git a/app/src/main/res/drawable-nodpi/ic_screen_rotation_white.png b/app/src/main/res/drawable-nodpi/ic_screen_rotation_white.png deleted file mode 100644 index 449b6725f..000000000 Binary files a/app/src/main/res/drawable-nodpi/ic_screen_rotation_white.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldrtl-xhdpi/ic_menu_more.png b/app/src/main/res/drawable-xhdpi/ic_menu_more.png similarity index 100% rename from app/src/main/res/drawable-ldrtl-xhdpi/ic_menu_more.png rename to app/src/main/res/drawable-xhdpi/ic_menu_more.png diff --git a/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white_24dp.png new file mode 100644 index 000000000..9092249af Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_screen_rotation_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png new file mode 100644 index 000000000..81c80b700 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white_24dp.png new file mode 100644 index 000000000..cc0fa87f7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_screen_rotation_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png new file mode 100644 index 000000000..784933ad5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white_24dp.png new file mode 100644 index 000000000..d67b21ae8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_screen_rotation_white_24dp.png differ diff --git a/app/src/main/res/drawable-nodpi/ic_share_black.png b/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png similarity index 100% rename from app/src/main/res/drawable-nodpi/ic_share_black.png rename to app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png diff --git a/app/src/main/res/layout/channel_item.xml b/app/src/main/res/layout/channel_item.xml new file mode 100644 index 000000000..188ab3922 --- /dev/null +++ b/app/src/main/res/layout/channel_item.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +