fix merge with add_cahnnels

This commit is contained in:
Christian Schabesberger 2016-09-12 00:12:00 +02:00
commit 54ab0ab17e
79 changed files with 2611 additions and 1659 deletions

View file

@ -31,6 +31,7 @@ android {
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1' compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.android.support:support-v4:24.1.1' compile 'com.android.support:support-v4:24.1.1'
compile 'com.android.support:design:24.1.1' compile 'com.android.support:design:24.1.1'

View file

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

View file

@ -16,10 +16,10 @@
android:logo="@mipmap/ic_launcher" android:logo="@mipmap/ic_launcher"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="AllowBackup"> tools:ignore="AllowBackup">
<activity <activity
android:name=".VideoItemListActivity" android:name=".MainActivity"
android:label="@string/app_name" android:label="@string/app_name">
android:theme="@style/SplashScreenTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -27,12 +27,12 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".VideoItemDetailActivity" android:name=".detail.VideoItemDetailActivity"
android:label="@string/title_videoitem_detail" android:label="@string/title_videoitem_detail"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".VideoItemListActivity" /> android:value=".MainActivity" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -81,10 +81,12 @@
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/VideoPlayerTheme" android:theme="@style/VideoPlayerTheme"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />
<service <service
android:name=".player.BackgroundPlayer" android:name=".player.BackgroundPlayer"
android:exported="false" android:exported="false"
android:label="@string/background_player_name" /> android:label="@string/background_player_name" />
<activity <activity
android:name=".player.ExoPlayerActivity" android:name=".player.ExoPlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
@ -103,12 +105,14 @@
<data android:scheme="file" /> <data android:scheme="file" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".player.BackgroundPlayer" android:name=".player.BackgroundPlayer"
android:label="@string/background_player_name" android:exported="false"
android:exported="false" /> android:label="@string/background_player_name" />
<activity <activity
android:name=".SettingsActivity" android:name=".settings.SettingsActivity"
android:label="@string/settings_activity_title" /> android:label="@string/settings_activity_title" />
<activity <activity
android:name=".PanicResponderActivity" android:name=".PanicResponderActivity"
@ -131,12 +135,10 @@
<activity <activity
android:name=".download.MainActivity" android:name=".download.MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" android:launchMode="singleTask"
android:launchMode="singleTask"> android:theme="@style/AppTheme" />
</activity>
<service <service android:name="us.shandian.giga.service.DownloadManagerService" />
android:name="us.shandian.giga.service.DownloadManagerService"/>
<activity <activity
android:name="com.nononsenseapps.filepicker.FilePickerActivity" android:name="com.nononsenseapps.filepicker.FilePickerActivity"
@ -145,5 +147,11 @@
android:launchMode="singleTop"> android:launchMode="singleTop">
</activity> </activity>
<activity
android:name=".ChannelActivity"
android:label="@string/title_activity_channel"
android:theme="@style/AppTheme.NoActionBar" />
</application> </application>
</manifest> </manifest>

View file

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

View file

@ -2,12 +2,12 @@ package org.schabi.newpipe;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import org.schabi.newpipe.settings.SettingsActivity;
import info.guardianproject.netcipher.NetCipher; import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper; import info.guardianproject.netcipher.proxy.OrbotHelper;

View file

@ -0,0 +1,248 @@
package org.schabi.newpipe;
import android.content.Intent;
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.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.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
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 org.schabi.newpipe.info_list.InfoListAdapter;
import java.io.IOException;
public class ChannelActivity extends AppCompatActivity {
private static final String TAG = ChannelActivity.class.toString();
private View rootView = null;
// 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 int pageNumber = 0;
private boolean hasNextPage = true;
private boolean isLoading = false;
private ImageLoader imageLoader = ImageLoader.getInstance();
private InfoListAdapter infoListAdapter = null;
@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);
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 InfoListAdapter.OnItemSelectedListener() {
@Override
public void selected(String url) {
Intent detailIntent = new Intent(ChannelActivity.this, VideoItemDetailActivity.class);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
detailIntent.putExtra(
VideoItemDetailFragment.STREAMING_SERVICE, serviceId);
startActivity(detailIntent);
}
});
// detect if list has ben scrolled to the bottom
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
if(dy > 0) //check for scroll down
{
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount
&& !isLoading
&& hasNextPage)
{
pageNumber++;
requestData(true);
}
}
}
});
requestData(false);
}
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);
FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
ImageView avatarView = (ImageView) findViewById(R.id.channel_avatar_view);
ImageView haloView = (ImageView) findViewById(R.id.channel_avatar_halo);
progressBar.setVisibility(View.GONE);
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 ImageErrorLoadingListener(this, rootView ,info.service_id));
}
if(info.avatar_url != null && !info.avatar_url.isEmpty()) {
avatarView.setVisibility(View.VISIBLE);
haloView.setVisibility(View.VISIBLE);
imageLoader.displayImage(info.avatar_url, avatarView,
new ImageErrorLoadingListener(this, rootView ,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));
startActivity(i);
}
});
} else {
feedButton.setVisibility(View.GONE);
}
}
private void addVideos(final ChannelInfo info) {
infoListAdapter.addStreamItemList(info.related_streams);
}
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();
}
});
}
private void requestData(final boolean onlyVideos) {
// start processing
isLoading = true;
Thread channelExtractorThread = new Thread(new Runnable() {
Handler h = new Handler();
@Override
public void run() {
StreamingService service = null;
try {
service = ServiceList.getService(serviceId);
ChannelExtractor extractor = service.getChannelExtractorInstance(
channelUrl, pageNumber, new Downloader());
final ChannelInfo info = ChannelInfo.getInfo(extractor, new Downloader());
h.post(new Runnable() {
@Override
public void run() {
isLoading = false;
if(!onlyVideos) {
updateUi(info);
}
hasNextPage = info.hasNextPage;
addVideos(info);
}
});
// look for non critical errors during extraction
if(info != null &&
!info.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for (Throwable e : info.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
View rootView = findViewById(android.R.id.content);
ErrorActivity.reportError(h, ChannelActivity.this,
info.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, channelUrl, 0 /* no message for the user */));
}
} catch(IOException ioe) {
postNewErrorToast(h, R.string.network_error);
ioe.printStackTrace();
} catch(ParsingException pe) {
ErrorActivity.reportError(h, ChannelActivity.this, pe, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, channelUrl, R.string.parsing_error));
h.post(new Runnable() {
@Override
public void run() {
ChannelActivity.this.finish();
}
});
pe.printStackTrace();
} catch(ExtractionException ex) {
ErrorActivity.reportError(h, ChannelActivity.this, ex, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, channelUrl, R.string.parsing_error));
h.post(new Runnable() {
@Override
public void run() {
ChannelActivity.this.finish();
}
});
ex.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, ChannelActivity.this, e, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, channelUrl, R.string.general_error));
h.post(new Runnable() {
@Override
public void run() {
ChannelActivity.this.finish();
}
});
e.printStackTrace();
}
}
});
channelExtractorThread.start();
}
}

View file

@ -5,10 +5,12 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import info.guardianproject.netcipher.NetCipher;
/** /**
* Created by Christian Schabesberger on 28.01.16. * Created by Christian Schabesberger on 28.01.16.
@ -40,10 +42,26 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
* @param language the language (usually a 2-character code) to set as the preferred language * @param language the language (usually a 2-character code) to set as the preferred language
* @return the contents of the specified text file*/ * @return the contents of the specified text file*/
public String download(String siteUrl, String language) throws IOException { public String download(String siteUrl, String language) throws IOException {
Map<String, String> requestProperties = new HashMap<>();
requestProperties.put("Accept-Language", language);
return download(siteUrl, requestProperties);
}
/**Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string.
* @param siteUrl the URL of the text file to return the contents of
* @param customProperties set request header properties
* @return the contents of the specified text file
* @throws IOException*/
public String download(String siteUrl, Map<String, String> customProperties) throws IOException {
URL url = new URL(siteUrl); URL url = new URL(siteUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
//HttpsURLConnection con = NetCipher.getHttpsURLConnection(url); Iterator it = customProperties.entrySet().iterator();
con.setRequestProperty("Accept-Language", language); while(it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
con.setRequestProperty((String)pair.getKey(), (String)pair.getValue());
}
return dl(con); return dl(con);
} }

View file

@ -79,16 +79,19 @@ public class ErrorActivity extends AppCompatActivity {
public static final int GET_SUGGESTIONS = 2; public static final int GET_SUGGESTIONS = 2;
public static final int SOMETHING_ELSE = 3; public static final int SOMETHING_ELSE = 3;
public static final int USER_REPORT = 4; 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 SEARCHED_STRING = "searched";
public static final String REQUESTED_STREAM_STRING = "requested stream"; public static final String REQUESTED_STREAM_STRING = "requested stream";
public static final String GET_SUGGESTIONS_STRING = "get suggestions"; public static final String GET_SUGGESTIONS_STRING = "get suggestions";
public static final String SOMETHING_ELSE_STRING = "something"; public static final String SOMETHING_ELSE_STRING = "something";
public static final String USER_REPORT_STRING = "user report"; 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_ADDRESS = "crashreport@newpipe.schabi.org";
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME; 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 ErrorInfo errorInfo;
private Class returnActivity; private Class returnActivity;
private String currentTimeStamp; private String currentTimeStamp;
@ -102,7 +105,7 @@ public class ErrorActivity extends AppCompatActivity {
private TextView infoView; private TextView infoView;
private TextView errorMessageView; 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) { final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
if (rootView != null) { 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) { final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
List<Exception> el = null; List<Throwable> el = null;
if(e != null) { if(e != null) {
el = new Vector<>(); el = new Vector<>();
el.add(e); el.add(e);
@ -140,10 +143,10 @@ public class ErrorActivity extends AppCompatActivity {
} }
// async call // 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) { final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
List<Exception> el = null; List<Throwable> el = null;
if(e != null) { if(e != null) {
el = new Vector<>(); el = new Vector<>();
el.add(e); el.add(e);
@ -152,7 +155,7 @@ public class ErrorActivity extends AppCompatActivity {
} }
// async call // 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) { final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
handler.post(new Runnable() { handler.post(new Runnable() {
@Override @Override
@ -171,7 +174,7 @@ public class ErrorActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.error_report_title); actionBar.setTitle(R.string.error_report_title);
actionBar.setDisplayShowTitleEnabled(true); actionBar.setDisplayShowTitleEnabled(true);
} catch (Exception e) { } catch (Throwable e) {
Log.e(TAG, "Error turing exception handling"); Log.e(TAG, "Error turing exception handling");
e.printStackTrace(); e.printStackTrace();
} }
@ -252,10 +255,10 @@ public class ErrorActivity extends AppCompatActivity {
return sw.getBuffer().toString(); return sw.getBuffer().toString();
} }
private String formErrorText(List<Exception> el) { private String formErrorText(List<Throwable> el) {
String text = ""; String text = "";
if(el != null) { if(el != null) {
for (Exception e : el) { for (Throwable e : el) {
text += "-------------------------------------\n" text += "-------------------------------------\n"
+ getStackTrace(e); + getStackTrace(e);
} }
@ -273,7 +276,7 @@ public class ErrorActivity extends AppCompatActivity {
returnActivity.isAssignableFrom(Activity.class)) { returnActivity.isAssignableFrom(Activity.class)) {
intent = new Intent(this, returnActivity); intent = new Intent(this, returnActivity);
} else { } else {
intent = new Intent(this, VideoItemListActivity.class); intent = new Intent(this, MainActivity.class);
} }
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent); NavUtils.navigateUpTo(this, intent);
@ -313,7 +316,7 @@ public class ErrorActivity extends AppCompatActivity {
JSONArray exceptionArray = new JSONArray(); JSONArray exceptionArray = new JSONArray();
if(errorList != null) { if(errorList != null) {
for (Exception e : errorList) { for (Throwable e : errorList) {
exceptionArray.put(getStackTrace(e)); exceptionArray.put(getStackTrace(e));
} }
} }
@ -322,7 +325,7 @@ public class ErrorActivity extends AppCompatActivity {
errorObject.put("user_comment", userCommentBox.getText().toString()); errorObject.put("user_comment", userCommentBox.getText().toString());
return errorObject.toString(3); return errorObject.toString(3);
} catch (Exception e) { } catch (Throwable e) {
Log.e(TAG, "Error while erroring: Could not build json"); Log.e(TAG, "Error while erroring: Could not build json");
e.printStackTrace(); e.printStackTrace();
} }
@ -342,6 +345,8 @@ public class ErrorActivity extends AppCompatActivity {
return SOMETHING_ELSE_STRING; return SOMETHING_ELSE_STRING;
case USER_REPORT: case USER_REPORT:
return USER_REPORT_STRING; return USER_REPORT_STRING;
case LOAD_IMAGE:
return LOAD_IMAGE_STRING;
default: default:
return "Your description is in another castle."; return "Your description is in another castle.";
} }
@ -390,7 +395,7 @@ public class ErrorActivity extends AppCompatActivity {
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip) ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
+ "0.0"; + "0.0";
} catch(Exception e) { } catch(Throwable e) {
Log.d(TAG, "Error while error: could not get iprange"); Log.d(TAG, "Error while error: could not get iprange");
e.printStackTrace(); e.printStackTrace();
} finally { } finally {

View file

@ -0,0 +1,65 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.graphics.Bitmap;
import android.view.View;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList;
/**
* Created by Christian Schabesberger on 01.08.16.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* StreamInfoItemViewCreator.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 ImageErrorLoadingListener implements ImageLoadingListener {
private int serviceId = -1;
private Activity activity = null;
private View rootView = null;
public ImageErrorLoadingListener(Activity activity, View rootView, int serviceId) {
this.activity = activity;
this.serviceId= serviceId;
this.rootView = rootView;
}
@Override
public void onLoadingStarted(String imageUri, View view) {}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(activity,
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) {}
}

View file

@ -0,0 +1,69 @@
package org.schabi.newpipe;
import android.content.Intent;
import android.media.AudioManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import org.schabi.newpipe.settings.SettingsActivity;
/**
* Created by Christian Schabesberger on 02.08.16.
*/
public class MainActivity extends AppCompatActivity {
private Fragment mainFragment = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
mainFragment = getSupportFragmentManager()
.findFragmentById(R.id.search_fragment);
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
mainFragment.onCreateOptionsMenu(menu, inflater);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case android.R.id.home: {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
}
case R.id.action_settings: {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
}
case R.id.action_show_downloads: {
Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
startActivity(intent);
return true;
}
default:
return mainFragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
}
}
}

View file

@ -1,411 +0,0 @@
package org.schabi.newpipe;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoItemListActivity.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 VideoItemListActivity extends AppCompatActivity
implements VideoItemListFragment.Callbacks {
private static final String TAG = VideoItemListFragment.class.toString();
// arguments to give to this activity
public static final String VIDEO_INFO_ITEMS = "video_info_items";
// savedInstanceBundle arguments
private static final String QUERY = "query";
private static final String STREAMING_SERVICE = "streaming_service";
// activity modes
private static final int SEARCH_MODE = 0;
private static final int PRESENT_VIDEOS_MODE = 1;
private int mode;
private int currentStreamingServiceId = -1;
private String searchQuery = "";
private VideoItemListFragment listFragment;
private VideoItemDetailFragment videoFragment;
private Menu menu;
private SuggestionListAdapter suggestionListAdapter;
private SuggestionSearchRunnable suggestionSearchRunnable;
private Thread searchThread;
private class SearchVideoQueryListener implements SearchView.OnQueryTextListener {
@Override
public boolean onQueryTextSubmit(String query) {
try {
searchQuery = query;
listFragment.search(query);
// hide virtual keyboard
InputMethodManager inputManager =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
try {
//noinspection ConstantConditions
inputManager.hideSoftInputFromWindow(
getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
} catch(NullPointerException e) {
Log.e(TAG, "Could not get widget with focus");
Toast.makeText(VideoItemListActivity.this, "Could not get widget with focus",
Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
// clear focus
// 1. to not open up the keyboard after switching back to this
// 2. It's a workaround to a seeming bug by the Android OS it self, causing
// onQueryTextSubmit to trigger twice when focus is not cleared.
// See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
getCurrentFocus().clearFocus();
} catch(Exception e) {
e.printStackTrace();
}
View bg = findViewById(R.id.mainBG);
bg.setVisibility(View.GONE);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
if(!newText.isEmpty()) {
searchSuggestions(newText);
}
return true;
}
}
private class SearchSuggestionListener implements SearchView.OnSuggestionListener{
private SearchView searchView;
private SearchSuggestionListener(SearchView searchView) {
this.searchView = searchView;
}
@Override
public boolean onSuggestionSelect(int position) {
String suggestion = suggestionListAdapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
@Override
public boolean onSuggestionClick(int position) {
String suggestion = suggestionListAdapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
}
private class SuggestionResultRunnable implements Runnable{
private List<String> suggestions;
private SuggestionResultRunnable(List<String> suggestions) {
this.suggestions = suggestions;
}
@Override
public void run() {
suggestionListAdapter.updateAdapter(suggestions);
}
}
private class SuggestionSearchRunnable implements Runnable{
private final int serviceId;
private final String query;
final Handler h = new Handler();
private Context context;
private SuggestionSearchRunnable(int serviceId, String query) {
this.serviceId = serviceId;
this.query = query;
context = VideoItemListActivity.this;
}
@Override
public void run() {
try {
SearchEngine engine =
ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
String searchLanguageKey = context.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
getString(R.string.default_language_value));
List<String> suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
h.post(new SuggestionResultRunnable(suggestions));
} catch (ExtractionException e) {
ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(serviceId), query, R.string.parsing_error));
e.printStackTrace();
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
} catch (Exception e) {
ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(serviceId), query, R.string.general_error));
}
}
}
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_videoitem_list);
StreamingService streamingService = null;
View bg = findViewById(R.id.mainBG);
bg.setVisibility(View.VISIBLE);
try {
//------ todo: remove this line when multiservice support is implemented ------
currentStreamingServiceId = ServiceList.getIdOfService("Youtube");
streamingService = ServiceList.getService(currentStreamingServiceId);
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(currentStreamingServiceId), "", R.string.general_error));
}
setVolumeControlStream(AudioManager.STREAM_MUSIC);
listFragment = (VideoItemListFragment) getSupportFragmentManager()
.findFragmentById(R.id.videoitem_list);
listFragment.setStreamingService(streamingService);
if(savedInstanceState != null
&& mode != PRESENT_VIDEOS_MODE) {
searchQuery = savedInstanceState.getString(QUERY);
currentStreamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
if(!searchQuery.isEmpty()) {
listFragment.search(searchQuery);
}
}
if (findViewById(R.id.videoitem_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((VideoItemListFragment) getSupportFragmentManager()
.findFragmentById(R.id.videoitem_list))
.setActivateOnItemClick(true);
SearchView searchView = (SearchView)findViewById(R.id.searchViewTablet);
if(mode != PRESENT_VIDEOS_MODE) {
// Somehow the seticonifiedbydefault property set by the layout xml is not working on
// the support version on SearchView, so it needs to be set programmatically.
searchView.setIconifiedByDefault(false);
searchView.setIconified(false);
if(!searchQuery.isEmpty()) {
searchView.setQuery(searchQuery,false);
}
searchView.setOnQueryTextListener(new SearchVideoQueryListener());
suggestionListAdapter = new SuggestionListAdapter(this);
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView));
} else {
searchView.setVisibility(View.GONE);
}
}
PreferenceManager.setDefaultValues(this, R.xml.settings, false);
}
@Override
public void onResume() {
super.onResume();
App.checkStartTor(this);
}
/**
* Callback method from {@link VideoItemListFragment.Callbacks}
* indicating that the item with the given ID was selected.
*/
@Override
public void onItemSelected(String id) {
VideoListAdapter listAdapter = (VideoListAdapter) ((VideoItemListFragment)
getSupportFragmentManager()
.findFragmentById(R.id.videoitem_list))
.getListAdapter();
String webpageUrl = listAdapter.getVideoList().get((int) Long.parseLong(id)).webpage_url;
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
//arguments.putString(VideoItemDetailFragment.ARG_ITEM_ID, id);
arguments.putString(VideoItemDetailFragment.VIDEO_URL, webpageUrl);
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
videoFragment = new VideoItemDetailFragment();
videoFragment.setArguments(arguments);
videoFragment.setOnInvokeCreateOptionsMenuListener(new VideoItemDetailFragment.OnInvokeCreateOptionsMenuListener() {
@Override
public void createOptionsMenu() {
menu.clear();
onCreateOptionsMenu(menu);
}
});
getSupportFragmentManager().beginTransaction()
.replace(R.id.videoitem_detail_container, videoFragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, VideoItemDetailActivity.class);
//detailIntent.putExtra(VideoItemDetailFragment.ARG_ITEM_ID, id);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, webpageUrl);
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
startActivity(detailIntent);
}
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
this.menu = menu;
MenuInflater inflater = getMenuInflater();
if(mode != PRESENT_VIDEOS_MODE &&
findViewById(R.id.videoitem_detail_container) == null) {
inflater.inflate(R.menu.videoitem_list, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setFocusable(false);
searchView.setOnQueryTextListener(
new SearchVideoQueryListener());
suggestionListAdapter = new SuggestionListAdapter(this);
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView));
if(!searchQuery.isEmpty()) {
searchView.setQuery(searchQuery,false);
searchView.setIconifiedByDefault(false);
}
} else if (videoFragment != null){
videoFragment.onCreateOptionsMenu(menu, inflater);
} else {
inflater.inflate(R.menu.videoitem_two_pannel, menu);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id) {
case android.R.id.home: {
Intent intent = new Intent(this, VideoItemListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
}
case R.id.action_settings: {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
}
case R.id.action_report_error: {
ErrorActivity.reportError(VideoItemListActivity.this, new Vector<Exception>(),
null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
ServiceList.getNameOfService(currentStreamingServiceId),
"user_report", R.string.user_report));
return true;
}
case R.id.action_show_downloads: {
Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
startActivity(intent);
return true;
}
default:
return videoFragment.onOptionsItemSelected(item) ||
super.onOptionsItemSelected(item);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/*
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
*/
outState.putString(QUERY, searchQuery);
outState.putInt(STREAMING_SERVICE, currentStreamingServiceId);
}
private void searchSuggestions(String query) {
suggestionSearchRunnable =
new SuggestionSearchRunnable(currentStreamingServiceId, query);
searchThread = new Thread(suggestionSearchRunnable);
searchThread.start();
}
private void postNewErrorToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(VideoItemListActivity.this, getString(stringResource),
Toast.LENGTH_SHORT).show();
}
});
}
}

