diff --git a/app/build.gradle b/app/build.gradle index 62cd8dc7b..dc301755c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 25 - versionCode 24 - versionName "0.8.10" + versionCode 25 + versionName "0.8.11" } buildTypes { release { @@ -32,10 +32,10 @@ android { dependencies { testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:25.1.1' - compile 'com.android.support:support-v4:25.1.1' - compile 'com.android.support:design:25.1.1' - compile 'com.android.support:recyclerview-v7:25.1.1' + compile 'com.android.support:appcompat-v7:25.2.0' + compile 'com.android.support:support-v4:25.2.0' + compile 'com.android.support:design:25.2.0' + compile 'com.android.support:recyclerview-v7:25.2.0' compile 'org.jsoup:jsoup:1.8.3' compile 'org.mozilla:rhino:1.7.7' compile 'info.guardianproject.netcipher:netcipher:1.2' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e070a62d2..bc050f2ce 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,6 @@ android:logo="@mipmap/ic_launcher" android:theme="@style/AppTheme" tools:ignore="AllowBackup"> - @@ -34,53 +33,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:launchMode="singleTop" + android:theme="@style/FilePickerTheme"/> - + android:launchMode="singleTask" /> @@ -170,8 +117,64 @@ android:grantUriPermissions="true"> + android:resource="@xml/provider_paths" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java index c8e0ad4a8..37ef28a13 100644 --- a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java @@ -1,31 +1,24 @@ package org.schabi.newpipe; -import android.annotation.TargetApi; -import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.preference.PreferenceManager; -import android.support.design.widget.CollapsingToolbarLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; -import android.view.Window; -import android.view.WindowManager; +import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; +import android.widget.TextView; 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.NewPipe; import org.schabi.newpipe.extractor.StreamingService; @@ -36,13 +29,12 @@ 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.settings.SettingsActivity; import org.schabi.newpipe.util.NavStack; - import java.io.IOException; -import java.util.Objects; - -import static android.os.Build.VERSION.SDK; import static android.os.Build.VERSION.SDK_INT; +import org.schabi.newpipe.util.ThemeHelper; + /** * Copyright (C) Christian Schabesberger 2016 @@ -75,38 +67,36 @@ public class ChannelActivity extends AppCompatActivity { private ImageLoader imageLoader = ImageLoader.getInstance(); private InfoListAdapter infoListAdapter = null; + private String subS = ""; + + ProgressBar progressBar = null; + ImageView channelBanner = null; + ImageView avatarView = null; + TextView titleView = null; + TextView subscirberView = null; + Button subscriberButton = null; + View subscriberLayout = null; + + View header = null; + @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); - } - setTranslucentStatusBar(getWindow()); - + ThemeHelper.setTheme(this, true); setContentView(R.layout.activity_channel); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - rootView = findViewById(R.id.rootView); - setSupportActionBar(toolbar); - if(savedInstanceState == null) { - Intent i = getIntent(); - channelUrl = i.getStringExtra(NavStack.URL); - serviceId = i.getIntExtra(NavStack.SERVICE_ID, -1); - } else { - channelUrl = savedInstanceState.getString(NavStack.URL); - serviceId = savedInstanceState.getInt(NavStack.SERVICE_ID); - NavStack.getInstance() - .restoreSavedInstanceState(savedInstanceState); - } + rootView = findViewById(android.R.id.content); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setDisplayShowTitleEnabled(true); infoListAdapter = new InfoListAdapter(this, rootView); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.channel_streams_view); final LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); + header = getLayoutInflater().inflate(R.layout.channel_header, recyclerView, false); + infoListAdapter.setHeader(header); + infoListAdapter.setFooter( + getLayoutInflater().inflate(R.layout.pignate_footer, recyclerView, false)); recyclerView.setAdapter(infoListAdapter); infoListAdapter.setOnStreamInfoItemSelectedListener( new InfoItemBuilder.OnInfoItemSelectedListener() { @@ -140,6 +130,36 @@ public class ChannelActivity extends AppCompatActivity { } }); + subS = getString(R.string.subscriber); + + progressBar = (ProgressBar) findViewById(R.id.progressBar); + channelBanner = (ImageView) header.findViewById(R.id.channel_banner_image); + avatarView = (ImageView) header.findViewById(R.id.channel_avatar_view); + titleView = (TextView) header.findViewById(R.id.channel_title_view); + subscirberView = (TextView) header.findViewById(R.id.channel_subscriber_view); + subscriberButton = (Button) header.findViewById(R.id.channel_subscribe_button); + subscriberLayout = header.findViewById(R.id.channel_subscriber_layout); + + if(savedInstanceState == null) { + handleIntent(getIntent()); + } else { + channelUrl = savedInstanceState.getString(NavStack.URL); + serviceId = savedInstanceState.getInt(NavStack.SERVICE_ID); + NavStack.getInstance() + .restoreSavedInstanceState(savedInstanceState); + } + + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private void handleIntent(Intent i) { + channelUrl = i.getStringExtra(NavStack.URL); + serviceId = i.getIntExtra(NavStack.SERVICE_ID, -1); requestData(false); } @@ -153,33 +173,36 @@ public class ChannelActivity extends AppCompatActivity { } 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); - final 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); - + findViewById(R.id.channel_header_layout).setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); if(info.channel_name != null && !info.channel_name.isEmpty()) { - ctl.setTitle(info.channel_name); + getSupportActionBar().setTitle(info.channel_name); + titleView.setText(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)); + 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.subscriberCount != -1) { + subscirberView.setText(buildSubscriberString(info.subscriberCount)); + } + + if((info.feed_url != null && !info.feed_url.isEmpty()) || + (info.subscriberCount != -1)) { + subscriberLayout.setVisibility(View.VISIBLE); + } + if(info.feed_url != null && !info.feed_url.isEmpty()) { - feedButton.setOnClickListener(new View.OnClickListener() { + subscriberButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d(TAG, info.feed_url); @@ -188,8 +211,9 @@ public class ChannelActivity extends AppCompatActivity { } }); } else { - feedButton.setVisibility(View.GONE); + subscriberButton.setVisibility(View.INVISIBLE); } + } private void addVideos(final ChannelInfo info) { @@ -209,6 +233,22 @@ public class ChannelActivity extends AppCompatActivity { private void requestData(final boolean onlyVideos) { // start processing isLoading = true; + + if(!onlyVideos) { + //delete already displayed content + progressBar.setVisibility(View.VISIBLE); + infoListAdapter.clearSteamItemList(); + pageNumber = 0; + subscriberLayout.setVisibility(View.GONE); + titleView.setText(""); + getSupportActionBar().setTitle(""); + if (SDK_INT >= 21) { + channelBanner.setImageDrawable(getDrawable(R.drawable.channel_banner)); + avatarView.setImageDrawable(getDrawable(R.drawable.buddy)); + } + infoListAdapter.showFooter(false); + } + Thread channelExtractorThread = new Thread(new Runnable() { Handler h = new Handler(); @@ -229,8 +269,12 @@ public class ChannelActivity extends AppCompatActivity { isLoading = false; if(!onlyVideos) { updateUi(info); + infoListAdapter.showFooter(true); } hasNextPage = info.hasNextPage; + if(!hasNextPage) { + infoListAdapter.showFooter(false); + } addVideos(info); } }); @@ -297,30 +341,6 @@ public class ChannelActivity extends AppCompatActivity { channelExtractorThread.start(); } - - // fix transparent statusbar fuckup (fuck google why can't they just leave something that worked - // as it is, and everyone gets happy) - public static void setTranslucentStatusBar(Window window) { - if (window == null) return; - int sdkInt = Build.VERSION.SDK_INT; - if (sdkInt >= Build.VERSION_CODES.LOLLIPOP) { - setTranslucentStatusBarLollipop(window); - } else if (sdkInt >= Build.VERSION_CODES.KITKAT) { - setTranslucentStatusBarKiKat(window); - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private static void setTranslucentStatusBarLollipop(Window window) { - window.setStatusBarColor( - ContextCompat.getColor(window.getContext(), android.R.color.transparent)); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private static void setTranslucentStatusBarKiKat(Window window) { - window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - } - @Override public void onBackPressed() { try { @@ -331,4 +351,54 @@ public class ChannelActivity extends AppCompatActivity { } } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.menu_channel, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + switch(item.getItemId()) { + case R.id.action_settings: { + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); + return true; + } + case R.id.menu_item_openInBrowser: { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(channelUrl)); + + startActivity(Intent.createChooser(intent, getString(R.string.choose_browser))); + } + case R.id.menu_item_share: + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, channelUrl); + intent.setType("text/plain"); + startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); + case android.R.id.home: + NavStack.getInstance().openMainActivity(this); + default: + return super.onOptionsItemSelected(item); + } + } + + private String buildSubscriberString(long count) { + String out = ""; + if(count >= 1000000000){ + out += Long.toString((count/1000000000)%1000)+"."; + } + if(count>=1000000){ + out += Long.toString((count/1000000)%1000) + "."; + } + if(count>=1000){ + out += Long.toString((count/1000)%1000)+"."; + } + out += Long.toString(count%1000) + " " + subS; + return out; + } } diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index b7375fd16..eb9fd313c 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -2,19 +2,17 @@ package org.schabi.newpipe; import android.content.Intent; import android.media.AudioManager; +import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.NavUtils; -import android.os.Bundle; -import android.util.Log; +import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; - import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.PermissionHelper; +import org.schabi.newpipe.util.ThemeHelper; /** * Created by Christian Schabesberger on 02.08.16. @@ -36,13 +34,15 @@ import org.schabi.newpipe.util.PermissionHelper; * along with NewPipe. If not, see . */ -public class MainActivity extends ThemableActivity { + +public class MainActivity extends AppCompatActivity { private Fragment mainFragment = null; private static final String TAG = MainActivity.class.toString(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeHelper.setTheme(this, true); setContentView(R.layout.activity_main); setVolumeControlStream(AudioManager.STREAM_MUSIC); mainFragment = getSupportFragmentManager() @@ -53,7 +53,6 @@ public class MainActivity extends ThemableActivity { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main_menu, menu); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java new file mode 100644 index 000000000..b87d97683 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -0,0 +1,158 @@ +package org.schabi.newpipe; + +import android.app.Activity; +import android.content.Intent; +import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +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 org.schabi.newpipe.util.NavStack; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Copyright (C) Christian Schabesberger 2017 + * RouterActivity .java is part of NewPipe. + * + * OpenHitboxStreams 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. + * + * OpenHitboxStreams 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 OpenHitboxStreams. If not, see . + */ + +/** + * This Acitivty is designed to route share/open intents to the specified service, and + * to the part of the service which can handle the url. + */ +public class RouterActivity extends Activity { + private static final String TAG = RouterActivity.class.toString(); + + /** + * Removes invisible separators (\p{Z}) and punctuation characters including + * brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for + * more details. + */ + private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + handleIntent(getIntent()); + finish(); + } + + + private static String removeHeadingGibberish(final String input) { + int start = 0; + for (int i = input.indexOf("://") - 1; i >= 0; i--) { + if (!input.substring(i, i + 1).matches("\\p{L}")) { + start = i + 1; + break; + } + } + return input.substring(start, input.length()); + } + + private static String trim(final String input) { + if (input == null || input.length() < 1) { + return input; + } else { + String output = input; + while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) { + output = output.substring(1); + } + while (output.length() > 0 + && output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) { + output = output.substring(0, output.length() - 1); + } + return output; + } + } + + /** + * Retrieves all Strings which look remotely like URLs from a text. + * Used if NewPipe was called through share menu. + * + * @param sharedText text to scan for URLs. + * @return potential URLs + */ + private String[] getUris(final String sharedText) { + final Collection result = new HashSet<>(); + if (sharedText != null) { + final String[] array = sharedText.split("\\p{Space}"); + for (String s : array) { + s = trim(s); + if (s.length() != 0) { + if (s.matches(".+://.+")) { + result.add(removeHeadingGibberish(s)); + } else if (s.matches(".+\\..+")) { + result.add("http://" + s); + } + } + } + } + return result.toArray(new String[result.size()]); + } + + private void handleIntent(Intent intent) { + String videoUrl = ""; + StreamingService service = null; + + // first gather data and find service + if (intent.getData() != null) { + // this means the video was called though another app + videoUrl = intent.getData().toString(); + } else if(intent.getStringExtra(Intent.EXTRA_TEXT) != null) { + //this means that vidoe was called through share menu + String extraText = intent.getStringExtra(Intent.EXTRA_TEXT); + videoUrl = getUris(extraText)[0]; + } + + service = NewPipe.getServiceByUrl(videoUrl); + if(service == null) { + Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG) + .show(); + return; + } else { + Intent callIntent = new Intent(); + switch(service.getLinkTypeByUrl(videoUrl)) { + case CHANNEL: + callIntent.setClass(this, ChannelActivity.class); + break; + case STREAM: + callIntent.setClass(this, VideoItemDetailActivity.class); + callIntent.putExtra(VideoItemDetailFragment.AUTO_PLAY, + PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean( + getString(R.string.autoplay_through_intent_key), false)); + break; + case PLAYLIST: + Log.e(TAG, "NOT YET DEFINED"); + break; + default: + Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG) + .show(); + return; + } + + callIntent.putExtra(NavStack.URL, videoUrl); + callIntent.putExtra(NavStack.SERVICE_ID, service.getServiceId()); + startActivity(callIntent); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java index 261e219cf..97935edf4 100644 --- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java +++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java @@ -1,29 +1,17 @@ package org.schabi.newpipe.detail; -import android.content.Context; import android.content.Intent; import android.media.AudioManager; import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v4.app.NavUtils; +import android.support.v7.app.AppCompatActivity; import android.util.Log; -import android.view.Menu; import android.view.MenuItem; -import android.widget.Toast; import org.schabi.newpipe.App; -import org.schabi.newpipe.MainActivity; 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; +import org.schabi.newpipe.util.ThemeHelper; /** @@ -44,15 +32,7 @@ import static android.os.Build.VERSION.SDK_INT; * along with NewPipe. If not, see . */ -public class VideoItemDetailActivity extends ThemableActivity { - - /** - * Removes invisible separators (\p{Z}) and punctuation characters including - * brackets (\p{P}). See http://www.regular-expressions.info/unicode.html for - * more details. - */ - private final static String REGEX_REMOVE_FROM_URL = "[\\p{Z}\\p{P}]"; - +public class VideoItemDetailActivity extends AppCompatActivity { private static final String TAG = VideoItemDetailActivity.class.toString(); private VideoItemDetailFragment fragment; @@ -62,6 +42,7 @@ public class VideoItemDetailActivity extends ThemableActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeHelper.setTheme(this, true); setContentView(R.layout.activity_videoitem_detail); setVolumeControlStream(AudioManager.STREAM_MUSIC); // Show the Up button in the action bar. @@ -104,27 +85,13 @@ public class VideoItemDetailActivity extends ThemableActivity { private void handleIntent(Intent intent) { Bundle arguments = new Bundle(); boolean autoplay = false; - if (intent.getData() != null) { - // this means the video was called though another app - videoUrl = intent.getData().toString(); - currentStreamingService = getServiceIdByUrl(videoUrl); - if(currentStreamingService == -1) { - Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG) - .show(); - } - autoplay = PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(getString(R.string.autoplay_through_intent_key), false); - } else if(intent.getStringExtra(Intent.EXTRA_TEXT) != null) { - //this means that vidoe was called through share menu - String extraText = intent.getStringExtra(Intent.EXTRA_TEXT); - videoUrl = getUris(extraText)[0]; - currentStreamingService = getServiceIdByUrl(videoUrl); - } else { - //this is if the video was called through another NewPipe activity - videoUrl = intent.getStringExtra(NavStack.URL); - currentStreamingService = intent.getIntExtra(NavStack.SERVICE_ID, -1); + + videoUrl = intent.getStringExtra(NavStack.URL); + currentStreamingService = intent.getIntExtra(NavStack.SERVICE_ID, -1); + if(intent.hasExtra(VideoItemDetailFragment.AUTO_PLAY)) { + arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, + intent.getBooleanExtra(VideoItemDetailFragment.AUTO_PLAY, false)); } - arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, autoplay); arguments.putString(NavStack.URL, videoUrl); arguments.putInt(NavStack.SERVICE_ID, currentStreamingService); addFragment(arguments); @@ -185,69 +152,4 @@ public class VideoItemDetailActivity extends ThemableActivity { ErrorActivity.reportUiError(this, e); } } - - /** - * Retrieves all Strings which look remotely like URLs from a text. - * Used if NewPipe was called through share menu. - * - * @param sharedText text to scan for URLs. - * @return potential URLs - */ - private String[] getUris(final String sharedText) { - final Collection result = new HashSet<>(); - if (sharedText != null) { - final String[] array = sharedText.split("\\p{Space}"); - for (String s : array) { - s = trim(s); - if (s.length() != 0) { - if (s.matches(".+://.+")) { - result.add(removeHeadingGibberish(s)); - } else if (s.matches(".+\\..+")) { - result.add("http://" + s); - } - } - } - } - return result.toArray(new String[result.size()]); - } - - private static String removeHeadingGibberish(final String input) { - int start = 0; - for (int i = input.indexOf("://") - 1; i >= 0; i--) { - if (!input.substring(i, i + 1).matches("\\p{L}")) { - start = i + 1; - break; - } - } - return input.substring(start, input.length()); - } - - private static String trim(final String input) { - if (input == null || input.length() < 1) { - return input; - } else { - String output = input; - while (output.length() > 0 && output.substring(0, 1).matches(REGEX_REMOVE_FROM_URL)) { - output = output.substring(1); - } - while (output.length() > 0 - && output.substring(output.length() - 1, output.length()).matches(REGEX_REMOVE_FROM_URL)) { - output = output.substring(0, output.length() - 1); - } - return output; - } - } - - private int getServiceIdByUrl(String url) { - StreamingService[] serviceList = NewPipe.getServices(); - int service = -1; - for (int i = 0; i < serviceList.length; i++) { - if (serviceList[i].getStreamUrlIdHandlerInstance().acceptUrl(videoUrl)) { - service = i; - //videoExtractor = ServiceList.getService(i).getExtractorInstance(); - break; - } - } - return service; - } } diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java index bf9a32ae2..8dc403f11 100644 --- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java @@ -33,6 +33,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; + import com.google.android.exoplayer.util.Util; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; @@ -797,8 +798,11 @@ public class VideoItemDetailFragment extends Fragment { @Override public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - return actionBarHandler.onItemSelected(item); + if(!actionBarHandler.onItemSelected(item)) { + return super.onOptionsItemSelected(item); + } else { + return true; + } } public void setOnInvokeCreateOptionsMenuListener(OnInvokeCreateOptionsMenuListener listener) { diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java index 3c80e3be0..61e0a12b7 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java @@ -3,18 +3,15 @@ package org.schabi.newpipe.download; import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.FragmentTransaction; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.Bundle; -import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NavUtils; import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -28,32 +25,28 @@ import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; -import org.schabi.newpipe.ThemableActivity; -import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.SettingsActivity; +import org.schabi.newpipe.util.ThemeHelper; import java.io.File; import java.util.Vector; -import us.shandian.giga.get.DownloadManager; import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.ui.fragment.AllMissionsFragment; import us.shandian.giga.ui.fragment.MissionsFragment; import us.shandian.giga.util.CrashHandler; import us.shandian.giga.util.Utility; -public class DownloadActivity extends ThemableActivity implements AdapterView.OnItemClickListener{ +public class DownloadActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { public static final String INTENT_DOWNLOAD = "us.shandian.giga.intent.DOWNLOAD"; public static final String INTENT_LIST = "us.shandian.giga.intent.LIST"; - - private static final String TAG = DownloadActivity.class.toString(); public static final String THREADS = "threads"; - - + private static final String TAG = DownloadActivity.class.toString(); private MissionsFragment mFragment; @@ -72,9 +65,9 @@ public class DownloadActivity extends ThemableActivity implements AdapterView.On startService(i); super.onCreate(savedInstanceState); + ThemeHelper.setTheme(this, true); setContentView(R.layout.activity_downloader); - //noinspection ConstantConditions // its ok if this fails, we will catch that error later, and send it as report diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java index 18bf67550..fe7b9f7eb 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java @@ -54,6 +54,7 @@ public abstract class ChannelExtractor { public abstract String getBannerUrl() throws ParsingException; public abstract String getFeedUrl() throws ParsingException; public abstract StreamInfoItemCollector getStreams() throws ParsingException; + public abstract long getSubscriberCount() throws ParsingException; public abstract boolean hasNextPage() throws ParsingException; public int getServiceId() { return serviceId; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java index 68862ec32..e1fd96ada 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java @@ -29,8 +29,6 @@ import java.util.Vector; */ public class ChannelInfo { - - public void addException(Exception e) { errors.add(e); } @@ -66,6 +64,11 @@ public class ChannelInfo { } catch(Exception e) { info.errors.add(e); } + try { + info.subscriberCount = extractor.getSubscriberCount(); + } catch (Exception e) { + info.errors.add(e); + } return info; } @@ -76,6 +79,7 @@ public class ChannelInfo { public String banner_url = ""; public String feed_url = ""; public List related_streams = null; + public long subscriberCount = -1; public boolean hasNextPage = false; public List errors = new Vector<>(); diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java index 413471eda..0b8fda717 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java @@ -55,6 +55,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor { private static String avatarUrl = ""; private static String bannerUrl = ""; private static String feedUrl = ""; + private static long subscriberCount = -1; // 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 = ""; @@ -153,7 +154,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor { @Override public StreamInfoItemCollector getStreams() throws ParsingException { StreamInfoItemCollector collector = getStreamPreviewInfoCollector(); - Element ul = null; + Element ul; if(isAjaxPage) { ul = doc.select("body").first(); } else { @@ -168,6 +169,15 @@ public class YoutubeChannelExtractor extends ChannelExtractor { return AbstractStreamInfo.StreamType.VIDEO_STREAM; } + @Override + public boolean isAd() throws ParsingException { + if(!li.select("span[class*=\"icon-not-available\"]").isEmpty()) { + return true; + } else { + return false; + } + } + @Override public String getWebPageUrl() throws ParsingException { try { @@ -213,9 +223,14 @@ public class YoutubeChannelExtractor extends ChannelExtractor { @Override public String getUploadDate() throws ParsingException { try { - return li.select("div[class=\"yt-lockup-meta\"]").first() - .select("li").first() - .text(); + Element meta = li.select("div[class=\"yt-lockup-meta\"]").first(); + Element li = meta.select("li").first(); + if (li == null && meta != null) { + //this means we have a youtube red video + return ""; + }else { + return li.text(); + } } catch(Exception e) { throw new ParsingException("Could not get uplaod date", e); } @@ -230,13 +245,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor { .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); - } + return -1; } output = Parser.matchGroup1("([0-9,\\. ]*)", input) @@ -294,6 +303,18 @@ public class YoutubeChannelExtractor extends ChannelExtractor { return collector; } + @Override + public long getSubscriberCount() throws ParsingException { + Element el = doc.select("span[class*=\"yt-subscription-button-subscriber-count\"]") + .first(); + if(el != null) { + subscriberCount = Long.parseLong(el.text().replaceAll("\\D+","")); + } else if(el == null && subscriberCount == -1) { + throw new ParsingException("Could not get subscriber count"); + } + return subscriberCount; + } + @Override public String getFeedUrl() throws ParsingException { try { diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java index 30846fedc..52a42f645 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java @@ -10,6 +10,7 @@ import org.mozilla.javascript.Function; import org.mozilla.javascript.ScriptableObject; import org.schabi.newpipe.extractor.AbstractStreamInfo; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.FoundAdException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.stream_info.AudioStream; @@ -420,9 +421,15 @@ public class YoutubeStreamExtractor extends StreamExtractor { @Override public String getDashMpdUrl() throws ParsingException { - /* try { - String dashManifestUrl = videoInfoPage.get("dashmpd"); + String dashManifestUrl = ""; + if(videoInfoPage != null && videoInfoPage.containsKey("dashmpd")) { + dashManifestUrl = videoInfoPage.get("dashmpd"); + } else if (playerArgs.has("dashmpd")) { + dashManifestUrl = playerArgs.getString("dashmpd"); + } else { + return ""; + } if(!dashManifestUrl.contains("/signature/")) { String encryptedSig = Parser.matchGroup1("/s/([a-fA-F0-9\\.]+)", dashManifestUrl); String decryptedSig; @@ -435,8 +442,6 @@ public class YoutubeStreamExtractor extends StreamExtractor { throw new ParsingException( "Could not get \"dashmpd\" maybe VideoInfoPage is broken.", e); } - */ - return ""; } @@ -447,9 +452,17 @@ public class YoutubeStreamExtractor extends StreamExtractor { String encodedUrlMap; // playerArgs could be null if the video is age restricted if (playerArgs == null) { - encodedUrlMap = videoInfoPage.get("adaptive_fmts"); + if(videoInfoPage.containsKey("adaptive_fmts")) { + encodedUrlMap = videoInfoPage.get("adaptive_fmts"); + } else { + return null; + } } else { - encodedUrlMap = playerArgs.getString("adaptive_fmts"); + if(playerArgs.has("adaptive_fmts")) { + encodedUrlMap = playerArgs.getString("adaptive_fmts"); + } else { + return null; + } } for(String url_data_str : encodedUrlMap.split(",")) { // This loop iterates through multiple streams, therefor tags @@ -713,7 +726,16 @@ public class YoutubeStreamExtractor extends StreamExtractor { return new StreamInfoItemExtractor() { @Override public AbstractStreamInfo.StreamType getStreamType() throws ParsingException { - return null; + return AbstractStreamInfo.StreamType.VIDEO_STREAM; + } + + @Override + public boolean isAd() throws ParsingException { + if(!li.select("span[class*=\"icon-not-available\"]").isEmpty()) { + return true; + } else { + return false; + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java index 1afb8f57d..44d606e2d 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamInfoItemExtractor.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.extractor.services.youtube; import org.jsoup.nodes.Element; import org.schabi.newpipe.extractor.AbstractStreamInfo; import org.schabi.newpipe.extractor.Parser; +import org.schabi.newpipe.extractor.exceptions.FoundAdException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.stream_info.StreamInfoItemExtractor; @@ -28,7 +29,7 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor { private final Element item; - public YoutubeStreamInfoItemExtractor(Element item) { + public YoutubeStreamInfoItemExtractor(Element item) throws FoundAdException { this.item = item; } @@ -161,6 +162,15 @@ public class YoutubeStreamInfoItemExtractor implements StreamInfoItemExtractor { } } + @Override + public boolean isAd() throws ParsingException { + if(!item.select("span[class*=\"icon-not-available\"]").isEmpty()) { + return true; + } else { + return false; + } + } + private boolean isLiveStream(Element item) { Element bla = item.select("span[class*=\"yt-badge-live\"]").first(); diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java index 355da0402..055cd5970 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java @@ -15,7 +15,6 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; -import java.util.regex.Matcher; /** * Created by Christian Schabesberger on 02.02.16. @@ -149,9 +148,9 @@ public class YoutubeStreamUrlIdHandler implements UrlIdHandler { @Override public boolean acceptUrl(String videoUrl) { - videoUrl = videoUrl.toLowerCase(); - if(videoUrl.contains("youtube") || - videoUrl.contains("youtu.be")) { + String lowercaseUrl = videoUrl.toLowerCase(); + if(lowercaseUrl.contains("youtube") || + lowercaseUrl.contains("youtu.be")) { // bad programming I know try { getId(videoUrl); diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java index 5c2a8e74d..03ff7bd83 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemCollector.java @@ -43,6 +43,9 @@ public class StreamInfoItemCollector extends InfoItemCollector { } public StreamInfoItem extract(StreamInfoItemExtractor extractor) throws Exception { + if(extractor.isAd()) { + throw new FoundAdException("Found ad"); + } StreamInfoItem resultItem = new StreamInfoItem(); // importand information @@ -91,7 +94,7 @@ public class StreamInfoItemCollector extends InfoItemCollector { try { addItem(extract(extractor)); } catch(FoundAdException ae) { - System.out.println("AD_WARNING: " + ae.getMessage()); + //System.out.println("AD_WARNING: " + ae.getMessage()); } catch (Exception e) { addError(e); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java index 4a4080a88..b5432b42d 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/stream_info/StreamInfoItemExtractor.java @@ -32,4 +32,5 @@ public interface StreamInfoItemExtractor { String getUploadDate() throws ParsingException; long getViewCount() throws ParsingException; String getThumbnailUrl() throws ParsingException; + boolean isAd() throws ParsingException; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 67b19520a..4ec6123ad 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -105,7 +105,7 @@ public class InfoItemBuilder { switch(info.infoType()) { case STREAM: itemView = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.stream_item, parent, false); + .inflate(R.layout.stream_item, parent, false); holder = new StreamInfoItemHolder(itemView); break; case CHANNEL: @@ -202,15 +202,15 @@ public class InfoItemBuilder { } } - public String shortSubscriber(Long viewCount){ - if(viewCount >= 1000000000){ - return Long.toString(viewCount/1000000000)+ billion + " " + subsS; - }else if(viewCount>=1000000){ - return Long.toString(viewCount/1000000)+ million + " " + subsS; - }else if(viewCount>=1000){ - return Long.toString(viewCount/1000)+ thousand + " " + subsS; + public String shortSubscriber(Long count){ + if(count >= 1000000000){ + return Long.toString(count/1000000000)+ billion + " " + subsS; + }else if(count>=1000000){ + return Long.toString(count/1000000)+ million + " " + subsS; + }else if(count>=1000){ + return Long.toString(count/1000)+ thousand + " " + subsS; }else { - return Long.toString(viewCount)+ " " + subsS; + return Long.toString(count)+ " " + subsS; } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index adbdc22e7..d6f173350 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -33,11 +33,27 @@ import java.util.Vector; * along with NewPipe. If not, see . */ -public class InfoListAdapter extends RecyclerView.Adapter { +public class InfoListAdapter extends RecyclerView.Adapter { private static final String TAG = InfoListAdapter.class.toString(); private final InfoItemBuilder infoItemBuilder; private final List infoItemList; + private boolean showFooter = false; + private View header = null; + private View footer = null; + + public class HFHolder extends RecyclerView.ViewHolder { + public HFHolder(View v) { + super(v); + view = v; + } + public View view; + } + + public void showFooter(boolean show) { + showFooter = show; + notifyDataSetChanged(); + } public InfoListAdapter(Activity a, View rootView) { infoItemBuilder = new InfoItemBuilder(a, rootView); @@ -54,9 +70,9 @@ public class InfoListAdapter extends RecyclerView.Adapter { infoItemBuilder.setOnChannelInfoItemSelectedListener(listener); } - public void addInfoItemList(List videos) { - if(videos!= null) { - infoItemList.addAll(videos); + public void addInfoItemList(List data) { + if(data != null) { + infoItemList.addAll(data); notifyDataSetChanged(); } } @@ -66,21 +82,42 @@ public class InfoListAdapter extends RecyclerView.Adapter { notifyDataSetChanged(); } + public void setHeader(View header) { + this.header = header; + notifyDataSetChanged(); + } + + public void setFooter(View view) { + this.footer = view; + notifyDataSetChanged(); + } + @Override public int getItemCount() { - return infoItemList.size(); + int cound = infoItemList.size(); + if(header != null) cound++; + if(footer != null && showFooter) cound++; + return cound; } // don't ask why we have to do that this way... it's android accept it -.- @Override public int getItemViewType(int position) { + if(header != null && position == 0) { + return 0; + } else if(header != null) { + position--; + } + if(footer != null && position == infoItemList.size() && showFooter) { + return 1; + } switch(infoItemList.get(position).infoType()) { case STREAM: - return 0; - case CHANNEL: - return 1; - case PLAYLIST: return 2; + case CHANNEL: + return 3; + case PLAYLIST: + return 4; default: Log.e(TAG, "Trollolo"); return -1; @@ -88,15 +125,19 @@ public class InfoListAdapter extends RecyclerView.Adapter { } @Override - public InfoItemHolder onCreateViewHolder(ViewGroup parent, int type) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { switch(type) { case 0: + return new HFHolder(header); + case 1: + return new HFHolder(footer); + case 2: return new StreamInfoItemHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.stream_item, parent, false)); - case 1: + case 3: return new ChannelInfoItemHolder(LayoutInflater.from(parent.getContext()) .inflate(R.layout.channel_item, parent, false)); - case 2: + case 4: Log.e(TAG, "Playlist is not yet implemented"); return null; default: @@ -106,7 +147,17 @@ public class InfoListAdapter extends RecyclerView.Adapter { } @Override - public void onBindViewHolder(InfoItemHolder holder, int i) { - infoItemBuilder.buildByHolder(holder, infoItemList.get(i)); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { + //god damen f*** ANDROID SH** + if(holder instanceof InfoItemHolder) { + if(header != null) { + i--; + } + infoItemBuilder.buildByHolder((InfoItemHolder) holder, infoItemList.get(i)); + } else if(holder instanceof HFHolder && i == 0 && header != null) { + ((HFHolder) holder).view = header; + } else if(holder instanceof HFHolder && i == infoItemList.size() && footer != null && showFooter) { + ((HFHolder) holder).view = footer; + } } } diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index e95a5e710..c15042de8 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -1,5 +1,3 @@ - - package org.schabi.newpipe.report; import android.app.Activity; @@ -8,14 +6,14 @@ import android.content.Intent; import android.graphics.Color; import android.net.Uri; import android.os.Build; +import android.os.Bundle; +import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.preference.PreferenceManager; import android.support.design.widget.Snackbar; 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; @@ -35,8 +33,8 @@ import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.ThemableActivity; import org.schabi.newpipe.extractor.Parser; +import org.schabi.newpipe.util.ThemeHelper; import java.io.PrintWriter; import java.io.StringWriter; @@ -66,65 +64,12 @@ import java.util.Vector; * along with NewPipe. If not, see . */ -public class ErrorActivity extends ThemableActivity { - public static class ErrorInfo implements Parcelable { - public int userAction; - public String request; - public String serviceName; - public int message; - - public static ErrorInfo make(int userAction, String serviceName, String request, int message) { - ErrorInfo info = new ErrorInfo(); - info.userAction = userAction; - info.serviceName = serviceName; - info.request = request; - info.message = message; - return info; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(this.userAction); - dest.writeString(this.request); - dest.writeString(this.serviceName); - dest.writeInt(this.message); - } - - public ErrorInfo() { - } - - protected ErrorInfo(Parcel in) { - this.userAction = in.readInt(); - this.request = in.readString(); - this.serviceName = in.readString(); - this.message = in.readInt(); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - @Override - public ErrorInfo createFromParcel(Parcel source) { - return new ErrorInfo(source); - } - - @Override - public ErrorInfo[] newArray(int size) { - return new ErrorInfo[size]; - } - }; - } - +public class ErrorActivity extends AppCompatActivity { // LOG TAGS public static final String TAG = ErrorActivity.class.toString(); - // BUNDLE TAGS public static final String ERROR_INFO = "error_info"; public static final String ERROR_LIST = "error_list"; - // MESSAGE ID public static final int SEARCHED = 0; public static final int REQUESTED_STREAM = 1; @@ -134,7 +79,6 @@ public class ErrorActivity extends ThemableActivity { public static final int LOAD_IMAGE = 5; public static final int UI_ERROR = 6; public static final int REQUESTED_CHANNEL = 7; - // MESSAGE STRING public static final String SEARCHED_STRING = "searched"; public static final String REQUESTED_STREAM_STRING = "requested stream"; @@ -144,17 +88,14 @@ public class ErrorActivity extends ThemableActivity { public static final String LOAD_IMAGE_STRING = "load image"; public static final String UI_ERROR_STRING = "ui error"; public static final String REQUESTED_CHANNEL_STRING = "requested channel"; - public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME; - + Thread globIpRangeThread; private String[] errorList; private ErrorInfo errorInfo; private Class returnActivity; private String currentTimeStamp; private String globIpRange; - Thread globIpRangeThread; - // views private TextView errorView; private EditText userCommentBox; @@ -243,9 +184,26 @@ public class ErrorActivity extends ThemableActivity { context.startActivity(intent); } + private static String getStackTrace(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + // errorList to StringList + private static String[] elToSl(List stackTraces) { + String[] out = new String[stackTraces.size()]; + for (int i = 0; i < stackTraces.size(); i++) { + out[i] = getStackTrace(stackTraces.get(i)); + } + return out; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeHelper.setTheme(this, true); setContentView(R.layout.activity_error); Intent intent = getIntent(); @@ -335,13 +293,6 @@ public class ErrorActivity extends ThemableActivity { return false; } - private static String getStackTrace(final Throwable throwable) { - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw, true); - throwable.printStackTrace(pw); - return sw.getBuffer().toString(); - } - private String formErrorText(String[] el) { String text = ""; if(el != null) { @@ -478,6 +429,56 @@ public class ErrorActivity extends ThemableActivity { return df.format(new Date()); } + public static class ErrorInfo implements Parcelable { + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public ErrorInfo createFromParcel(Parcel source) { + return new ErrorInfo(source); + } + + @Override + public ErrorInfo[] newArray(int size) { + return new ErrorInfo[size]; + } + }; + public int userAction; + public String request; + public String serviceName; + public int message; + + public ErrorInfo() { + } + + protected ErrorInfo(Parcel in) { + this.userAction = in.readInt(); + this.request = in.readString(); + this.serviceName = in.readString(); + this.message = in.readInt(); + } + + public static ErrorInfo make(int userAction, String serviceName, String request, int message) { + ErrorInfo info = new ErrorInfo(); + info.userAction = userAction; + info.serviceName = serviceName; + info.request = request; + info.message = message; + return info; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.userAction); + dest.writeString(this.request); + dest.writeString(this.serviceName); + dest.writeInt(this.message); + } + } + private class IpRagneRequester implements Runnable { Handler h = new Handler(); public void run() { @@ -497,8 +498,6 @@ public class ErrorActivity extends ThemableActivity { } } - - private class IpRageReturnRunnable implements Runnable { String ipRange; public IpRageReturnRunnable(String ipRange) { @@ -514,13 +513,4 @@ public class ErrorActivity extends ThemableActivity { } } } - - // errorList to StringList - private static String[] elToSl(List stackTraces) { - String[] out = new String[stackTraces.size()]; - for(int i = 0; i < stackTraces.size(); i++) { - out[i] = getStackTrace(stackTraces.get(i)); - } - return out; - } } diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java index b710a7977..6bf261e14 100644 --- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java +++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java @@ -205,7 +205,6 @@ public class SearchInfoItemFragment extends Fragment { RECAPTCHA_REQUEST); } }); - } @Override @@ -221,6 +220,8 @@ public class SearchInfoItemFragment extends Fragment { infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content)); + infoListAdapter.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false)); + infoListAdapter.showFooter(false); infoListAdapter.setOnStreamInfoItemSelectedListener( new InfoItemBuilder.OnInfoItemSelectedListener() { @Override @@ -324,6 +325,7 @@ public class SearchInfoItemFragment extends Fragment { private void search(String query) { infoListAdapter.clearSteamItemList(); + infoListAdapter.showFooter(false); pageNumber = 0; searchQuery = query; search(query, pageNumber); @@ -344,6 +346,7 @@ public class SearchInfoItemFragment extends Fragment { private void setDoneLoading() { this.isLoading = false; loadingIndicator.setVisibility(View.GONE); + infoListAdapter.showFooter(true); } /** diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index 7765f9653..1ad2747a7 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.preference.PreferenceActivity; -import android.preference.PreferenceManager; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v7.app.ActionBar; @@ -16,8 +15,7 @@ import android.view.View; import android.view.ViewGroup; import org.schabi.newpipe.R; - -import java.util.Objects; +import org.schabi.newpipe.util.ThemeHelper; /** @@ -41,16 +39,16 @@ import java.util.Objects; */ public class SettingsActivity extends PreferenceActivity { - private AppCompatDelegate mDelegate = null; SettingsFragment f = new SettingsFragment(); + private AppCompatDelegate mDelegate = null; + + public static void initSettings(Context context) { + NewPipeSettings.initSettings(context); + } @Override protected void onCreate(Bundle savedInstanceBundle) { - 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); - } + ThemeHelper.setTheme(this, true); getDelegate().installViewFactory(); getDelegate().onCreate(savedInstanceBundle); super.onCreate(savedInstanceBundle); @@ -156,8 +154,4 @@ public class SettingsActivity extends PreferenceActivity { } return true; } - - public static void initSettings(Context context) { - NewPipeSettings.initSettings(context); - } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java index a6466daf9..d6af888fb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java @@ -1,27 +1,27 @@ package org.schabi.newpipe.settings; import android.app.Activity; -import android.app.ListActivity; import android.content.ClipData; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.support.v7.app.AlertDialog; import com.nononsenseapps.filepicker.FilePickerActivity; import org.schabi.newpipe.App; +import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import java.util.ArrayList; -import java.util.Objects; import info.guardianproject.netcipher.proxy.OrbotHelper; @@ -48,8 +48,8 @@ import info.guardianproject.netcipher.proxy.OrbotHelper; public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + public static final int REQUEST_INSTALL_ORBOT = 0x1234; SharedPreferences.OnSharedPreferenceChangeListener prefListener; - // get keys String DEFAULT_RESOLUTION_PREFERENCE; String DEFAULT_AUDIO_FORMAT_PREFERENCE; @@ -58,9 +58,6 @@ public class SettingsFragment extends PreferenceFragment String DOWNLOAD_PATH_AUDIO_PREFERENCE; String USE_TOR_KEY; String THEME; - - public static final int REQUEST_INSTALL_ORBOT = 0x1234; - private ListPreference defaultResolutionPreference; private ListPreference defaultAudioFormatPreference; private ListPreference searchLanguagePreference; @@ -98,6 +95,8 @@ public class SettingsFragment extends PreferenceFragment downloadPathAudioPreference = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE); themePreference = findPreference(THEME); + final String currentTheme = defaultPreferences.getString(THEME, "Light"); + prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @@ -139,8 +138,27 @@ public class SettingsFragment extends PreferenceFragment } else if (key == THEME) { - String theme = sharedPreferences.getString(THEME, "Light"); - themePreference.setSummary(theme); + String selectedTheme = sharedPreferences.getString(THEME, "Light"); + themePreference.setSummary(selectedTheme); + + if(!selectedTheme.equals(currentTheme)) { // If it's not the current theme + new AlertDialog.Builder(activity) + .setTitle(R.string.restart_title) + .setMessage(R.string.msg_restart) + .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intentToMain = new Intent(activity, MainActivity.class); + intentToMain.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + activity.startActivity(intentToMain); + + activity.finish(); + Runtime.getRuntime().exit(0); + } + }) + .setNegativeButton(R.string.later, null) + .create().show(); + } } updateSummary(); } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java new file mode 100644 index 000000000..a4fdb72f7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -0,0 +1,31 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.preference.PreferenceManager; + +import org.schabi.newpipe.R; + +public class ThemeHelper { + + public static void setTheme(Context context, boolean mode) { + // mode is true for normal theme, false for no action bar theme. + + String themeKey = context.getString(R.string.theme_key); + //String lightTheme = context.getResources().getString(R.string.light_theme_title); + String darkTheme = context.getResources().getString(R.string.dark_theme_title); + String blackTheme = context.getResources().getString(R.string.black_theme_title); + + String sp = PreferenceManager.getDefaultSharedPreferences(context) + .getString(themeKey, context.getResources().getString(R.string.light_theme_title)); + + if (mode) { + if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme); + else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme); + else context.setTheme(R.style.AppTheme); + } else { + if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme_NoActionBar); + else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme_NoActionBar); + else context.setTheme(R.style.AppTheme_NoActionBar); + } + } +} diff --git a/app/src/main/res/drawable-nodpi/channel_banner.png b/app/src/main/res/drawable-nodpi/channel_banner.png new file mode 100644 index 000000000..94b25e98b Binary files /dev/null and b/app/src/main/res/drawable-nodpi/channel_banner.png differ diff --git a/app/src/main/res/drawable-v21/splash_screen.xml b/app/src/main/res/drawable-v21/splash_screen.xml new file mode 100644 index 000000000..34a890727 --- /dev/null +++ b/app/src/main/res/drawable-v21/splash_screen.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_screen.xml b/app/src/main/res/drawable/splash_screen.xml index e049c2941..af9458579 100644 --- a/app/src/main/res/drawable/splash_screen.xml +++ b/app/src/main/res/drawable/splash_screen.xml @@ -1,9 +1,6 @@ - - + + + + + + + + + + + + + + +