get channel banner

This commit is contained in:
Christian Schabesberger 2016-08-01 01:56:19 +02:00
parent 9a0f61e60b
commit 6beb36f92f
25 changed files with 420 additions and 57 deletions

View File

@ -45,4 +45,5 @@ dependencies {
compile 'com.google.code.gson:gson:2.4'
compile 'com.nononsenseapps:filepicker:2.0.5'
testCompile 'junit:junit:4.12'
//compile 'net.sourceforge.cssparser:cssparser:0.9.20'
}

View File

@ -79,6 +79,10 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
assertTrue(extractor.getUploadDate().length() > 0);
}
public void testGetChannelUrl() throws ParsingException {
assertTrue(extractor.getChannelUrl().length() > 0);
}
public void testGetThumbnailUrl() throws ParsingException {
assertTrue(extractor.getThumbnailUrl(),
extractor.getThumbnailUrl().contains(HTTPS));

View File

@ -43,7 +43,7 @@ public class ActivityCommunicator {
public volatile Bitmap backgroundPlayerThumbnail;
// Sent from any activity to ErrorActivity.
public volatile List<Exception> errorList;
public volatile List<Throwable> errorList;
public volatile Class returnActivity;
public volatile ErrorActivity.ErrorInfo errorInfo;
}

View File

@ -1,28 +1,170 @@
package org.schabi.newpipe;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.ChannelInfo;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import java.io.IOException;
public class ChannelActivity extends AppCompatActivity {
private static final String TAG = ChannelActivity.class.toString();
private View rootView = null;
class FailedThumbnailListener implements ImageLoadingListener {
int serviceId = -1;
public FailedThumbnailListener(int serviceId) {
this.serviceId= serviceId;
}
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(ChannelActivity.this,
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ServiceList.getNameOfService(serviceId), imageUri,
R.string.could_not_load_image));
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
}
}
// intent const
public static final String CHANNEL_URL = "channel_url";
public static final String SERVICE_ID = "service_id";
private int serviceId = -1;
private String channelUrl = "";
private ImageLoader imageLoader = ImageLoader.getInstance();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_channel);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
rootView = findViewById(R.id.rootView);
setSupportActionBar(toolbar);
Intent i = getIntent();
channelUrl = i.getStringExtra(CHANNEL_URL);
serviceId = i.getIntExtra(SERVICE_ID, -1);
// start processing
Thread channelExtractorThread = new Thread(new Runnable() {
Handler h = new Handler();
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
public void run() {
try {
StreamingService service = ServiceList.getService(serviceId);
ChannelExtractor extractor = service.getChannelExtractorInstance(
channelUrl, new Downloader());
final ChannelInfo info = ChannelInfo.getInfo(extractor, new Downloader());
h.post(new Runnable() {
@Override
public void run() {
updateUi(info);
}
});
} catch(IOException ioe) {
postNewErrorToast(h, R.string.network_error);
ioe.printStackTrace();
} catch(ParsingException pe) {
pe.printStackTrace();
} catch(ExtractionException ex) {
ex.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
});
channelExtractorThread.start();
}
private void updateUi(final ChannelInfo info) {
CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
ImageView channelBanner = (ImageView) findViewById(R.id.channel_banner_image);
View channelContentView = (View) findViewById(R.id.channel_content_view);
FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
progressBar.setVisibility(View.GONE);
channelContentView.setVisibility(View.VISIBLE);
if(info.channel_name != null && !info.channel_name.isEmpty()) {
ctl.setTitle(info.channel_name);
}
if(info.banner_url != null && !info.banner_url.isEmpty()) {
imageLoader.displayImage(info.banner_url, channelBanner,
new FailedThumbnailListener(info.service_id));
}
if(info.feed_url != null && !info.feed_url.isEmpty()) {
feedButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, info.feed_url);
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(info.feed_url + ".rss"));
startActivity(i);
}
});
} else {
feedButton.setVisibility(View.GONE);
}
}
private void postNewErrorToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(ChannelActivity.this,
stringResource, Toast.LENGTH_LONG).show();
}
});
}
}