View file

@ -1,370 +0,0 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.Toast;
import java.io.IOException;
import java.util.List;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.StreamingService;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoItemListFragment.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 VideoItemListFragment extends ListFragment {
private static final String TAG = VideoItemListFragment.class.toString();
private StreamingService streamingService;
private VideoListAdapter videoListAdapter;
// activity modes
private static final int SEARCH_MODE = 0;
private static final int PRESENT_VIDEOS_MODE = 1;
private int mode;
private String query = "";
private int lastPage;
private Thread searchThread;
private SearchRunnable searchRunnable;
// used to track down if results posted by threads ar still valid
private int currentRequestId = -1;
private ListView list;
private View footer;
// used to suppress request for loading a new page while another page is already loading.
private boolean loadingNextPage = true;
private class ResultRunnable implements Runnable {
private final SearchResult result;
private final int requestId;
public ResultRunnable(SearchResult result, int requestId) {
this.result = result;
this.requestId = requestId;
}
@Override
public void run() {
updateListOnResult(result, requestId);
if (android.os.Build.VERSION.SDK_INT >= 19) {
getListView().removeFooterView(footer);
}
}
}
private class SearchRunnable implements Runnable {
public static final String YOUTUBE = "Youtube";
private final SearchEngine engine;
private final String query;
private final int page;
final Handler h = new Handler();
private volatile boolean runs = true;
private final int requestId;
public SearchRunnable(SearchEngine engine, String query, int page, int requestId) {
this.engine = engine;
this.query = query;
this.page = page;
this.requestId = requestId;
}
void terminate() {
runs = false;
}
@Override
public void run() {
SearchResult result = null;
try {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
String searchLanguageKey = getContext().getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
getString(R.string.default_language_value));
result = SearchResult
.getSearchResult(engine, query, page, searchLanguage, new Downloader());
if(runs) {
h.post(new ResultRunnable(result, requestId));
}
// look for errors during extraction
// soft errors:
if(result != null &&
!result.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
for(Exception e : result.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
Activity a = getActivity();
View rootView = a.findViewById(R.id.videoitem_list);
ErrorActivity.reportError(h, getActivity(), result.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
}
// hard errors:
} catch(IOException e) {
postNewNothingFoundToast(h, R.string.network_error);
e.printStackTrace();
} catch(SearchEngine.NothingFoundException e) {
postNewErrorToast(h, e.getMessage());
} catch(ExtractionException e) {
ErrorActivity.reportError(h, getActivity(), e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */
YOUTUBE, query, R.string.parsing_error));
//postNewErrorToast(h, R.string.parsing_error);
e.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, getActivity(), e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
e.printStackTrace();
}
}
}
public void present(List<StreamPreviewInfo> videoList) {
mode = PRESENT_VIDEOS_MODE;
setListShown(true);
getListView().smoothScrollToPosition(0);
updateList(videoList);
}
public void search(String query) {
mode = SEARCH_MODE;
this.query = query;
this.lastPage = 1;
videoListAdapter.clearVideoList();
setListShown(false);
startSearch(query, lastPage);
//todo: Somehow this command is not working on older devices,
// although it was introduced with API level 8. Test this and find a solution.
getListView().smoothScrollToPosition(0);
}
private void nextPage() {
loadingNextPage = true;
lastPage++;
Log.d(TAG, getString(R.string.search_page) + Integer.toString(lastPage));
startSearch(query, lastPage);
}
private void startSearch(String query, int page) {
currentRequestId++;
terminateThreads();
searchRunnable = new SearchRunnable(streamingService.getSearchEngineInstance(new Downloader()),
query, page, currentRequestId);
searchThread = new Thread(searchRunnable);
searchThread.start();
}
public void setStreamingService(StreamingService streamingService) {
this.streamingService = streamingService;
}
private void updateListOnResult(SearchResult result, int requestId) {
if(requestId == currentRequestId) {
setListShown(true);
updateList(result.resultList);
if(!result.suggestion.isEmpty()) {
Toast.makeText(getActivity(),
String.format(getString(R.string.did_you_mean), result.suggestion),
Toast.LENGTH_LONG).show();
}
}
}
private void updateList(List<StreamPreviewInfo> list) {
try {
videoListAdapter.addVideoList(list);
terminateThreads();
} catch(java.lang.IllegalStateException e) {
Toast.makeText(getActivity(), "Trying to set value while activity doesn't exist anymore.",
Toast.LENGTH_SHORT).show();
Log.w(TAG, "Trying to set value while activity doesn't exist anymore.");
} catch(Exception e) {
Toast.makeText(getActivity(), getString(R.string.general_error),
Toast.LENGTH_SHORT).show();
e.printStackTrace();
} finally {
loadingNextPage = false;
}
}
private void terminateThreads() {
if(searchThread != null) {
searchRunnable.terminate();
// No need to join, since we don't really terminate the thread. We just demand
// it to post its result runnable into the gui main loop.
}
}
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
void onItemSelected(String id);
}
private Callbacks mCallbacks;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
list = getListView();
videoListAdapter = new VideoListAdapter(getActivity(), this);
footer = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.paginate_footer, null, false);
setListAdapter(videoListAdapter);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
long lastScrollDate;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (mode != PRESENT_VIDEOS_MODE
&& list.getChildAt(0) != null
&& list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
&& list.getChildAt(list.getChildCount() - 1).getBottom() >= list.getHeight()) {
long time = System.currentTimeMillis();
if ((time - lastScrollDate) > 200
&& !loadingNextPage) {
lastScrollDate = time;
getListView().addFooterView(footer);
nextPage();
}
}
}
});
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
// Activities containing this fragment must implement its callbacks.
if (!(context instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) context;
}
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
setActivatedPosition(position);
mCallbacks.onItemSelected(Long.toString(id));
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(@SuppressWarnings("SameParameterValue") boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(activateOnItemClick
? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
private void postNewErrorToast(Handler h, final String message) {
h.post(new Runnable() {
@Override
public void run() {
setListShown(true);
Toast.makeText(getActivity(), message,
Toast.LENGTH_SHORT).show();
}
});
}
private void postNewNothingFoundToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
setListShown(true);
Toast.makeText(getActivity(), getString(stringResource),
Toast.LENGTH_LONG).show();
}
});
}
}

View file

@ -1,91 +0,0 @@
package org.schabi.newpipe;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import java.util.List;
import java.util.Vector;
/**
* Created by Christian Schabesberger on 11.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoListAdapter.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/>.
*/
class VideoListAdapter extends BaseAdapter {
private final Context context;
private final VideoInfoItemViewCreator viewCreator;
private Vector<StreamPreviewInfo> videoList = new Vector<>();
private final ListView listView;
public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
viewCreator = new VideoInfoItemViewCreator(LayoutInflater.from(context));
this.listView = videoListFragment.getListView();
this.listView.setDivider(null);
this.listView.setDividerHeight(0);
this.context = context;
}
public void addVideoList(List<StreamPreviewInfo> videos) {
videoList.addAll(videos);
notifyDataSetChanged();
}
public void clearVideoList() {
videoList = new Vector<>();
notifyDataSetChanged();
}
public Vector<StreamPreviewInfo> getVideoList() {
return videoList;
}
@Override
public int getCount() {
return videoList.size();
}
@Override
public Object getItem(int position) {
return videoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position));
if(listView.isItemChecked(position)) {
convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.light_youtube_primary_color));
} else {
convertView.setBackgroundColor(0);
}
return convertView;
}
}

View file

@ -1,4 +1,4 @@
package org.schabi.newpipe; package org.schabi.newpipe.detail;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -11,8 +11,9 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.VideoStream; import org.schabi.newpipe.extractor.VideoStream;
import java.util.List; import java.util.List;

View file

@ -0,0 +1,204 @@
package org.schabi.newpipe.detail;
import android.app.Activity;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import java.io.IOException;
/**
* Created by the-scrabi on 02.08.16.
*/
public class StreamInfoWorker {
private static final String TAG = StreamInfoWorker.class.toString();
public interface OnStreamInfoReceivedListener {
void onReceive(StreamInfo info);
void onError(int messageId);
void onBlockedByGemaError();
void onContentErrorWithMessage(int messageId);
void onContentError();
}
private class StreamExtractorRunnable implements Runnable {
private final Handler h = new Handler();
private StreamExtractor streamExtractor;
private final int serviceId;
private final String videoUrl;
private Activity a;
public StreamExtractorRunnable(Activity a, String videoUrl, int serviceId) {
this.serviceId = serviceId;
this.videoUrl = videoUrl;
this.a = a;
}
@Override
public void run() {
StreamInfo streamInfo = null;
StreamingService service = null;
try {
service = ServiceList.getService(serviceId);
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
"", videoUrl, R.string.could_not_get_stream));
return;
}
try {
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
final StreamInfo info = streamInfo;
h.post(new Runnable() {
@Override
public void run() {
onStreamInfoReceivedListener.onReceive(info);
}
});
// look for errors during extraction
// this if statement only covers extra information.
// if these are not available or caused an error, they are just not available
// but don't render the stream information unusalbe.
if(streamInfo != null &&
!streamInfo.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for (Throwable e : streamInfo.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
View rootView = a != null ? a.findViewById(R.id.video_item_detail) : null;
ErrorActivity.reportError(h, a,
streamInfo.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, 0 /* no message for the user */));
}
// These errors render the stream information unusable.
} catch (IOException e) {
h.post(new Runnable() {
@Override
public void run() {
onStreamInfoReceivedListener.onError(R.string.network_error);
}
});
e.printStackTrace();
}
// custom service related exceptions
catch (YoutubeStreamExtractor.DecryptException de) {
h.post(new Runnable() {
@Override
public void run() {
onStreamInfoReceivedListener.onError(R.string.youtube_signature_decryption_error);
}
});
de.printStackTrace();
} catch (YoutubeStreamExtractor.GemaException ge) {
h.post(new Runnable() {
@Override
public void run() {
onStreamInfoReceivedListener.onBlockedByGemaError();
}
});
} catch(YoutubeStreamExtractor.LiveStreamException e) {
h.post(new Runnable() {
@Override
public void run() {
onStreamInfoReceivedListener
.onContentErrorWithMessage(R.string.live_streams_not_supported);
}
});
}
// ----------------------------------------
catch(StreamExtractor.ContentNotAvailableException e) {
h.post(new Runnable() {
@Override
public void run() {
onStreamInfoReceivedListener
.onContentError();
}
});
e.printStackTrace();
} catch(StreamInfo.StreamExctractException e) {
if(!streamInfo.errors.isEmpty()) {
// !!! if this case ever kicks in someone gets kicked out !!!
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
} else {
ErrorActivity.reportError(h, a, streamInfo.errors, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
}
h.post(new Runnable() {
@Override
public void run() {
a.finish();
}
});
e.printStackTrace();
} catch (ParsingException e) {
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.parsing_error));
h.post(new Runnable() {
@Override
public void run() {
a.finish();
}
});
e.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.general_error));
h.post(new Runnable() {
@Override
public void run() {
a.finish();
}
});
e.printStackTrace();
}
}
}
private static StreamInfoWorker streamInfoWorker = null;
private StreamExtractorRunnable runnable = null;
private OnStreamInfoReceivedListener onStreamInfoReceivedListener = null;
private StreamInfoWorker() {
}
public static StreamInfoWorker getInstance() {
return streamInfoWorker == null ? (streamInfoWorker = new StreamInfoWorker()) : streamInfoWorker;
}
public void search(int serviceId, String url, Activity a) {
runnable = new StreamExtractorRunnable(a, url, serviceId);
Thread thread = new Thread(runnable);
thread.start();
}
public void setOnStreamInfoReceivedListener(
OnStreamInfoReceivedListener onStreamInfoReceivedListener) {
this.onStreamInfoReceivedListener = onStreamInfoReceivedListener;
}
}

