add initial support for NavStack

This commit is contained in:
Christian Schabesberger 2017-02-18 21:59:48 +01:00
parent e9c20ac8b0
commit 7a6b5dd5b7
16 changed files with 267 additions and 81 deletions

View File

@ -156,6 +156,7 @@
<activity
android:name=".ChannelActivity"
android:label="@string/title_activity_channel"
android:launchMode="singleTask"
android:theme="@style/AppTheme.NoActionBar" />
<activity

View File

@ -36,6 +36,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack;
import java.io.IOException;
import java.util.Objects;
@ -62,15 +63,9 @@ import static android.os.Build.VERSION.SDK_INT;
*/
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;
@ -82,21 +77,24 @@ public class ChannelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//since we set themeing we have to set translucent statusBar by hand
if (PreferenceManager.getDefaultSharedPreferences(this)
.getString("theme", getResources().getString(R.string.light_theme_title)).
equals(getResources().getString(R.string.dark_theme_title))) {
setTheme(R.style.DarkTheme_NoActionBar);
}
super.onCreate(savedInstanceState);
setTranslucentStatusBar(getWindow());
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);
channelUrl = i.getStringExtra(NavStack.URL);
serviceId = i.getIntExtra(NavStack.SERVICE_ID, -1);
setTranslucentStatusBar(getWindow());
infoListAdapter = new InfoListAdapter(this, rootView);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.channel_streams_view);
@ -107,11 +105,8 @@ public class ChannelActivity extends AppCompatActivity {
new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(String url, int serviceId) {
Intent detailIntent = new Intent(ChannelActivity.this, VideoItemDetailActivity.class);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
detailIntent.putExtra(
VideoItemDetailFragment.STREAMING_SERVICE, serviceId);
startActivity(detailIntent);
NavStack.getInstance()
.openDetailActivity(ChannelActivity.this, url, serviceId);
}
});
@ -141,8 +136,6 @@ public class ChannelActivity extends AppCompatActivity {
requestData(false);
}
private void updateUi(final ChannelInfo info) {
CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
@ -312,4 +305,14 @@ public class ChannelActivity extends AppCompatActivity {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
@Override
public void onBackPressed() {
try {
NavStack.getInstance()
.navBack(this);
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
}

View File

@ -86,4 +86,9 @@ public class MainActivity extends ThemableActivity {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onBackPressed() {
//ignore back
}
}

View File

@ -1,5 +1,6 @@
package org.schabi.newpipe.detail;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
@ -16,10 +17,14 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.ThemableActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack;
import java.util.Collection;
import java.util.HashSet;
import static android.os.Build.VERSION.SDK_INT;
/**
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
@ -81,8 +86,8 @@ public class VideoItemDetailActivity extends ThemableActivity {
if (savedInstanceState == null) {
handleIntent(getIntent());
} else {
videoUrl = savedInstanceState.getString(VideoItemDetailFragment.VIDEO_URL);
currentStreamingService = savedInstanceState.getInt(VideoItemDetailFragment.STREAMING_SERVICE);
videoUrl = savedInstanceState.getString(NavStack.URL);
currentStreamingService = savedInstanceState.getInt(NavStack.SERVICE_ID);
addFragment(savedInstanceState);
}
}
@ -114,12 +119,12 @@ public class VideoItemDetailActivity extends ThemableActivity {
currentStreamingService = getServiceIdByUrl(videoUrl);
} else {
//this is if the video was called through another NewPipe activity
videoUrl = intent.getStringExtra(VideoItemDetailFragment.VIDEO_URL);
currentStreamingService = intent.getIntExtra(VideoItemDetailFragment.STREAMING_SERVICE, -1);
videoUrl = intent.getStringExtra(NavStack.URL);
currentStreamingService = intent.getIntExtra(NavStack.SERVICE_ID, -1);
}
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, autoplay);
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingService);
arguments.putString(NavStack.URL, videoUrl);
arguments.putInt(NavStack.SERVICE_ID, currentStreamingService);
addFragment(arguments);
}
@ -142,8 +147,8 @@ public class VideoItemDetailActivity extends ThemableActivity {
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
outState.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingService);
outState.putString(NavStack.URL, videoUrl);
outState.putInt(NavStack.SERVICE_ID, currentStreamingService);
outState.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false);
}
@ -168,6 +173,16 @@ public class VideoItemDetailActivity extends ThemableActivity {
}
}
@Override
public void onBackPressed() {
try {
NavStack.getInstance()
.navBack(this);
} catch (Exception e) {
ErrorActivity.reportUiError(this, e);
}
}
/**
* Retrieves all Strings which look remotely like URLs from a text.
* Used if NewPipe was called through share menu.
@ -224,7 +239,7 @@ public class VideoItemDetailActivity extends ThemableActivity {
StreamingService[] serviceList = NewPipe.getServices();
int service = -1;
for (int i = 0; i < serviceList.length; i++) {
if (serviceList[i].getUrlIdHandlerInstance().acceptUrl(videoUrl)) {
if (serviceList[i].getStreamUrlIdHandlerInstance().acceptUrl(videoUrl)) {
service = i;
//videoExtractor = ServiceList.getService(i).getExtractorInstance();
break;

View File

@ -40,7 +40,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.ChannelActivity;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.Localization;
import org.schabi.newpipe.R;
@ -51,7 +50,6 @@ import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.BackgroundPlayer;
@ -59,6 +57,8 @@ import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.report.ErrorActivity;
import java.util.Vector;
import org.schabi.newpipe.util.NavStack;
import org.schabi.newpipe.util.PermissionHelper;
import static android.app.Activity.RESULT_OK;
@ -92,8 +92,6 @@ public class VideoItemDetailFragment extends Fragment {
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String VIDEO_URL = "video_url";
public static final String STREAMING_SERVICE = "streaming_service";
public static final String AUTO_PLAY = "auto_play";
private AppCompatActivity activity;
@ -289,10 +287,8 @@ public class VideoItemDetailFragment extends Fragment {
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);
NavStack.getInstance()
.openChannelActivity(getActivity(), info.channel_url, info.service_id);
}
});
} else {
@ -544,7 +540,8 @@ public class VideoItemDetailFragment extends Fragment {
new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(String url, int serviceId) {
openStreamUrl(url);
NavStack.getInstance()
.openDetailActivity(getContext(), url, serviceId);
}
});
}
@ -679,8 +676,8 @@ public class VideoItemDetailFragment extends Fragment {
// then we must not try to access objects of this fragment.
// Otherwise the applications would crash.
if(backgroundButton != null) {
streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
String videoUrl = getArguments().getString(VIDEO_URL);
streamingServiceId = getArguments().getInt(NavStack.SERVICE_ID);
String videoUrl = getArguments().getString(NavStack.URL);
StreamInfoWorker siw = StreamInfoWorker.getInstance();
siw.search(streamingServiceId, videoUrl, getActivity());
@ -813,21 +810,13 @@ public class VideoItemDetailFragment extends Fragment {
stringResource, Toast.LENGTH_LONG).show();
}
private void openStreamUrl(String url) {
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
detailIntent.putExtra(
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
activity.startActivity(detailIntent);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case RECAPTCHA_REQUEST:
if (resultCode == RESULT_OK) {
String videoUrl = getArguments().getString(VIDEO_URL);
String videoUrl = getArguments().getString(NavStack.URL);
StreamInfoWorker siw = StreamInfoWorker.getInstance();
siw.search(streamingServiceId, videoUrl, getActivity());
} else {

View File

@ -34,25 +34,22 @@ public class NewPipe {
private static final String TAG = NewPipe.class.toString();
private static final StreamingService[] serviceList = {
new YoutubeService(0)
};
private static Downloader downloader = null;
public static StreamingService[] getServices() {
return serviceList;
return ServiceList.serviceList;
}
public static StreamingService getService(int serviceId)throws ExtractionException {
for(StreamingService s : serviceList) {
for(StreamingService s : ServiceList.serviceList) {
if(s.getServiceId() == serviceId) {
return s;
}
}
throw new ExtractionException("Service not known: " + Integer.toString(serviceId));
return null;
}
public static StreamingService getService(String serviceName) throws ExtractionException {
return serviceList[getIdOfService(serviceName)];
return ServiceList.serviceList[getIdOfService(serviceName)];
}
public static String getNameOfService(int id) {
try {
@ -63,13 +60,13 @@ public class NewPipe {
return "";
}
}
public static int getIdOfService(String serviceName) throws ExtractionException {
for(int i = 0; i < serviceList.length; i++) {
if(serviceList[i].getServiceInfo().name.equals(serviceName)) {
public static int getIdOfService(String serviceName) {
for(int i = 0; i < ServiceList.serviceList.length; i++) {
if(ServiceList.serviceList[i].getServiceInfo().name.equals(serviceName)) {
return i;
}
}
throw new ExtractionException("Error: Service " + serviceName + " not known.");
return -1;
}
public static void init(Downloader d) {
@ -79,4 +76,13 @@ public class NewPipe {
public static Downloader getDownloader() {
return downloader;
}
public static StreamingService getServiceByUrl(String url) {
for(StreamingService s : ServiceList.serviceList) {
if(s.getLinkTypeByUrl(url) != StreamingService.LinkType.NONE) {
return s;
}
}
return null;
}
}

View File

@ -0,0 +1,13 @@
package org.schabi.newpipe.extractor;
import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
/**
* Created by the-scrabi on 18.02.17.
*/
class ServiceList {
public static final StreamingService[] serviceList = {
new YoutubeService(0)
};
}

View File

@ -32,6 +32,13 @@ public abstract class StreamingService {
public String name = "";
}
public enum LinkType {
NONE,
STREAM,
CHANNEL,
PLAYLIST
}
private int serviceId;
public StreamingService(int id) {
@ -43,7 +50,7 @@ public abstract class StreamingService {
public abstract StreamExtractor getExtractorInstance(String url)
throws IOException, ExtractionException;
public abstract SearchEngine getSearchEngineInstance();
public abstract UrlIdHandler getUrlIdHandlerInstance();
public abstract UrlIdHandler getStreamUrlIdHandlerInstance();
public abstract UrlIdHandler getChannelUrlIdHandlerInstance();
public abstract ChannelExtractor getChannelExtractorInstance(String url, int page)
throws ExtractionException, IOException;
@ -52,4 +59,20 @@ public abstract class StreamingService {
public final int getServiceId() {
return serviceId;
}
/**
* figure out where the link is pointing to (a channel, video, playlist, etc.)
*/
public final LinkType getLinkTypeByUrl(String url) {
UrlIdHandler sH = getStreamUrlIdHandlerInstance();
UrlIdHandler cH = getChannelUrlIdHandlerInstance();
if(sH.acceptUrl(url)) {
return LinkType.STREAM;
} else if(cH.acceptUrl(url)) {
return LinkType.CHANNEL;
} else {
return LinkType.NONE;
}
}
}

View File

@ -23,6 +23,7 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
*/
public interface UrlIdHandler {
String getUrl(String videoId);
String getId(String siteUrl) throws ParsingException;
String cleanUrl(String siteUrl) throws ParsingException;

View File

@ -56,11 +56,11 @@ public class YoutubeService extends StreamingService {
}
@Override
public SearchEngine getSearchEngineInstance() {
return new YoutubeSearchEngine(getUrlIdHandlerInstance(), getServiceId());
return new YoutubeSearchEngine(getStreamUrlIdHandlerInstance(), getServiceId());
}
@Override
public UrlIdHandler getUrlIdHandlerInstance() {
public UrlIdHandler getStreamUrlIdHandlerInstance() {
return YoutubeStreamUrlIdHandler.getInstance();
}

View File

@ -150,7 +150,17 @@ public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
@Override
public boolean acceptUrl(String videoUrl) {
videoUrl = videoUrl.toLowerCase();
return videoUrl.contains("youtube") ||
videoUrl.contains("youtu.be");
if(videoUrl.contains("youtube") ||
videoUrl.contains("youtu.be")) {
// bad programming I know
try {
getId(videoUrl);
return true;
} catch (Exception e) {
return false;
}
} else {
return false;
}
}
}

View File

@ -52,7 +52,7 @@ public class StreamInfoItemCollector extends InfoItemCollector {
throw new ParsingException("Error: UrlIdHandler not set");
} else if (!resultItem.webpage_url.isEmpty()) {
resultItem.id = NewPipe.getService(getServiceId())
.getUrlIdHandlerInstance()
.getStreamUrlIdHandlerInstance()
.getId(resultItem.webpage_url);
}
resultItem.title = extractor.getTitle();

View File

@ -27,6 +27,7 @@ import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.util.NavStack;
import java.io.IOException;
import java.util.Arrays;
@ -355,8 +356,8 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
//build intent to return to video, on tapping notification
Intent openDetailViewIntent = new Intent(getApplicationContext(),
VideoItemDetailActivity.class);
openDetailViewIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, serviceId);
openDetailViewIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, webUrl);
openDetailViewIntent.putExtra(NavStack.SERVICE_ID, serviceId);
openDetailViewIntent.putExtra(NavStack.URL, webUrl);
openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID,
openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT);

View File

@ -16,6 +16,7 @@ import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@ -161,6 +162,10 @@ public class ErrorActivity extends ThemableActivity {
private TextView infoView;
private TextView errorMessageView;
public static void reportUiError(final AppCompatActivity activity, final Throwable el) {
reportError(activity, el, activity.getClass(), null, ErrorInfo.make(UI_ERROR, "none", "", R.string.app_ui_crash));
}
public static void reportError(final Context context, final List<Throwable> el,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {

View File

@ -30,6 +30,7 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.util.NavStack;
import java.util.EnumSet;
@ -224,13 +225,15 @@ public class SearchInfoItemFragment extends Fragment {
new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(String url, int serviceId) {
startDetailActivity(url);
NavStack.getInstance()
.openDetailActivity(getContext(), url, serviceId);
}
});
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(String url, int serviceId) {
startChannelActivity(url, serviceId);
NavStack.getInstance()
.openChannelActivity(getContext(), url, serviceId);
}
});
recyclerView.setAdapter(infoListAdapter);
@ -257,20 +260,6 @@ public class SearchInfoItemFragment extends Fragment {
return view;
}
private void startDetailActivity(String url) {
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
getActivity().startActivity(i);
}
private void startChannelActivity(String url, int serviceId) {
Intent i = new Intent(getActivity(), ChannelActivity.class);
i.putExtra(ChannelActivity.CHANNEL_URL, url);
i.putExtra(ChannelActivity.SERVICE_ID, serviceId);
startActivity(i);
}
@Override
public void onStart() {
super.onStart();

View File

@ -0,0 +1,125 @@
package org.schabi.newpipe.util;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import org.schabi.newpipe.ChannelActivity;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.detail.VideoItemDetailFragment;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import java.util.Stack;
/**
* Created by Christian Schabesberger on 16.02.17.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* NavStack.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 helps to navigate within the app
* IMPORTAND: the top of the stack is the current activity !!!
*/
public class NavStack {
private static final String TAG = NavStack.class.toString();
public static final String SERVICE_ID = "service_id";
public static final String URL = "url";
private enum ActivityId {
CHANNEL,
DETAIL
}
private class NavEntry {
public NavEntry(String url, int serviceId) {
this.url = url;
this.serviceId = serviceId;
}
public String url;
public int serviceId;
}
private static NavStack instance = new NavStack();
private Stack<NavEntry> stack = new Stack<NavEntry>();
private NavStack() {
}
public static NavStack getInstance() {
return instance;
}
private void addEntry(String url, Class ac, int serviceId) {
stack.push(new NavEntry(url, serviceId));
}
public void navBack(Activity activity) throws Exception {
stack.pop(); // remove curent activty, since we dont want to return to itself
if(stack.size() == 0) {
openMainActivity(activity); // if no more page is on the stack this means we are home
} else {
NavEntry entry = stack.pop(); // this element will reapear, since by calling the old page
// this element will be pushed on top again
try {
StreamingService service = NewPipe.getService(entry.serviceId);
switch (service.getLinkTypeByUrl(entry.url)) {
case STREAM:
openDetailActivity(activity, entry.url, entry.serviceId);
break;
case CHANNEL:
openChannelActivity(activity, entry.url, entry.serviceId);
break;
case NONE:
throw new Exception("Url not known to service. service="
+ Integer.toString(entry.serviceId) + " url=" + entry.url);
default:
openMainActivity(activity);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void openChannelActivity(Context context, String url, int serviceId) {
openActivity(context, url, serviceId, ChannelActivity.class);
}
public void openDetailActivity(Context context, String url, int serviceId) {
openActivity(context, url, serviceId, VideoItemDetailActivity.class);
}
private void openActivity(Context context, String url, int serviceId, Class acitivtyClass) {
stack.push(new NavEntry(url, serviceId));
Intent i = new Intent(context, acitivtyClass);
i.putExtra(SERVICE_ID, serviceId);
i.putExtra(URL, url);
context.startActivity(i);
}
private void openMainActivity(Activity a) {
Intent i = new Intent(a, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(a, i);
}
}