View File

@ -79,16 +79,19 @@ public class ErrorActivity extends AppCompatActivity {
public static final int GET_SUGGESTIONS = 2;
public static final int SOMETHING_ELSE = 3;
public static final int USER_REPORT = 4;
public static final int LOAD_IMAGE = 5;
public static final String SEARCHED_STRING = "searched";
public static final String REQUESTED_STREAM_STRING = "requested stream";
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
public static final String SOMETHING_ELSE_STRING = "something";
public static final String USER_REPORT_STRING = "user report";
public static final String LOAD_IMAGE_STRING = "load image";
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
private List<Exception> errorList;
private List<Throwable> errorList;
private ErrorInfo errorInfo;
private Class returnActivity;
private String currentTimeStamp;
@ -102,7 +105,7 @@ public class ErrorActivity extends AppCompatActivity {
private TextView infoView;
private TextView errorMessageView;
public static void reportError(final Context context, final List<Exception> el,
public static void reportError(final Context context, final List<Throwable> el,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
if (rootView != null) {
@ -129,9 +132,9 @@ public class ErrorActivity extends AppCompatActivity {
}
}
public static void reportError(final Context context, final Exception e,
public static void reportError(final Context context, final Throwable e,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
List<Exception> el = null;
List<Throwable> el = null;
if(e != null) {
el = new Vector<>();
el.add(e);
@ -140,10 +143,10 @@ public class ErrorActivity extends AppCompatActivity {
}
// async call
public static void reportError(Handler handler, final Context context, final Exception e,
public static void reportError(Handler handler, final Context context, final Throwable e,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
List<Exception> el = null;
List<Throwable> el = null;
if(e != null) {
el = new Vector<>();
el.add(e);
@ -152,7 +155,7 @@ public class ErrorActivity extends AppCompatActivity {
}
// async call
public static void reportError(Handler handler, final Context context, final List<Exception> el,
public static void reportError(Handler handler, final Context context, final List<Throwable> el,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
handler.post(new Runnable() {
@Override
@ -171,7 +174,7 @@ public class ErrorActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.error_report_title);
actionBar.setDisplayShowTitleEnabled(true);
} catch (Exception e) {
} catch (Throwable e) {
Log.e(TAG, "Error turing exception handling");
e.printStackTrace();
}
@ -252,10 +255,10 @@ public class ErrorActivity extends AppCompatActivity {
return sw.getBuffer().toString();
}
private String formErrorText(List<Exception> el) {
private String formErrorText(List<Throwable> el) {
String text = "";
if(el != null) {
for (Exception e : el) {
for (Throwable e : el) {
text += "-------------------------------------\n"
+ getStackTrace(e);
}
@ -313,7 +316,7 @@ public class ErrorActivity extends AppCompatActivity {
JSONArray exceptionArray = new JSONArray();
if(errorList != null) {
for (Exception e : errorList) {
for (Throwable e : errorList) {
exceptionArray.put(getStackTrace(e));
}
}
@ -322,7 +325,7 @@ public class ErrorActivity extends AppCompatActivity {
errorObject.put("user_comment", userCommentBox.getText().toString());
return errorObject.toString(3);
} catch (Exception e) {
} catch (Throwable e) {
Log.e(TAG, "Error while erroring: Could not build json");
e.printStackTrace();
}
@ -390,7 +393,7 @@ public class ErrorActivity extends AppCompatActivity {
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
+ "0.0";
} catch(Exception e) {
} catch(Throwable e) {
Log.d(TAG, "Error while error: could not get iprange");
e.printStackTrace();
} finally {

View File

@ -116,6 +116,8 @@ public class VideoItemDetailFragment extends Fragment {
private DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().cacheInMemory(true).build();
private View rootView = null;
public interface OnInvokeCreateOptionsMenuListener {
void createOptionsMenu();
@ -150,7 +152,7 @@ public class VideoItemDetailFragment extends Fragment {
if(streamInfo != null &&
!streamInfo.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for (Exception e : streamInfo.errors) {
for (Throwable e : streamInfo.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
@ -449,13 +451,19 @@ public class VideoItemDetailFragment extends Fragment {
}
});
channelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(activity, ChannelActivity.class);
startActivity(i);
}
});
if(info.channel_url != null && info.channel_url != "") {
channelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent i = new Intent(activity, ChannelActivity.class);
i.putExtra(ChannelActivity.CHANNEL_URL, info.channel_url);
i.putExtra(ChannelActivity.SERVICE_ID, info.service_id);
startActivity(i);
}
});
} else {
channelButton.setVisibility(Button.GONE);
}
} catch (java.lang.NullPointerException e) {
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
@ -463,7 +471,7 @@ public class VideoItemDetailFragment extends Fragment {
}
}
private void initThumbnailViews(StreamInfo info, View nextVideoFrame) {
private void initThumbnailViews(final StreamInfo info, View nextVideoFrame) {
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
ImageView uploaderThumb
= (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView);
@ -482,6 +490,12 @@ public class VideoItemDetailFragment extends Fragment {
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
failReason.getCause().printStackTrace();
ErrorActivity.reportError(getActivity(),
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ServiceList.getNameOfService(info.service_id), imageUri,
R.string.could_not_load_thumbnails));
}
@Override
@ -797,7 +811,7 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
progressBar = (ProgressBar) rootView.findViewById(R.id.detailProgressBar);
actionBarHandler = new ActionBarHandler(activity);

View File

@ -360,7 +360,7 @@ public class VideoItemListActivity extends AppCompatActivity
return true;
}
case R.id.action_report_error: {
ErrorActivity.reportError(VideoItemListActivity.this, new Vector<Exception>(),
ErrorActivity.reportError(VideoItemListActivity.this, new Vector<Throwable>(),
null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
ServiceList.getNameOfService(currentStreamingServiceId),

View File

@ -121,7 +121,7 @@ public class VideoItemListFragment extends ListFragment {
if(result != null &&
!result.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
for(Exception e : result.errors) {
for(Throwable e : result.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}

View File

@ -268,7 +268,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true;
}
case R.id.action_report_error: {
ErrorActivity.reportError(MainActivity.this, new Vector<Exception>(),
ErrorActivity.reportError(MainActivity.this, new Vector<Throwable>(),
null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
null,

View File

@ -43,4 +43,8 @@ public abstract class ChannelExtractor {
public abstract String getChannelName() throws ParsingException;
public abstract String getAvatarUrl() throws ParsingException;
public abstract String getBannerUrl() throws ParsingException;
public abstract String getFeedUrl() throws ParsingException;
public int getServiceId() {
return serviceId;
}
}

View File

@ -0,0 +1,69 @@
package org.schabi.newpipe.extractor;
import android.util.Log;
import java.util.List;
import java.util.Vector;
/**
* Created by Christian Schabesberger on 31.07.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelInfo.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 <http://www.gnu.org/licenses/>.
*/
public class ChannelInfo {
public void addException(Exception e) {
errors.add(e);
}
public static ChannelInfo getInfo(ChannelExtractor extractor, Downloader dl)
throws ParsingException {
ChannelInfo info = new ChannelInfo();
// importand data
info.service_id = extractor.getServiceId();
info.channel_name = extractor.getChannelName();
try {
info.avatar_url = extractor.getAvatarUrl();
} catch (Exception e) {
info.errors.add(e);
}
try {
info.banner_url = extractor.getBannerUrl();
} catch (Exception e) {
info.errors.add(e);
}
try {
info.feed_url = extractor.getFeedUrl();
} catch(Exception e) {
info.errors.add(e);
}
return info;
}
public int service_id = -1;
public String channel_name = "";
public String avatar_url = "";
public String banner_url = "";
public String feed_url = "";
public List<Throwable> errors = new Vector<>();
}

View File

@ -43,5 +43,5 @@ public class SearchResult {
public String suggestion = "";
public List<StreamPreviewInfo> resultList = new Vector<>();
public List<Exception> errors = new Vector<>();
public List<Throwable> errors = new Vector<>();
}

View File

@ -81,6 +81,7 @@ public abstract class StreamExtractor {
public abstract String getTitle() throws ParsingException;
public abstract String getDescription() throws ParsingException;
public abstract String getUploader() throws ParsingException;
public abstract String getChannelUrl() throws ParsingException;
public abstract int getLength() throws ParsingException;
public abstract long getViewCount() throws ParsingException;
public abstract String getUploadDate() throws ParsingException;

View File

@ -188,6 +188,11 @@ public class StreamInfo extends AbstractVideoInfo {
} catch(Exception e) {
streamInfo.addException(e);
}
try {
streamInfo.channel_url = extractor.getChannelUrl();
} catch(Exception e) {
streamInfo.addException(e);
}
try {
streamInfo.description = extractor.getDescription();
} catch(Exception e) {
@ -258,6 +263,7 @@ public class StreamInfo extends AbstractVideoInfo {
}
public String uploader_thumbnail_url = "";
public String channel_url = "";
public String description = "";
public List<VideoStream> video_streams = null;
@ -279,5 +285,5 @@ public class StreamInfo extends AbstractVideoInfo {
//in seconds. some metadata is not passed using a StreamInfo object!
public int start_position = 0;
public List<Exception> errors = new Vector<>();
public List<Throwable> errors = new Vector<>();
}

View File

@ -27,7 +27,7 @@ import java.util.Vector;
public class StreamPreviewInfoCollector {
private List<StreamPreviewInfo> itemList = new Vector<>();
private List<Exception> errors = new Vector<>();
private List<Throwable> errors = new Vector<>();
private UrlIdHandler urlIdHandler;
private int serviceId = -1;
@ -40,7 +40,7 @@ public class StreamPreviewInfoCollector {
return itemList;
}
public List<Exception> getErrors() {
public List<Throwable> getErrors() {
return errors;
}

View File

@ -2,14 +2,30 @@ package org.schabi.newpipe.extractor.services.youtube;
import android.util.Log;
/*
import com.steadystate.css.dom.CSSStyleDeclarationImpl;
import com.steadystate.css.dom.CSSStyleSheetImpl;
import com.steadystate.css.parser.CSSOMParser;
import com.steadystate.css.parser.SACParserCSS3;
import org.w3c.css.sac.CSSParseException;
import org.w3c.css.sac.InputSource;
import org.w3c.dom.css.CSSRule;
import org.w3c.dom.css.CSSRuleList;
import org.w3c.dom.css.CSSStyleSheet;
import java.io.StringReader;
*/
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.UrlIdHandler;
import java.io.IOException;
/**
@ -36,6 +52,8 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
private static final String TAG = YoutubeChannelExtractor.class.toString();
// private CSSOMParser cssParser = new CSSOMParser(new SACParserCSS3());
private Downloader downloader;
private final Document doc;
private final String siteUrl;
@ -45,17 +63,21 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
throws ExtractionException, IOException {
super(urlIdHandler, url, dl, serviceId);
siteUrl = url;
siteUrl = urlIdHandler.cleanUrl(url);
Log.d(TAG, siteUrl);
downloader = dl;
String pageContent = downloader.download(url);
doc = Jsoup.parse(pageContent, url);
Log.d(TAG, pageContent);
}
@Override
public String getChannelName() throws ParsingException {
return getUrlIdHandler().getId(siteUrl);
try {
return doc.select("span[class=\"qualified-channel-title-text\"]").first()
.select("a").first().text();
} catch(Exception e) {
throw new ParsingException("Could not get channel name");
}
}
@Override
@ -70,6 +92,41 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
@Override
public String getBannerUrl() throws ParsingException {
return "https://yt3.ggpht.com/-oF0YbeAGkaA/VBgrKvEGY1I/AAAAAAAACdw/nx02iZSseFw/w2120-fcrop64=1,00005a57ffffa5a8-nd-c0xffffffff-rj-k-no/Channel-Art-Template-%2528Photoshop%2529.png";
String cssContent = "";
try {
Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first();
cssContent = el.html();
// todo: parse this using a css parser
/*
CSSStyleSheet sheet = cssParser.parseStyleSheet(
new org.w3c.css.sac.InputSource(
new StringReader(cssContent)), null, null);
CSSRuleList rules = sheet.getCssRules();
for (int i = 0; i < rules.getLength(); i++) {
final CSSRule rule = rules.item(i);
System.out.println(rule.getCssText());
}
*/
String url = "https:" + Parser.matchGroup1("url\\((.*)\\)", cssContent);
if(url.contains("s.ytimg.com")) {
return null;
} else {
return url;
}
/* } catch(CSSParseException csse) {
throw new ParsingException("Could not parse css: " + cssContent); */
} catch(Exception e) {
throw new ParsingException("Could not get Banner", e);
}
}
@Override
public String getFeedUrl() throws ParsingException {
return siteUrl + "/feed";
}
private String getUserUrl() throws ParsingException {
return doc.select("span[class=\"qualified-channel-title-text\"]").first()
.select("a").first().attr("abs:href");
}
}

View File

@ -27,15 +27,11 @@ import org.schabi.newpipe.extractor.UrlIdHandler;
public class YoutubeChannelUrlIdHandler implements UrlIdHandler {
public String getUrl(String channelId) {
return "https://www.youtube.com/user/" + channelId + "/videos";
return "https://www.youtube.com/" + channelId;
}
public String getId(String siteUrl) throws ParsingException {
try {
return Parser.matchGroup1("/user/(.*)", siteUrl);
} catch(Exception e) {
throw new ParsingException("Could not get channel/user id", e);
}
return Parser.matchGroup1("/(user/[A-Za-z0-9_-]*|channel/[A-Za-z0-9_-]*)", siteUrl);
}
public String cleanUrl(String siteUrl) throws ParsingException {
@ -45,6 +41,7 @@ public class YoutubeChannelUrlIdHandler implements UrlIdHandler {
public boolean acceptUrl(String videoUrl) {
return (videoUrl.contains("youtube") ||
videoUrl.contains("youtu.be")) &&
videoUrl.contains("/user/");
( videoUrl.contains("/user/") ||
videoUrl.contains("/channel/"));
}
}

View File

@ -685,6 +685,16 @@ public class YoutubeStreamExtractor extends StreamExtractor {
return pageUrl;
}
@Override
public String getChannelUrl() throws ParsingException {
try {
return doc.select("div[class=\"yt-user-info\"]").first().children()
.select("a").first().attr("abs:href");
} catch(Exception e) {
throw new ParsingException("Could not get channel link", e);
}
}
@Override
public StreamInfo.StreamType getStreamType() throws ParsingException {
//todo: if implementing livestream support this value should be generated dynamically

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

View File

@ -5,25 +5,36 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:id="@+id/rootView"
tools:context="org.schabi.newpipe.ChannelActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:id="@+id/channel_app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:id="@+id/channel_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:contentScrim="@color/light_youtube_primary_color"
app:statusBarScrim="@color/light_youtube_dark_color"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/channel_banner_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:background="@color/light_youtube_dark_color"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:id="@+id/cannel_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
@ -32,15 +43,58 @@
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_channel" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:id="@+id/channel_rss_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"
app:layout_anchor="@id/app_bar"
android:src="@drawable/ic_rss_feed_white_24dp"
app:layout_anchor="@id/channel_app_bar"
app:layout_anchorGravity="bottom|end" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/channel_loading">
<ProgressBar android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"/>
</RelativeLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="fill_vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/channel_content_view"
android:visibility="gone">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lorem ipsum dolor sit amet, sed in vocent nonumes maluisset. No dicant munere apeirian qui, iisque invenire ius in, sea duis illud dolor ex. Ei cum dolorem conclusionemque, in persius feugait efficiendi est, duo cu nonumy graeco ponderum. Efficiendi interpretaris ei pro. Ea salutandi suavitate qualisque qui.
Cum ad sumo modus, duo et libris posidonium reprehendunt. Ex nam ornatus delectus. Eum quidam repudiandae ne. Te vidit senserit eos, has justo copiosae probatus ne. Ea vim nisl aeterno fabulas, per an legimus laoreet, natum abhorreant sit no. Epicurei cotidieque cu eum, novum tantas at has, ad eam malis reprimique intellegam.
Posse doming consulatu no mel. Eu tale iudico his. Nibh nostrum ea mei, te amet esse consequat mea. Corrumpit iracundia an eam, omnesque suavitate erroribus mel ei. Ea suavitate urbanitas nec, mei te dolore menandri theophrastus, ad quaeque delicata vix.
Has sale mucius menandri eu, pri veniam partem deterruisset ei. In nihil nominavi accusata mea, te sit quis veniam. Nam dissentiet conclusionemque id. Ex novum verterem usu.
Eum fastidii consulatu cu. In nobis iuvaret usu, paulo tincidunt no usu, minim corpora his te. Nam graeco delenit omittam cu. Et sed ipsum fastidii, mea cu altera ullamcorper. Sea id altera menandri deseruisse, munere audire utroque cu pri. Nec solet facilis id. Mucius delectus eu vis, has in augue veniam.
Lorem ipsum dolor sit amet, sed in vocent nonumes maluisset. No dicant munere apeirian qui, iisque invenire ius in, sea duis illud dolor ex. Ei cum dolorem conclusionemque, in persius feugait efficiendi est, duo cu nonumy graeco ponderum. Efficiendi interpretaris ei pro. Ea salutandi suavitate qualisque qui.
Cum ad sumo modus, duo et libris posidonium reprehendunt. Ex nam ornatus delectus. Eum quidam repudiandae ne. Te vidit senserit eos, has justo copiosae probatus ne. Ea vim nisl aeterno fabulas, per an legimus laoreet, natum abhorreant sit no. Epicurei cotidieque cu eum, novum tantas at has, ad eam malis reprimique intellegam.
Posse doming consulatu no mel. Eu tale iudico his. Nibh nostrum ea mei, te amet esse consequat mea. Corrumpit iracundia an eam, omnesque suavitate erroribus mel ei. Ea suavitate urbanitas nec, mei te dolore menandri theophrastus, ad quaeque delicata vix.
Has sale mucius menandri eu, pri veniam partem deterruisset ei. In nihil nominavi accusata mea, te sit quis veniam. Nam dissentiet conclusionemque id. Ex novum verterem usu.
Eum fastidii consulatu cu. In nobis iuvaret usu, paulo tincidunt no usu, minim corpora his te. Nam graeco delenit omittam cu. Et sed ipsum fastidii, mea cu altera ullamcorper. Sea id altera menandri deseruisse, munere audire utroque cu pri. Nec solet facilis id. Mucius delectus eu vis, has in augue veniam."/>
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

View File

@ -96,6 +96,7 @@
<string name="could_not_setup_download_menu">Could not setup download menu.</string>
<string name="live_streams_not_supported">This is a LIVE STREAM. These are not yet supported.</string>
<string name="could_not_get_stream">Could not get any stream.</string>
<string name="could_not_load_image">Could not load Image</string>
<!-- error activity -->
<string name="sorry_string">Sorry that should not happen.</string>
<string name="guru_meditation" translatable="false">Guru Meditation.</string>