View file

@ -1,4 +1,4 @@
package org.schabi.newpipe; package org.schabi.newpipe.detail;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
@ -11,6 +11,9 @@ import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
@ -85,7 +88,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
.show(); .show();
} }
//arguments.putString(VideoItemDetailFragment.VIDEO_URL, //arguments.putString(VideoItemDetailFragment.VIDEO_URL,
// videoExtractor.getVideoUrl(videoExtractor.getVideoId(videoUrl)));//cleans URL // videoExtractor.getUrl(videoExtractor.getId(videoUrl)));//cleans URL
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl); arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
@ -138,7 +141,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// http://developer.android.com/design/patterns/navigation.html#up-vs-back // http://developer.android.com/design/patterns/navigation.html#up-vs-back
Intent intent = new Intent(this, VideoItemListActivity.class); Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent); NavUtils.navigateUpTo(this, intent);
return true; return true;

View file

@ -1,22 +1,21 @@
package org.schabi.newpipe; package org.schabi.newpipe.detail;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Point; import android.graphics.Point;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.Log; import android.util.Log;
@ -27,38 +26,34 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import java.io.IOException;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import java.util.ArrayList;
import java.util.Vector; import java.util.Vector;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.ChannelActivity;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.Localization;
import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadDialog; import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.AudioStream; import org.schabi.newpipe.extractor.AudioStream;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamInfo; import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.VideoStream; import org.schabi.newpipe.extractor.VideoStream;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.player.BackgroundPlayer; import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.PlayVideoActivity; import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.ExoPlayerActivity; import org.schabi.newpipe.player.ExoPlayerActivity;
@ -102,19 +97,20 @@ public class VideoItemDetailFragment extends Fragment {
private int streamingServiceId = -1; private int streamingServiceId = -1;
private boolean autoPlayEnabled; private boolean autoPlayEnabled;
private boolean showNextVideoItem; private boolean showNextStreamItem;
private Bitmap videoThumbnail;
private View thumbnailWindowLayout; private View thumbnailWindowLayout;
//this only remains due to downwards compatibility //this only remains due to downwards compatibility
private FloatingActionButton playVideoButton; private FloatingActionButton playVideoButton;
private final Point initialThumbnailPos = new Point(0, 0); private final Point initialThumbnailPos = new Point(0, 0);
private View rootView = null;
private Bitmap streamThumbnail = null;
private ImageLoader imageLoader = ImageLoader.getInstance(); private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = private DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().cacheInMemory(true).build(); new DisplayImageOptions.Builder().cacheInMemory(true).build();
private InfoListAdapter similarStreamsAdapter = null;
public interface OnInvokeCreateOptionsMenuListener { public interface OnInvokeCreateOptionsMenuListener {
void createOptionsMenu(); void createOptionsMenu();
@ -122,215 +118,57 @@ public class VideoItemDetailFragment extends Fragment {
private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener; private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener;
private class VideoExtractorRunnable implements Runnable {
private final Handler h = new Handler();
private StreamExtractor streamExtractor;
private final StreamingService service;
private final String videoUrl;
public VideoExtractorRunnable(String videoUrl, StreamingService service) {
this.service = service;
this.videoUrl = videoUrl;
}
@Override
public void run() {
StreamInfo streamInfo = null;
try {
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
h.post(new VideoResultReturnedRunnable(streamInfo));
// look for errors during extraction
// this if statement only covers extra information.
// if these are not available or caused an error, they are just not available
// but don't render the stream information unusalbe.
if(streamInfo != null &&
!streamInfo.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for (Exception e : streamInfo.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
Activity a = getActivity();
View rootView = a != null ? a.findViewById(R.id.videoitem_detail) : null;
ErrorActivity.reportError(h, getActivity(),
streamInfo.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, 0 /* no message for the user */));
}
// These errors render the stream information unusable.
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
}
// custom service related exceptions
catch (YoutubeStreamExtractor.DecryptException de) {
postNewErrorToast(h, R.string.youtube_signature_decryption_error);
de.printStackTrace();
} catch (YoutubeStreamExtractor.GemaException ge) {
h.post(new Runnable() {
@Override
public void run() {
onErrorBlockedByGema();
}
});
} catch(YoutubeStreamExtractor.LiveStreamException e) {
h.post(new Runnable() {
@Override
public void run() {
onNotSpecifiedContentErrorWithMessage(R.string.live_streams_not_supported);
}
});
}
// ----------------------------------------
catch(StreamExtractor.ContentNotAvailableException e) {
h.post(new Runnable() {
@Override
public void run() {
onNotSpecifiedContentError();
}
});
e.printStackTrace();
} catch(StreamInfo.StreamExctractException e) {
if(!streamInfo.errors.isEmpty()) {
// !!! if this case ever kicks in someone gets kicked out !!!
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
} else {
ErrorActivity.reportError(h, getActivity(), streamInfo.errors, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
}
h.post(new Runnable() {
@Override
public void run() {
getActivity().finish();
}
});
e.printStackTrace();
} catch (ParsingException e) {
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.parsing_error));
h.post(new Runnable() {
@Override
public void run() {
getActivity().finish();
}
});
e.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.general_error));
h.post(new Runnable() {
@Override
public void run() {
getActivity().finish();
}
});
e.printStackTrace();
}
}
}
private class VideoResultReturnedRunnable implements Runnable {
private final StreamInfo streamInfo;
public VideoResultReturnedRunnable(StreamInfo streamInfo) {
this.streamInfo = streamInfo;
}
@Override
public void run() {
Activity a = getActivity();
if(a != null) {
boolean showAgeRestrictedContent = PreferenceManager.getDefaultSharedPreferences(a)
.getBoolean(activity.getString(R.string.show_age_restricted_content), false);
if (streamInfo.age_limit == 0 || showAgeRestrictedContent) {
updateInfo(streamInfo);
} else {
onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted);
}
}
}
}
private class ThumbnailLoadingListener implements ImageLoadingListener {
@Override
public void onLoadingStarted(String imageUri, View view) {}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
if(getContext() != null) {
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
}
failReason.getCause().printStackTrace();
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {}
@Override
public void onLoadingCancelled(String imageUri, View view) {}
}
private void updateInfo(final StreamInfo info) { private void updateInfo(final StreamInfo info) {
try { try {
Context c = getContext(); Activity a = getActivity();
VideoInfoItemViewCreator videoItemViewCreator =
new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()));
RelativeLayout textContentLayout = RelativeLayout textContentLayout =
(RelativeLayout) activity.findViewById(R.id.detailTextContentLayout); (RelativeLayout) activity.findViewById(R.id.detail_text_content_layout);
final TextView videoTitleView = final TextView videoTitleView =
(TextView) activity.findViewById(R.id.detailVideoTitleView); (TextView) activity.findViewById(R.id.detail_video_title_view);
TextView uploaderView = (TextView) activity.findViewById(R.id.detailUploaderView); TextView uploaderView = (TextView) activity.findViewById(R.id.detail_uploader_view);
TextView viewCountView = (TextView) activity.findViewById(R.id.detailViewCountView); TextView viewCountView = (TextView) activity.findViewById(R.id.detail_view_count_view);
TextView thumbsUpView = (TextView) activity.findViewById(R.id.detailThumbsUpCountView); TextView thumbsUpView = (TextView) activity.findViewById(R.id.detail_thumbs_up_count_view);
TextView thumbsDownView = TextView thumbsDownView =
(TextView) activity.findViewById(R.id.detailThumbsDownCountView); (TextView) activity.findViewById(R.id.detail_thumbs_down_count_view);
TextView uploadDateView = (TextView) activity.findViewById(R.id.detailUploadDateView); TextView uploadDateView = (TextView) activity.findViewById(R.id.detail_upload_date_view);
TextView descriptionView = (TextView) activity.findViewById(R.id.detailDescriptionView); TextView descriptionView = (TextView) activity.findViewById(R.id.detail_description_view);
FrameLayout nextVideoFrame = RecyclerView nextStreamView =
(FrameLayout) activity.findViewById(R.id.detailNextVideoFrame); (RecyclerView) activity.findViewById(R.id.detail_next_stream_content);
RelativeLayout nextVideoRootFrame = RelativeLayout nextVideoRootFrame =
(RelativeLayout) activity.findViewById(R.id.detailNextVideoRootLayout); (RelativeLayout) activity.findViewById(R.id.detail_next_stream_root_layout);
Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton); TextView similarTitle = (TextView) activity.findViewById(R.id.detail_similar_title);
TextView similarTitle = (TextView) activity.findViewById(R.id.detailSimilarTitle);
Button backgroundButton = (Button) Button backgroundButton = (Button)
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton); activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
View topView = activity.findViewById(R.id.detailTopView); View topView = activity.findViewById(R.id.detailTopView);
View nextVideoView = null; Button channelButton = (Button) activity.findViewById(R.id.channel_button);
if(info.next_video != null) {
nextVideoView = videoItemViewCreator
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
} else {
activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE);
activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE);
activity.findViewById(R.id.detailNextVideoButton).setVisibility(View.GONE);
}
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
if(nextVideoView != null) { if(info.next_video != null) {
nextVideoFrame.addView(nextVideoView); InfoListAdapter adapter = new InfoListAdapter(a, rootView);
nextStreamView.setAdapter(adapter);
nextStreamView.setLayoutManager(new LinearLayoutManager(a));
adapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
@Override
public void selected(String url) {
openStreamUrl(url);
}
});
} else {
nextStreamView.setVisibility(View.GONE);
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
} }
initThumbnailViews(info, nextVideoFrame);
textContentLayout.setVisibility(View.VISIBLE); textContentLayout.setVisibility(View.VISIBLE);
if (android.os.Build.VERSION.SDK_INT < 18) { if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton.setVisibility(View.VISIBLE); playVideoButton.setVisibility(View.VISIBLE);
} else { } else {
ImageView playArrowView = (ImageView) activity.findViewById(R.id.playArrowView); ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view);
playArrowView.setVisibility(View.VISIBLE); playArrowView.setVisibility(View.VISIBLE);
} }
if (!showNextVideoItem) { if (!showNextStreamItem) {
nextVideoRootFrame.setVisibility(View.GONE); nextVideoRootFrame.setVisibility(View.GONE);
similarTitle.setVisibility(View.GONE); similarTitle.setVisibility(View.GONE);
} }
@ -341,7 +179,7 @@ public class VideoItemDetailFragment extends Fragment {
@Override @Override
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == android.view.MotionEvent.ACTION_UP) { if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
ImageView arrow = (ImageView) activity.findViewById(R.id.toggleDescriptionView); ImageView arrow = (ImageView) activity.findViewById(R.id.toggle_description_view);
View extra = activity.findViewById(R.id.detailExtraView); View extra = activity.findViewById(R.id.detailExtraView);
if (extra.getVisibility() == View.VISIBLE) { if (extra.getVisibility() == View.VISIBLE) {
extra.setVisibility(View.GONE); extra.setVisibility(View.GONE);
@ -361,29 +199,29 @@ public class VideoItemDetailFragment extends Fragment {
if(!info.uploader.isEmpty()) { if(!info.uploader.isEmpty()) {
uploaderView.setText(info.uploader); uploaderView.setText(info.uploader);
} else { } else {
activity.findViewById(R.id.detailUploaderWrapView).setVisibility(View.GONE); activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE);
} }
if(info.view_count >= 0) { if(info.view_count >= 0) {
viewCountView.setText(Localization.localizeViewCount(info.view_count, c)); viewCountView.setText(Localization.localizeViewCount(info.view_count, a));
} else { } else {
viewCountView.setVisibility(View.GONE); viewCountView.setVisibility(View.GONE);
} }
if(info.dislike_count >= 0) { if(info.dislike_count >= 0) {
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, c)); thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a));
} else { } else {
thumbsDownView.setVisibility(View.INVISIBLE); thumbsDownView.setVisibility(View.INVISIBLE);
activity.findViewById(R.id.detailThumbsDownImgView).setVisibility(View.GONE); activity.findViewById(R.id.detail_thumbs_down_count_view).setVisibility(View.GONE);
} }
if(info.like_count >= 0) { if(info.like_count >= 0) {
thumbsUpView.setText(Localization.localizeNumber(info.like_count, c)); thumbsUpView.setText(Localization.localizeNumber(info.like_count, a));
} else { } else {
thumbsUpView.setVisibility(View.GONE); thumbsUpView.setVisibility(View.GONE);
activity.findViewById(R.id.detailThumbsUpImgView).setVisibility(View.GONE); activity.findViewById(R.id.detail_thumbs_up_img_view).setVisibility(View.GONE);
thumbsDownView.setVisibility(View.GONE); thumbsDownView.setVisibility(View.GONE);
activity.findViewById(R.id.detailThumbsDownImgView).setVisibility(View.GONE); activity.findViewById(R.id.detail_thumbs_down_img_view).setVisibility(View.GONE);
} }
if(!info.upload_date.isEmpty()) { if(!info.upload_date.isEmpty()) {
uploadDateView.setText(Localization.localizeDate(info.upload_date, c)); uploadDateView.setText(Localization.localizeDate(info.upload_date, a));
} else { } else {
uploadDateView.setVisibility(View.GONE); uploadDateView.setVisibility(View.GONE);
} }
@ -403,26 +241,17 @@ public class VideoItemDetailFragment extends Fragment {
} }
} }
nextVideoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent detailIntent =
new Intent(getActivity(), VideoItemDetailActivity.class);
/*detailIntent.putExtra(
VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id); */
detailIntent.putExtra(
VideoItemDetailFragment.VIDEO_URL, info.next_video.webpage_url);
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
startActivity(detailIntent);
}
});
textContentLayout.setVisibility(View.VISIBLE); textContentLayout.setVisibility(View.VISIBLE);
if(info.related_videos != null && !info.related_videos.isEmpty()) { if(info.next_video == null) {
initSimilarVideos(info, videoItemViewCreator); activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE);
}
if(info.related_streams != null && !info.related_streams.isEmpty()) {
initSimilarVideos(info);
} else { } else {
activity.findViewById(R.id.detailSimilarTitle).setVisibility(View.GONE); activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
activity.findViewById(R.id.similarVideosView).setVisibility(View.GONE); activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE);
} }
setupActionBarHandler(info); setupActionBarHandler(info);
@ -447,18 +276,32 @@ public class VideoItemDetailFragment extends Fragment {
} }
}); });
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);
}
initThumbnailViews(info);
} catch (java.lang.NullPointerException e) { } catch (java.lang.NullPointerException e) {
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else"); Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
e.printStackTrace(); e.printStackTrace();
} }
} }
private void initThumbnailViews(StreamInfo info, View nextVideoFrame) { private void initThumbnailViews(final StreamInfo info) {
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView); ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
ImageView uploaderThumb ImageView uploaderThumb
= (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView); = (ImageView) activity.findViewById(R.id.detail_uploader_thumbnail_view);
ImageView nextVideoThumb =
(ImageView) nextVideoFrame.findViewById(R.id.itemThumbnailView);
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) { if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, videoThumbnailView, imageLoader.displayImage(info.thumbnail_url, videoThumbnailView,
@ -469,14 +312,16 @@ public class VideoItemDetailFragment extends Fragment {
@Override @Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) { public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
Toast.makeText(VideoItemDetailFragment.this.getActivity(), ErrorActivity.reportError(getActivity(),
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show(); failReason.getCause(), null, rootView,
failReason.getCause().printStackTrace(); ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ServiceList.getNameOfService(info.service_id), imageUri,
R.string.could_not_load_thumbnails));
} }
@Override @Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
videoThumbnail = loadedImage; streamThumbnail = loadedImage;
} }
@Override @Override
@ -488,11 +333,8 @@ public class VideoItemDetailFragment extends Fragment {
} }
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) { if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.uploader_thumbnail_url, imageLoader.displayImage(info.uploader_thumbnail_url,
uploaderThumb, displayImageOptions, new ThumbnailLoadingListener()); uploaderThumb, displayImageOptions,
} new ImageErrorLoadingListener(activity, rootView, info.service_id));
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty() && info.next_video != null) {
imageLoader.displayImage(info.next_video.thumbnail_url,
nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
} }
} }
@ -602,9 +444,9 @@ public class VideoItemDetailFragment extends Fragment {
info.audio_streams.get(getPreferredAudioStreamId(info)); info.audio_streams.get(getPreferredAudioStreamId(info));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) { if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent //internal music player: explicit intent
if (!BackgroundPlayer.isRunning && videoThumbnail != null) { if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
ActivityCommunicator.getCommunicator() ActivityCommunicator.getCommunicator()
.backgroundPlayerThumbnail = videoThumbnail; .backgroundPlayerThumbnail = streamThumbnail;
intent = new Intent(activity, BackgroundPlayer.class); intent = new Intent(activity, BackgroundPlayer.class);
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
@ -684,47 +526,14 @@ public class VideoItemDetailFragment extends Fragment {
return 0; return 0;
} }
private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) { private void initSimilarVideos(final StreamInfo info) {
LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView); similarStreamsAdapter.addStreamItemList(info.related_streams);
ArrayList<StreamPreviewInfo> similar = new ArrayList<>(info.related_videos);
for (final StreamPreviewInfo item : similar) {
View similarView = videoItemViewCreator
.getViewFromVideoInfoItem(null, similarLayout, item);
similarView.setClickable(true);
similarView.setFocusable(true);
int[] attrs = new int[]{R.attr.selectableItemBackground};
TypedArray typedArray = activity.obtainStyledAttributes(attrs);
int backgroundResource = typedArray.getResourceId(0, 0);
similarView.setBackgroundResource(backgroundResource);
typedArray.recycle();
similarView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, item.webpage_url);
detailIntent.putExtra(
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
startActivity(detailIntent);
return true;
}
return false;
}
});
similarLayout.addView(similarView);
ImageView rthumb = (ImageView)similarView.findViewById(R.id.itemThumbnailView);
imageLoader.displayImage(item.thumbnail_url, rthumb,
displayImageOptions, new ThumbnailLoadingListener());
}
} }
private void onErrorBlockedByGema() { private void onErrorBlockedByGema() {
Button backgroundButton = (Button) Button backgroundButton = (Button)
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton); activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView); ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
thumbnailView.setImageBitmap(BitmapFactory.decodeResource( thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
@ -744,7 +553,7 @@ public class VideoItemDetailFragment extends Fragment {
} }
private void onNotSpecifiedContentError() { private void onNotSpecifiedContentError() {
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView); ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
thumbnailView.setImageBitmap(BitmapFactory.decodeResource( thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.not_available_monkey)); getResources(), R.drawable.not_available_monkey));
@ -753,7 +562,7 @@ public class VideoItemDetailFragment extends Fragment {
} }
private void onNotSpecifiedContentErrorWithMessage(int resourceId) { private void onNotSpecifiedContentErrorWithMessage(int resourceId) {
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView); ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
thumbnailView.setImageBitmap(BitmapFactory.decodeResource( thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.not_available_monkey)); getResources(), R.drawable.not_available_monkey));
@ -779,16 +588,44 @@ public class VideoItemDetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
activity = (AppCompatActivity) getActivity(); activity = (AppCompatActivity) getActivity();
showNextVideoItem = PreferenceManager.getDefaultSharedPreferences(getActivity()) showNextStreamItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean(activity.getString(R.string.show_next_video_key), true); .getBoolean(activity.getString(R.string.show_next_video_key), true);
StreamInfoWorker siw = StreamInfoWorker.getInstance();
siw.setOnStreamInfoReceivedListener(new StreamInfoWorker.OnStreamInfoReceivedListener() {
@Override
public void onReceive(StreamInfo info) {
updateInfo(info);
}
@Override
public void onError(int messageId) {
postNewErrorToast(messageId);
}
@Override
public void onBlockedByGemaError() {
onErrorBlockedByGema();
}
@Override
public void onContentErrorWithMessage(int messageId) {
onNotSpecifiedContentErrorWithMessage(messageId);
}
@Override
public void onContentError() {
onNotSpecifiedContentError();
}
});
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { 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); progressBar = (ProgressBar) rootView.findViewById(R.id.detail_progress_bar);
actionBarHandler = new ActionBarHandler(activity); actionBarHandler = new ActionBarHandler(activity);
actionBarHandler.setupNavMenu(activity); actionBarHandler.setupNavMenu(activity);
@ -804,30 +641,25 @@ public class VideoItemDetailFragment extends Fragment {
super.onActivityCreated(savedInstanceBundle); super.onActivityCreated(savedInstanceBundle);
Activity a = getActivity(); Activity a = getActivity();
if (android.os.Build.VERSION.SDK_INT < 18) { if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton = (FloatingActionButton) a.findViewById(R.id.playVideoButton); playVideoButton = (FloatingActionButton) a.findViewById(R.id.play_video_button);
} }
thumbnailWindowLayout = a.findViewById(R.id.detailVideoThumbnailWindowLayout); thumbnailWindowLayout = a.findViewById(R.id.detail_stream_thumbnail_window_layout);
Button backgroundButton = (Button) Button backgroundButton = (Button)
a.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton); a.findViewById(R.id.detail_stream_thumbnail_window_background_button);
// Sometimes when this fragment is not visible it still gets initiated // Sometimes when this fragment is not visible it still gets initiated
// then we must not try to access objects of this fragment. // then we must not try to access objects of this fragment.
// Otherwise the applications would crash. // Otherwise the applications would crash.
if(backgroundButton != null) { if(backgroundButton != null) {
try {
streamingServiceId = getArguments().getInt(STREAMING_SERVICE); streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
StreamingService streamingService = ServiceList.getService(streamingServiceId); String videoUrl = getArguments().getString(VIDEO_URL);
Thread videoExtractorThread = new Thread(new VideoExtractorRunnable( StreamInfoWorker siw = StreamInfoWorker.getInstance();
getArguments().getString(VIDEO_URL), streamingService)); siw.search(streamingServiceId, videoUrl, getActivity());
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY); autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
videoExtractorThread.start();
} catch (Exception e) {
e.printStackTrace();
}
if(Build.VERSION.SDK_INT >= 18) { if(Build.VERSION.SDK_INT >= 18) {
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView); ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
thumbnailView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { thumbnailView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
// This is used to synchronize the thumbnailWindowButton and the playVideoButton // This is used to synchronize the thumbnailWindowButton and the playVideoButton
// inside the ScrollView with the actual size of the thumbnail. // inside the ScrollView with the actual size of the thumbnail.
@ -849,6 +681,17 @@ public class VideoItemDetailFragment extends Fragment {
} }
}); });
} }
similarStreamsAdapter = new InfoListAdapter(getActivity(), rootView);
RecyclerView rv = (RecyclerView) getActivity().findViewById(R.id.similar_streams_view);
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
rv.setAdapter(similarStreamsAdapter);
similarStreamsAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
@Override
public void selected(String url) {
openStreamUrl(url);
}
});
} }
} }
@ -946,23 +789,16 @@ public class VideoItemDetailFragment extends Fragment {
this.onInvokeCreateOptionsMenuListener = listener; this.onInvokeCreateOptionsMenuListener = listener;
} }
private void postNewErrorToast(Handler h, final int stringResource) { private void postNewErrorToast(final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(VideoItemDetailFragment.this.getActivity(), Toast.makeText(VideoItemDetailFragment.this.getActivity(),
stringResource, Toast.LENGTH_LONG).show(); stringResource, Toast.LENGTH_LONG).show();
} }
});
}
private void postNewErrorToast(Handler h, final String message) { private void openStreamUrl(String url) {
h.post(new Runnable() { Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
@Override detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
public void run() { detailIntent.putExtra(
Toast.makeText(VideoItemDetailFragment.this.getActivity(), VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
message, Toast.LENGTH_LONG).show(); activity.startActivity(detailIntent);
}
});
} }
} }

View file

@ -25,7 +25,6 @@ import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import org.schabi.newpipe.App; import org.schabi.newpipe.App;
import org.schabi.newpipe.NewPipeSettings;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import java.io.File; import java.io.File;

View file

@ -8,14 +8,12 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -23,25 +21,16 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.support.v7.widget.SearchView;
import org.schabi.newpipe.ErrorActivity; import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.SettingsActivity; import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.VideoItemDetailActivity;
import org.schabi.newpipe.VideoItemListActivity;
import org.schabi.newpipe.extractor.ServiceList;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Vector; import java.util.Vector;
import us.shandian.giga.get.DownloadManager; import us.shandian.giga.get.DownloadManager;
@ -257,7 +246,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
switch (id) { switch (id) {
case android.R.id.home: { case android.R.id.home: {
Intent intent = new Intent(this, VideoItemListActivity.class); Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent); NavUtils.navigateUpTo(this, intent);
return true; return true;
@ -268,7 +257,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true; return true;
} }
case R.id.action_report_error: { case R.id.action_report_error: {
ErrorActivity.reportError(MainActivity.this, new Vector<Exception>(), ErrorActivity.reportError(MainActivity.this, new Vector<Throwable>(),
null, null, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT, ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
null, null,

View file

@ -0,0 +1,55 @@
package org.schabi.newpipe.extractor;
import java.io.IOException;
/**
* Created by Christian Schabesberger on 25.07.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelExtractor.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 abstract class ChannelExtractor {
private int serviceId;
private String url;
private UrlIdHandler urlIdHandler;
private Downloader downloader;
private StreamPreviewInfoCollector previewInfoCollector;
public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
throws ExtractionException, IOException {
this.serviceId = serviceId;
this.urlIdHandler = urlIdHandler;
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
}
public String getUrl() { return url; }
public UrlIdHandler getUrlIdHandler() { return urlIdHandler; }
public Downloader getDownloader() { return downloader; }
public StreamPreviewInfoCollector getStreamPreviewInfoCollector() {
return previewInfoCollector;
}
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 abstract StreamPreviewInfoCollector getStreams() throws ParsingException;
public abstract boolean hasNextPage() throws ParsingException;
public int getServiceId() {
return serviceId;
}
}

View file

@ -0,0 +1,79 @@
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();
info.hasNextPage = extractor.hasNextPage();
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);
}
try {
StreamPreviewInfoCollector c = extractor.getStreams();
info.related_streams = c.getItemList();
info.errors.addAll(c.getErrors());
} 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<StreamPreviewInfo> related_streams = null;
public boolean hasNextPage = false;
public List<Throwable> errors = new Vector<>();
}

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.extractor; package org.schabi.newpipe.extractor;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
/** /**
* Created by Christian Schabesberger on 28.01.16. * Created by Christian Schabesberger on 28.01.16.
@ -32,6 +33,14 @@ public interface Downloader {
* @throws IOException*/ * @throws IOException*/
String download(String siteUrl, String language) throws IOException; String download(String siteUrl, String language) throws IOException;
/**Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string.
* @param siteUrl the URL of the text file to return the contents of
* @param customProperties set request header properties
* @return the contents of the specified text file
* @throws IOException*/
String download(String siteUrl, Map<String, String> customProperties) throws IOException;
/**Download (via HTTP) the text file located at the supplied URL, and return its contents. /**Download (via HTTP) the text file located at the supplied URL, and return its contents.
* Primarily intended for downloading web pages. * Primarily intended for downloading web pages.
* @param siteUrl the URL of the text file to download * @param siteUrl the URL of the text file to download

View file

@ -33,7 +33,7 @@ public abstract class SearchEngine {
private StreamPreviewInfoSearchCollector collector; private StreamPreviewInfoSearchCollector collector;
public SearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) { public SearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId); collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId);
} }

View file

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

View file

@ -30,7 +30,7 @@ public abstract class StreamExtractor {
private int serviceId; private int serviceId;
private String url; private String url;
private StreamUrlIdHandler urlIdHandler; private UrlIdHandler urlIdHandler;
private Downloader downloader; private Downloader downloader;
private StreamPreviewInfoCollector previewInfoCollector; private StreamPreviewInfoCollector previewInfoCollector;
@ -55,7 +55,7 @@ public abstract class StreamExtractor {
} }
} }
public StreamExtractor(StreamUrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) { public StreamExtractor(UrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) {
this.serviceId = serviceId; this.serviceId = serviceId;
this.urlIdHandler = urlIdHandler; this.urlIdHandler = urlIdHandler;
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId); previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
@ -69,7 +69,7 @@ public abstract class StreamExtractor {
return url; return url;
} }
public StreamUrlIdHandler getUrlIdHandler() { public UrlIdHandler getUrlIdHandler() {
return urlIdHandler; return urlIdHandler;
} }
@ -81,6 +81,7 @@ public abstract class StreamExtractor {
public abstract String getTitle() throws ParsingException; public abstract String getTitle() throws ParsingException;
public abstract String getDescription() throws ParsingException; public abstract String getDescription() throws ParsingException;
public abstract String getUploader() throws ParsingException; public abstract String getUploader() throws ParsingException;
public abstract String getChannelUrl() throws ParsingException;
public abstract int getLength() throws ParsingException; public abstract int getLength() throws ParsingException;
public abstract long getViewCount() throws ParsingException; public abstract long getViewCount() throws ParsingException;
public abstract String getUploadDate() throws ParsingException; public abstract String getUploadDate() throws ParsingException;

View file

@ -85,12 +85,12 @@ public class StreamInfo extends AbstractVideoInfo {
/* ---- importand data, withoug the video can't be displayed goes here: ---- */ /* ---- importand data, withoug the video can't be displayed goes here: ---- */
// if one of these is not available an exception is ment to be thrown directly into the frontend. // if one of these is not available an exception is ment to be thrown directly into the frontend.
StreamUrlIdHandler uiconv = extractor.getUrlIdHandler(); UrlIdHandler uiconv = extractor.getUrlIdHandler();
streamInfo.service_id = extractor.getServiceId(); streamInfo.service_id = extractor.getServiceId();
streamInfo.webpage_url = extractor.getPageUrl(); streamInfo.webpage_url = extractor.getPageUrl();
streamInfo.stream_type = extractor.getStreamType(); streamInfo.stream_type = extractor.getStreamType();
streamInfo.id = uiconv.getVideoId(extractor.getPageUrl()); streamInfo.id = uiconv.getId(extractor.getPageUrl());
streamInfo.title = extractor.getTitle(); streamInfo.title = extractor.getTitle();
streamInfo.age_limit = extractor.getAgeLimit(); streamInfo.age_limit = extractor.getAgeLimit();
@ -188,6 +188,11 @@ public class StreamInfo extends AbstractVideoInfo {
} catch(Exception e) { } catch(Exception e) {
streamInfo.addException(e); streamInfo.addException(e);
} }
try {
streamInfo.channel_url = extractor.getChannelUrl();
} catch(Exception e) {
streamInfo.addException(e);
}
try { try {
streamInfo.description = extractor.getDescription(); streamInfo.description = extractor.getDescription();
} catch(Exception e) { } catch(Exception e) {
@ -248,7 +253,7 @@ public class StreamInfo extends AbstractVideoInfo {
try { try {
// get related videos // get related videos
StreamPreviewInfoCollector c = extractor.getRelatedVideos(); StreamPreviewInfoCollector c = extractor.getRelatedVideos();
streamInfo.related_videos = c.getItemList(); streamInfo.related_streams = c.getItemList();
streamInfo.errors.addAll(c.getErrors()); streamInfo.errors.addAll(c.getErrors());
} catch(Exception e) { } catch(Exception e) {
streamInfo.addException(e); streamInfo.addException(e);
@ -258,6 +263,7 @@ public class StreamInfo extends AbstractVideoInfo {
} }
public String uploader_thumbnail_url = ""; public String uploader_thumbnail_url = "";
public String channel_url = "";
public String description = ""; public String description = "";
public List<VideoStream> video_streams = null; public List<VideoStream> video_streams = null;
@ -275,9 +281,9 @@ public class StreamInfo extends AbstractVideoInfo {
public int dislike_count = -1; public int dislike_count = -1;
public String average_rating = ""; public String average_rating = "";
public StreamPreviewInfo next_video = null; public StreamPreviewInfo next_video = null;
public List<StreamPreviewInfo> related_videos = null; public List<StreamPreviewInfo> related_streams = null;
//in seconds. some metadata is not passed using a StreamInfo object! //in seconds. some metadata is not passed using a StreamInfo object!
public int start_position = 0; public int start_position = 0;
public List<Exception> errors = new Vector<>(); public List<Throwable> errors = new Vector<>();
} }

View file

@ -27,11 +27,11 @@ import java.util.Vector;
public class StreamPreviewInfoCollector { public class StreamPreviewInfoCollector {
private List<StreamPreviewInfo> itemList = new Vector<>(); private List<StreamPreviewInfo> itemList = new Vector<>();
private List<Exception> errors = new Vector<>(); private List<Throwable> errors = new Vector<>();
private StreamUrlIdHandler urlIdHandler; private UrlIdHandler urlIdHandler;
private int serviceId = -1; private int serviceId = -1;
public StreamPreviewInfoCollector(StreamUrlIdHandler handler, int serviceId) { public StreamPreviewInfoCollector(UrlIdHandler handler, int serviceId) {
urlIdHandler = handler; urlIdHandler = handler;
this.serviceId = serviceId; this.serviceId = serviceId;
} }
@ -40,7 +40,7 @@ public class StreamPreviewInfoCollector {
return itemList; return itemList;
} }
public List<Exception> getErrors() { public List<Throwable> getErrors() {
return errors; return errors;
} }
@ -57,7 +57,7 @@ public class StreamPreviewInfoCollector {
if (urlIdHandler == null) { if (urlIdHandler == null) {
throw new ParsingException("Error: UrlIdHandler not set"); throw new ParsingException("Error: UrlIdHandler not set");
} else if(!resultItem.webpage_url.isEmpty()) { } else if(!resultItem.webpage_url.isEmpty()) {
resultItem.id = (new YoutubeStreamUrlIdHandler()).getVideoId(resultItem.webpage_url); resultItem.id = (new YoutubeStreamUrlIdHandler()).getId(resultItem.webpage_url);
} }
resultItem.title = extractor.getTitle(); resultItem.title = extractor.getTitle();
resultItem.stream_type = extractor.getStreamType(); resultItem.stream_type = extractor.getStreamType();

View file

@ -24,7 +24,7 @@ public class StreamPreviewInfoSearchCollector extends StreamPreviewInfoCollector
private String suggestion = ""; private String suggestion = "";
public StreamPreviewInfoSearchCollector(StreamUrlIdHandler handler, int serviceId) { public StreamPreviewInfoSearchCollector(UrlIdHandler handler, int serviceId) {
super(handler, serviceId); super(handler, serviceId);
} }

View file

@ -38,7 +38,10 @@ public abstract class StreamingService {
public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader) public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader)
throws IOException, ExtractionException; throws IOException, ExtractionException;
public abstract SearchEngine getSearchEngineInstance(Downloader downloader); public abstract SearchEngine getSearchEngineInstance(Downloader downloader);
public abstract StreamUrlIdHandler getUrlIdHandlerInstance(); public abstract UrlIdHandler getUrlIdHandlerInstance();
public abstract UrlIdHandler getChannelUrlIdHandlerInstance();
public abstract ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
throws ExtractionException, IOException;
public final int getServiceId() { public final int getServiceId() {
return serviceId; return serviceId;

View file

@ -1,10 +1,10 @@
package org.schabi.newpipe.extractor; package org.schabi.newpipe.extractor;
/** /**
* Created by Christian Schabesberger on 02.02.16. * Created by Christian Schabesberger on 26.07.16.
* *
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamUrlIdHandler.java is part of NewPipe. * UrlIdHandler.java is part of NewPipe.
* *
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -20,9 +20,9 @@ package org.schabi.newpipe.extractor;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
public interface StreamUrlIdHandler { public interface UrlIdHandler {
String getVideoUrl(String videoId); String getUrl(String videoId);
String getVideoId(String siteUrl) throws ParsingException; String getId(String siteUrl) throws ParsingException;
String cleanUrl(String siteUrl) throws ParsingException; String cleanUrl(String siteUrl) throws ParsingException;
/**When a VIEW_ACTION is caught this function will test if the url delivered within the calling /**When a VIEW_ACTION is caught this function will test if the url delivered within the calling

View file

@ -0,0 +1,331 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
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.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.UrlIdHandler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Christian Schabesberger on 25.07.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* YoutubeChannelExtractor.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 YoutubeChannelExtractor extends ChannelExtractor {
private static final String TAG = YoutubeChannelExtractor.class.toString();
// private CSSOMParser cssParser = new CSSOMParser(new SACParserCSS3());
private Downloader downloader;
private Document doc = null;
private boolean isAjaxPage = false;
private static String userUrl = "";
private static String channelName = "";
private static String avatarUrl = "";
private static String bannerUrl = "";
private static String feedUrl = "";
// the fist page is html all other pages are ajax. Every new page can be requested by sending
// this request url.
private static String nextPageUrl = "";
public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
throws ExtractionException, IOException {
super(urlIdHandler, url, page, dl, serviceId);
url = urlIdHandler.cleanUrl(url) ; //+ "/video?veiw=0&flow=list&sort=dd";
downloader = dl;
if(page == 0) {
if (isUserUrl(url)) {
userUrl = url;
} else {
// we first need to get the user url. Otherwise we can't find videos
String channelPageContent = downloader.download(url);
Document channelDoc = Jsoup.parse(channelPageContent, url);
userUrl = getUserUrl(channelDoc);
}
userUrl = userUrl + "/videos?veiw=0&flow=list&sort=dd&live_view=10000";
String pageContent = downloader.download(userUrl);
doc = Jsoup.parse(pageContent, userUrl);
nextPageUrl = getNextPageUrl(doc);
isAjaxPage = false;
} else {
String ajaxDataRaw = downloader.download(nextPageUrl);
JSONObject ajaxData;
try {
ajaxData = new JSONObject(ajaxDataRaw);
String htmlDataRaw = ajaxData.getString("content_html");
doc = Jsoup.parse(htmlDataRaw, nextPageUrl);
String nextPageHtmlDataRaw = ajaxData.getString("load_more_widget_html");
if(!nextPageHtmlDataRaw.isEmpty()) {
Document nextPageData = Jsoup.parse(nextPageHtmlDataRaw, nextPageUrl);
nextPageUrl = getNextPageUrl(nextPageData);
} else {
nextPageUrl = "";
}
} catch (JSONException e) {
throw new ParsingException("Could not parse json data for next page", e);
}
isAjaxPage = true;
}
}
@Override
public String getChannelName() throws ParsingException {
try {
if(!isAjaxPage) {
channelName = doc.select("span[class=\"qualified-channel-title-text\"]").first()
.select("a").first().text();
}
return channelName;
} catch(Exception e) {
throw new ParsingException("Could not get channel name");
}
}
@Override
public String getAvatarUrl() throws ParsingException {
try {
if(!isAjaxPage) {
avatarUrl = doc.select("img[class=\"channel-header-profile-image\"]")
.first().attr("abs:src");
}
return avatarUrl;
} catch(Exception e) {
throw new ParsingException("Could not get avatar", e);
}
}
@Override
public String getBannerUrl() throws ParsingException {
try {
if(!isAjaxPage) {
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")) {
bannerUrl = null;
} else {
bannerUrl = url;
}
}
return bannerUrl;
} catch(Exception e) {
throw new ParsingException("Could not get Banner", e);
}
}
@Override
public StreamPreviewInfoCollector getStreams() throws ParsingException {
StreamPreviewInfoCollector collector = getStreamPreviewInfoCollector();
Element ul = null;
if(isAjaxPage) {
ul = doc.select("body").first();
} else {
ul = doc.select("ul[id=\"browse-items-primary\"]").first();
}
for(final Element li : ul.children()) {
if (li.select("div[class=\"feed-item-dismissable\"]").first() != null) {
collector.commit(new StreamPreviewInfoExtractor() {
@Override
public AbstractVideoInfo.StreamType getStreamType() throws ParsingException {
return AbstractVideoInfo.StreamType.VIDEO_STREAM;
}
@Override
public String getWebPageUrl() throws ParsingException {
try {
Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
Element dl = el.select("h3").first().select("a").first();
return dl.attr("abs:href");
} catch (Exception e) {
throw new ParsingException("Could not get web page url for the video", e);
}
}
@Override
public String getTitle() throws ParsingException {
try {
Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
Element dl = el.select("h3").first().select("a").first();
return dl.text();
} catch (Exception e) {
throw new ParsingException("Could not get title", e);
}
}
@Override
public int getDuration() throws ParsingException {
try {
return YoutubeParsingHelper.parseDurationString(
li.select("span[class=\"video-time\"]").first().text());
} catch(Exception e) {
if(isLiveStream(li)) {
// -1 for no duration
return -1;
} else {
throw new ParsingException("Could not get Duration: " + getTitle(), e);
}
}
}
@Override
public String getUploader() throws ParsingException {
return getChannelName();
}
@Override
public String getUploadDate() throws ParsingException {
try {
return li.select("div[class=\"yt-lockup-meta\"]").first()
.select("li").first()
.text();
} catch(Exception e) {
throw new ParsingException("Could not get uplaod date", e);
}
}
@Override
public long getViewCount() throws ParsingException {
String output;
String input;
try {
input = li.select("div[class=\"yt-lockup-meta\"]").first()
.select("li").get(1)
.text();
} catch (IndexOutOfBoundsException e) {
if(isLiveStream(li)) {
// -1 for no view count
return -1;
} else {
throw new ParsingException(
"Could not parse yt-lockup-meta although available: " + getTitle(), e);
}
}
output = Parser.matchGroup1("([0-9,\\. ]*)", input)
.replace(" ", "")
.replace(".", "")
.replace(",", "");
try {
return Long.parseLong(output);
} catch (NumberFormatException e) {
// if this happens the video probably has no views
if(!input.isEmpty()) {
return 0;
} else {
throw new ParsingException("Could not handle input: " + input, e);
}
}
}
@Override
public String getThumbnailUrl() throws ParsingException {
try {
String url;
Element te = li.select("span[class=\"yt-thumb-clip\"]").first()
.select("img").first();
url = te.attr("abs:src");
// Sometimes youtube sends links to gif files which somehow seem to not exist
// anymore. Items with such gif also offer a secondary image source. So we are going
// to use that if we've caught such an item.
if (url.contains(".gif")) {
url = te.attr("abs:data-thumb");
}
return url;
} catch (Exception e) {
throw new ParsingException("Could not get thumbnail url", e);
}
}
private boolean isLiveStream(Element item) {
Element bla = item.select("span[class*=\"yt-badge-live\"]").first();
if(bla == null) {
// sometimes livestreams dont have badges but sill are live streams
// if video time is not available we most likly have an offline livestream
if(item.select("span[class*=\"video-time\"]").first() == null) {
return true;
}
}
return bla != null;
}
});
}
}
return collector;
}
@Override
public String getFeedUrl() throws ParsingException {
try {
if(!isAjaxPage) {
feedUrl = doc.select("link[title=\"RSS\"]").first().attr("abs:href");
}
return feedUrl;
} catch(Exception e) {
throw new ParsingException("Could not get feed url", e);
}
}
@Override
public boolean hasNextPage() throws ParsingException {
return !nextPageUrl.isEmpty();
}
private String getUserUrl(Document d) throws ParsingException {
return d.select("span[class=\"qualified-channel-title-text\"]").first()
.select("a").first().attr("abs:href");
}
private boolean isUserUrl(String url) throws ParsingException {
return url.contains("/user/");
}
private String getNextPageUrl(Document d) throws ParsingException {
try {
Element button = d.select("button[class*=\"yt-uix-load-more\"]").first();
return button.attr("abs:data-uix-load-more-href");
} catch(Exception e) {
throw new ParsingException("could not load next page url", e);
}
}
}

View file

@ -0,0 +1,47 @@
package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.UrlIdHandler;
/**
* Created by Christian Schabesberger on 25.07.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* YoutubeChannelUrlIdHandler.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 YoutubeChannelUrlIdHandler implements UrlIdHandler {
public String getUrl(String channelId) {
return "https://www.youtube.com/" + channelId;
}
public String getId(String siteUrl) throws ParsingException {
return Parser.matchGroup1("/(user/[A-Za-z0-9_-]*|channel/[A-Za-z0-9_-]*)", siteUrl);
}
public String cleanUrl(String siteUrl) throws ParsingException {
return getUrl(getId(siteUrl));
}
public boolean acceptUrl(String videoUrl) {
return (videoUrl.contains("youtube") ||
videoUrl.contains("youtu.be")) &&
( videoUrl.contains("/user/") ||
videoUrl.contains("/channel/"));
}
}

View file

@ -14,7 +14,7 @@ import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector; import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor; import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.StreamPreviewInfoSearchCollector; import org.schabi.newpipe.extractor.StreamPreviewInfoSearchCollector;
import org.schabi.newpipe.extractor.StreamUrlIdHandler; import org.schabi.newpipe.extractor.UrlIdHandler;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
@ -55,7 +55,7 @@ public class YoutubeSearchEngine extends SearchEngine {
private static final String TAG = YoutubeSearchEngine.class.toString(); private static final String TAG = YoutubeSearchEngine.class.toString();
public static final String CHARSET_UTF_8 = "UTF-8"; public static final String CHARSET_UTF_8 = "UTF-8";
public YoutubeSearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) { public YoutubeSearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
super(urlIdHandler, serviceId); super(urlIdHandler, serviceId);
} }
@ -91,7 +91,6 @@ public class YoutubeSearchEngine extends SearchEngine {
site = downloader.download(url); site = downloader.download(url);
} }
Document doc = Jsoup.parse(site, url); Document doc = Jsoup.parse(site, url);
Element list = doc.select("ol[class=\"item-section\"]").first(); Element list = doc.select("ol[class=\"item-section\"]").first();

View file

@ -1,10 +1,11 @@
package org.schabi.newpipe.extractor.services.youtube; package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.StreamExtractor; import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamUrlIdHandler; import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.SearchEngine; import org.schabi.newpipe.extractor.SearchEngine;
import java.io.IOException; import java.io.IOException;
@ -45,7 +46,7 @@ public class YoutubeService extends StreamingService {
@Override @Override
public StreamExtractor getExtractorInstance(String url, Downloader downloader) public StreamExtractor getExtractorInstance(String url, Downloader downloader)
throws ExtractionException, IOException { throws ExtractionException, IOException {
StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler(); UrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
if(urlIdHandler.acceptUrl(url)) { if(urlIdHandler.acceptUrl(url)) {
return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId()); return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId());
} }
@ -59,7 +60,18 @@ public class YoutubeService extends StreamingService {
} }
@Override @Override
public StreamUrlIdHandler getUrlIdHandlerInstance() { public UrlIdHandler getUrlIdHandlerInstance() {
return new YoutubeStreamUrlIdHandler(); return new YoutubeStreamUrlIdHandler();
} }
@Override
public UrlIdHandler getChannelUrlIdHandlerInstance() {
return new YoutubeChannelUrlIdHandler();
}
@Override
public ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
throws ExtractionException, IOException {
return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, page, downloader, getServiceId());
}
} }

View file

@ -15,10 +15,9 @@ import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamInfo; import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector; import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor; import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.StreamUrlIdHandler; import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.StreamExtractor; import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.VideoStream; import org.schabi.newpipe.extractor.VideoStream;
@ -183,12 +182,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// cached values // cached values
private static volatile String decryptionCode = ""; private static volatile String decryptionCode = "";
StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler(); UrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
String pageUrl = ""; String pageUrl = "";
private Downloader downloader; private Downloader downloader;
public YoutubeStreamExtractor(StreamUrlIdHandler urlIdHandler, String pageUrl, public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl,
Downloader dl, int serviceId) Downloader dl, int serviceId)
throws ExtractionException, IOException { throws ExtractionException, IOException {
super(urlIdHandler ,pageUrl, dl, serviceId); super(urlIdHandler ,pageUrl, dl, serviceId);
@ -203,7 +202,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// Check if the video is age restricted // Check if the video is age restricted
if (pageContent.contains("<meta property=\"og:restrictions:age")) { if (pageContent.contains("<meta property=\"og:restrictions:age")) {
String videoInfoUrl = GET_VIDEO_INFO_URL.replace("%%video_id%%", String videoInfoUrl = GET_VIDEO_INFO_URL.replace("%%video_id%%",
urlidhandler.getVideoId(pageUrl)).replace("$$el_type$$", "&" + EL_INFO); urlidhandler.getId(pageUrl)).replace("$$el_type$$", "&" + EL_INFO);
String videoInfoPageString = downloader.download(videoInfoUrl); String videoInfoPageString = downloader.download(videoInfoUrl);
videoInfoPage = Parser.compatParseMap(videoInfoPageString); videoInfoPage = Parser.compatParseMap(videoInfoPageString);
playerUrl = getPlayerUrlFromRestrictedVideo(pageUrl); playerUrl = getPlayerUrlFromRestrictedVideo(pageUrl);
@ -286,7 +285,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException { private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException {
try { try {
String playerUrl = ""; String playerUrl = "";
String videoId = urlidhandler.getVideoId(pageUrl); String videoId = urlidhandler.getId(pageUrl);
String embedUrl = "https://www.youtube.com/embed/" + videoId; String embedUrl = "https://www.youtube.com/embed/" + videoId;
String embedPageContent = downloader.download(embedUrl); String embedPageContent = downloader.download(embedUrl);
//todo: find out if this can be reapaced by Parser.matchGroup1() //todo: find out if this can be reapaced by Parser.matchGroup1()
@ -686,6 +685,16 @@ public class YoutubeStreamExtractor extends StreamExtractor {
return pageUrl; 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 @Override
public StreamInfo.StreamType getStreamType() throws ParsingException { public StreamInfo.StreamType getStreamType() throws ParsingException {
//todo: if implementing livestream support this value should be generated dynamically //todo: if implementing livestream support this value should be generated dynamically

View file

@ -66,8 +66,6 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra
} else { } else {
throw new ParsingException("Could not get Duration: " + getTitle(), e); throw new ParsingException("Could not get Duration: " + getTitle(), e);
} }
} }
} }

View file

@ -2,7 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamUrlIdHandler; import org.schabi.newpipe.extractor.UrlIdHandler;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
@ -27,16 +27,16 @@ import java.net.URLDecoder;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler { public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Override @Override
public String getVideoUrl(String videoId) { public String getUrl(String videoId) {
return "https://www.youtube.com/watch?v=" + videoId; return "https://www.youtube.com/watch?v=" + videoId;
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Override @Override
public String getVideoId(String url) throws ParsingException, IllegalArgumentException { public String getId(String url) throws ParsingException, IllegalArgumentException {
if(url.isEmpty()) if(url.isEmpty())
{ {
throw new IllegalArgumentException("The url parameter should not be empty"); throw new IllegalArgumentException("The url parameter should not be empty");
@ -81,7 +81,7 @@ public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
} }
public String cleanUrl(String complexUrl) throws ParsingException { public String cleanUrl(String complexUrl) throws ParsingException {
return getVideoUrl(getVideoId(complexUrl)); return getUrl(getId(complexUrl));
} }
@Override @Override

View file

@ -0,0 +1,34 @@
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;
/**
* Created by the-scrabi on 01.08.16.
*/
public class InfoItemHolder extends RecyclerView.ViewHolder {
public ImageView itemThumbnailView;
public TextView itemVideoTitleView,
itemUploaderView,
itemDurationView,
itemUploadDateView,
itemViewCountView;
public Button itemButton;
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);
}
}

View file

@ -1,73 +1,79 @@
package org.schabi.newpipe; package org.schabi.newpipe.info_list;
import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.AbstractVideoInfo; import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo; import org.schabi.newpipe.extractor.StreamPreviewInfo;
import java.util.List;
import java.util.Vector;
/** /**
* Created by Christian Schabesberger on 24.10.15. * Created by the-scrabi on 01.08.16.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoInfoItemViewCreator.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 InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
public class VideoInfoItemViewCreator { public interface OnItemSelectedListener {
private final LayoutInflater inflater; void selected(String url);
}
private Activity activity = null;
private View rootView = null;
private List<StreamPreviewInfo> streamList = new Vector<>();
private ImageLoader imageLoader = ImageLoader.getInstance(); private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build(); private DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().cacheInMemory(true).build();
private OnItemSelectedListener onItemSelectedListener;
public VideoInfoItemViewCreator(LayoutInflater inflater) {
this.inflater = inflater;
public InfoListAdapter(Activity a, View rootView) {
activity = a;
this.rootView = rootView;
} }
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) { public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
ViewHolder holder; this.onItemSelectedListener = onItemSelectedListener;
// generate holder
if(convertView == null) {
convertView = inflater.inflate(R.layout.video_item, parent, false);
holder = new ViewHolder();
holder.itemThumbnailView = (ImageView) convertView.findViewById(R.id.itemThumbnailView);
holder.itemVideoTitleView = (TextView) convertView.findViewById(R.id.itemVideoTitleView);
holder.itemUploaderView = (TextView) convertView.findViewById(R.id.itemUploaderView);
holder.itemDurationView = (TextView) convertView.findViewById(R.id.itemDurationView);
holder.itemUploadDateView = (TextView) convertView.findViewById(R.id.itemUploadDateView);
holder.itemViewCountView = (TextView) convertView.findViewById(R.id.itemViewCountView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
} }
// fill with information public void addStreamItemList(List<StreamPreviewInfo> videos) {
if(videos!= null) {
/* streamList.addAll(videos);
if(info.thumbnail == null) { notifyDataSetChanged();
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
} else {
holder.itemThumbnailView.setImageBitmap(info.thumbnail);
} }
*/ }
public void clearSteamItemList() {
streamList = new Vector<>();
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return streamList.size();
}
@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);
}
@Override
public void onBindViewHolder(InfoItemHolder holder, int i) {
final StreamPreviewInfo info = streamList.get(i);
// fill holder with information
holder.itemVideoTitleView.setText(info.title); holder.itemVideoTitleView.setText(info.title);
if(info.uploader != null && !info.uploader.isEmpty()) { if(info.uploader != null && !info.uploader.isEmpty()) {
holder.itemUploaderView.setText(info.uploader); holder.itemUploaderView.setText(info.uploader);
@ -94,18 +100,22 @@ public class VideoInfoItemViewCreator {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail); holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) { if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, holder.itemThumbnailView, displayImageOptions); imageLoader.displayImage(info.thumbnail_url,
holder.itemThumbnailView,
displayImageOptions,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
} }
return convertView; holder.itemButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onItemSelectedListener.selected(info.webpage_url);
}
});
} }
private class ViewHolder {
public ImageView itemThumbnailView;
public TextView itemVideoTitleView, itemUploaderView, itemDurationView, itemUploadDateView, itemViewCountView;
}
private String shortViewCount(Long viewCount){ public static String shortViewCount(Long viewCount){
if(viewCount >= 1000000000){ if(viewCount >= 1000000000){
return Long.toString(viewCount/1000000000)+"B views"; return Long.toString(viewCount/1000000000)+"B views";
}else if(viewCount>=1000000){ }else if(viewCount>=1000000){

View file

@ -23,8 +23,8 @@ import android.widget.Toast;
import org.schabi.newpipe.ActivityCommunicator; import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.VideoItemDetailActivity; import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.VideoItemDetailFragment; import org.schabi.newpipe.detail.VideoItemDetailFragment;
import java.io.IOException; import java.io.IOException;

View file

@ -0,0 +1,265 @@
package org.schabi.newpipe.search_fragment;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.info_list.InfoListAdapter;
/**
* Created by Christian Schabesberger on 02.08.16.
*/
public class SearchInfoItemFragment extends Fragment {
private static final String TAG = SearchInfoItemFragment.class.toString();
public class SearchQueryListener implements SearchView.OnQueryTextListener {
@Override
public boolean onQueryTextSubmit(String query) {
Activity a = getActivity();
try {
searchQuery = query;
search(query);
// hide virtual keyboard
InputMethodManager inputManager =
(InputMethodManager) a.getSystemService(Context.INPUT_METHOD_SERVICE);
try {
//noinspection ConstantConditions
inputManager.hideSoftInputFromWindow(
a.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
} catch(NullPointerException e) {
e.printStackTrace();
ErrorActivity.reportError(a, e, null,
a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(streamingServiceId),
"Could not get widget with focus", R.string.general_error));
}
// clear focus
// 1. to not open up the keyboard after switching back to this
// 2. It's a workaround to a seeming bug by the Android OS it self, causing
// onQueryTextSubmit to trigger twice when focus is not cleared.
// See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
a.getCurrentFocus().clearFocus();
} catch(Exception e) {
e.printStackTrace();
}
View bg = a.findViewById(R.id.mainBG);
bg.setVisibility(View.GONE);
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
if(!newText.isEmpty()) {
searchSuggestions(newText);
}
return true;
}
}
private int streamingServiceId = -1;
private String searchQuery = "";
private boolean isLoading = false;
private ProgressBar loadingIndicator = null;
private SearchView searchView = null;
private int pageNumber = 0;
private SuggestionListAdapter suggestionListAdapter = null;
private InfoListAdapter infoListAdapter = null;
private LinearLayoutManager streamInfoListLayoutManager = null;
private RecyclerView recyclerView = null;
// savedInstanceBundle arguments
private static final String QUERY = "query";
private static final String STREAMING_SERVICE = "streaming_service";
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public SearchInfoItemFragment() {
}
// TODO: Customize parameter initialization
@SuppressWarnings("unused")
public static SearchInfoItemFragment newInstance(int columnCount) {
SearchInfoItemFragment fragment = new SearchInfoItemFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY);
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
} else {
try {
streamingServiceId = ServiceList.getIdOfService("Youtube");
} catch(Exception e) {
e.printStackTrace();
ErrorActivity.reportError(getActivity(), e, null,
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(streamingServiceId),
"", R.string.general_error));
}
}
SearchWorker sw = SearchWorker.getInstance();
sw.setSearchWorkerResultListner(new SearchWorker.SearchWorkerResultListner() {
@Override
public void onResult(SearchResult result) {
infoListAdapter.addStreamItemList(result.resultList);
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
}
@Override
public void onNothingFound(int stringResource) {
//setListShown(true);
Toast.makeText(getActivity(), getString(stringResource),
Toast.LENGTH_SHORT).show();
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
}
@Override
public void onError(String message) {
//setListShown(true);
Toast.makeText(getActivity(), message,
Toast.LENGTH_LONG).show();
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_searchinfoitem, container, false);
Context context = view.getContext();
loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar);
recyclerView = (RecyclerView) view.findViewById(R.id.list);
streamInfoListLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(streamInfoListLayoutManager);
infoListAdapter = new InfoListAdapter(getActivity(),
getActivity().findViewById(android.R.id.content));
infoListAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
@Override
public void selected(String url) {
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
getActivity().startActivity(i);
}
});
recyclerView.setAdapter(infoListAdapter);
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
if(dy > 0) //check for scroll down
{
visibleItemCount = streamInfoListLayoutManager.getChildCount();
totalItemCount = streamInfoListLayoutManager.getItemCount();
pastVisiblesItems = streamInfoListLayoutManager.findFirstVisibleItemPosition();
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading)
{
pageNumber++;
search(searchQuery, pageNumber);
}
}
}
});
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public void onDetach() {
super.onDetach();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
searchView = (SearchView) searchItem.getActionView();
setupSearchView(searchView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
private void setupSearchView(SearchView searchView) {
suggestionListAdapter = new SuggestionListAdapter(getActivity());
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
searchView.setOnQueryTextListener(new SearchQueryListener());
if(searchQuery != null && !searchQuery.isEmpty()) {
searchView.setQuery(searchQuery, false);
searchView.setIconifiedByDefault(false);
}
}
private void search(String query) {
infoListAdapter.clearSteamItemList();
pageNumber = 0;
search(query, pageNumber);
loadingIndicator.setVisibility(View.VISIBLE);
}
private void search(String query, int page) {
isLoading = true;
SearchWorker sw = SearchWorker.getInstance();
sw.search(streamingServiceId, query, page, getActivity());
}
private void searchSuggestions(String query) {
SuggestionSearchRunnable suggestionSearchRunnable =
new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
Thread suggestionThread = new Thread(suggestionSearchRunnable);
suggestionThread.start();
}
}

View file

@ -0,0 +1,32 @@
package org.schabi.newpipe.search_fragment;
import android.support.v7.widget.SearchView;
/**
* Created by the-scrabi on 02.08.16.
*/
public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
private SearchView searchView;
private SuggestionListAdapter adapter;
public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
this.searchView = searchView;
this.adapter = adapter;
}
@Override
public boolean onSuggestionSelect(int position) {
String suggestion = adapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
@Override
public boolean onSuggestionClick(int position) {
String suggestion = adapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
}

View file

@ -0,0 +1,174 @@
package org.schabi.newpipe.search_fragment;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.ServiceList;
import java.io.IOException;
/**
* Created by the-scrabi on 02.08.16.
*/
public class SearchWorker {
private static final String TAG = SearchWorker.class.toString();
public interface SearchWorkerResultListner {
void onResult(SearchResult result);
void onNothingFound(final int stringResource);
void onError(String message);
}
private class ResultRunnable implements Runnable {
private final SearchResult result;
private int requestId = 0;
public ResultRunnable(SearchResult result, int requestId) {
this.result = result;
this.requestId = requestId;
}
@Override
public void run() {
if(this.requestId == SearchWorker.this.requestId) {
searchWorkerResultListner.onResult(result);
}
}
}
private class SearchRunnable implements Runnable {
public static final String YOUTUBE = "Youtube";
private final String query;
private final int page;
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) {
this.serviceId = serviceId;
this.query = query;
this.page = page;
this.a = activity;
}
void terminate() {
runs = false;
}
@Override
public void run() {
SearchResult result = null;
SearchEngine engine = null;
try {
engine = ServiceList.getService(serviceId)
.getSearchEngineInstance(new Downloader());
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
Integer.toString(serviceId), query, R.string.general_error));
return;
}
try {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
result = SearchResult
.getSearchResult(engine, query, page, searchLanguage, new Downloader());
if(runs) {
h.post(new ResultRunnable(result, requestId));
}
// look for errors during extraction
// soft errors:
if(result != null &&
!result.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
for(Throwable e : result.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
View rootView = a.findViewById(android.R.id.content);
ErrorActivity.reportError(h, a, result.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
}
// hard errors:
} catch(IOException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListner.onNothingFound(R.string.network_error);
}
});
e.printStackTrace();
} catch(final SearchEngine.NothingFoundException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListner.onError(e.getMessage());
}
});
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */
YOUTUBE, query, R.string.parsing_error));
//postNewErrorToast(h, R.string.parsing_error);
e.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
e.printStackTrace();
}
}
}
private static SearchWorker searchWorker = null;
private SearchWorkerResultListner searchWorkerResultListner = null;
private SearchRunnable runnable = null;
private int requestId = 0; //prevents running requests that have already ben expired
public static SearchWorker getInstance() {
return searchWorker == null ? (searchWorker = new SearchWorker()) : searchWorker;
}
public void setSearchWorkerResultListner(SearchWorkerResultListner listener) {
searchWorkerResultListner = listener;
}
private SearchWorker() {
}
public void search(int serviceId, String query, int page, Activity a) {
if(runnable != null) {
terminate();
}
runnable = new SearchRunnable(serviceId, query, page, a, requestId);
Thread thread = new Thread(runnable);
thread.start();
}
public void terminate() {
requestId++;
runnable.terminate();
}
}

View file

@ -1,4 +1,4 @@
package org.schabi.newpipe; package org.schabi.newpipe.search_fragment;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -9,27 +9,10 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Created by Madiyar on 23.02.2016. * Created by the-scrabi on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* SuggestionListAdapter.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 SuggestionListAdapter extends CursorAdapter { public class SuggestionListAdapter extends CursorAdapter {

View file

@ -0,0 +1,90 @@
package org.schabi.newpipe.search_fragment;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.widget.Toast;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.ServiceList;
import java.io.IOException;
import java.util.List;
/**
* Created by the-scrabi on 02.08.16.
*/
public class SuggestionSearchRunnable implements Runnable{
private class SuggestionResultRunnable implements Runnable{
private List<String> suggestions;
private SuggestionListAdapter adapter;
private SuggestionResultRunnable(List<String> suggestions, SuggestionListAdapter adapter) {
this.suggestions = suggestions;
this.adapter = adapter;
}
@Override
public void run() {
adapter.updateAdapter(suggestions);
}
}
private final int serviceId;
private final String query;
final Handler h = new Handler();
private Activity a = null;
private SuggestionListAdapter adapter;
public SuggestionSearchRunnable(int serviceId, String query,
Activity activity, SuggestionListAdapter adapter) {
this.serviceId = serviceId;
this.query = query;
this.a = activity;
this.adapter = adapter;
}
@Override
public void run() {
try {
SearchEngine engine =
ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
List<String> suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
h.post(new SuggestionResultRunnable(suggestions, adapter));
} catch (ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(serviceId), query, R.string.parsing_error));
e.printStackTrace();
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
} catch (Exception e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
ServiceList.getNameOfService(serviceId), query, R.string.general_error));
}
}
private void postNewErrorToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(a, a.getString(stringResource),
Toast.LENGTH_SHORT).show();
}
});
}
}

View file

@ -18,7 +18,7 @@
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.schabi.newpipe; package org.schabi.newpipe.settings;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -26,6 +26,8 @@ import android.os.Environment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import org.schabi.newpipe.R;
import java.io.File; import java.io.File;
import us.shandian.giga.util.Utility; import us.shandian.giga.util.Utility;

View file

@ -1,4 +1,4 @@
package org.schabi.newpipe; package org.schabi.newpipe.settings;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -14,6 +14,8 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.schabi.newpipe.R;
/** /**
* Created by Christian Schabesberger on 31.08.15. * Created by Christian Schabesberger on 31.08.15.

View file

@ -1,4 +1,4 @@
package org.schabi.newpipe; package org.schabi.newpipe.settings;
import android.app.Activity; import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
@ -16,6 +16,9 @@ import android.preference.PreferenceScreen;
import com.nononsenseapps.filepicker.FilePickerActivity; import com.nononsenseapps.filepicker.FilePickerActivity;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
import java.util.ArrayList; import java.util.ArrayList;
import info.guardianproject.netcipher.proxy.OrbotHelper; import info.guardianproject.netcipher.proxy.OrbotHelper;

View file

@ -5,7 +5,7 @@ import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.schabi.newpipe.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import java.io.File; import java.io.File;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;

View file

@ -14,7 +14,7 @@ import android.os.Message;
import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log; import android.util.Log;
import org.schabi.newpipe.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager; import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadManagerImpl; import us.shandian.giga.get.DownloadManagerImpl;

View file

@ -17,9 +17,8 @@ import java.io.IOException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.schabi.newpipe.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadMission;
import com.nononsenseapps.filepicker.FilePickerActivity; import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.AbstractFilePickerFragment; import com.nononsenseapps.filepicker.AbstractFilePickerFragment;

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: 1,012 B

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@android:color/white"/>
<size
android:height="@dimen/channel_avatar_halo_size"
android:width="@dimen/channel_avatar_halo_size"/>
</shape>

View file

@ -1,55 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:divider="?android:attr/dividerHorizontal"
android:orientation="horizontal"
android:showDividers="middle"
tools:context=".VideoItemListActivity">
<!--
This layout is a two-pane layout for the VideoItems
master/detail flow.
-->
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight = "2">
<android.support.v7.widget.SearchView
android:id="@+id/searchViewTablet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:iconifiedByDefault="false"
android:focusable="false"
tools:ignore="InconsistentLayout" />
<include layout="@layout/main_bg" />
<fragment android:id="@+id/videoitem_list"
android:name="org.schabi.newpipe.VideoItemListFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@android:layout/list_content" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="4">
<FrameLayout android:id="@+id/videoitem_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="InconsistentLayout" />
</LinearLayout>
</LinearLayout>

View file

@ -3,15 +3,15 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".VideoItemDetailFragment" tools:context=".detail.VideoItemDetailFragment"
android:textIsSelectable="true" android:textIsSelectable="true"
style="?android:attr/textAppearanceLarge" style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/videoitem_detail"> android:id="@+id/video_item_detail">
<com.nirhart.parallaxscroll.views.ParallaxScrollView <com.nirhart.parallaxscroll.views.ParallaxScrollView
android:id="@+id/detailMainContent" android:id="@+id/detail_main_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="visible" android:visibility="visible"
@ -23,12 +23,12 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<RelativeLayout <RelativeLayout
android:id="@+id/detailVideoThumbnailWindowLayout" android:id="@+id/detail_stream_thumbnail_window_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"> android:background="?attr/selectableItemBackground">
<ImageView android:id="@+id/detailThumbnailView" <ImageView android:id="@+id/detail_thumbnail_view"
android:contentDescription="@string/detail_thumbnail_view_description" android:contentDescription="@string/detail_thumbnail_view_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -40,13 +40,13 @@
android:background="@android:color/black" android:background="@android:color/black"
android:src="@drawable/dummy_thumbnail_dark"/> android:src="@drawable/dummy_thumbnail_dark"/>
<ProgressBar android:id="@+id/detailProgressBar" <ProgressBar android:id="@+id/detail_progress_bar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:indeterminate="true"/> android:indeterminate="true"/>
<ImageView android:id="@+id/playArrowView" <ImageView android:id="@+id/play_arrow_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/transparent" android:background="@android:color/transparent"
@ -55,18 +55,17 @@
android:visibility="invisible"/> android:visibility="invisible"/>
<Button <Button
android:id="@+id/detailVideoThumbnailWindowBackgroundButton" android:id="@+id/detail_stream_thumbnail_window_background_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"/> android:background="?attr/selectableItemBackground"/>
</RelativeLayout> </RelativeLayout>
<RelativeLayout android:id="@+id/detailTextContentLayout" <RelativeLayout android:id="@+id/detail_text_content_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/detailVideoThumbnailWindowLayout" android:layout_below="@id/detail_stream_thumbnail_window_layout"
android:background="@color/light_background_color" android:background="@color/light_background_color"
android:visibility="gone"> android:visibility="gone">
@ -75,7 +74,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/detailTopView"> android:id="@+id/detailTopView">
<TextView android:id="@+id/detailVideoTitleView" <TextView android:id="@+id/detail_video_title_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_weight=".7" android:layout_weight=".7"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -89,7 +88,7 @@
<ImageView <ImageView
android:layout_width="15dp" android:layout_width="15dp"
android:layout_height="30dp" android:layout_height="30dp"
android:id="@+id/toggleDescriptionView" android:id="@+id/toggle_description_view"
android:src="@drawable/arrow_down" android:src="@drawable/arrow_down"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
@ -99,7 +98,7 @@
</LinearLayout> </LinearLayout>
<TextView android:id="@+id/detailViewCountView" <TextView android:id="@+id/detail_view_count_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_views_text_size" android:textSize="@dimen/video_item_detail_views_text_size"
@ -115,14 +114,14 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/detailViewCountView" android:layout_below="@id/detail_view_count_view"
android:id="@+id/detailExtraView" android:id="@+id/detailExtraView"
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginRight="12dp" android:layout_marginRight="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:visibility="gone"> android:visibility="gone">
<TextView android:id="@+id/detailUploadDateView" <TextView android:id="@+id/detail_upload_date_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_upload_date_text_size" android:textSize="@dimen/video_item_detail_upload_date_text_size"
@ -130,12 +129,12 @@
android:text="Upload date" android:text="Upload date"
android:layout_marginTop="3dp" /> android:layout_marginTop="3dp" />
<TextView android:id="@+id/detailDescriptionView" <TextView android:id="@+id/detail_description_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_description_text_size" android:textSize="@dimen/video_item_detail_description_text_size"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_below="@id/detailUploadDateView" android:layout_below="@id/detail_upload_date_view"
android:text="Description............." android:text="Description............."
android:layout_marginTop="3dp" /> android:layout_marginTop="3dp" />
</RelativeLayout> </RelativeLayout>
@ -143,27 +142,27 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/linearLayout" android:id="@+id/stream_info_layout"
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_below="@+id/detailExtraView" android:layout_below="@+id/detailExtraView"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginTop="5dp"> android:layout_marginTop="5dp">
<ImageView android:id="@+id/detailThumbsUpImgView" <ImageView android:id="@+id/detail_thumbs_up_img_view"
android:contentDescription="@string/detail_likes_img_view_description" android:contentDescription="@string/detail_likes_img_view_description"
android:layout_width="@dimen/video_item_detail_like_image_width" android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height" android:layout_height="@dimen/video_item_detail_like_image_height"
android:src="@drawable/thumbs_up" /> android:src="@drawable/thumbs_up" />
<TextView android:id="@+id/detailThumbsUpCountView" <TextView android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_likes_text_size" android:textSize="@dimen/video_item_detail_likes_text_size"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="200" /> android:text="200" />
<ImageView android:id="@+id/detailThumbsDownImgView" <ImageView android:id="@+id/detail_thumbs_down_img_view"
android:contentDescription="@string/detail_dislikes_img_view_description" android:contentDescription="@string/detail_dislikes_img_view_description"
android:layout_width="@dimen/video_item_detail_like_image_width" android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height" android:layout_height="@dimen/video_item_detail_like_image_height"
@ -171,7 +170,7 @@
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"/> android:layout_marginStart="10dp"/>
<TextView android:id="@+id/detailThumbsDownCountView" <TextView android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_likes_text_size" android:textSize="@dimen/video_item_detail_likes_text_size"
@ -179,11 +178,15 @@
android:text="100" /> android:text="100" />
</LinearLayout> </LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/detail_uploader_frame"
android:layout_below="@id/stream_info_layout">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/linearLayout" android:id="@+id/detail_uploader_layout"
android:id="@+id/detailUploaderWrapView"
android:layout_marginTop="12dp"> android:layout_marginTop="12dp">
<View <View
@ -191,7 +194,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1px" /> android:layout_height="1px" />
<de.hdodenhof.circleimageview.CircleImageView android:id="@+id/detailUploaderThumbnailView" <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
android:contentDescription="@string/detail_uploader_thumbnail_view_description" android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size"
@ -203,7 +207,7 @@
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"/> android:layout_marginBottom="5dp"/>
<TextView android:id="@+id/detailUploaderView" <TextView android:id="@+id/detail_uploader_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textStyle="bold" android:textStyle="bold"
@ -211,8 +215,8 @@
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Uploader" android:text="Uploader"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toRightOf="@+id/detailUploaderThumbnailView" android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
android:layout_toEndOf="@+id/detailUploaderThumbnailView" android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
android:layout_marginLeft="15dp" android:layout_marginLeft="15dp"
android:layout_marginStart="28dp" /> android:layout_marginStart="28dp" />
@ -220,18 +224,26 @@
android:background="#000" android:background="#000"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1px" android:layout_height="1px"
android:layout_below="@id/detailUploaderThumbnailView"/> android:layout_below="@id/detail_uploader_thumbnail_view"/>
</RelativeLayout> </RelativeLayout>
<Button
android:layout_marginTop="13dp"
android:id="@+id/channel_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"/>
</FrameLayout>
<RelativeLayout android:id="@+id/detailNextVideoRootLayout" <RelativeLayout android:id="@+id/detail_next_stream_root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_below="@+id/detailUploaderWrapView" android:layout_below="@+id/detail_uploader_frame"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"> android:layout_marginTop="10dp">
<TextView android:id="@+id/detailNextVideoTitle" <TextView
android:id="@+id/detail_next_stream_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
@ -240,38 +252,25 @@
android:text="@string/next_video_title" android:text="@string/next_video_title"
android:textAllCaps="true" /> android:textAllCaps="true" />
<RelativeLayout android:id="@+id/detailNextVidButtonAndContentLayout" <android.support.v7.widget.RecyclerView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_below="@id/detailNextVideoTitle"> android:id="@+id/detail_next_stream_content"/>
<FrameLayout <TextView android:id="@+id/detail_similar_title"
android:id="@+id/detailNextVideoFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/detailNextVideoButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@id/detailNextVideoFrame"
android:layout_alignBottom="@id/detailNextVideoFrame"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>
<TextView android:id="@+id/detailSimilarTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:textSize="@dimen/video_item_detail_next_text_size" android:textSize="@dimen/video_item_detail_next_text_size"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/similar_videos_btn_text" android:text="@string/similar_videos_btn_text"
android:layout_below="@id/detailNextVidButtonAndContentLayout" android:layout_below="@id/detail_next_stream_content"
android:textAllCaps="true" /> android:textAllCaps="true" />
<LinearLayout <android.support.v7.widget.RecyclerView
android:id="@+id/similarVideosView" android:id="@+id/similar_streams_view"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/detailSimilarTitle"> android:layout_below="@id/detail_similar_title"/>
</LinearLayout>
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android: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/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/channel_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
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" />
<ImageView
android:id="@+id/channel_avatar_halo"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="28dp"
android:layout_marginStart="28dp"
android:layout_marginTop="38dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:src="@drawable/white_circle"/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/channel_avatar_view"
android:visibility="gone"
android:layout_width="@dimen/channel_avatar_size"
android:layout_height="@dimen/channel_avatar_size"
android:src="@drawable/buddy"
android:layout_marginLeft="30dp"
android:layout_marginStart="30dp"
android:layout_marginTop="40dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/>
<android.support.v7.widget.Toolbar
android:id="@+id/cannel_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/channel_rss_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
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.v7.widget.RecyclerView
android:id="@+id/channel_streams_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:scrollbars="vertical"/>
</android.support.design.widget.CoordinatorLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.schabi.newpipe.MainActivity"
android:orientation="vertical">
<include layout="@layout/main_bg" />
<fragment
android:id="@+id/search_fragment"
android:name="org.schabi.newpipe.search_fragment.SearchInfoItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>

View file

@ -1,4 +1,4 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/videoitem_detail_container" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/videoitem_detail_container"
android:layout_width="match_parent" android:layout_height="match_parent" android:layout_width="match_parent" android:layout_height="match_parent"
tools:context=".VideoItemDetailActivity" tools:ignore="MergeRootFrame" /> tools:context=".detail.VideoItemDetailActivity" tools:ignore="MergeRootFrame" />

View file

@ -1,18 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/main_bg" />
<fragment
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/videoitem_list"
android:name="org.schabi.newpipe.VideoItemListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".VideoItemListActivity"
tools:layout="@android:layout/list_content" />
</LinearLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="org.schabi.newpipe.ChannelActivity"
tools:showIn="@layout/activity_channel">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:name="org.schabi.newpipe.SearchInfoItemFragment"
tools:context=".search_fragment.SearchInfoItemFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/video_item"
android:scrollbars="vertical"/>
<ProgressBar android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"
android:visibility="gone"/>
</RelativeLayout>

View file

@ -3,15 +3,15 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".VideoItemDetailFragment" tools:context=".detail.VideoItemDetailFragment"
android:textIsSelectable="true" android:textIsSelectable="true"
style="?android:attr/textAppearanceLarge" style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/videoitem_detail"> android:id="@+id/video_item_detail">
<com.nirhart.parallaxscroll.views.ParallaxScrollView <com.nirhart.parallaxscroll.views.ParallaxScrollView
android:id="@+id/detailMainContent" android:id="@+id/detail_main_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="visible" android:visibility="visible"
@ -23,12 +23,12 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<RelativeLayout <RelativeLayout
android:id="@+id/detailVideoThumbnailWindowLayout" android:id="@+id/detail_stream_thumbnail_window_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"> android:background="?attr/selectableItemBackground">
<ImageView android:id="@+id/detailThumbnailView" <ImageView android:id="@+id/detail_thumbnail_view"
android:contentDescription="@string/detail_thumbnail_view_description" android:contentDescription="@string/detail_thumbnail_view_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -40,14 +40,14 @@
android:background="@android:color/black" android:background="@android:color/black"
android:src="@drawable/dummy_thumbnail_dark"/> android:src="@drawable/dummy_thumbnail_dark"/>
<ProgressBar android:id="@+id/detailProgressBar" <ProgressBar android:id="@+id/detail_progress_bar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:indeterminate="true"/> android:indeterminate="true"/>
<android.support.design.widget.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:id="@+id/playVideoButton" android:id="@+id/play_video_button"
android:visibility="invisible" android:visibility="invisible"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -57,18 +57,17 @@
android:layout_margin="@dimen/video_item_detail_play_fab_margin"/> android:layout_margin="@dimen/video_item_detail_play_fab_margin"/>
<Button <Button
android:id="@+id/detailVideoThumbnailWindowBackgroundButton" android:id="@+id/detail_stream_thumbnail_window_background_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"/> android:background="?attr/selectableItemBackground"/>
</RelativeLayout> </RelativeLayout>
<RelativeLayout android:id="@+id/detailTextContentLayout" <RelativeLayout android:id="@+id/detail_text_content_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/detailVideoThumbnailWindowLayout" android:layout_below="@id/detail_stream_thumbnail_window_layout"
android:background="@color/light_background_color" android:background="@color/light_background_color"
android:visibility="gone"> android:visibility="gone">
@ -77,7 +76,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/detailTopView"> android:id="@+id/detailTopView">
<TextView android:id="@+id/detailVideoTitleView" <TextView android:id="@+id/detail_video_title_view"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_weight=".7" android:layout_weight=".7"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -91,7 +90,7 @@
<ImageView <ImageView
android:layout_width="15dp" android:layout_width="15dp"
android:layout_height="30dp" android:layout_height="30dp"
android:id="@+id/toggleDescriptionView" android:id="@+id/toggle_description_view"
android:src="@drawable/arrow_down" android:src="@drawable/arrow_down"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
@ -101,7 +100,7 @@
</LinearLayout> </LinearLayout>
<TextView android:id="@+id/detailViewCountView" <TextView android:id="@+id/detail_view_count_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_views_text_size" android:textSize="@dimen/video_item_detail_views_text_size"
@ -117,14 +116,14 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/detailViewCountView" android:layout_below="@id/detail_view_count_view"
android:id="@+id/detailExtraView" android:id="@+id/detailExtraView"
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_marginRight="12dp" android:layout_marginRight="12dp"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:visibility="gone"> android:visibility="gone">
<TextView android:id="@+id/detailUploadDateView" <TextView android:id="@+id/detail_upload_date_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_upload_date_text_size" android:textSize="@dimen/video_item_detail_upload_date_text_size"
@ -132,12 +131,12 @@
android:text="Upload date" android:text="Upload date"
android:layout_marginTop="3dp" /> android:layout_marginTop="3dp" />
<TextView android:id="@+id/detailDescriptionView" <TextView android:id="@+id/detail_description_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_description_text_size" android:textSize="@dimen/video_item_detail_description_text_size"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_below="@id/detailUploadDateView" android:layout_below="@id/detail_upload_date_view"
android:text="Description............." android:text="Description............."
android:layout_marginTop="3dp" /> android:layout_marginTop="3dp" />
</RelativeLayout> </RelativeLayout>
@ -145,27 +144,27 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/linearLayout" android:id="@+id/stream_info_layout"
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_below="@+id/detailExtraView" android:layout_below="@+id/detailExtraView"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginTop="5dp"> android:layout_marginTop="5dp">
<ImageView android:id="@+id/detailThumbsUpImgView" <ImageView android:id="@+id/detail_thumbs_up_img_view"
android:contentDescription="@string/detail_likes_img_view_description" android:contentDescription="@string/detail_likes_img_view_description"
android:layout_width="@dimen/video_item_detail_like_image_width" android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height" android:layout_height="@dimen/video_item_detail_like_image_height"
android:src="@drawable/thumbs_up" /> android:src="@drawable/thumbs_up" />
<TextView android:id="@+id/detailThumbsUpCountView" <TextView android:id="@+id/detail_thumbs_up_count_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_likes_text_size" android:textSize="@dimen/video_item_detail_likes_text_size"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="200" /> android:text="200" />
<ImageView android:id="@+id/detailThumbsDownImgView" <ImageView android:id="@+id/detail_thumbs_down_img_view"
android:contentDescription="@string/detail_dislikes_img_view_description" android:contentDescription="@string/detail_dislikes_img_view_description"
android:layout_width="@dimen/video_item_detail_like_image_width" android:layout_width="@dimen/video_item_detail_like_image_width"
android:layout_height="@dimen/video_item_detail_like_image_height" android:layout_height="@dimen/video_item_detail_like_image_height"
@ -173,7 +172,7 @@
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"/> android:layout_marginStart="10dp"/>
<TextView android:id="@+id/detailThumbsDownCountView" <TextView android:id="@+id/detail_thumbs_down_count_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/video_item_detail_likes_text_size" android:textSize="@dimen/video_item_detail_likes_text_size"
@ -181,11 +180,15 @@
android:text="100" /> android:text="100" />
</LinearLayout> </LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/detail_uploader_frame"
android:layout_below="@id/stream_info_layout">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/linearLayout" android:id="@+id/detail_uploader_layout"
android:id="@+id/detailUploaderWrapView"
android:layout_marginTop="12dp"> android:layout_marginTop="12dp">
<View <View
@ -193,7 +196,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1px" /> android:layout_height="1px" />
<de.hdodenhof.circleimageview.CircleImageView android:id="@+id/detailUploaderThumbnailView" <de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/detail_uploader_thumbnail_view"
android:contentDescription="@string/detail_uploader_thumbnail_view_description" android:contentDescription="@string/detail_uploader_thumbnail_view_description"
android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_width="@dimen/video_item_detail_uploader_image_size"
android:layout_height="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size"
@ -205,7 +209,7 @@
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"/> android:layout_marginBottom="5dp"/>
<TextView android:id="@+id/detailUploaderView" <TextView android:id="@+id/detail_uploader_view"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textStyle="bold" android:textStyle="bold"
@ -213,8 +217,8 @@
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Uploader" android:text="Uploader"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toRightOf="@+id/detailUploaderThumbnailView" android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
android:layout_toEndOf="@+id/detailUploaderThumbnailView" android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
android:layout_marginLeft="15dp" android:layout_marginLeft="15dp"
android:layout_marginStart="28dp" /> android:layout_marginStart="28dp" />
@ -222,18 +226,26 @@
android:background="#000" android:background="#000"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1px" android:layout_height="1px"
android:layout_below="@id/detailUploaderThumbnailView"/> android:layout_below="@id/detail_uploader_thumbnail_view"/>
</RelativeLayout> </RelativeLayout>
<Button
android:layout_marginTop="13dp"
android:id="@+id/channel_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"/>
</FrameLayout>
<RelativeLayout android:id="@+id/detailNextVideoRootLayout" <RelativeLayout android:id="@+id/detail_next_stream_root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_below="@+id/detailUploaderWrapView" android:layout_below="@+id/detail_uploader_frame"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"> android:layout_marginTop="10dp">
<TextView android:id="@+id/detailNextVideoTitle" <TextView
android:id="@+id/detail_next_stream_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
@ -242,38 +254,25 @@
android:text="@string/next_video_title" android:text="@string/next_video_title"
android:textAllCaps="true" /> android:textAllCaps="true" />
<RelativeLayout android:id="@+id/detailNextVidButtonAndContentLayout" <android.support.v7.widget.RecyclerView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_below="@id/detailNextVideoTitle"> android:id="@+id/detail_next_stream_content"/>
<FrameLayout <TextView android:id="@+id/detail_similar_title"
android:id="@+id/detailNextVideoFrame"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/detailNextVideoButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@id/detailNextVideoFrame"
android:layout_alignBottom="@id/detailNextVideoFrame"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>
<TextView android:id="@+id/detailSimilarTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:textSize="@dimen/video_item_detail_next_text_size" android:textSize="@dimen/video_item_detail_next_text_size"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/similar_videos_btn_text" android:text="@string/similar_videos_btn_text"
android:layout_below="@id/detailNextVidButtonAndContentLayout" android:layout_below="@id/detail_next_stream_content"
android:textAllCaps="true" /> android:textAllCaps="true" />
<LinearLayout <android.support.v7.widget.RecyclerView
android:id="@+id/similar_streams_view"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/similarVideosView" android:layout_below="@id/detail_similar_title"/>
android:layout_below="@id/detailSimilarTitle">
</LinearLayout>
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -4,8 +4,7 @@
android:orientation="vertical" android:layout_width="match_parent" android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/mainBG" android:id="@+id/mainBG"
tools:context=".VideoItemDetailActivity" tools:context=".detail.VideoItemDetailActivity">
tools:showIn="@layout/activity_videoitem_list">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<ProgressBar
android:id="@+id/paginate_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingBottom="10dp"/>
</LinearLayout>

View file

@ -1,8 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout
android:id="@+id/item_main_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
@ -93,3 +99,10 @@
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
<Button
android:id="@+id/item_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"/>
</FrameLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_settings"
app:showAsAction="never"
android:title="@string/settings"/>
<item android:id="@+id/action_show_downloads"
app:showAsAction="never"
android:title="@string/downloads" />
</menu>

View file

@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.schabi.newpipe.ChannelActivity">
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_search"
android:icon="@android:drawable/ic_menu_search"
app:showAsAction="ifRoom"
android:title="@string/search"
app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

View file

@ -35,4 +35,11 @@
<item name="background">@color/video_overlay_color</item> <item name="background">@color/video_overlay_color</item>
</style> </style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources> </resources>

View file

@ -31,11 +31,16 @@
<dimen name="video_item_detail_uploader_image_size">50dp</dimen> <dimen name="video_item_detail_uploader_image_size">50dp</dimen>
<dimen name="video_item_detail_like_image_height">18sp</dimen> <dimen name="video_item_detail_like_image_height">18sp</dimen>
<dimen name="video_item_detail_like_image_width">18sp</dimen> <dimen name="video_item_detail_like_image_width">18sp</dimen>
<dimen name="channel_avatar_size">70dp</dimen>
<dimen name="channel_avatar_halo_size">74dp</dimen>
<!-- Paddings & Margins --> <!-- Paddings & Margins -->
<dimen name="video_item_detail_like_margin">6sp</dimen> <dimen name="video_item_detail_like_margin">6sp</dimen>
<dimen name="video_item_detail_play_fab_margin">20dp</dimen> <dimen name="video_item_detail_play_fab_margin">20dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. --> <!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="app_bar_height">180dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
</resources> </resources>

View file

@ -96,6 +96,7 @@
<string name="could_not_setup_download_menu">Could not setup download menu.</string> <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="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_get_stream">Could not get any stream.</string>
<string name="could_not_load_image">Could not load Image</string>
<!-- error activity --> <!-- error activity -->
<string name="sorry_string">Sorry that should not happen.</string> <string name="sorry_string">Sorry that should not happen.</string>
<string name="guru_meditation" translatable="false">Guru Meditation.</string> <string name="guru_meditation" translatable="false">Guru Meditation.</string>
@ -176,6 +177,97 @@
<!-- Checksum types --> <!-- Checksum types -->
<string name="md5" translatable="false">MD5</string> <string name="md5" translatable="false">MD5</string>
<string name="sha1" translatable="false">SHA1</string> <string name="sha1" translatable="false">SHA1</string>
<string name="title_activity_channel">ChannelActivity</string>
<string name="large_text">
"Material is the metaphor.\n\n"
"A material metaphor is the unifying theory of a rationalized space and a system of motion."
"The material is grounded in tactile reality, inspired by the study of paper and ink, yet "
"technologically advanced and open to imagination and magic.\n"
"Surfaces and edges of the material provide visual cues that are grounded in reality. The "
"use of familiar tactile attributes helps users quickly understand affordances. Yet the "
"flexibility of the material creates new affordances that supercede those in the physical "
"world, without breaking the rules of physics.\n"
"The fundamentals of light, surface, and movement are key to conveying how objects move, "
"interact, and exist in space and in relation to each other. Realistic lighting shows "
"seams, divides space, and indicates moving parts.\n\n"
"Bold, graphic, intentional.\n\n"
"The foundational elements of print based design typography, grids, space, scale, color, "
"and use of imagery guide visual treatments. These elements do far more than please the "
"eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
"imagery, large scale typography, and intentional white space create a bold and graphic "
"interface that immerse the user in the experience.\n"
"An emphasis on user actions makes core functionality immediately apparent and provides "
"waypoints for the user.\n\n"
"Motion provides meaning.\n\n"
"Motion respects and reinforces the user as the prime mover. Primary user actions are "
"inflection points that initiate motion, transforming the whole design.\n"
"All action takes place in a single environment. Objects are presented to the user without "
"breaking the continuity of experience even as they transform and reorganize.\n"
"Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
"Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
"3D world.\n\n"
"The material environment is a 3D space, which means all objects have x, y, and z "
"dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the "
"positive z-axis extending towards the viewer. Every sheet of material occupies a single "
"position along the z-axis and has a standard 1dp thickness.\n"
"On the web, the z-axis is used for layering and not for perspective. The 3D world is "
"emulated by manipulating the y-axis.\n\n"
"Light and shadow.\n\n"
"Within the material environment, virtual lights illuminate the scene. Key lights create "
"directional shadows, while ambient light creates soft shadows from all angles.\n"
"Shadows in the material environment are cast by these two light sources. In Android "
"development, shadows occur when light sources are blocked by sheets of material at "
"various positions along the z-axis. On the web, shadows are depicted by manipulating the "
"y-axis only. The following example shows the card with a height of 6dp.\n\n"
"Resting elevation.\n\n"
"All material objects, regardless of size, have a resting elevation, or default elevation "
"that does not change. If an object changes elevation, it should return to its resting "
"elevation as soon as possible.\n\n"
"Component elevations.\n\n"
"The resting elevation for a component type is consistent across apps (e.g., FAB elevation "
"does not vary from 6dp in one app to 16dp in another app).\n"
"Components may have different resting elevations across platforms, depending on the depth "
"of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n"
"Responsive elevation and dynamic elevation offsets.\n\n"
"Some component types have responsive elevation, meaning they change elevation in response "
"to user input (e.g., normal, focused, and pressed) or system events. These elevation "
"changes are consistently implemented using dynamic elevation offsets.\n"
"Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
"to the components resting state. They ensure that elevation changes are consistent "
"across actions and component types. For example, all components that lift on press have "
"the same elevation change relative to their resting elevation.\n"
"Once the input event is completed or cancelled, the component will return to its resting "
"elevation.\n\n"
"Avoiding elevation interference.\n\n"
"Components with responsive elevations may encounter other components as they move between "
"their resting elevations and dynamic elevation offsets. Because material cannot pass "
"through other material, components avoid interfering with one another any number of ways, "
"whether on a per component basis or using the entire app layout.\n"
"On a component level, components can move or be removed before they cause interference. "
"For example, a floating action button (FAB) can disappear or move off screen before a "
"user picks up a card, or it can move if a snackbar appears.\n"
"On the layout level, design your app layout to minimize opportunities for interference. "
"For example, position the FAB to one side of stream of a cards so the FAB wont interfere "
"when a user tries to pick up one of cards.\n\n"
</string>
<string name="action_settings">Settings</string>
<!-- End of GigaGet's Strings --> <!-- End of GigaGet's Strings -->

View file

@ -1,7 +1,6 @@
<resources> <resources>
<style name="RootTheme" parent="android:Theme.Holo"> <style name="RootTheme" parent="android:Theme.Holo"></style>
</style>
<style name="PlayerTheme" parent="@style/RootTheme"> <style name="PlayerTheme" parent="@style/RootTheme">
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
@ -72,4 +71,13 @@
<item name="colorAccent">@color/light_youtube_accent_color</item> <item name="colorAccent">@color/light_youtube_accent_color</item>
</style> </style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources> </resources>

View file

@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.1.2' classpath 'com.android.tools.build:gradle:2.1.3'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View file

@ -1,6 +1,6 @@
#Mon Aug 10 20:55:41 CEST 2015 #Sat Aug 20 00:40:54 CEST 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip