diff --git a/.travis.yml b/.travis.yml
index 3df95873e..57388d529 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,7 @@ android:
components:
# The BuildTools version used by NewPipe
- tools
- - build-tools-25.0.0
+ - build-tools-25.0.2
# The SDK version used to compile NewPipe
- android-25
diff --git a/app/build.gradle b/app/build.gradle
index dcc662279..f8b61d1cd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -39,14 +39,16 @@ dependencies {
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
+
+ compile 'com.google.code.gson:gson:2.7'
compile 'org.jsoup:jsoup:1.8.3'
compile 'org.mozilla:rhino:1.7.7'
- compile 'info.guardianproject.netcipher:netcipher:1.2'
- compile 'de.hdodenhof:circleimageview:2.0.0'
- compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
- compile 'com.github.nirhart:parallaxscroll:1.0'
- compile 'com.google.code.gson:gson:2.7'
- compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'ch.acra:acra:4.9.0'
+ compile 'info.guardianproject.netcipher:netcipher:1.2'
+
+ compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
+ compile 'de.hdodenhof:circleimageview:2.0.0'
+ compile 'com.github.nirhart:parallaxscroll:1.0'
+ compile 'com.nononsenseapps:filepicker:3.0.0'
compile 'com.google.android.exoplayer:exoplayer:r2.3.1'
}
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index cb4dce3ea..e2bdc9f68 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -15,6 +15,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.report.AcraReportSenderFactory;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.settings.SettingsActivity;
+import org.schabi.newpipe.util.ThemeHelper;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
@@ -82,6 +83,8 @@ public class App extends Application {
// DO NOT REMOVE THIS FUNCTION!!!
// Otherwise downloadPathPreference has invalid value.
SettingsActivity.initSettings(this);
+
+ ThemeHelper.setTheme(getApplicationContext());
}
/**
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index 681711d22..1bb0858ee 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -21,19 +21,23 @@
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.FragmentManager;
+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.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.View;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.fragments.MainFragment;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.fragments.channel.ChannelFragment;
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
@@ -45,7 +49,8 @@ import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
- //private static final String TAG = "MainActivity";
+ private static final String TAG = "MainActivity";
+ public static final boolean DEBUG = false;
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
@@ -53,18 +58,22 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
@Override
protected void onCreate(Bundle savedInstanceState) {
- ThemeHelper.setTheme(this, true);
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+ ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) {
initFragments();
}
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
}
@Override
protected void onNewIntent(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onNewIntent() called with: intent = [" + intent + "]");
if (intent != null) {
// Return if launched from a launcher (e.g. Nova Launcher, Pixel Launcher ...)
// to not destroy the already created backstack
@@ -79,22 +88,11 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
@Override
public void onBackPressed() {
+ if (DEBUG) Log.d(TAG, "onBackPressed() called");
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (fragment instanceof VideoDetailFragment) if (((VideoDetailFragment) fragment).onActivityBackPressed()) return;
- if (getSupportFragmentManager().getBackStackEntryCount() >= 2) {
- getSupportFragmentManager().popBackStackImmediate();
- } else {
- if (fragment instanceof SearchFragment) {
- SearchFragment searchFragment = (SearchFragment) fragment;
- if (!searchFragment.isMainBgVisible()) {
- getSupportFragmentManager().beginTransaction().remove(fragment).commitNow();
- NavigationHelper.openMainActivity(this);
- return;
- }
- }
- finish();
- }
+ super.onBackPressed();
}
/*//////////////////////////////////////////////////////////////////////////
@@ -103,14 +101,32 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
@Override
public boolean onCreateOptionsMenu(Menu menu) {
+ if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "]");
super.onCreateOptionsMenu(menu);
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.main_menu, menu);
+
+ Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
+ if (!(fragment instanceof VideoDetailFragment)) {
+ findViewById(R.id.toolbar).findViewById(R.id.toolbar_spinner).setVisibility(View.GONE);
+ }
+
+ if (!(fragment instanceof SearchFragment)) {
+ findViewById(R.id.toolbar).findViewById(R.id.toolbar_search_container).setVisibility(View.GONE);
+
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main_menu, menu);
+ }
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setDisplayHomeAsUpEnabled(false);
+ }
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
int id = item.getItemId();
switch (id) {
@@ -144,9 +160,10 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
//////////////////////////////////////////////////////////////////////////*/
private void initFragments() {
+ openMainFragment();
if (getIntent() != null && getIntent().hasExtra(Constants.KEY_URL)) {
handleIntent(getIntent());
- } else openSearchFragment();
+ }
}
/*//////////////////////////////////////////////////////////////////////////
@@ -170,6 +187,13 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
//////////////////////////////////////////////////////////////////////////*/
private void handleIntent(Intent intent) {
+ if (intent.hasExtra(Constants.KEY_THEME_CHANGE) && intent.getBooleanExtra(Constants.KEY_THEME_CHANGE, false)) {
+ this.recreate();
+ Intent setI = new Intent(this, SettingsActivity.class);
+ startActivity(setI);
+ return;
+ }
+
if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
String url = intent.getStringExtra(Constants.KEY_URL);
int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
@@ -187,24 +211,34 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
} catch (Exception e) {
e.printStackTrace();
}
+ } else if (intent.hasExtra(Constants.KEY_OPEN_SEARCH)) {
+ String searchQuery = intent.getStringExtra(Constants.KEY_QUERY);
+ if (searchQuery == null) searchQuery = "";
+ int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
+ openSearchFragment(serviceId, searchQuery);
} else {
getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- openSearchFragment();
+ openMainFragment();//openSearchFragment();
}
}
- private void openSearchFragment() {
+ private void openMainFragment() {
ImageLoader.getInstance().clearMemoryCache();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
- .replace(R.id.fragment_holder, new SearchFragment())
+ .replace(R.id.fragment_holder, new MainFragment())
+ .commit();
+ }
+
+ private void openSearchFragment(int serviceId, String query) {
+ getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
+ .replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query))
.addToBackStack(null)
.commit();
}
private void openVideoDetailFragment(int serviceId, String url, String title, boolean autoPlay) {
- ImageLoader.getInstance().clearMemoryCache();
-
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
if (title == null) title = "";
@@ -226,11 +260,10 @@ public class MainActivity extends AppCompatActivity implements OnItemSelectedLis
}
private void openChannelFragment(int serviceId, String url, String name) {
- ImageLoader.getInstance().clearMemoryCache();
if (name == null) name = "";
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
- .replace(R.id.fragment_holder, ChannelFragment.newInstance(serviceId, url, name))
+ .replace(R.id.fragment_holder, ChannelFragment.getInstance(serviceId, url, name))
.addToBackStack(null)
.commit();
}
diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
index 45c4dfeb8..b97e0566d 100644
--- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java
@@ -8,6 +8,7 @@ import android.os.Bundle;
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.view.MenuItem;
import android.webkit.CookieManager;
import android.webkit.ValueCallback;
@@ -48,10 +49,15 @@ public class ReCaptchaActivity extends AppCompatActivity {
// Set return to Cancel by default
setResult(RESULT_CANCELED);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(R.string.reCaptcha_title);
- actionBar.setDisplayShowTitleEnabled(true);
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(R.string.reCaptcha_title);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
WebView myWebView = (WebView) findViewById(R.id.reCaptchaWebView);
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 61e0a12b7..aef3d3bce 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadActivity.java
@@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
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;
@@ -26,13 +25,11 @@ import android.widget.TextView;
import android.widget.Toast;
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.service.DownloadManagerService;
import us.shandian.giga.ui.fragment.AllMissionsFragment;
@@ -64,17 +61,19 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
i.setClass(this, DownloadManagerService.class);
startService(i);
+ ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
- ThemeHelper.setTheme(this, true);
setContentView(R.layout.activity_downloader);
- //noinspection ConstantConditions
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
- // its ok if this fails, we will catch that error later, and send it as report
ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(R.string.downloads_title);
- actionBar.setDisplayShowTitleEnabled(true);
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(R.string.downloads_title);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -159,7 +158,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
name.setText(getIntent().getStringExtra("fileName"));
toolbar.setTitle(R.string.add);
- toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
+ toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(this) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
// Show the dialog
@@ -183,7 +182,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
if (item.getItemId() == R.id.okay) {
String location;
- if(audioButton.isChecked()) {
+ if (audioButton.isChecked()) {
location = NewPipeSettings.getAudioDownloadPath(DownloadActivity.this);
} else {
location = NewPipeSettings.getVideoDownloadPath(DownloadActivity.this);
@@ -201,7 +200,7 @@ public class DownloadActivity extends AppCompatActivity implements AdapterView.O
audioButton.isChecked(), threads.getProgress() + 1);
mFragment.notifyChange();
- mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).commit();
+ mPrefs.edit().putInt(THREADS, threads.getProgress() + 1).apply();
mPendingUrl = null;
dialog.dismiss();
}
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index 22911fc19..4902b63f2 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -1,14 +1,11 @@
package org.schabi.newpipe.download;
import android.Manifest;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.DialogFragment;
@@ -26,34 +23,32 @@ import android.widget.TextView;
import org.schabi.newpipe.App;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.settings.NewPipeSettings;
+import org.schabi.newpipe.util.ThemeHelper;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
-import us.shandian.giga.get.DownloadManager;
-import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManagerService;
/**
* Created by Christian Schabesberger on 21.09.15.
- *
+ *
* Copyright (C) Christian Schabesberger 2015
* DownloadDialog.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 .
*/
@@ -71,8 +66,7 @@ public class DownloadDialog extends DialogFragment {
}
- public static DownloadDialog newInstance(Bundle args)
- {
+ public static DownloadDialog newInstance(Bundle args) {
DownloadDialog dialog = new DownloadDialog();
dialog.setArguments(args);
dialog.setStyle(DialogFragment.STYLE_NO_TITLE, 0);
@@ -100,7 +94,7 @@ public class DownloadDialog extends DialogFragment {
final SeekBar threads = (SeekBar) view.findViewById(R.id.threads);
toolbar.setTitle(R.string.download_dialog_title);
- toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
+ toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp);
toolbar.inflateMenu(R.menu.dialog_url);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
@@ -151,16 +145,16 @@ public class DownloadDialog extends DialogFragment {
}
- protected void checkDownloadOptions(){
+ protected void checkDownloadOptions() {
View view = getView();
Bundle arguments = getArguments();
RadioButton audioButton = (RadioButton) view.findViewById(R.id.audio_button);
RadioButton videoButton = (RadioButton) view.findViewById(R.id.video_button);
- if(arguments.getString(AUDIO_URL) == null) {
+ if (arguments.getString(AUDIO_URL) == null) {
audioButton.setVisibility(View.GONE);
videoButton.setChecked(true);
- } else if(arguments.getString(VIDEO_URL) == null) {
+ } else if (arguments.getString(VIDEO_URL) == null) {
videoButton.setVisibility(View.GONE);
audioButton.setChecked(true);
}
@@ -169,11 +163,11 @@ public class DownloadDialog extends DialogFragment {
/**
* #143 #44 #42 #22: make shure that the filename does not contain illegal chars.
* This should fix some of the "cannot download" problems.
- * */
+ */
private String createFileName(String fName) {
// from http://eng-przemelek.blogspot.de/2009/07/how-to-create-valid-file-name.html
- List forbiddenCharsPatterns = new ArrayList<> ();
+ List forbiddenCharsPatterns = new ArrayList<>();
forbiddenCharsPatterns.add("[:]+"); // Mac OS, but it looks that also Windows XP
forbiddenCharsPatterns.add("[\\*\"/\\\\\\[\\]\\:\\;\\|\\=\\,]+"); // Windows
forbiddenCharsPatterns.add("[^\\w\\d\\.]+"); // last chance... only latin letters and digits
@@ -186,8 +180,7 @@ public class DownloadDialog extends DialogFragment {
//download audio, video or both?
- private void download()
- {
+ private void download() {
View view = getView();
Bundle arguments = getArguments();
final EditText name = (EditText) view.findViewById(R.id.file_name);
@@ -199,7 +192,7 @@ public class DownloadDialog extends DialogFragment {
boolean isAudio = audioButton.isChecked();
String url, location, filename;
- if(isAudio) {
+ if (isAudio) {
url = arguments.getString(AUDIO_URL);
location = NewPipeSettings.getAudioDownloadPath(getContext());
filename = fName + arguments.getString(FILE_SUFFIX_AUDIO);
@@ -218,11 +211,11 @@ public class DownloadDialog extends DialogFragment {
private void download(String url, String title,
String fileSuffix, File downloadDir, Context context) {
- File saveFilePath = new File(downloadDir,createFileName(title) + fileSuffix);
+ File saveFilePath = new File(downloadDir, createFileName(title) + fileSuffix);
long id = 0;
- Log.i(TAG,"Started downloading '" + url +
+ Log.i(TAG, "Started downloading '" + url +
"' => '" + saveFilePath + "' #" + id);
if (App.isUsingTor()) {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java
new file mode 100644
index 000000000..d51267104
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java
@@ -0,0 +1,243 @@
+package org.schabi.newpipe.fragments;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.annotation.AttrRes;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.nostra13.universalimageloader.core.DisplayImageOptions;
+import com.nostra13.universalimageloader.core.ImageLoader;
+import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
+
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public abstract class BaseFragment extends Fragment {
+ protected final String TAG = "BaseFragment@" + Integer.toHexString(hashCode());
+ protected static final boolean DEBUG = MainActivity.DEBUG;
+
+ protected AppCompatActivity activity;
+ protected OnItemSelectedListener onItemSelectedListener;
+
+ protected AtomicBoolean isLoading = new AtomicBoolean(false);
+ protected AtomicBoolean wasLoading = new AtomicBoolean(false);
+
+ protected static final ImageLoader imageLoader = ImageLoader.getInstance();
+ protected static final DisplayImageOptions displayImageOptions =
+ new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Views
+ //////////////////////////////////////////////////////////////////////////*/
+
+ protected Toolbar toolbar;
+
+ protected View errorPanel;
+ protected Button errorButtonRetry;
+ protected TextView errorTextView;
+ protected ProgressBar loadingProgressBar;
+ //protected SwipeRefreshLayout swipeRefreshLayout;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Fragment's Lifecycle
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (DEBUG) Log.d(TAG, "onAttach() called with: context = [" + context + "]");
+
+ activity = (AppCompatActivity) context;
+ onItemSelectedListener = (OnItemSelectedListener) context;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+
+ isLoading.set(false);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onViewCreated(View rootView, Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
+ initViews(rootView, savedInstanceState);
+ initListeners();
+ wasLoading.set(false);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (DEBUG) Log.d(TAG, "onDestroyView() called");
+ toolbar = null;
+
+ errorPanel = null;
+ errorButtonRetry = null;
+ errorTextView = null;
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
+ protected void initViews(View rootView, Bundle savedInstanceState) {
+ toolbar = (Toolbar) activity.findViewById(R.id.toolbar);
+
+ loadingProgressBar = (ProgressBar) rootView.findViewById(R.id.loading_progress_bar);
+ //swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh);
+
+ errorPanel = rootView.findViewById(R.id.error_panel);
+ errorButtonRetry = (Button) rootView.findViewById(R.id.error_button_retry);
+ errorTextView = (TextView) rootView.findViewById(R.id.error_message_view);
+ }
+
+ protected void initListeners() {
+ errorButtonRetry.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onRetryButtonClicked();
+ }
+ });
+ }
+
+ protected abstract void reloadContent();
+
+ protected void onRetryButtonClicked() {
+ if (DEBUG) Log.d(TAG, "onRetryButtonClicked() called");
+ reloadContent();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public void animateView(final View view, final boolean enterOrExit, long duration) {
+ animateView(view, enterOrExit, duration, 0, null);
+ }
+
+ public void animateView(final View view, final boolean enterOrExit, long duration, final Runnable execOnEnd) {
+ animateView(view, enterOrExit, duration, 0, execOnEnd);
+ }
+
+ public void animateView(final View view, final boolean enterOrExit, long duration, long delay) {
+ animateView(view, enterOrExit, duration, delay, null);
+ }
+
+ /**
+ * Animate the view
+ *
+ * @param view view that will be animated
+ * @param enterOrExit true to enter, false to exit
+ * @param duration how long the animation will take, in milliseconds
+ * @param delay how long the animation will take to start, in milliseconds
+ * @param execOnEnd runnable that will be executed when the animation ends
+ */
+ public void animateView(final View view, final boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) {
+ if (DEBUG) Log.d(TAG, "animateView() called with: view = [" + view + "], enterOrExit = [" + enterOrExit + "], duration = [" + duration + "], execOnEnd = [" + execOnEnd + "]");
+ if (view == null) return;
+
+ if (view.getVisibility() == View.VISIBLE && enterOrExit) {
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.VISIBLE);
+ view.setAlpha(1f);
+ if (execOnEnd != null) execOnEnd.run();
+ return;
+ } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.GONE);
+ view.setAlpha(0f);
+ if (execOnEnd != null) execOnEnd.run();
+ return;
+ }
+
+ view.animate().setListener(null).cancel();
+ view.setVisibility(View.VISIBLE);
+
+ if (enterOrExit) {
+ view.animate().alpha(1f).setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ }).start();
+ } else {
+ view.animate().alpha(0f)
+ .setDuration(duration).setStartDelay(delay)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ if (execOnEnd != null) execOnEnd.run();
+ }
+ })
+ .start();
+ }
+ }
+
+ protected void setErrorMessage(String message, boolean showRetryButton) {
+ if (errorTextView == null || activity == null) return;
+
+ errorTextView.setText(message);
+ if (showRetryButton) animateView(errorButtonRetry, true, 300);
+ else animateView(errorButtonRetry, false, 0);
+
+ animateView(errorPanel, true, 300);
+ isLoading.set(false);
+
+ animateView(loadingProgressBar, false, 200);
+ }
+
+ protected int getResourceIdFromAttr(@AttrRes int attr) {
+ TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr});
+ int attributeResourceId = a.getResourceId(0, 0);
+ a.recycle();
+ return attributeResourceId;
+ }
+
+ public static void showMenuTooltip(View v, String message) {
+ final int[] screenPos = new int[2];
+ final Rect displayFrame = new Rect();
+ v.getLocationOnScreen(screenPos);
+ v.getWindowVisibleDisplayFrame(displayFrame);
+
+ final Context context = v.getContext();
+ final int width = v.getWidth();
+ final int height = v.getHeight();
+ final int midy = screenPos[1] + height / 2;
+ int referenceX = screenPos[0] + width / 2;
+ if (ViewCompat.getLayoutDirection(v) == View.LAYOUT_DIRECTION_LTR) {
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+ referenceX = screenWidth - referenceX; // mirror
+ }
+ Toast cheatSheet = Toast.makeText(context, message, Toast.LENGTH_SHORT);
+ if (midy < displayFrame.height()) {
+ // Show along the top; follow action buttons
+ cheatSheet.setGravity(Gravity.TOP | Gravity.END, referenceX,
+ screenPos[1] + height - displayFrame.top);
+ } else {
+ // Show along the bottom center
+ cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+ }
+ cheatSheet.show();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
new file mode 100644
index 000000000..97563e15e
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java
@@ -0,0 +1,76 @@
+package org.schabi.newpipe.fragments;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+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 org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.NavigationHelper;
+
+public class MainFragment extends Fragment {
+ private final String TAG = "MainFragment@" + Integer.toHexString(hashCode());
+ private static final boolean DEBUG = MainActivity.DEBUG;
+
+ private AppCompatActivity activity;
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Fragment's LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (DEBUG) Log.d(TAG, "onAttach() called with: context = [" + context + "]");
+ activity = ((AppCompatActivity) context);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
+ return inflater.inflate(R.layout.fragment_main, container, false);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Menu
+ //////////////////////////////////////////////////////////////////////////*/
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
+ inflater.inflate(R.menu.main_fragment_menu, menu);
+
+ ActionBar supportActionBar = activity.getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayShowTitleEnabled(false);
+ supportActionBar.setDisplayHomeAsUpEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_search:
+ NavigationHelper.openSearch(activity, 0, "");
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java
index 52f42cf84..fa02dbfdb 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java
@@ -1,6 +1,5 @@
package org.schabi.newpipe.fragments.channel;
-import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -8,9 +7,9 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -20,222 +19,144 @@ import android.view.View;
import android.view.ViewGroup;
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.ImageErrorLoadingListener;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
-import org.schabi.newpipe.fragments.OnItemSelectedListener;
+import org.schabi.newpipe.fragments.BaseFragment;
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.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.workers.ChannelExtractorWorker;
+import java.io.Serializable;
import java.text.NumberFormat;
+import java.util.ArrayList;
-import static android.os.Build.VERSION.SDK_INT;
+public class ChannelFragment extends BaseFragment implements ChannelExtractorWorker.OnChannelInfoReceive {
+ private final String TAG = "ChannelFragment@" + Integer.toHexString(hashCode());
+ private static final String INFO_LIST_KEY = "info_list_key";
+ private static final String CHANNEL_INFO_KEY = "channel_info_key";
+ private static final String PAGE_NUMBER_KEY = "page_number_key";
-/**
- * Copyright (C) Christian Schabesberger 2016
- * ChannelFragment.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.
- *
* Copyright (C) Christian Schabesberger 2015
* DetailsMenuHandler.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 .
*/
class ActionBarHandler {
- private static final String TAG = ActionBarHandler.class.toString();
+ private static final String TAG = "ActionBarHandler";
private AppCompatActivity activity;
private int selectedVideoStream = -1;
@@ -52,11 +54,8 @@ class ActionBarHandler {
// those are edited directly. Typically VideoDetailFragment will implement those callbacks.
private OnActionListener onShareListener;
private OnActionListener onOpenInBrowserListener;
- private OnActionListener onOpenInPopupListener;
private OnActionListener onDownloadListener;
private OnActionListener onPlayWithKodiListener;
- private OnActionListener onPlayAudioListener;
-
// Triggered when a stream related action is triggered.
public interface OnActionListener {
@@ -67,46 +66,32 @@ class ActionBarHandler {
this.activity = activity;
}
- @SuppressWarnings({"deprecation", "ConstantConditions"})
- public void setupNavMenu(AppCompatActivity activity) {
- this.activity = activity;
- try {
- activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- } catch (NullPointerException e) {
- e.printStackTrace();
+ public void setupStreamList(final List videoStreams, Spinner toolbarSpinner) {
+ if (activity == null) return;
+ selectedVideoStream = 0;
+
+ // this array will be shown in the dropdown menu for selecting the stream/resolution.
+ String[] itemArray = new String[videoStreams.size()];
+ for (int i = 0; i < videoStreams.size(); i++) {
+ VideoStream item = videoStreams.get(i);
+ itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
}
- }
- public void setupStreamList(final List videoStreams) {
- if (activity != null) {
- selectedVideoStream = 0;
-
-
- // this array will be shown in the dropdown menu for selecting the stream/resolution.
- String[] itemArray = new String[videoStreams.size()];
- for (int i = 0; i < videoStreams.size(); i++) {
- VideoStream item = videoStreams.get(i);
- itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
+ int defaultResolutionIndex = Utils.getDefaultResolution(activity, videoStreams);
+ ArrayAdapter itemAdapter = new ArrayAdapter<>(activity.getBaseContext(), android.R.layout.simple_spinner_dropdown_item, itemArray);
+ toolbarSpinner.setAdapter(itemAdapter);
+ toolbarSpinner.setSelection(defaultResolutionIndex);
+ toolbarSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ selectedVideoStream = position;
}
- int defaultResolution = Utils.getDefaultResolution(activity, videoStreams);
- ArrayAdapter itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
- android.R.layout.simple_spinner_dropdown_item, itemArray);
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ });
- ActionBar ab = activity.getSupportActionBar();
- //todo: make this throwsable
- assert ab != null : "Could not get actionbar";
- ab.setListNavigationCallbacks(itemAdapter
- , new ActionBar.OnNavigationListener() {
- @Override
- public boolean onNavigationItemSelected(int itemPosition, long itemId) {
- selectedVideoStream = (int) itemId;
- return true;
- }
- });
-
- ab.setSelectedNavigationItem(defaultResolution);
- }
}
public void setupMenu(Menu menu, MenuInflater inflater) {
@@ -116,55 +101,36 @@ class ActionBarHandler {
// appcompat itemsinflater.inflate(R.menu.videoitem_detail, menu);
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
- inflater.inflate(R.menu.videoitem_detail, menu);
+ inflater.inflate(R.menu.video_detail_menu, menu);
- showPlayWithKodiAction(defaultPreferences
- .getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
+ showPlayWithKodiAction(defaultPreferences.getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
}
public boolean onItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_item_share: {
- /*
- Intent intent = new Intent();
- intent.setAction(Intent.ACTION_SEND);
- intent.putExtra(Intent.EXTRA_TEXT, websiteUrl);
- intent.setType("text/plain");
- activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
- */
- if(onShareListener != null) {
+ if (onShareListener != null) {
onShareListener.onActionSelected(selectedVideoStream);
}
return true;
}
case R.id.menu_item_openInBrowser: {
- if(onOpenInBrowserListener != null) {
+ if (onOpenInBrowserListener != null) {
onOpenInBrowserListener.onActionSelected(selectedVideoStream);
}
+ return true;
}
- return true;
case R.id.menu_item_download:
- if(onDownloadListener != null) {
+ if (onDownloadListener != null) {
onDownloadListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.action_play_with_kodi:
- if(onPlayWithKodiListener != null) {
+ if (onPlayWithKodiListener != null) {
onPlayWithKodiListener.onActionSelected(selectedVideoStream);
}
return true;
- case R.id.menu_item_play_audio:
- if(onPlayAudioListener != null) {
- onPlayAudioListener.onActionSelected(selectedVideoStream);
- }
- return true;
- case R.id.menu_item_popup: {
- if(onOpenInPopupListener != null) {
- onOpenInPopupListener.onActionSelected(selectedVideoStream);
- }
- return true;
- }
default:
Log.e(TAG, "Menu Item not known");
}
@@ -183,10 +149,6 @@ class ActionBarHandler {
onOpenInBrowserListener = listener;
}
- public void setOnOpenInPopupListener(OnActionListener listener) {
- onOpenInPopupListener = listener;
- }
-
public void setOnDownloadListener(OnActionListener listener) {
onDownloadListener = listener;
}
@@ -195,14 +157,6 @@ class ActionBarHandler {
onPlayWithKodiListener = listener;
}
- public void setOnPlayAudioListener(OnActionListener listener) {
- onPlayAudioListener = listener;
- }
-
- public void showAudioAction(boolean visible) {
- menu.findItem(R.id.menu_item_play_audio).setVisible(visible);
- }
-
public void showDownloadAction(boolean visible) {
menu.findItem(R.id.menu_item_download).setVisible(visible);
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java
index e335be1e3..647637fec 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java
@@ -1,11 +1,14 @@
package org.schabi.newpipe.fragments.detail;
+import org.schabi.newpipe.extractor.stream_info.StreamInfo;
+
import java.io.Serializable;
@SuppressWarnings("WeakerAccess")
public class StackItem implements Serializable {
private String title, url;
+ private StreamInfo info;
public StackItem(String url, String title) {
this.title = title;
@@ -24,6 +27,14 @@ public class StackItem implements Serializable {
return url;
}
+ public void setInfo(StreamInfo info) {
+ this.info = info;
+ }
+
+ public StreamInfo getInfo() {
+ return info;
+ }
+
@Override
public String toString() {
return getUrl() + " > " + getTitle();
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/StreamInfoCache.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/StreamInfoCache.java
new file mode 100644
index 000000000..c7bf80245
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/StreamInfoCache.java
@@ -0,0 +1,85 @@
+package org.schabi.newpipe.fragments.detail;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.extractor.stream_info.StreamInfo;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+
+
+@SuppressWarnings("WeakerAccess")
+public class StreamInfoCache {
+ private static String TAG = "StreamInfoCache@";
+ private static final boolean DEBUG = MainActivity.DEBUG;
+ private static final StreamInfoCache instance = new StreamInfoCache();
+ private static final int MAX_ITEMS_ON_CACHE = 20;
+
+ private final LinkedHashMap myCache = new LinkedHashMap<>();
+
+ private StreamInfoCache() {
+ TAG += "" + Integer.toHexString(hashCode());
+ }
+
+ public static StreamInfoCache getInstance() {
+ if (DEBUG) Log.d(TAG, "getInstance() called");
+ return instance;
+ }
+
+ public boolean hasKey(@NonNull String url) {
+ if (DEBUG) Log.d(TAG, "hasKey() called with: url = [" + url + "]");
+ return !TextUtils.isEmpty(url) && myCache.containsKey(url) && myCache.get(url) != null;
+ }
+
+ public StreamInfo getFromKey(@NonNull String url) {
+ if (DEBUG) Log.d(TAG, "getFromKey() called with: url = [" + url + "]");
+ return myCache.get(url);
+ }
+
+ public void putInfo(@NonNull StreamInfo info) {
+ if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]");
+ putInfo(info.webpage_url, info);
+ }
+
+ public void putInfo(@NonNull String url, @NonNull StreamInfo info) {
+ if (DEBUG) Log.d(TAG, "putInfo() called with: url = [" + url + "], info = [" + info + "]");
+ myCache.put(url, info);
+ }
+
+ public void removeInfo(@NonNull StreamInfo info) {
+ if (DEBUG) Log.d(TAG, "removeInfo() called with: info = [" + info + "]");
+ myCache.remove(info.webpage_url);
+ }
+
+ public void removeInfo(@NonNull String url) {
+ if (DEBUG) Log.d(TAG, "removeInfo() called with: url = [" + url + "]");
+ myCache.remove(url);
+ }
+
+ @SuppressWarnings("unused")
+ public void clearCache() {
+ if (DEBUG) Log.d(TAG, "clearCache() called");
+ myCache.clear();
+ }
+
+ public void removeOldEntries() {
+ if (DEBUG) Log.d(TAG, "removeOldEntries() called , size = " + getSize());
+ if (getSize() > MAX_ITEMS_ON_CACHE) {
+ Iterator iterator = myCache.keySet().iterator();
+ while (iterator.hasNext()) {
+ iterator.next();
+ iterator.remove();
+ if (DEBUG) Log.d(TAG, "getSize() = " + getSize());
+ if (getSize() <= MAX_ITEMS_ON_CACHE) break;
+ }
+ }
+ }
+
+ public int getSize() {
+ return myCache.size();
+ }
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 611b90d28..7ad62d99f 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -1,23 +1,21 @@
package org.schabi.newpipe.fragments.detail;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v4.app.Fragment;
+import android.support.annotation.FloatRange;
+import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatActivity;
import android.text.Html;
+import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue;
@@ -28,18 +26,16 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.ProgressBar;
import android.widget.RelativeLayout;
+import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.nirhart.parallaxscroll.views.ParallaxScrollView;
-import com.nostra13.universalimageloader.core.DisplayImageOptions;
-import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
-import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import org.schabi.newpipe.ImageErrorLoadingListener;
@@ -53,7 +49,7 @@ 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.VideoStream;
-import org.schabi.newpipe.fragments.OnItemSelectedListener;
+import org.schabi.newpipe.fragments.BaseFragment;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.MainVideoPlayer;
import org.schabi.newpipe.player.PlayVideoActivity;
@@ -67,23 +63,27 @@ import org.schabi.newpipe.workers.StreamExtractorWorker;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Stack;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-@SuppressWarnings("FieldCanBeLocal")
-public class VideoDetailFragment extends Fragment implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener {
+public class VideoDetailFragment extends BaseFragment implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener {
private final String TAG = "VideoDetailFragment@" + Integer.toHexString(hashCode());
+ // Amount of videos to show on start
+ private static final int INITIAL_RELATED_VIDEOS = 8;
+
private static final String KORE_PACKET = "org.xbmc.kore";
private static final String SERVICE_ID_KEY = "service_id_key";
private static final String VIDEO_URL_KEY = "video_url_key";
private static final String VIDEO_TITLE_KEY = "video_title_key";
private static final String STACK_KEY = "stack_key";
+ private static final String INFO_KEY = "info_key";
+ private static final String WAS_RELATED_EXPANDED_KEY = "was_related_expanded_key";
public static final String AUTO_PLAY = "auto_play";
- private AppCompatActivity activity;
- private OnItemSelectedListener onItemSelectedListener;
+ private String thousand;
+ private String million;
+ private String billion;
+
private ArrayList sortedStreamVideosList;
private ActionBarHandler actionBarHandler;
@@ -95,27 +95,22 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
private String videoUrl;
private int serviceId = -1;
- private AtomicBoolean wasLoading = new AtomicBoolean(false);
- private AtomicBoolean isLoading = new AtomicBoolean(false);
private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1;
private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2;
private int updateFlags = 0;
private boolean autoPlayEnabled;
private boolean showRelatedStreams;
-
- private static final ImageLoader imageLoader = ImageLoader.getInstance();
- private static final DisplayImageOptions displayImageOptions =
- new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
+ private boolean wasRelatedStreamsExpanded = false;
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
- private ProgressBar loadingProgressBar;
+ private Spinner spinnerToolbar;
private ParallaxScrollView parallaxScrollRootView;
- private RelativeLayout contentRootLayout;
+ private RelativeLayout contentRootLayoutHiding;
private Button thumbnailBackgroundButton;
private ImageView thumbnailImageView;
@@ -126,10 +121,14 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
private ImageView videoTitleToggleArrow;
private TextView videoCountView;
+ private TextView detailControlsBackground;
+ private TextView detailControlsPopup;
+
private RelativeLayout videoDescriptionRootLayout;
private TextView videoUploadDateView;
private TextView videoDescriptionView;
+ private View uploaderRootLayout;
private Button uploaderButton;
private TextView uploaderTextView;
private ImageView uploaderThumb;
@@ -143,6 +142,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
private TextView nextStreamTitle;
private RelativeLayout relatedStreamRootLayout;
private LinearLayout relatedStreamsView;
+ private ImageButton relatedStreamExpandButton;
/*////////////////////////////////////////////////////////////////////////*/
@@ -159,24 +159,21 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
public static VideoDetailFragment getInstance() {
return new VideoDetailFragment();
}
+
/*//////////////////////////////////////////////////////////////////////////
// Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- activity = (AppCompatActivity) context;
- onItemSelectedListener = (OnItemSelectedListener) context;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
+
if (savedInstanceState != null) {
videoTitle = savedInstanceState.getString(VIDEO_TITLE_KEY);
videoUrl = savedInstanceState.getString(VIDEO_URL_KEY);
- serviceId = savedInstanceState.getInt(SERVICE_ID_KEY);
+ serviceId = savedInstanceState.getInt(SERVICE_ID_KEY, 0);
+ wasRelatedStreamsExpanded = savedInstanceState.getBoolean(WAS_RELATED_EXPANDED_KEY, false);
Serializable serializable = savedInstanceState.getSerializable(STACK_KEY);
if (serializable instanceof Stack) {
//noinspection unchecked
@@ -184,38 +181,76 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
stack.clear();
stack.addAll(list);
}
+
+ Serializable serial = savedInstanceState.getSerializable(INFO_KEY);
+ if (serial instanceof StreamInfo) currentStreamInfo = (StreamInfo) serial;
}
showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.show_next_video_key), true);
PreferenceManager.getDefaultSharedPreferences(activity).registerOnSharedPreferenceChangeListener(this);
- activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
- isLoading.set(false);
- setHasOptionsMenu(true);
+
+ thousand = getString(R.string.short_thousand);
+ million = getString(R.string.short_million);
+ billion = getString(R.string.short_billion);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
return inflater.inflate(R.layout.fragment_video_detail, container, false);
}
@Override
public void onViewCreated(View rootView, Bundle savedInstanceState) {
- initViews(rootView);
- initListeners();
- selectAndLoadVideo(serviceId, videoUrl, videoTitle);
- wasLoading.set(false);
+ super.onViewCreated(rootView, savedInstanceState);
+ if (currentStreamInfo == null) selectAndLoadVideo(serviceId, videoUrl, videoTitle);
+ else prepareAndLoad(currentStreamInfo, false);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Currently only used for enable/disable related videos
+ // but can be extended for other live settings changes
+ if (updateFlags != 0) {
+ if (!isLoading.get() && currentStreamInfo != null) {
+ if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentStreamInfo);
+ if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBarHandler(currentStreamInfo);
+ }
+ updateFlags = 0;
+ }
+
+ // Check if it was loading when the activity was stopped/paused,
+ // because when this happen, the curExtractorWorker is cancelled
+ if (wasLoading.getAndSet(false)) selectAndLoadVideo(serviceId, videoUrl, videoTitle);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ wasLoading.set(curExtractorWorker != null && curExtractorWorker.isRunning());
+ if (curExtractorWorker != null && curExtractorWorker.isRunning()) curExtractorWorker.cancel();
+ StreamInfoCache.getInstance().removeOldEntries();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onDestroyView() {
- super.onDestroyView();
+ if (DEBUG) Log.d(TAG, "onDestroyView() called");
thumbnailImageView.setImageBitmap(null);
relatedStreamsView.removeAllViews();
+ spinnerToolbar.setOnItemSelectedListener(null);
- loadingProgressBar = null;
+ spinnerToolbar = null;
parallaxScrollRootView = null;
- contentRootLayout = null;
+ contentRootLayoutHiding = null;
thumbnailBackgroundButton = null;
thumbnailImageView = null;
@@ -226,6 +261,9 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
videoTitleToggleArrow = null;
videoCountView = null;
+ detailControlsBackground = null;
+ detailControlsPopup = null;
+
videoDescriptionRootLayout = null;
videoUploadDateView = null;
videoDescriptionView = null;
@@ -243,47 +281,27 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
nextStreamTitle = null;
relatedStreamRootLayout = null;
relatedStreamsView = null;
- }
+ relatedStreamExpandButton = null;
- @Override
- public void onResume() {
- super.onResume();
-
- // Currently only used for enable/disable related videos
- // but can be extended for other live settings changes
- if (updateFlags != 0) {
- if (!isLoading.get()) {
- if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentStreamInfo);
- if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBarHandler(currentStreamInfo);
- }
- updateFlags = 0;
- }
-
- // Check if it was loading when the activity was stopped/paused,
- // because when this happen, the curExtractorWorker is cancelled
- if (wasLoading.getAndSet(false)) selectAndLoadVideo(serviceId, videoUrl, videoTitle);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- wasLoading.set(curExtractorWorker.isRunning());
- if (curExtractorWorker != null && curExtractorWorker.isRunning()) curExtractorWorker.cancel();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
- imageLoader.clearMemoryCache();
+ super.onDestroyView();
}
@Override
public void onSaveInstanceState(Bundle outState) {
+ if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]");
outState.putString(VIDEO_URL_KEY, videoUrl);
outState.putString(VIDEO_TITLE_KEY, videoTitle);
outState.putInt(SERVICE_ID_KEY, serviceId);
outState.putSerializable(STACK_KEY, stack);
+
+ int nextCount = currentStreamInfo != null && currentStreamInfo.next_video != null ? 2 : 0;
+ if (relatedStreamsView != null && relatedStreamsView.getChildCount() > INITIAL_RELATED_VIDEOS + nextCount) {
+ outState.putSerializable(WAS_RELATED_EXPANDED_KEY, true);
+ }
+
+ if (!isLoading.get() && (curExtractorWorker == null || !curExtractorWorker.isRunning())) {
+ outState.putSerializable(INFO_KEY, currentStreamInfo);
+ }
}
@Override
@@ -313,27 +331,164 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // OnClick
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onClick(View v) {
+ if (isLoading.get() || currentStreamInfo == null) return;
+
+ switch (v.getId()) {
+ case R.id.detail_controls_background:
+ openInBackground();
+ break;
+ case R.id.detail_controls_popup:
+ openInPopup();
+ break;
+ case R.id.detail_uploader_button:
+ NavigationHelper.openChannel(onItemSelectedListener, currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
+ break;
+ case R.id.detail_thumbnail_background_button:
+ playVideo(currentStreamInfo);
+ break;
+ case R.id.detail_title_root_layout:
+ toggleTitleAndDescription();
+ break;
+ case R.id.detail_related_streams_expand:
+ toggleExpandRelatedVideos(currentStreamInfo);
+ break;
+ }
+ }
+
+ @Override
+ protected void reloadContent() {
+ if (DEBUG) Log.d(TAG, "reloadContent() called");
+ if (currentStreamInfo != null) StreamInfoCache.getInstance().removeInfo(currentStreamInfo);
+ currentStreamInfo = null;
+ for (StackItem stackItem : stack) if (stackItem.getUrl().equals(videoUrl)) stackItem.setInfo(null);
+ prepareAndLoad(null, true);
+ }
+
+ private void openInBackground() {
+ if (isLoading.get()) return;
+
+ boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
+ .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
+ Intent intent;
+ AudioStream audioStream = currentStreamInfo.audio_streams.get(Utils.getPreferredAudioFormat(activity, currentStreamInfo.audio_streams));
+ if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
+ activity.startService(NavigationHelper.getOpenBackgroundPlayerIntent(activity, currentStreamInfo, audioStream));
+ Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
+ } else {
+ intent = new Intent();
+ try {
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.parse(audioStream.url),
+ MediaFormat.getMimeById(audioStream.format));
+ intent.putExtra(Intent.EXTRA_TITLE, currentStreamInfo.title);
+ intent.putExtra("title", currentStreamInfo.title);
+ // HERE !!!
+ activity.startActivity(intent);
+ } catch (Exception e) {
+ e.printStackTrace();
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setMessage(R.string.no_player_found)
+ .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
+ activity.startActivity(intent);
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Log.i(TAG, "You unlocked a secret unicorn.");
+ }
+ });
+ builder.create().show();
+ Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void openInPopup() {
+ if (isLoading.get()) return;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
+ Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
+ Intent mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, PopupVideoPlayer.class, currentStreamInfo, actionBarHandler.getSelectedVideoStream());
+ activity.startService(mIntent);
+ }
+
+ private void toggleTitleAndDescription() {
+ if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
+ videoTitleTextView.setMaxLines(1);
+ videoDescriptionRootLayout.setVisibility(View.GONE);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ } else {
+ videoTitleTextView.setMaxLines(10);
+ videoDescriptionRootLayout.setVisibility(View.VISIBLE);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
+ }
+ }
+
+ private void toggleExpandRelatedVideos(StreamInfo info) {
+ if (DEBUG) Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "]");
+ if (!showRelatedStreams) return;
+
+ int nextCount = info.next_video != null ? 2 : 0;
+ int initialCount = INITIAL_RELATED_VIDEOS + nextCount;
+
+ if (relatedStreamsView.getChildCount() > initialCount) {
+ relatedStreamsView.removeViews(initialCount, relatedStreamsView.getChildCount() - (initialCount));
+ relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, getResourceIdFromAttr(R.attr.expand)));
+ return;
+ }
+
+ //Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "], from = [" + INITIAL_RELATED_VIDEOS + "]");
+ for (int i = INITIAL_RELATED_VIDEOS; i < info.related_streams.size(); i++) {
+ InfoItem item = info.related_streams.get(i);
+ //Log.d(TAG, "i = " + i);
+ relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
+ }
+ relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, getResourceIdFromAttr(R.attr.collapse)));
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
- private void initViews(View rootView) {
- loadingProgressBar = (ProgressBar) rootView.findViewById(R.id.detail_loading_progress_bar);
+ protected void initViews(View rootView, Bundle savedInstanceState) {
+ super.initViews(rootView, savedInstanceState);
+
+ spinnerToolbar = (Spinner) toolbar.findViewById(R.id.toolbar_spinner);
parallaxScrollRootView = (ParallaxScrollView) rootView.findViewById(R.id.detail_main_content);
//thumbnailRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_thumbnail_root_layout);
- thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_stream_thumbnail_background_button);
+ thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_thumbnail_background_button);
thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view);
thumbnailPlayButton = (ImageView) rootView.findViewById(R.id.detail_thumbnail_play_button);
- contentRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_content_root_layout);
+ contentRootLayoutHiding = (RelativeLayout) rootView.findViewById(R.id.detail_content_root_hiding);
videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
videoTitleTextView = (TextView) rootView.findViewById(R.id.detail_video_title_view);
videoTitleToggleArrow = (ImageView) rootView.findViewById(R.id.detail_toggle_description_view);
videoCountView = (TextView) rootView.findViewById(R.id.detail_view_count_view);
+ detailControlsBackground = (TextView) rootView.findViewById(R.id.detail_controls_background);
+ detailControlsPopup = (TextView) rootView.findViewById(R.id.detail_controls_popup);
+
videoDescriptionRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_description_root_layout);
videoUploadDateView = (TextView) rootView.findViewById(R.id.detail_upload_date_view);
videoDescriptionView = (TextView) rootView.findViewById(R.id.detail_description_view);
@@ -345,7 +500,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
thumbsDownImageView = (ImageView) rootView.findViewById(R.id.detail_thumbs_down_img_view);
thumbsDisabledTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_disabled_view);
- //uploaderRootLayout = (FrameLayout) rootView.findViewById(R.id.detail_uploader_root_layout);
+ uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
uploaderButton = (Button) rootView.findViewById(R.id.detail_uploader_button);
uploaderTextView = (TextView) rootView.findViewById(R.id.detail_uploader_text_view);
uploaderThumb = (ImageView) rootView.findViewById(R.id.detail_uploader_thumbnail_view);
@@ -353,6 +508,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
relatedStreamRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
nextStreamTitle = (TextView) rootView.findViewById(R.id.detail_next_stream_title);
relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view);
+ relatedStreamExpandButton = ((ImageButton) rootView.findViewById(R.id.detail_related_streams_expand));
actionBarHandler = new ActionBarHandler(activity);
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
@@ -362,28 +518,8 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
setHeightThumbnail();
}
- private void initListeners() {
- videoTitleRoot.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) {
- videoTitleTextView.setMaxLines(1);
- videoDescriptionRootLayout.setVisibility(View.GONE);
- videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
- } else {
- videoTitleTextView.setMaxLines(10);
- videoDescriptionRootLayout.setVisibility(View.VISIBLE);
- videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
- }
- }
- });
- thumbnailBackgroundButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!isLoading.get() && currentStreamInfo != null) playVideo(currentStreamInfo);
- }
- });
-
+ protected void initListeners() {
+ super.initListeners();
infoItemBuilder.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
@@ -392,33 +528,26 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
});
- uploaderButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- NavigationHelper.openChannel(onItemSelectedListener, currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
- }
- });
+ videoTitleRoot.setOnClickListener(this);
+ uploaderButton.setOnClickListener(this);
+ thumbnailBackgroundButton.setOnClickListener(this);
+ detailControlsBackground.setOnClickListener(this);
+ detailControlsPopup.setOnClickListener(this);
+ relatedStreamExpandButton.setOnClickListener(this);
}
private void initThumbnailViews(StreamInfo info) {
if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
- imageLoader.displayImage(info.thumbnail_url, thumbnailImageView,
- displayImageOptions, new SimpleImageLoadingListener() {
- @Override
- public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
- ErrorActivity.reportError(activity,
- failReason.getCause(), null, activity.findViewById(android.R.id.content),
- ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
- NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri,
- R.string.could_not_load_thumbnails));
- }
-
- });
+ imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, displayImageOptions, new SimpleImageLoadingListener() {
+ @Override
+ public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
+ ErrorActivity.reportError(activity, failReason.getCause(), null, activity.findViewById(android.R.id.content), ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, R.string.could_not_load_thumbnails));
+ }
+ });
} else thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark);
if (info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
- imageLoader.displayImage(info.uploader_thumbnail_url,
- uploaderThumb, displayImageOptions,
+ imageLoader.displayImage(info.uploader_thumbnail_url, uploaderThumb, displayImageOptions,
new ImageErrorLoadingListener(activity, activity.findViewById(android.R.id.content), info.service_id));
}
}
@@ -434,11 +563,24 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
} else nextStreamTitle.setVisibility(View.GONE);
if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) {
- for (InfoItem item : info.related_streams) {
+ //long first = System.nanoTime(), each;
+ int to = info.related_streams.size() >= INITIAL_RELATED_VIDEOS ? INITIAL_RELATED_VIDEOS : info.related_streams.size();
+ for (int i = 0; i < to; i++) {
+ InfoItem item = info.related_streams.get(i);
+ //each = System.nanoTime();
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
+ //if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms");
}
+ //if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms");
+
relatedStreamRootLayout.setVisibility(View.VISIBLE);
- } else if (info.next_video == null) relatedStreamRootLayout.setVisibility(View.GONE);
+ relatedStreamExpandButton.setVisibility(View.VISIBLE);
+
+ relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, getResourceIdFromAttr(R.attr.expand)));
+ } else {
+ if (info.next_video == null) relatedStreamRootLayout.setVisibility(View.GONE);
+ relatedStreamExpandButton.setVisibility(View.GONE);
+ }
}
/*//////////////////////////////////////////////////////////////////////////
@@ -448,13 +590,10 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
actionBarHandler.setupMenu(menu, inflater);
- actionBarHandler.setupNavMenu(activity);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(true);
supportActionBar.setDisplayShowTitleEnabled(false);
- //noinspection deprecation
- supportActionBar.setNavigationMode(0);
}
}
@@ -464,13 +603,8 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
private void setupActionBarHandler(final StreamInfo info) {
- if (activity.getSupportActionBar() != null) {
- //noinspection deprecation
- activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- }
-
sortedStreamVideosList = Utils.getSortedStreamVideosList(activity, info.video_streams, info.video_only_streams, false);
- actionBarHandler.setupStreamList(sortedStreamVideosList);
+ actionBarHandler.setupStreamList(sortedStreamVideosList, spinnerToolbar);
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() {
@Override
public void onActionSelected(int selectedStreamId) {
@@ -496,22 +630,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
});
- actionBarHandler.setOnOpenInPopupListener(new ActionBarHandler.OnActionListener() {
- @Override
- public void onActionSelected(int selectedStreamId) {
- if (isLoading.get()) return;
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
- Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
- return;
- }
-
- Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
- Intent mIntent = NavigationHelper.getOpenVideoPlayerIntent(activity, PopupVideoPlayer.class, info, selectedStreamId);
- activity.startService(mIntent);
- }
- });
-
actionBarHandler.setOnPlayWithKodiListener(new ActionBarHandler.OnActionListener() {
@Override
public void onActionSelected(int selectedStreamId) {
@@ -586,59 +704,6 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
}
});
-
- if (info.audio_streams == null) {
- actionBarHandler.showAudioAction(false);
- } else {
- actionBarHandler.setOnPlayAudioListener(new ActionBarHandler.OnActionListener() {
- @Override
- public void onActionSelected(int selectedStreamId) {
- if (isLoading.get()) return;
-
- boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
- .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
- Intent intent;
- AudioStream audioStream = info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
- if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) {
- activity.startService(NavigationHelper.getOpenBackgroundPlayerIntent(activity, info, audioStream));
- Toast.makeText(activity, R.string.background_player_playing_toast, Toast.LENGTH_SHORT).show();
- } else {
- intent = new Intent();
- try {
- intent.setAction(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.parse(audioStream.url),
- MediaFormat.getMimeById(audioStream.format));
- intent.putExtra(Intent.EXTRA_TITLE, info.title);
- intent.putExtra("title", info.title);
- // HERE !!!
- activity.startActivity(intent);
- } catch (Exception e) {
- e.printStackTrace();
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setMessage(R.string.no_player_found)
- .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Intent intent = new Intent();
- intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
- activity.startActivity(intent);
- }
- })
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Log.i(TAG, "You unlocked a secret unicorn.");
- }
- });
- builder.create().show();
- Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:");
- e.printStackTrace();
- }
- }
- }
- });
- }
}
/*//////////////////////////////////////////////////////////////////////////
@@ -656,10 +721,9 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
public void pushToStack(String videoUrl, String videoTitle) {
-
+ if (DEBUG) Log.d(TAG, "pushToStack() called with: videoUrl = [" + videoUrl + "], videoTitle = [" + videoTitle + "]");
if (stack.size() > 0 && stack.peek().getUrl().equals(videoUrl)) return;
stack.push(new StackItem(videoUrl, videoTitle));
-
}
public void setTitleToUrl(String videoUrl, String videoTitle) {
@@ -670,7 +734,16 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
}
+ public void setStreamInfoToUrl(String videoUrl, StreamInfo info) {
+ if (info != null) {
+ for (StackItem stackItem : stack) {
+ if (stackItem.getUrl().equals(videoUrl)) stackItem.setInfo(info);
+ }
+ }
+ }
+
public boolean onActivityBackPressed() {
+ if (DEBUG) Log.d(TAG, "onActivityBackPressed() called");
// That means that we are on the start of the stack,
// return false to let the MainActivity handle the onBack
if (stack.size() == 1) return false;
@@ -678,18 +751,16 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
stack.pop();
// Get url from the new top
StackItem peek = stack.peek();
- selectAndLoadVideo(0, peek.getUrl(),
- peek.getTitle() != null && !peek.getTitle().isEmpty() ? peek.getTitle() : ""
- );
+
+ if (peek.getInfo() != null) selectAndHandleInfo(peek.getInfo());
+ else selectAndLoadVideo(0, peek.getUrl(), !TextUtils.isEmpty(peek.getTitle()) ? peek.getTitle() : "");
return true;
}
-
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
-
public void setAutoplay(boolean autoplay) {
this.autoPlayEnabled = autoplay;
}
@@ -700,52 +771,159 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
this.serviceId = serviceId;
}
- public void selectAndLoadVideo(int serviceId, String videoUrl, String videoTitle) {
- selectVideo(serviceId, videoUrl, videoTitle);
- loadSelectedVideo();
+ public void selectAndHandleInfo(StreamInfo info) {
+ selectAndHandleInfo(info, true);
}
- public void loadSelectedVideo() {
+ public void selectAndHandleInfo(StreamInfo info, boolean scrollToTop) {
+ if (DEBUG) Log.d(TAG, "selectAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]");
+ selectVideo(info.service_id, info.webpage_url, info.title);
+ prepareAndLoad(info, scrollToTop);
+ }
+
+ public void selectAndLoadVideo(int serviceId, String videoUrl, String videoTitle) {
+ selectAndLoadVideo(serviceId, videoUrl, videoTitle, true);
+ }
+
+ public void selectAndLoadVideo(int serviceId, String videoUrl, String videoTitle, boolean scrollToTop) {
+ if (DEBUG) {
+ Log.d(TAG, "selectAndLoadVideo() called with: serviceId = [" + serviceId + "], videoUrl = [" + videoUrl + "], videoTitle = [" + videoTitle + "], scrollToTop = [" + scrollToTop + "]");
+ }
+
+ selectVideo(serviceId, videoUrl, videoTitle);
+ prepareAndLoad(null, scrollToTop);
+ }
+
+ /**
+ * Prepare the UI for loading the info.
+ * If the argument info is not null, it'll be passed in {@link #handleStreamInfo(StreamInfo, boolean)}.
+ * If it is, check if the cache contains the info already.
+ * If the cache doesn't have the info, load from the network.
+ *
+ * @param info info to prepare and load, can be null
+ * @param scrollToTop whether or not scroll the scrollView to y = 0
+ */
+ public void prepareAndLoad(StreamInfo info, boolean scrollToTop) {
+ if (DEBUG) Log.d(TAG, "prepareAndLoad() called with: info = [" + info + "]");
isLoading.set(true);
+
+ // Only try to get from the cache if the passed info IS null
+ if (info == null && StreamInfoCache.getInstance().hasKey(videoUrl)) {
+ info = StreamInfoCache.getInstance().getFromKey(videoUrl);
+ }
+
+ if (info != null) selectVideo(info.service_id, info.webpage_url, info.title);
pushToStack(videoUrl, videoTitle);
if (curExtractorWorker != null && curExtractorWorker.isRunning()) curExtractorWorker.cancel();
+ animateView(spinnerToolbar, false, 200);
+ animateView(errorPanel, false, 200);
- if (activity.getSupportActionBar() != null) {
- //noinspection deprecation
- activity.getSupportActionBar().setNavigationMode(0);
- }
-
- animateView(contentRootLayout, false, 50, null);
-
+ videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
videoTitleTextView.setMaxLines(1);
- int scrollY = parallaxScrollRootView.getScrollY();
- if (scrollY < 30) animateView(videoTitleTextView, false, 200, new Runnable() {
- @Override
- public void run() {
- if (videoTitleTextView == null) return;
- videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
- animateView(videoTitleTextView, true, 400, null);
- }
- });
- else videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
- //videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
+ animateView(videoTitleTextView, true, 0);
+
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.GONE);
videoTitleRoot.setClickable(false);
- //thumbnailPlayButton.setVisibility(View.GONE);
- animateView(thumbnailPlayButton, false, 50, null);
- loadingProgressBar.setVisibility(View.VISIBLE);
-
+ animateView(thumbnailPlayButton, false, 50);
imageLoader.cancelDisplayTask(thumbnailImageView);
imageLoader.cancelDisplayTask(uploaderThumb);
thumbnailImageView.setImageBitmap(null);
uploaderThumb.setImageBitmap(null);
- curExtractorWorker = new StreamExtractorWorker(activity, serviceId, videoUrl, this);
- curExtractorWorker.start();
+ if (info != null) {
+ final StreamInfo infoFinal = info;
+ final boolean greaterThanThreshold = parallaxScrollRootView.getScrollY() >
+ (int) (getResources().getDisplayMetrics().heightPixels * .1f);
+
+ if (scrollToTop) {
+ if (greaterThanThreshold) parallaxScrollRootView.smoothScrollTo(0, 0);
+ else parallaxScrollRootView.scrollTo(0, 0);
+ }
+
+ animateView(contentRootLayoutHiding, false, greaterThanThreshold ? 250 : 0, new Runnable() {
+ @Override
+ public void run() {
+ handleStreamInfo(infoFinal, false);
+ isLoading.set(false);
+ showContentWithAnimation(greaterThanThreshold ? 120 : 200, 0, .02f);
+ }
+ });
+ } else {
+ if (scrollToTop) parallaxScrollRootView.smoothScrollTo(0, 0);
+ curExtractorWorker = new StreamExtractorWorker(activity, serviceId, videoUrl, this);
+ curExtractorWorker.start();
+ animateView(loadingProgressBar, true, 200);
+ animateView(contentRootLayoutHiding, false, 200);
+ }
+ }
+
+ private void handleStreamInfo(@NonNull StreamInfo info, boolean fromNetwork) {
+ if (DEBUG) Log.d(TAG, "handleStreamInfo() called with: info = [" + info + "]");
+ currentStreamInfo = info;
+ selectVideo(info.service_id, info.webpage_url, info.title);
+
+ loadingProgressBar.setVisibility(View.GONE);
+ animateView(thumbnailPlayButton, true, 200);
+
+ // Since newpipe is designed to work even if certain information is not available,
+ // the UI has to react on missing information.
+ if (fromNetwork) videoTitleTextView.setText(info.title);
+
+ if (!TextUtils.isEmpty(info.uploader)) uploaderTextView.setText(info.uploader);
+ uploaderTextView.setVisibility(!TextUtils.isEmpty(info.uploader) ? View.VISIBLE : View.GONE);
+ uploaderButton.setVisibility(!TextUtils.isEmpty(info.channel_url) ? View.VISIBLE : View.GONE);
+ uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
+
+ if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, activity));
+ videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE);
+
+ if (info.dislike_count == -1 && info.like_count == -1) {
+ thumbsDownImageView.setVisibility(View.VISIBLE);
+ thumbsUpImageView.setVisibility(View.VISIBLE);
+ thumbsUpTextView.setVisibility(View.GONE);
+ thumbsDownTextView.setVisibility(View.GONE);
+
+ thumbsDisabledTextView.setVisibility(View.VISIBLE);
+ } else {
+ thumbsDisabledTextView.setVisibility(View.GONE);
+
+ if (info.dislike_count >= 0) thumbsDownTextView.setText(getShortCount((long) info.dislike_count));
+ thumbsDownTextView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
+ thumbsDownImageView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
+
+ if (info.like_count >= 0) thumbsUpTextView.setText(getShortCount((long) info.like_count));
+ thumbsUpTextView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
+ thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
+ }
+
+ if (!TextUtils.isEmpty(info.upload_date)) videoUploadDateView.setText(Localization.localizeDate(info.upload_date, activity));
+ videoUploadDateView.setVisibility(!TextUtils.isEmpty(info.upload_date) ? View.VISIBLE : View.GONE);
+
+ if (!TextUtils.isEmpty(info.description)) { //noinspection deprecation
+ videoDescriptionView.setText(Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description));
+ }
+ videoDescriptionView.setVisibility(!TextUtils.isEmpty(info.description) ? View.VISIBLE : View.GONE);
+
+ videoDescriptionRootLayout.setVisibility(View.GONE);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ videoTitleToggleArrow.setVisibility(View.VISIBLE);
+ videoTitleRoot.setClickable(true);
+
+ animateView(spinnerToolbar, true, 500);
+ setupActionBarHandler(info);
+ initThumbnailViews(info);
+ initRelatedVideos(info);
+ if (wasRelatedStreamsExpanded) {
+ toggleExpandRelatedVideos(currentStreamInfo);
+ wasRelatedStreamsExpanded = false;
+ }
+
+ setTitleToUrl(info.webpage_url, info.title);
+ setStreamInfoToUrl(info.webpage_url, info);
}
public void playVideo(StreamInfo info) {
@@ -813,6 +991,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
TypedValue typedValue = new TypedValue();
activity.getTheme().resolveAttribute(R.attr.separatorColor, typedValue, true);
separator.setBackgroundColor(typedValue.data);
+
return separator;
}
@@ -827,118 +1006,77 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
thumbnailBackgroundButton.setMinimumHeight(height);
}
- /**
- * Animate the view
- *
- * @param view view that will be animated
- * @param enterOrExit true to enter, false to exit
- * @param duration how long the animation will take, in milliseconds
- * @param execOnEnd runnable that will be executed when the animation ends
- */
- public void animateView(final View view, final boolean enterOrExit, long duration, final Runnable execOnEnd) {
- if (view.getVisibility() == View.VISIBLE && enterOrExit) {
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
- view.setAlpha(1f);
- if (execOnEnd != null) execOnEnd.run();
- return;
- } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) {
- view.animate().setListener(null).cancel();
- view.setVisibility(View.GONE);
- view.setAlpha(0f);
- if (execOnEnd != null) execOnEnd.run();
- return;
- }
-
- view.animate().setListener(null).cancel();
- view.setVisibility(View.VISIBLE);
-
- if (enterOrExit) {
- view.animate().alpha(1f).setDuration(duration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (execOnEnd != null) execOnEnd.run();
- }
- }).start();
+ public String getShortCount(Long viewCount) {
+ if (viewCount >= 1000000000) {
+ return Long.toString(viewCount / 1000000000) + billion;
+ } else if (viewCount >= 1000000) {
+ return Long.toString(viewCount / 1000000) + million;
+ } else if (viewCount >= 1000) {
+ return Long.toString(viewCount / 1000) + thousand;
} else {
- view.animate().alpha(0f)
- .setDuration(duration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.GONE);
- if (execOnEnd != null) execOnEnd.run();
- }
- })
- .start();
+ return Long.toString(viewCount);
}
}
+ private void showContentWithAnimation(long duration, long delay, @FloatRange(from = 0.0f, to = 1.0f) float translationPercent) {
+ int translationY = (int) (getResources().getDisplayMetrics().heightPixels *
+ (translationPercent > 0.0f ? translationPercent : .12f));
+
+ contentRootLayoutHiding.animate().setListener(null).cancel();
+ contentRootLayoutHiding.setAlpha(0f);
+ contentRootLayoutHiding.setTranslationY(translationY);
+ contentRootLayoutHiding.setVisibility(View.VISIBLE);
+ contentRootLayoutHiding.animate().alpha(1f).translationY(0)
+ .setStartDelay(delay).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()).start();
+
+ uploaderRootLayout.animate().setListener(null).cancel();
+ uploaderRootLayout.setAlpha(0f);
+ uploaderRootLayout.setTranslationY(translationY);
+ uploaderRootLayout.setVisibility(View.VISIBLE);
+ uploaderRootLayout.animate().alpha(1f).translationY(0)
+ .setStartDelay((long) (duration * .5f) + delay).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()).start();
+
+ if (showRelatedStreams) {
+ relatedStreamRootLayout.animate().setListener(null).cancel();
+ relatedStreamRootLayout.setAlpha(0f);
+ relatedStreamRootLayout.setTranslationY(translationY);
+ relatedStreamRootLayout.setVisibility(View.VISIBLE);
+ relatedStreamRootLayout.animate().alpha(1f).translationY(0)
+ .setStartDelay((long) (duration * .8f) + delay).setDuration(duration).setInterpolator(new FastOutSlowInInterpolator()).start();
+ }
+ }
/*//////////////////////////////////////////////////////////////////////////
// OnStreamInfoReceivedListener callbacks
//////////////////////////////////////////////////////////////////////////*/
+ private void setErrorImage(final int imageResource) {
+ if (thumbnailImageView == null || activity == null) return;
+ thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource));
+ animateView(thumbnailImageView, false, 0, new Runnable() {
+ @Override
+ public void run() {
+ animateView(thumbnailImageView, true, 500);
+ }
+ });
+ }
+
+ @Override
+ protected void setErrorMessage(String message, boolean showRetryButton) {
+ super.setErrorMessage(message, showRetryButton);
+
+ if (!TextUtils.isEmpty(videoUrl)) StreamInfoCache.getInstance().removeInfo(videoUrl);
+ currentStreamInfo = null;
+ }
+
@Override
public void onReceive(StreamInfo info) {
+ if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + info + "]");
if (info == null || isRemoving() || !isVisible()) return;
- currentStreamInfo = info;
- loadingProgressBar.setVisibility(View.GONE);
- animateView(thumbnailPlayButton, true, 200, null);
- parallaxScrollRootView.scrollTo(0, 0);
+ handleStreamInfo(info, true);
+ showContentWithAnimation(300, 0, 0);
- // Since newpipe is designed to work even if certain information is not available,
- // the UI has to react on missing information.
- videoTitle = info.title;
- videoTitleTextView.setText(info.title);
- if (!info.uploader.isEmpty()) uploaderTextView.setText(info.uploader);
- uploaderTextView.setVisibility(!info.uploader.isEmpty() ? View.VISIBLE : View.GONE);
- uploaderButton.setVisibility(!info.channel_url.isEmpty() ? View.VISIBLE : View.GONE);
- uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
-
- if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, activity));
- videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE);
-
- if (info.dislike_count == -1 && info.like_count == -1) {
- thumbsDownImageView.setVisibility(View.VISIBLE);
- thumbsUpImageView.setVisibility(View.VISIBLE);
- thumbsUpTextView.setVisibility(View.GONE);
- thumbsDownTextView.setVisibility(View.GONE);
-
- thumbsDisabledTextView.setVisibility(View.VISIBLE);
- } else {
- thumbsDisabledTextView.setVisibility(View.GONE);
-
- if (info.dislike_count >= 0) thumbsDownTextView.setText(Localization.localizeNumber(info.dislike_count, activity));
- thumbsDownTextView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
- thumbsDownImageView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
-
- if (info.like_count >= 0) thumbsUpTextView.setText(Localization.localizeNumber(info.like_count, activity));
- thumbsUpTextView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
- thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
- }
-
- if (!info.upload_date.isEmpty()) videoUploadDateView.setText(Localization.localizeDate(info.upload_date, activity));
- videoUploadDateView.setVisibility(!info.upload_date.isEmpty() ? View.VISIBLE : View.GONE);
-
- if (!info.description.isEmpty()) { //noinspection deprecation
- videoDescriptionView.setText(Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description));
- }
- videoDescriptionView.setVisibility(!info.description.isEmpty() ? View.VISIBLE : View.GONE);
-
- videoDescriptionRootLayout.setVisibility(View.GONE);
- videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
- videoTitleToggleArrow.setVisibility(View.VISIBLE);
- videoTitleRoot.setClickable(true);
-
- setupActionBarHandler(info);
- initRelatedVideos(info);
- initThumbnailViews(info);
-
- setTitleToUrl(info.webpage_url, info.title);
-
- animateView(contentRootLayout, true, 200, null);
+ animateView(loadingProgressBar, false, 200);
if (autoPlayEnabled) {
playVideo(info);
@@ -946,15 +1084,17 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
autoPlayEnabled = false;
}
+ StreamInfoCache.getInstance().putInfo(info);
isLoading.set(false);
+
}
@Override
public void onError(int messageId) {
- Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
- loadingProgressBar.setVisibility(View.GONE);
- videoTitleTextView.setText(getString(messageId));
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
+ if (DEBUG) Log.d(TAG, "onError() called with: messageId = [" + messageId + "]");
+ //Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
+ setErrorImage(R.drawable.not_available_monkey);
+ setErrorMessage(getString(messageId), true);
}
@Override
@@ -962,12 +1102,13 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
startActivityForResult(new Intent(activity, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
+
+ setErrorMessage(getString(R.string.recaptcha_request_toast), false);
}
@Override
public void onBlockedByGemaError() {
- loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.gruese_die_gema));
+
thumbnailBackgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -978,21 +1119,20 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork
}
});
- Toast.makeText(activity, R.string.blocked_by_gema, Toast.LENGTH_LONG).show();
+ setErrorImage(R.drawable.gruese_die_gema);
+ setErrorMessage(getString(R.string.blocked_by_gema), false);
}
@Override
public void onContentErrorWithMessage(int messageId) {
- loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
- Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
+ setErrorImage(R.drawable.not_available_monkey);
+ setErrorMessage(getString(messageId), false);
}
@Override
public void onContentError() {
- loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
- Toast.makeText(activity, R.string.content_not_available, Toast.LENGTH_LONG).show();
+ setErrorImage(R.drawable.not_available_monkey);
+ setErrorMessage(getString(R.string.content_not_available), false);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
index e95473b2b..8071f4a36 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
@@ -3,245 +3,189 @@ package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.SearchView;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
import android.util.Log;
+import android.view.KeyEvent;
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.animation.DecelerateInterpolator;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import android.widget.ProgressBar;
+import android.widget.AdapterView;
+import android.widget.AutoCompleteTextView;
+import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
-import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
-import org.schabi.newpipe.fragments.OnItemSelectedListener;
+import org.schabi.newpipe.fragments.BaseFragment;
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.NavigationHelper;
+import org.schabi.newpipe.workers.SearchWorker;
+import org.schabi.newpipe.workers.SuggestionWorker;
+import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.List;
-import static android.app.Activity.RESULT_OK;
-import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
-
-/**
- * Created by Christian Schabesberger on 02.08.16.
- *
- * Copyright (C) Christian Schabesberger 2016
- * SearchFragment.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
-
-public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener, SearchWorker.SearchWorkerResultListener {
-
- private static final String TAG = SearchFragment.class.toString();
-
+public class SearchFragment extends BaseFragment implements SuggestionWorker.OnSuggestionResult, SearchWorker.OnSearchResult {
+ private final String TAG = "SearchFragment@" + Integer.toHexString(hashCode());
// savedInstanceBundle arguments
- private static final String QUERY = "query";
- private static final String STREAMING_SERVICE = "streaming_service";
+ private static final String QUERY_KEY = "query_key";
+ private static final String PAGE_NUMBER_KEY = "page_number_key";
+ private static final String SERVICE_KEY = "service_key";
+ private static final String INFO_LIST_KEY = "info_list_key";
+ private static final String WAS_LOADING_KEY = "was_loading_key";
+ private static final String ERROR_KEY = "error_key";
+ private static final String FILTER_CHECKED_ID_KEY = "filter_checked_id_key";
- private int streamingServiceId = -1;
+ /*//////////////////////////////////////////////////////////////////////////
+ // Search
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private int filterItemCheckedId = -1;
+ private EnumSet filter = EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
+
+ private int serviceId = -1;
private String searchQuery = "";
- private boolean isLoading = false;
-
- @SuppressWarnings("FieldCanBeLocal")
- private SearchView searchView;
- private RecyclerView recyclerView;
- private ProgressBar loadingIndicator;
private int pageNumber = 0;
+
+ private SearchWorker curSearchWorker;
+ private SuggestionWorker curSuggestionWorker;
private SuggestionListAdapter suggestionListAdapter;
private InfoListAdapter infoListAdapter;
- private LinearLayoutManager streamInfoListLayoutManager;
- private EnumSet filter = EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
- private OnItemSelectedListener onItemSelectedListener;
+ /*//////////////////////////////////////////////////////////////////////////
+ // Views
+ //////////////////////////////////////////////////////////////////////////*/
- /**
- * Mandatory empty constructor for the fragment manager to instantiate the
- * fragment (e.g. upon screen orientation changes).
- */
- public SearchFragment() {
- }
+ private View searchToolbarContainer;
+ private AutoCompleteTextView searchEditText;
+ private View searchClear;
- @SuppressWarnings("unused")
- public static SearchFragment newInstance(int streamingServiceId, String searchQuery) {
- Bundle args = new Bundle();
- args.putInt(STREAMING_SERVICE, streamingServiceId);
- args.putString(QUERY, searchQuery);
- SearchFragment fragment = new SearchFragment();
- fragment.setArguments(args);
- return fragment;
+ private RecyclerView resultRecyclerView;
+
+ /*////////////////////////////////////////////////////////////////////////*/
+
+ public static SearchFragment getInstance(int serviceId, String query) {
+ SearchFragment searchFragment = new SearchFragment();
+ searchFragment.setQuery(serviceId, query);
+ return searchFragment;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- onItemSelectedListener = ((OnItemSelectedListener) context);
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- searchQuery = "";
- isLoading = false;
- if (savedInstanceState != null) {
- searchQuery = savedInstanceState.getString(QUERY);
- streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
- } else {
- try {
- Bundle args = getArguments();
- if (args != null) {
- searchQuery = args.getString(QUERY);
- streamingServiceId = args.getInt(STREAMING_SERVICE);
- } else {
- streamingServiceId = NewPipe.getIdOfService("Youtube");
- }
- } catch (Exception e) {
- e.printStackTrace();
- ErrorActivity.reportError(getActivity(), e, null,
- getActivity().findViewById(android.R.id.content),
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- NewPipe.getNameOfService(streamingServiceId),
- "", R.string.general_error));
- }
- }
-
+ if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
setHasOptionsMenu(true);
-
- SearchWorker sw = SearchWorker.getInstance();
- sw.setSearchWorkerResultListener(this);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_search, container, false);
-
- Context context = view.getContext();
- loadingIndicator = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
- 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.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false));
- infoListAdapter.showFooter(false);
- infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
- @Override
- public void selected(int serviceId, String url, String title) {
- NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
- }
- });
- infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
- @Override
- public void selected(int serviceId, String url, String title) {
- NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
- }
- });
- recyclerView.setAdapter(infoListAdapter);
- recyclerView.clearOnScrollListeners();
- recyclerView.addOnScrollListener(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++;
- recyclerView.post(new Runnable() {
- @Override
- public void run() {
- infoListAdapter.showFooter(true);
-
- }
- });
- search(searchQuery, pageNumber);
- }
- }
- }
- });
-
- return view;
- }
-
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (!searchQuery.isEmpty()) {
- search(searchQuery);
+ if (savedInstanceState != null) {
+ searchQuery = savedInstanceState.getString(QUERY_KEY);
+ serviceId = savedInstanceState.getInt(SERVICE_KEY, 0);
+ pageNumber = savedInstanceState.getInt(PAGE_NUMBER_KEY, 0);
+ wasLoading.set(savedInstanceState.getBoolean(WAS_LOADING_KEY, false));
+ filterItemCheckedId = savedInstanceState.getInt(FILTER_CHECKED_ID_KEY, 0);
}
}
@Override
- public void onDestroyView() {
- super.onDestroyView();
- recyclerView.removeAllViews();
- infoListAdapter.clearSteamItemList();
- recyclerView = null;
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]");
+ return inflater.inflate(R.layout.fragment_search, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View rootView, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(rootView, savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onViewCreated() called with: rootView = [" + rootView + "], savedInstanceState = [" + savedInstanceState + "]");
+
+ if (savedInstanceState != null && savedInstanceState.getBoolean(ERROR_KEY, false)) {
+ search(searchQuery, 0, true);
+ }
+
}
@Override
public void onResume() {
super.onResume();
- if (isLoading && !searchQuery.isEmpty()) {
- search(searchQuery);
+ if (DEBUG) Log.d(TAG, "onResume() called");
+ if (wasLoading.getAndSet(false) && !TextUtils.isEmpty(searchQuery)) {
+ if (pageNumber > 0) search(searchQuery, pageNumber);
+ else search(searchQuery, 0, true);
}
}
@Override
public void onStop() {
super.onStop();
- SearchWorker.getInstance().terminate();
+ if (DEBUG) Log.d(TAG, "onStop() called");
+
+ hideSoftKeyboard(searchEditText);
+
+ wasLoading.set(curSearchWorker != null && curSearchWorker.isRunning());
+ if (curSearchWorker != null && curSearchWorker.isRunning()) curSearchWorker.cancel();
+ if (curSuggestionWorker != null && curSuggestionWorker.isRunning()) curSuggestionWorker.cancel();
+ }
+
+ @Override
+ public void onDestroyView() {
+ if (DEBUG) Log.d(TAG, "onDestroyView() called");
+ unsetSearchListeners();
+
+ resultRecyclerView.removeAllViews();
+
+ searchToolbarContainer = null;
+ searchEditText = null;
+ searchClear = null;
+
+ resultRecyclerView = null;
+
+ super.onDestroyView();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putString(QUERY, searchQuery);
- outState.putInt(STREAMING_SERVICE, streamingServiceId);
+ if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]");
+
+ String query = searchEditText != null && !TextUtils.isEmpty(searchEditText.getText().toString())
+ ? searchEditText.getText().toString() : searchQuery;
+ outState.putString(QUERY_KEY, query);
+ outState.putInt(SERVICE_KEY, serviceId);
+ outState.putInt(PAGE_NUMBER_KEY, pageNumber);
+ outState.putSerializable(INFO_LIST_KEY, ((ArrayList) infoListAdapter.getItemsList()));
+ outState.putBoolean(WAS_LOADING_KEY, curSearchWorker != null && curSearchWorker.isRunning());
+
+ if (errorPanel != null && errorPanel.getVisibility() == View.VISIBLE) outState.putBoolean(ERROR_KEY, true);
+ if (filterItemCheckedId != -1) outState.putInt(FILTER_CHECKED_ID_KEY, filterItemCheckedId);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
- case RECAPTCHA_REQUEST:
- if (resultCode == RESULT_OK && searchQuery.length() != 0) {
- search(searchQuery);
+ case ReCaptchaActivity.RECAPTCHA_REQUEST:
+ if (resultCode == Activity.RESULT_OK && searchQuery.length() != 0) {
+ search(searchQuery, pageNumber, true);
} else Log.e(TAG, "ReCaptcha failed");
break;
@@ -251,6 +195,88 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
}
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init's
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ protected void initViews(View rootView, Bundle savedInstanceState) {
+ super.initViews(rootView, savedInstanceState);
+ resultRecyclerView = ((RecyclerView) rootView.findViewById(R.id.result_list_view));
+ resultRecyclerView.setLayoutManager(new LinearLayoutManager(activity));
+
+ if (infoListAdapter == null) {
+ infoListAdapter = new InfoListAdapter(getActivity(), getActivity().findViewById(android.R.id.content));
+ if (savedInstanceState != null) {
+ //noinspection unchecked
+ ArrayList serializable = (ArrayList) savedInstanceState.getSerializable(INFO_LIST_KEY);
+ infoListAdapter.addInfoItemList(serializable);
+ }
+
+ infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, resultRecyclerView, false));
+ infoListAdapter.showFooter(false);
+ infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
+ @Override
+ public void selected(int serviceId, String url, String title) {
+ NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
+ }
+ });
+ infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
+ @Override
+ public void selected(int serviceId, String url, String title) {
+ NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
+ }
+ });
+ }
+
+ resultRecyclerView.setAdapter(infoListAdapter);
+ }
+
+ @Override
+ protected void initListeners() {
+ super.initListeners();
+ resultRecyclerView.clearOnScrollListeners();
+ resultRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ int pastVisiblesItems, visibleItemCount, totalItemCount;
+ super.onScrolled(recyclerView, dx, dy);
+ //check for scroll down
+ if (dy > 0) {
+ LinearLayoutManager layoutManager = (LinearLayoutManager) resultRecyclerView.getLayoutManager();
+ visibleItemCount = resultRecyclerView.getLayoutManager().getChildCount();
+ totalItemCount = resultRecyclerView.getLayoutManager().getItemCount();
+ pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
+
+ if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading.get()) {
+ pageNumber++;
+ recyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ infoListAdapter.showFooter(true);
+ }
+ });
+ search(searchQuery, pageNumber);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void reloadContent() {
+ if (DEBUG) Log.d(TAG, "reloadContent() called");
+ if (!TextUtils.isEmpty(searchQuery) || (searchEditText != null && !TextUtils.isEmpty(searchEditText.getText()))) {
+ search(!TextUtils.isEmpty(searchQuery) ? searchQuery : searchEditText.getText().toString(), 0, true);
+ } else {
+ if (searchEditText != null) {
+ searchEditText.setText("");
+ showSoftKeyboard(searchEditText);
+ }
+ animateView(errorPanel, false, 200);
+ }
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@@ -258,22 +284,47 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
- if (supportActionBar != null) {
- supportActionBar.setDisplayHomeAsUpEnabled(false);
- supportActionBar.setDisplayShowTitleEnabled(false);
- //noinspection deprecation
- supportActionBar.setNavigationMode(0);
- }
+ if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]");
inflater.inflate(R.menu.search_menu, menu);
- MenuItem searchItem = menu.findItem(R.id.action_search);
- searchView = (SearchView) searchItem.getActionView();
- setupSearchView(searchView);
+ ActionBar supportActionBar = activity.getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayShowTitleEnabled(false);
+ supportActionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
+ searchEditText = (AutoCompleteTextView) searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
+ searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
+ setupSearchView();
+
+ restoreFilterChecked(menu, filterItemCheckedId);
+ }
+
+ private void restoreFilterChecked(Menu menu, int itemId) {
+ if (itemId != -1) {
+ MenuItem item = menu.findItem(itemId);
+ if (item == null) return;
+
+ item.setChecked(true);
+ switch (itemId) {
+ case R.id.menu_filter_all:
+ filter = EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL);
+ break;
+ case R.id.menu_filter_video:
+ filter = EnumSet.of(SearchEngine.Filter.STREAM);
+ break;
+ case R.id.menu_filter_channel:
+ filter = EnumSet.of(SearchEngine.Filter.CHANNEL);
+ break;
+ }
+ }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
+ if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]");
+
switch (item.getItemId()) {
case R.id.menu_filter_all:
changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL));
@@ -289,119 +340,238 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
}
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Search
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private TextWatcher textWatcher;
+
+ private void setupSearchView() {
+ searchEditText.setText(searchQuery != null ? searchQuery : "");
+ searchEditText.setHint(getString(R.string.search) + "...");
+ ////searchEditText.setCursorVisible(true);
+
+ suggestionListAdapter = new SuggestionListAdapter(activity);
+ searchEditText.setAdapter(suggestionListAdapter);
+
+
+ if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) {
+ searchToolbarContainer.setTranslationX(100);
+ searchToolbarContainer.setAlpha(0f);
+ searchToolbarContainer.setVisibility(View.VISIBLE);
+ searchToolbarContainer.animate().translationX(0).alpha(1f).setDuration(400).setInterpolator(new DecelerateInterpolator()).start();
+ } else {
+ searchToolbarContainer.setTranslationX(0);
+ searchToolbarContainer.setAlpha(1f);
+ searchToolbarContainer.setVisibility(View.VISIBLE);
+ }
+
+ //
+ initSearchListeners();
+
+ if (TextUtils.isEmpty(searchQuery)) showSoftKeyboard(searchEditText);
+ else hideSoftKeyboard(searchEditText);
+
+ if (!TextUtils.isEmpty(searchQuery) && searchQuery.length() > 2 && suggestionListAdapter != null && suggestionListAdapter.isEmpty()) {
+ searchSuggestions(searchQuery);
+ }
+ }
+
+ private void initSearchListeners() {
+ searchClear.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]");
+ if (TextUtils.isEmpty(searchEditText.getText())) {
+ NavigationHelper.openMainActivity(activity);
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ searchEditText.setText("", false);
+ } else searchEditText.setText("");
+ suggestionListAdapter.updateAdapter(new ArrayList());
+ showSoftKeyboard(searchEditText);
+ }
+ });
+
+ searchClear.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (DEBUG) Log.d(TAG, "onLongClick() called with: v = [" + v + "]");
+ showMenuTooltip(v, getString(R.string.clear));
+ return true;
+ }
+ });
+
+ searchEditText.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ searchEditText.showDropDown();
+ }
+ });
+
+ searchEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) searchEditText.showDropDown();
+ }
+ });
+
+ searchEditText.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ if (DEBUG) Log.d(TAG, "onItemClick() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]");
+ String s = suggestionListAdapter.getSuggestion(position);
+ if (DEBUG) Log.d(TAG, "onItemClick text = " + s);
+ submitQuery(s);
+ }
+ });
+
+
+ if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
+ textWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ String newText = searchEditText.getText().toString();
+ if (!TextUtils.isEmpty(newText) && newText.length() > 1) onQueryTextChange(newText);
+ }
+ };
+ searchEditText.addTextChangedListener(textWatcher);
+
+ searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (DEBUG) Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]");
+ if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) {
+ submitQuery(searchEditText.getText().toString());
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void unsetSearchListeners() {
+ searchClear.setOnClickListener(null);
+ searchClear.setOnLongClickListener(null);
+ if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher);
+ searchEditText.setOnClickListener(null);
+ searchEditText.setOnItemClickListener(null);
+ searchEditText.setOnFocusChangeListener(null);
+ searchEditText.setOnEditorActionListener(null);
+
+ textWatcher = null;
+ }
+
+ public void showSoftKeyboard(View view) {
+ if (DEBUG) Log.d(TAG, "showSoftKeyboard() called with: view = [" + view + "]");
+ if (view == null) return;
+
+ if (view.requestFocus()) {
+ InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
+ }
+ }
+
+ public void hideSoftKeyboard(View view) {
+ if (DEBUG) Log.d(TAG, "hideSoftKeyboard() called with: view = [" + view + "]");
+ if (view == null) return;
+
+ InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+
+ view.clearFocus();
+ }
+
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeFilter(MenuItem item, EnumSet filter) {
this.filter = filter;
+ this.filterItemCheckedId = item.getItemId();
item.setChecked(true);
- if (searchQuery != null && !searchQuery.isEmpty()) {
- Log.e(TAG, "Fuck+ " + searchQuery);
- search(searchQuery);
- }
+ if (searchQuery != null && !searchQuery.isEmpty()) search(searchQuery, 0, true);
}
- private void setupSearchView(SearchView searchView) {
- suggestionListAdapter = new SuggestionListAdapter(getActivity());
- searchView.setSuggestionsAdapter(suggestionListAdapter);
- searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
- searchView.setOnQueryTextListener(this);
- if (searchQuery != null && !searchQuery.isEmpty()) {
- searchView.setQuery(searchQuery, false);
- searchView.setIconifiedByDefault(false);
- }
- }
-
- private void search(String query) {
- infoListAdapter.clearSteamItemList();
- infoListAdapter.showFooter(false);
- pageNumber = 0;
+ public void submitQuery(String query) {
+ if (DEBUG) Log.d(TAG, "submitQuery() called with: query = [" + query + "]");
+ if (query.isEmpty()) return;
+ search(query, 0, true);
searchQuery = query;
- search(query, pageNumber);
- hideBackground();
- loadingIndicator.setVisibility(View.VISIBLE);
}
- private void search(String query, int page) {
- isLoading = true;
- SearchWorker sw = SearchWorker.getInstance();
- sw.search(streamingServiceId,
- query,
- page,
- getActivity(),
- filter);
+ public void onQueryTextChange(String newText) {
+ if (DEBUG) Log.d(TAG, "onQueryTextChange() called with: newText = [" + newText + "]");
+ if (!newText.isEmpty()) searchSuggestions(newText);
}
- private void setDoneLoading() {
- isLoading = false;
- loadingIndicator.setVisibility(View.GONE);
- infoListAdapter.showFooter(false);
- }
-
- /**
- * Hides the "dummy" background when no results are shown
- */
- private void hideBackground() {
- View view = getView();
- if (view == null) return;
- view.findViewById(R.id.mainBG).setVisibility(View.GONE);
+ private void setQuery(int serviceId, String searchQuery) {
+ this.serviceId = serviceId;
+ this.searchQuery = searchQuery;
}
private void searchSuggestions(String query) {
- SuggestionSearchRunnable suggestionSearchRunnable =
- new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
- Thread suggestionThread = new Thread(suggestionSearchRunnable);
- suggestionThread.start();
+ if (DEBUG) Log.d(TAG, "searchSuggestions() called with: query = [" + query + "]");
+ if (curSuggestionWorker != null && curSuggestionWorker.isRunning()) curSuggestionWorker.cancel();
+ curSuggestionWorker = SuggestionWorker.startForQuery(activity, serviceId, query, this);
}
- public boolean isMainBgVisible() {
- return getActivity().findViewById(R.id.mainBG).getVisibility() == View.VISIBLE;
+ private void search(String query, int pageNumber) {
+ if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "], pageNumber = [" + pageNumber + "]");
+ search(query, pageNumber, false);
+ }
+
+ private void search(String query, int pageNumber, boolean clearList) {
+ if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "], pageNumber = [" + pageNumber + "], clearList = [" + clearList + "]");
+ isLoading.set(true);
+ hideSoftKeyboard(searchEditText);
+
+ searchQuery = query;
+ this.pageNumber = pageNumber;
+
+ if (clearList) {
+ animateView(resultRecyclerView, false, 50);
+ infoListAdapter.clearStreamItemList();
+ infoListAdapter.showFooter(false);
+ animateView(loadingProgressBar, true, 200);
+ }
+ animateView(errorPanel, false, 200);
+
+ if (curSearchWorker != null && curSearchWorker.isRunning()) curSearchWorker.cancel();
+ curSearchWorker = SearchWorker.startForQuery(activity, serviceId, query, pageNumber, filter, this);
+ }
+
+ protected void setErrorMessage(String message, boolean showRetryButton) {
+ super.setErrorMessage(message, showRetryButton);
+
+ animateView(resultRecyclerView, false, 400);
+ hideSoftKeyboard(searchEditText);
}
/*//////////////////////////////////////////////////////////////////////////
- // OnQueryTextListener
+ // OnSuggestionResult
//////////////////////////////////////////////////////////////////////////*/
@Override
- public boolean onQueryTextSubmit(String query) {
- Activity a = getActivity();
- try {
- 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,
- NewPipe.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();
- }
- return true;
+ public void onSuggestionResult(@NonNull List suggestions) {
+ if (DEBUG) Log.d(TAG, "onSuggestionResult() called with: suggestions = [" + suggestions + "]");
+ suggestionListAdapter.updateAdapter(suggestions);
}
@Override
- public boolean onQueryTextChange(String newText) {
- if (!newText.isEmpty()) {
- searchSuggestions(newText);
- }
- return true;
+ public void onSuggestionError(int messageId) {
+ if (DEBUG) Log.d(TAG, "onSuggestionError() called with: messageId = [" + messageId + "]");
+ setErrorMessage(getString(messageId), true);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -409,32 +579,35 @@ public class SearchFragment extends Fragment implements SearchView.OnQueryTextLi
//////////////////////////////////////////////////////////////////////////*/
@Override
- public void onResult(SearchResult result) {
+ public void onSearchResult(SearchResult result) {
+ if (DEBUG) Log.d(TAG, "onSearchResult() called with: result = [" + result + "]");
infoListAdapter.addInfoItemList(result.resultList);
- setDoneLoading();
+ animateView(resultRecyclerView, true, 400);
+ animateView(loadingProgressBar, false, 200);
+ isLoading.set(false);
}
@Override
- public void onNothingFound(int stringResource) {
- //setListShown(true);
- Toast.makeText(getActivity(), getString(stringResource), Toast.LENGTH_SHORT).show();
- setDoneLoading();
+ public void onNothingFound(String message) {
+ if (DEBUG) Log.d(TAG, "onNothingFound() called with: messageId = [" + message + "]");
+ setErrorMessage(message, false);
}
@Override
- public void onError(String message) {
- //setListShown(true);
- Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
- setDoneLoading();
+ public void onSearchError(int messageId) {
+ if (DEBUG) Log.d(TAG, "onSearchError() called with: messageId = [" + messageId + "]");
+ //Toast.makeText(getActivity(), messageId, Toast.LENGTH_LONG).show();
+ setErrorMessage(getString(messageId), true);
}
@Override
public void onReCaptchaChallenge() {
- Toast.makeText(getActivity(), "ReCaptcha Challenge requested",
- Toast.LENGTH_LONG).show();
+ if (DEBUG) Log.d(TAG, "onReCaptchaChallenge() called");
+ Toast.makeText(getActivity(), R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
+ setErrorMessage(getString(R.string.recaptcha_request_toast), false);
// Starting ReCaptcha Challenge Activity
- startActivityForResult(new Intent(getActivity(), ReCaptchaActivity.class), RECAPTCHA_REQUEST);
+ startActivityForResult(new Intent(getActivity(), ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchWorker.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchWorker.java
deleted file mode 100644
index 582df9950..000000000
--- a/app/src/main/java/org/schabi/newpipe/fragments/search/SearchWorker.java
+++ /dev/null
@@ -1,216 +0,0 @@
-package org.schabi.newpipe.fragments.search;
-
-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 org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
-import org.schabi.newpipe.extractor.search.SearchEngine;
-import org.schabi.newpipe.extractor.search.SearchResult;
-import org.schabi.newpipe.report.ErrorActivity;
-
-import java.io.IOException;
-import java.util.EnumSet;
-
-/**
- * Created by Christian Schabesberger on 02.08.16.
- *
- * Copyright (C) Christian Schabesberger 2016
- * SearchWorker.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
-
-
-public class SearchWorker {
- private static final String TAG = SearchWorker.class.toString();
-
- public interface SearchWorkerResultListener {
- void onResult(SearchResult result);
- void onNothingFound(final int stringResource);
- void onError(String message);
- void onReCaptchaChallenge();
- }
-
- 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) {
- searchWorkerResultListener.onResult(result);
- }
- }
- }
-
- private class SearchRunnable implements Runnable {
- public static final String YOUTUBE = "Youtube";
- private final String query;
- private final int page;
- private final EnumSet filter;
- final Handler h = new Handler();
- private volatile boolean runs = true;
- private Activity a = null;
- private int serviceId = -1;
- public SearchRunnable(int serviceId,
- String query,
- int page,
- EnumSet filter,
- Activity activity,
- int requestId) {
- this.serviceId = serviceId;
- this.query = query;
- this.page = page;
- this.filter = filter;
- this.a = activity;
- }
- void terminate() {
- runs = false;
- }
- @Override
- public void run() {
- final String serviceName = NewPipe.getNameOfService(serviceId);
- SearchResult result = null;
- SearchEngine engine = null;
-
- try {
- engine = NewPipe.getService(serviceId)
- .getSearchEngineInstance();
- } 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, filter);
- if(runs) {
- h.post(new ResultRunnable(result, requestId));
- }
-
- // look for errors during extraction
- // soft errors:
- View rootView = a.findViewById(android.R.id.content);
- if(result != null &&
- !result.errors.isEmpty()) {
- Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
- for(Throwable e : result.errors) {
- e.printStackTrace();
- Log.e(TAG, "------");
- }
-
- if(result.resultList.isEmpty()&& !result.errors.isEmpty()) {
- // if it compleatly failes dont show snackbar, instead show error directlry
- ErrorActivity.reportError(h, a, result.errors, null, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- serviceName, query, R.string.parsing_error));
- } else {
- // if it partly show snackbar
- ErrorActivity.reportError(h, a, result.errors, null, rootView,
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- serviceName, query, R.string.light_parsing_error));
- }
- }
- // hard errors:
- } catch (ReCaptchaException e) {
- h.post(new Runnable() {
- @Override
- public void run() {
- searchWorkerResultListener.onReCaptchaChallenge();
- }
- });
- } catch(IOException e) {
- h.post(new Runnable() {
- @Override
- public void run() {
- searchWorkerResultListener.onNothingFound(R.string.network_error);
- }
- });
- e.printStackTrace();
- } catch(final SearchEngine.NothingFoundException e) {
- h.post(new Runnable() {
- @Override
- public void run() {
- searchWorkerResultListener.onError(e.getMessage());
- }
- });
- } catch(ExtractionException e) {
- ErrorActivity.reportError(h, a, e, null, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- serviceName, 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 SearchWorkerResultListener searchWorkerResultListener = 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 setSearchWorkerResultListener(SearchWorkerResultListener listener) {
- searchWorkerResultListener = listener;
- }
-
- private SearchWorker() {
-
- }
-
- public void search(int serviceId,
- String query,
- int page,
- Activity a,
- EnumSet filter) {
- if(runnable != null) {
- terminate();
- }
- runnable = new SearchRunnable(serviceId, query, page, filter, a, requestId);
- Thread thread = new Thread(runnable);
- thread.start();
- }
-
- public void terminate() {
- if (runnable == null) return;
- requestId++;
- runnable.terminate();
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java
index b8ecaad1d..dda698a78 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java
@@ -75,6 +75,11 @@ public class SuggestionListAdapter extends ResourceCursorAdapter {
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
}
+ @Override
+ public CharSequence convertToString(Cursor cursor) {
+ return cursor.getString(INDEX_TITLE);
+ }
+
private class ViewHolder {
private final TextView suggestionTitle;
private ViewHolder(View view) {
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionSearchRunnable.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionSearchRunnable.java
deleted file mode 100644
index 10800c7e3..000000000
--- a/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionSearchRunnable.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.schabi.newpipe.fragments.search;
-
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.os.Handler;
-import android.preference.PreferenceManager;
-import android.widget.Toast;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.SuggestionExtractor;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-import org.schabi.newpipe.report.ErrorActivity;
-
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Created by Christian Schabesberger on 02.08.16.
- *
- * Copyright (C) Christian Schabesberger 2016
- * SuggestionSearchRunnable.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
-
-public class SuggestionSearchRunnable implements Runnable{
-
- /**
- * Runnable to update a {@link SuggestionListAdapter}
- */
- private class SuggestionResultRunnable implements Runnable{
-
- private final List suggestions;
-
- private SuggestionResultRunnable(List suggestions) {
- this.suggestions = suggestions;
- }
-
- @Override
- public void run() {
- adapter.updateAdapter(suggestions);
- }
- }
-
- private final int serviceId;
- private final String query;
- private final Handler h = new Handler();
- private final Activity a;
- private final 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 {
- SuggestionExtractor se =
- NewPipe.getService(serviceId).getSuggestionExtractorInstance();
- 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 suggestions = se.suggestionList(query, searchLanguage);
- h.post(new SuggestionResultRunnable(suggestions));
- } catch (ExtractionException e) {
- ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- NewPipe.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,
- NewPipe.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();
- }
- });
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java
index d47c63cdb..29eaafcd9 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/ChannelInfoItemHolder.java
@@ -1,7 +1,6 @@
package org.schabi.newpipe.info_list;
import android.view.View;
-import android.widget.Button;
import android.widget.TextView;
import org.schabi.newpipe.R;
@@ -35,16 +34,17 @@ public class ChannelInfoItemHolder extends InfoItemHolder {
public final TextView itemSubscriberCountView;
public final TextView itemVideoCountView;
public final TextView itemChannelDescriptionView;
- public final Button itemButton;
+
+ public final View itemRoot;
ChannelInfoItemHolder(View v) {
super(v);
+ itemRoot = v.findViewById(R.id.itemRoot);
itemThumbnailView = (CircleImageView) v.findViewById(R.id.itemThumbnailView);
itemChannelTitleView = (TextView) v.findViewById(R.id.itemChannelTitleView);
itemSubscriberCountView = (TextView) v.findViewById(R.id.itemSubscriberCountView);
itemVideoCountView = (TextView) v.findViewById(R.id.itemVideoCountView);
itemChannelDescriptionView = (TextView) v.findViewById(R.id.itemChannelDescriptionView);
- itemButton = (Button) v.findViewById(R.id.item_button);
}
@Override
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 20dda18a2..f83d954a2 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.info_list;
import android.content.Context;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -18,20 +19,20 @@ import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
/**
* Created by Christian Schabesberger on 26.09.16.
- *
+ *
* Copyright (C) Christian Schabesberger 2016
* InfoItemBuilder.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 .
*/
@@ -48,11 +49,13 @@ public class InfoItemBuilder {
private final String billion;
private static final String TAG = InfoItemBuilder.class.toString();
+
public interface OnInfoItemSelectedListener {
void selected(int serviceId, String url, String title);
}
private Context mContext = null;
+ private LayoutInflater inflater;
private View rootView = null;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions =
@@ -70,6 +73,7 @@ public class InfoItemBuilder {
thousand = context.getString(R.string.short_thousand);
million = context.getString(R.string.short_million);
billion = context.getString(R.string.short_billion);
+ inflater = LayoutInflater.from(context);
}
public void setOnStreamInfoItemSelectedListener(
@@ -83,9 +87,9 @@ public class InfoItemBuilder {
}
public void buildByHolder(InfoItemHolder holder, final InfoItem i) {
- if(i.infoType() != holder.infoType())
+ if (i.infoType() != holder.infoType())
return;
- switch(i.infoType()) {
+ switch (i.infoType()) {
case STREAM:
buildStreamInfoItem((StreamInfoItemHolder) holder, (StreamInfoItem) i);
break;
@@ -103,15 +107,15 @@ public class InfoItemBuilder {
public View buildView(ViewGroup parent, final InfoItem info) {
View itemView = null;
InfoItemHolder holder = null;
- switch(info.infoType()) {
+ switch (info.infoType()) {
case STREAM:
- itemView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.stream_item, parent, false);
+ //long start = System.nanoTime();
+ itemView = inflater.inflate(R.layout.stream_item, parent, false);
+ //Log.d(TAG, "time to inflate: " + ((System.nanoTime() - start) / 1000000L) + "ms");
holder = new StreamInfoItemHolder(itemView);
break;
case CHANNEL:
- itemView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.channel_item, parent, false);
+ itemView = inflater.inflate(R.layout.channel_item, parent, false);
holder = new ChannelInfoItemHolder(itemView);
break;
case PLAYLIST:
@@ -124,43 +128,39 @@ public class InfoItemBuilder {
}
private void buildStreamInfoItem(StreamInfoItemHolder holder, final StreamInfoItem info) {
- if(info.infoType() != InfoItem.InfoType.STREAM) {
+ if (info.infoType() != InfoItem.InfoType.STREAM) {
Log.e("InfoItemBuilder", "Info type not yet supported");
}
// fill holder with information
- holder.itemVideoTitleView.setText(info.title);
- if(info.uploader != null && !info.uploader.isEmpty()) {
- holder.itemUploaderView.setText(info.uploader);
- } else {
- holder.itemUploaderView.setVisibility(View.INVISIBLE);
- }
- if(info.duration > 0) {
+ if (!TextUtils.isEmpty(info.title)) holder.itemVideoTitleView.setText(info.title);
+
+ if (!TextUtils.isEmpty(info.uploader)) holder.itemUploaderView.setText(info.uploader);
+ else holder.itemUploaderView.setVisibility(View.INVISIBLE);
+
+ if (info.duration > 0) {
holder.itemDurationView.setText(getDurationString(info.duration));
} else {
- if(info.stream_type == AbstractStreamInfo.StreamType.LIVE_STREAM) {
+ if (info.stream_type == AbstractStreamInfo.StreamType.LIVE_STREAM) {
holder.itemDurationView.setText(R.string.duration_live);
} else {
holder.itemDurationView.setVisibility(View.GONE);
}
}
- if(info.view_count >= 0) {
+ if (info.view_count >= 0) {
holder.itemViewCountView.setText(shortViewCount(info.view_count));
} else {
holder.itemViewCountView.setVisibility(View.GONE);
}
- if(info.upload_date != null && !info.upload_date.isEmpty()) {
- holder.itemUploadDateView.setText(info.upload_date + " • ");
- }
+ if (!TextUtils.isEmpty(info.upload_date)) holder.itemUploadDateView.setText(info.upload_date + " • ");
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
- if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
+ if (!TextUtils.isEmpty(info.thumbnail_url)) {
imageLoader.displayImage(info.thumbnail_url,
- holder.itemThumbnailView,
- displayImageOptions,
+ holder.itemThumbnailView, displayImageOptions,
new ImageErrorLoadingListener(mContext, rootView, info.service_id));
}
- holder.itemButton.setOnClickListener(new View.OnClickListener() {
+ holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
@@ -169,20 +169,20 @@ public class InfoItemBuilder {
}
private void buildChannelInfoItem(ChannelInfoItemHolder holder, final ChannelInfoItem info) {
- holder.itemChannelTitleView.setText(info.getTitle());
+ if (!TextUtils.isEmpty(info.getTitle())) holder.itemChannelTitleView.setText(info.getTitle());
holder.itemSubscriberCountView.setText(shortSubscriber(info.subscriberCount) + " • ");
holder.itemVideoCountView.setText(info.videoAmount + " " + videosS);
- holder.itemChannelDescriptionView.setText(info.description);
+ if (!TextUtils.isEmpty(info.description)) holder.itemChannelDescriptionView.setText(info.description);
holder.itemThumbnailView.setImageResource(R.drawable.buddy_channel_item);
- if(info.thumbnailUrl != null && !info.thumbnailUrl.isEmpty()) {
+ if (!TextUtils.isEmpty(info.thumbnailUrl)) {
imageLoader.displayImage(info.thumbnailUrl,
holder.itemThumbnailView,
displayImageOptions,
new ImageErrorLoadingListener(mContext, rootView, info.serviceId));
}
- holder.itemButton.setOnClickListener(new View.OnClickListener() {
+ holder.itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
@@ -191,15 +191,15 @@ public class InfoItemBuilder {
}
- public String shortViewCount(Long viewCount){
- if(viewCount >= 1000000000){
- return Long.toString(viewCount/1000000000)+ billion + " " + viewsS;
- }else if(viewCount>=1000000){
- return Long.toString(viewCount/1000000)+ million + " " + viewsS;
- }else if(viewCount>=1000){
- return Long.toString(viewCount/1000)+ thousand + " " + viewsS;
- }else {
- return Long.toString(viewCount)+ " " + viewsS;
+ public String shortViewCount(Long viewCount) {
+ if (viewCount >= 1000000000) {
+ return Long.toString(viewCount / 1000000000) + billion + " " + viewsS;
+ } else if (viewCount >= 1000000) {
+ return Long.toString(viewCount / 1000000) + million + " " + viewsS;
+ } else if (viewCount >= 1000) {
+ return Long.toString(viewCount / 1000) + thousand + " " + viewsS;
+ } else {
+ return Long.toString(viewCount) + " " + viewsS;
}
}
@@ -227,13 +227,13 @@ public class InfoItemBuilder {
int seconds = duration % 60;
//handle days
- if(days > 0) {
+ if (days > 0) {
output = Integer.toString(days) + ":";
}
// handle hours
- if(hours > 0 || !output.isEmpty()) {
- if(hours > 0) {
- if(hours >= 10 || output.isEmpty()) {
+ if (hours > 0 || !output.isEmpty()) {
+ if (hours > 0) {
+ if (hours >= 10 || output.isEmpty()) {
output += Integer.toString(hours);
} else {
output += "0" + Integer.toString(hours);
@@ -244,9 +244,9 @@ public class InfoItemBuilder {
output += ":";
}
//handle minutes
- if(minutes > 0 || !output.isEmpty()) {
- if(minutes > 0) {
- if(minutes >= 10 || output.isEmpty()) {
+ if (minutes > 0 || !output.isEmpty()) {
+ if (minutes > 0) {
+ if (minutes >= 10 || output.isEmpty()) {
output += Integer.toString(minutes);
} else {
output += "0" + Integer.toString(minutes);
@@ -258,11 +258,11 @@ public class InfoItemBuilder {
}
//handle seconds
- if(output.isEmpty()) {
+ if (output.isEmpty()) {
output += "0:";
}
- if(seconds >= 10) {
+ if (seconds >= 10) {
output += Integer.toString(seconds);
} else {
output += "0" + Integer.toString(seconds);
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 d6f173350..e17e2aaac 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
@@ -10,8 +10,8 @@ import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.InfoItem;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Vector;
/**
* Created by Christian Schabesberger on 01.08.16.
@@ -57,7 +57,7 @@ public class InfoListAdapter extends RecyclerView.Adapter();
+ infoItemList = new ArrayList<>();
}
public void setOnStreamInfoItemSelectedListener
@@ -77,7 +77,7 @@ public class InfoListAdapter extends RecyclerView.Adapter getItemsList() {
+ return infoItemList;
+ }
+
@Override
public int getItemCount() {
- int cound = infoItemList.size();
- if(header != null) cound++;
- if(footer != null && showFooter) cound++;
- return cound;
+ int count = infoItemList.size();
+ if(header != null) count++;
+ if(footer != null && showFooter) count++;
+ return count;
}
// don't ask why we have to do that this way... it's android accept it -.-
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java
index 81981fd82..a527cd300 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/StreamInfoItemHolder.java
@@ -1,9 +1,6 @@
package org.schabi.newpipe.info_list;
-import android.icu.text.IDNA;
-import android.support.v7.widget.RecyclerView;
import android.view.View;
-import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -12,20 +9,20 @@ import org.schabi.newpipe.extractor.InfoItem;
/**
* Created by Christian Schabesberger on 01.08.16.
- *
+ *
* Copyright (C) Christian Schabesberger 2016
* StreamInfoItemHolder.java is part of NewPipe.
- *
+ *
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see .
*/
@@ -38,17 +35,17 @@ public class StreamInfoItemHolder extends InfoItemHolder {
itemDurationView,
itemUploadDateView,
itemViewCountView;
- public final Button itemButton;
+ public final View itemRoot;
public StreamInfoItemHolder(View v) {
super(v);
+ itemRoot = v.findViewById(R.id.itemRoot);
itemThumbnailView = (ImageView) v.findViewById(R.id.itemThumbnailView);
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView);
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView);
itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
- itemButton = (Button) v.findViewById(R.id.item_button);
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
index 8c9c80a3d..cbf4f2bf6 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -78,7 +78,7 @@ public class BackgroundPlayer extends Service {
powerManager = ((PowerManager) getSystemService(POWER_SERVICE));
wifiManager = ((WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE));
- ThemeHelper.setTheme(this, false);
+ ThemeHelper.setTheme(this);
basePlayerImpl = new BasePlayerImpl(this);
basePlayerImpl.setup();
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
index c5f0b2d3b..ce3525f17 100644
--- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java
@@ -55,7 +55,7 @@ public class MainVideoPlayer extends Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
- ThemeHelper.setTheme(this, false);
+ ThemeHelper.setTheme(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
@@ -67,7 +67,7 @@ public class MainVideoPlayer extends Activity {
}
showSystemUi();
- setContentView(R.layout.activity_exo_player);
+ setContentView(R.layout.activity_main_player);
playerImpl = new VideoPlayerImpl();
playerImpl.setup(findViewById(android.R.id.content));
playerImpl.handleIntent(getIntent());
@@ -474,7 +474,7 @@ public class MainVideoPlayer extends Activity {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//noinspection PointlessBooleanExpression
- if (DEBUG && true) Log.d(TAG, "MainVideoPlayer.onScroll = " +
+ if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
", distanceXy = [" + distanceX + ", " + distanceY + "]");
@@ -531,12 +531,14 @@ public class MainVideoPlayer extends Activity {
if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.animateView(playerImpl.getBrightnessTextView(), false, 200, 200);
if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == BasePlayer.STATE_PLAYING) {
- playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME);
+ playerImpl.animateView(playerImpl.getControlsRoot(), false, 300, VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME, true);
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
+ //noinspection PointlessBooleanExpression
+ if (DEBUG && false) Log.d(TAG, "onTouch() called with: v = [" + v + "], event = [" + event + "]");
gestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP && isMoving) {
isMoving = false;
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index 170e2f6aa..ded6f34f9 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -89,7 +89,7 @@ public class PopupVideoPlayer extends Service {
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
playerImpl = new VideoPlayerImpl();
- ThemeHelper.setTheme(this, false);
+ ThemeHelper.setTheme(this);
}
@Override
@@ -124,7 +124,6 @@ public class PopupVideoPlayer extends Service {
playerImpl.destroy();
if (playerImpl.getRootView() != null) windowManager.removeView(playerImpl.getRootView());
}
- if (imageLoader != null) imageLoader.clearMemoryCache();
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
if (currentExtractorWorker != null) {
currentExtractorWorker.cancel();
diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
index 3e0976242..d64e60747 100644
--- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java
@@ -125,7 +125,7 @@ public abstract class VideoPlayer extends BasePlayer implements SimpleExoPlayer.
this.aspectRatioFrameLayout = (AspectRatioFrameLayout) rootView.findViewById(R.id.aspectRatioLayout);
this.surfaceView = (SurfaceView) rootView.findViewById(R.id.surfaceView);
this.surfaceForeground = rootView.findViewById(R.id.surfaceForeground);
- this.loadingPanel = rootView.findViewById(R.id.loadingPanel);
+ this.loadingPanel = rootView.findViewById(R.id.loading_panel);
this.endScreen = (ImageView) rootView.findViewById(R.id.endScreen);
this.controlAnimationView = (ImageView) rootView.findViewById(R.id.controlAnimationView);
this.controlsRoot = rootView.findViewById(R.id.playbackControlRoot);
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 14c72c561..23ee471b3 100644
--- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java
@@ -15,6 +15,7 @@ import android.support.design.widget.Snackbar;
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.Menu;
import android.view.MenuInflater;
@@ -205,19 +206,19 @@ public class ErrorActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ThemeHelper.setTheme(this, true);
+ ThemeHelper.setTheme(this);
setContentView(R.layout.activity_error);
Intent intent = getIntent();
- try {
- ActionBar actionBar = getSupportActionBar();
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.error_report_title);
actionBar.setDisplayShowTitleEnabled(true);
- } catch (Throwable e) {
- Log.e(TAG, "Error turing exception handling");
- e.printStackTrace();
}
reportButton = (Button) findViewById(R.id.errorReportButton);
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 1ad2747a7..a1e8fb49b 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
@@ -1,18 +1,11 @@
package org.schabi.newpipe.settings;
import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.support.annotation.LayoutRes;
-import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatDelegate;
-import android.view.MenuInflater;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper;
@@ -38,9 +31,7 @@ import org.schabi.newpipe.util.ThemeHelper;
* along with NewPipe. If not, see .
*/
-public class SettingsActivity extends PreferenceActivity {
- SettingsFragment f = new SettingsFragment();
- private AppCompatDelegate mDelegate = null;
+public class SettingsActivity extends AppCompatActivity {
public static void initSettings(Context context) {
NewPipeSettings.initSettings(context);
@@ -48,104 +39,25 @@ public class SettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceBundle) {
- ThemeHelper.setTheme(this, true);
- getDelegate().installViewFactory();
- getDelegate().onCreate(savedInstanceBundle);
+ ThemeHelper.setTheme(this);
super.onCreate(savedInstanceBundle);
+ setContentView(R.layout.settings_layout);
+
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setTitle(R.string.settings_title);
- actionBar.setDisplayShowTitleEnabled(true);
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(R.string.settings_title);
+ actionBar.setDisplayShowTitleEnabled(true);
+ }
getFragmentManager().beginTransaction()
- .replace(android.R.id.content, f)
+ .replace(R.id.fragment_holder, new SettingsFragment())
.commit();
}
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- f.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- getDelegate().onPostCreate(savedInstanceState);
- }
-
- private ActionBar getSupportActionBar() {
- return getDelegate().getSupportActionBar();
- }
-
- @NonNull
- @Override
- public MenuInflater getMenuInflater() {
- return getDelegate().getMenuInflater();
- }
-
- @Override
- public void setContentView(@LayoutRes int layoutResID) {
- getDelegate().setContentView(layoutResID);
- }
-
- @Override
- public void setContentView(View view) {
- getDelegate().setContentView(view);
- }
-
- @Override
- public void setContentView(View view, ViewGroup.LayoutParams params) {
- getDelegate().setContentView(view, params);
- }
-
- @Override
- public void addContentView(View view, ViewGroup.LayoutParams params) {
- getDelegate().addContentView(view, params);
- }
-
- @Override
- protected void onPostResume() {
- super.onPostResume();
- getDelegate().onPostResume();
- }
-
- @Override
- protected void onTitleChanged(CharSequence title, int color) {
- super.onTitleChanged(title, color);
- getDelegate().setTitle(title);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- getDelegate().onConfigurationChanged(newConfig);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- getDelegate().onStop();
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- getDelegate().onDestroy();
- }
-
- public void invalidateOptionsMenu() {
- getDelegate().invalidateOptionsMenu();
- }
-
- private AppCompatDelegate getDelegate() {
- if (mDelegate == null) {
- mDelegate = AppCompatDelegate.create(this, null);
- }
- return mDelegate;
- }
-
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
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 5ca2fb180..8ac9b0b41 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java
@@ -1,205 +1,84 @@
package org.schabi.newpipe.settings;
import android.app.Activity;
-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.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
-import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.Log;
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 org.schabi.newpipe.util.Constants;
import info.guardianproject.netcipher.proxy.OrbotHelper;
-/**
- * Created by david on 15/06/16.
- *
- * Copyright (C) Christian Schabesberger 2016
- * SettingsFragment.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
+public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final int REQUEST_INSTALL_ORBOT = 0x1234;
+ private static final int REQUEST_DOWNLOAD_PATH = 0x1235;
+ private static final int REQUEST_DOWNLOAD_AUDIO_PATH = 0x1236;
-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_POPUP_RESOLUTION_PREFERENCE;
- String PREFERRED_VIDEO_FORMAT_PREFERENCE;
- String DEFAULT_AUDIO_FORMAT_PREFERENCE;
- String SEARCH_LANGUAGE_PREFERENCE;
- String DOWNLOAD_PATH_PREFERENCE;
- String DOWNLOAD_PATH_AUDIO_PREFERENCE;
- String USE_TOR_KEY;
- String THEME;
- private ListPreference defaultResolutionPreference;
- private ListPreference defaultPopupResolutionPreference;
- private ListPreference preferredVideoFormatPreference;
- private ListPreference defaultAudioFormatPreference;
- private ListPreference searchLanguagePreference;
- private Preference downloadPathPreference;
- private Preference downloadPathAudioPreference;
- private Preference themePreference;
+ private String DOWNLOAD_PATH_PREFERENCE;
+ private String DOWNLOAD_PATH_AUDIO_PREFERENCE;
+ private String USE_TOR_KEY;
+ private String THEME;
+
+ private String currentTheme;
private SharedPreferences defaultPreferences;
+ private Activity activity;
+
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ activity = getActivity();
addPreferencesFromResource(R.xml.settings);
- final Activity activity = getActivity();
-
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ initKeys();
+ updatePreferencesSummary();
- // get keys
- DEFAULT_RESOLUTION_PREFERENCE = getString(R.string.default_resolution_key);
- DEFAULT_POPUP_RESOLUTION_PREFERENCE = getString(R.string.default_popup_resolution_key);
- PREFERRED_VIDEO_FORMAT_PREFERENCE = getString(R.string.preferred_video_format_key);
- DEFAULT_AUDIO_FORMAT_PREFERENCE = getString(R.string.default_audio_format_key);
- SEARCH_LANGUAGE_PREFERENCE = getString(R.string.search_language_key);
+ currentTheme = defaultPreferences.getString(THEME, getString(R.string.default_theme_value));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ defaultPreferences.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ defaultPreferences.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ private void initKeys() {
DOWNLOAD_PATH_PREFERENCE = getString(R.string.download_path_key);
DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key);
THEME = getString(R.string.theme_key);
USE_TOR_KEY = getString(R.string.use_tor_key);
-
- // get pref objects
- defaultResolutionPreference =
- (ListPreference) findPreference(DEFAULT_RESOLUTION_PREFERENCE);
- defaultPopupResolutionPreference =
- (ListPreference) findPreference(DEFAULT_POPUP_RESOLUTION_PREFERENCE);
- preferredVideoFormatPreference =
- (ListPreference) findPreference(PREFERRED_VIDEO_FORMAT_PREFERENCE);
- defaultAudioFormatPreference =
- (ListPreference) findPreference(DEFAULT_AUDIO_FORMAT_PREFERENCE);
- searchLanguagePreference =
- (ListPreference) findPreference(SEARCH_LANGUAGE_PREFERENCE);
- downloadPathPreference = findPreference(DOWNLOAD_PATH_PREFERENCE);
- downloadPathAudioPreference = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE);
- themePreference = findPreference(THEME);
-
- final String currentTheme = defaultPreferences.getString(THEME, "Light");
-
- // TODO: Clean this, as the class is already implementing the class
- // and those double equals...
-
- prefListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
- Activity a = getActivity();
- if(a == null)
- {
- return;
- }
- if (key == USE_TOR_KEY)
- {
- if (defaultPreferences.getBoolean(USE_TOR_KEY, false)) {
- if (OrbotHelper.isOrbotInstalled(a)) {
- App.configureTor(true);
- OrbotHelper.requestStartTor(a);
- } else {
- Intent intent = OrbotHelper.getOrbotInstallIntent(a);
- a.startActivityForResult(intent, REQUEST_INSTALL_ORBOT);
- }
- } else {
- App.configureTor(false);
- }
- }
- else if (key == DOWNLOAD_PATH_PREFERENCE)
- {
- String downloadPath = sharedPreferences
- .getString(DOWNLOAD_PATH_PREFERENCE,
- getString(R.string.download_path_summary));
- downloadPathPreference
- .setSummary(downloadPath);
- }
- else if (key == DOWNLOAD_PATH_AUDIO_PREFERENCE)
- {
- String downloadPath = sharedPreferences
- .getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
- getString(R.string.download_path_audio_summary));
- downloadPathAudioPreference
- .setSummary(downloadPath);
- }
- else if (key == 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();
- }
- };
- defaultPreferences.registerOnSharedPreferenceChangeListener(prefListener);
-
- updateSummary();
}
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
-
- if(preference.getKey().equals(downloadPathPreference.getKey()) ||
- preference.getKey().equals(downloadPathAudioPreference.getKey()))
- {
- Activity activity = getActivity();
- Intent i = new Intent(activity, FilePickerActivity.class);
-
- i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
- i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
- i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
- if(preference.getKey().equals(downloadPathPreference.getKey()))
- {
- activity.startActivityForResult(i, R.string.download_path_key);
- }
- else if (preference.getKey().equals(downloadPathAudioPreference.getKey()))
- {
- activity.startActivityForResult(i, R.string.download_path_audio_key);
+ Log.d("TAG", "onPreferenceTreeClick() called with: preferenceScreen = [" + preferenceScreen + "], preference = [" + preference + "]");
+ if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
+ Intent i = new Intent(activity, FilePickerActivity.class)
+ .putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
+ .putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
+ .putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
+ if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE)) {
+ startActivityForResult(i, REQUEST_DOWNLOAD_PATH);
+ } else if (preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) {
+ startActivityForResult(i, REQUEST_DOWNLOAD_AUDIO_PATH);
}
}
return super.onPreferenceTreeClick(preferenceScreen, preference);
@@ -208,90 +87,56 @@ public class SettingsFragment extends PreferenceFragment
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- Activity a = getActivity();
+ Log.d("TAG", "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
- if ((requestCode == R.string.download_path_audio_key
- || requestCode == R.string.download_path_key)
- && resultCode == Activity.RESULT_OK) {
-
- Uri uri = null;
- if (data.getBooleanExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)) {
- // For JellyBean and above
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- ClipData clip = data.getClipData();
-
- if (clip != null) {
- for (int i = 0; i < clip.getItemCount(); i++) {
- uri = clip.getItemAt(i).getUri();
- }
- }
- // For Ice Cream Sandwich
- } else {
- ArrayList paths = data.getStringArrayListExtra
- (FilePickerActivity.EXTRA_PATHS);
-
- if (paths != null) {
- for (String path: paths) {
- uri = Uri.parse(path);
- }
- }
- }
- } else {
- uri = data.getData();
- }
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(a);
-
- //requestCode is equal to R.string.download_path_key or
- //R.string.download_path_audio_key
- String key = getString(requestCode);
+ if ((requestCode == REQUEST_DOWNLOAD_PATH || requestCode == REQUEST_DOWNLOAD_AUDIO_PATH) && resultCode == Activity.RESULT_OK) {
+ String key = getString(requestCode == REQUEST_DOWNLOAD_PATH ? R.string.download_path_key : R.string.download_path_audio_key);
String path = data.getData().toString().substring(7);
- prefs.edit()
- .putString(key, path)
- .apply();
-
- }
- else if(requestCode == REQUEST_INSTALL_ORBOT)
- {
+ defaultPreferences.edit().putString(key, path).apply();
+ updatePreferencesSummary();
+ } else if (requestCode == REQUEST_INSTALL_ORBOT) {
// try to start tor regardless of resultCode since clicking back after
// installing the app does not necessarily return RESULT_OK
- App.configureTor(requestCode == REQUEST_INSTALL_ORBOT
- && OrbotHelper.requestStartTor(a));
+ App.configureTor(OrbotHelper.requestStartTor(activity));
}
-
- updateSummary();
- super.onActivityResult(requestCode, resultCode, data);
}
- // This is used to show the status of some preference in the description
- private void updateSummary() {
- defaultResolutionPreference.setSummary(
- defaultPreferences.getString(DEFAULT_RESOLUTION_PREFERENCE,
- getString(R.string.default_resolution_value)));
- defaultPopupResolutionPreference.setSummary(
- defaultPreferences.getString(DEFAULT_POPUP_RESOLUTION_PREFERENCE,
- getString(R.string.default_popup_resolution_value)));
- preferredVideoFormatPreference.setSummary(
- defaultPreferences.getString(PREFERRED_VIDEO_FORMAT_PREFERENCE,
- getString(R.string.preferred_video_format_default)));
- defaultAudioFormatPreference.setSummary(
- defaultPreferences.getString(DEFAULT_AUDIO_FORMAT_PREFERENCE,
- getString(R.string.default_audio_format_value)));
- searchLanguagePreference.setSummary(
- defaultPreferences.getString(SEARCH_LANGUAGE_PREFERENCE,
- getString(R.string.default_language_value)));
- downloadPathPreference.setSummary(
- defaultPreferences.getString(DOWNLOAD_PATH_PREFERENCE,
- getString(R.string.download_path_summary)));
- downloadPathAudioPreference.setSummary(
- defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE,
- getString(R.string.download_path_audio_summary)));
- themePreference.setSummary(
- defaultPreferences.getString(THEME,
- getString(R.string.light_theme_title)));
+ /**
+ * Update ONLY the summary of some preferences that don't fire in the onSharedPreferenceChanged or CAN'T be update via xml (%s)
+ *
+ * For example, the download_path use the startActivityForResult, firing the onStop of this fragment,
+ * unregistering the listener (unregisterOnSharedPreferenceChangeListener)
+ */
+ private void updatePreferencesSummary() {
+ findPreference(DOWNLOAD_PATH_PREFERENCE).setSummary(defaultPreferences.getString(DOWNLOAD_PATH_PREFERENCE, getString(R.string.download_path_summary)));
+ findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE).setSummary(defaultPreferences.getString(DOWNLOAD_PATH_AUDIO_PREFERENCE, getString(R.string.download_path_audio_summary)));
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ Log.d("TAG", "onSharedPreferenceChanged() called with: sharedPreferences = [" + sharedPreferences + "], key = [" + key + "]");
+ String summary = null;
+ if (key.equals(USE_TOR_KEY)) {
+ if (defaultPreferences.getBoolean(USE_TOR_KEY, false)) {
+ if (OrbotHelper.isOrbotInstalled(activity)) {
+ App.configureTor(true);
+ OrbotHelper.requestStartTor(activity);
+ } else {
+ Intent intent = OrbotHelper.getOrbotInstallIntent(activity);
+ startActivityForResult(intent, REQUEST_INSTALL_ORBOT);
+ }
+ } else App.configureTor(false);
+ return;
+ } else if (key.equals(THEME)) {
+ summary = sharedPreferences.getString(THEME, getString(R.string.default_theme_value));
+ if (!summary.equals(currentTheme)) { // If it's not the current theme
+ Intent intentToMain = new Intent(activity, MainActivity.class);
+ intentToMain.putExtra(Constants.KEY_THEME_CHANGE, true);
+ startActivity(intentToMain);
+ }
+ }
+
+ if (!TextUtils.isEmpty(summary)) findPreference(key).setSummary(summary);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/Constants.java b/app/src/main/java/org/schabi/newpipe/util/Constants.java
index 9f94729ae..f9329b0be 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Constants.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Constants.java
@@ -5,4 +5,8 @@ public class Constants {
public static final String KEY_URL = "key_url";
public static final String KEY_TITLE = "key_title";
public static final String KEY_LINK_TYPE = "key_link_type";
+ public static final String KEY_OPEN_SEARCH = "key_open_search";
+ public static final String KEY_QUERY = "key_query";
+
+ public static final String KEY_THEME_CHANGE = "key_theme_change";
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 52a4eca1d..129adb5c2 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -19,6 +19,10 @@ import org.schabi.newpipe.player.VideoPlayer;
@SuppressWarnings({"unused", "WeakerAccess"})
public class NavigationHelper {
+ /*//////////////////////////////////////////////////////////////////////////
+ // Players
+ //////////////////////////////////////////////////////////////////////////*/
+
public static Intent getOpenVideoPlayerIntent(Context context, Class targetClazz, StreamInfo info, int selectedStreamIndex) {
Intent mIntent = new Intent(context, targetClazz)
.putExtra(BasePlayer.VIDEO_TITLE, info.title)
@@ -61,7 +65,6 @@ public class NavigationHelper {
return mIntent;
}
-
/*//////////////////////////////////////////////////////////////////////////
// Through Interface (faster)
//////////////////////////////////////////////////////////////////////////*/
@@ -115,6 +118,14 @@ public class NavigationHelper {
context.startActivity(mIntent);
}
+ public static void openSearch(Context context, int serviceId, String query) {
+ Intent mIntent = new Intent(context, MainActivity.class);
+ mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
+ mIntent.putExtra(Constants.KEY_QUERY, query);
+ mIntent.putExtra(Constants.KEY_OPEN_SEARCH, true);
+ context.startActivity(mIntent);
+ }
+
private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
Intent mIntent = new Intent(context, MainActivity.class);
mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
index 8021a0972..4f3be4a0d 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java
@@ -10,25 +10,33 @@ public class ThemeHelper {
/**
* Apply the selected theme (on NewPipe settings) in the context
*
- * @param context context that the theme will be applied
- * @param useActionbarTheme whether to use an action bar theme or not
+ * @param context context that the theme will be applied
*/
- public static void setTheme(Context context, boolean useActionbarTheme) {
+ public static void setTheme(Context context) {
+
String themeKey = context.getString(R.string.theme_key);
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));
+ String sp = PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, context.getResources().getString(R.string.light_theme_title));
- if (useActionbarTheme) {
- 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);
- }
+ 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);
+ }
+
+ /**
+ * Return true if the selected theme (on NewPipe settings) is the Light theme
+ *
+ * @param context context to get the preference
+ */
+ public static boolean isLightThemeSelected(Context context) {
+ String themeKey = context.getString(R.string.theme_key);
+ 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));
+
+ return !(sp.equals(darkTheme) || sp.equals(blackTheme));
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/AbstractWorker.java b/app/src/main/java/org/schabi/newpipe/workers/AbstractWorker.java
new file mode 100644
index 000000000..e67d5b119
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/workers/AbstractWorker.java
@@ -0,0 +1,118 @@
+package org.schabi.newpipe.workers;
+
+import android.content.Context;
+import android.os.Handler;
+
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.stream_info.StreamInfo;
+
+import java.io.InterruptedIOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Common properties of Workers
+ *
+ * @author mauriciocolli
+ */
+@SuppressWarnings("WeakerAccess")
+public abstract class AbstractWorker extends Thread {
+
+ private final AtomicBoolean isRunning = new AtomicBoolean(false);
+
+ private final int serviceId;
+ private Context context;
+ private Handler handler;
+ private StreamingService service;
+
+ public AbstractWorker(Context context, int serviceId) {
+ this.context = context;
+ this.serviceId = serviceId;
+ this.handler = new Handler(context.getMainLooper());
+ }
+
+ @Override
+ public void run() {
+ try {
+ isRunning.set(true);
+ service = NewPipe.getService(serviceId);
+ doWork(serviceId);
+ } catch (Exception e) {
+ // Handle the exception only if thread is not interrupted
+ e.printStackTrace();
+ if (!isInterrupted() && !(e instanceof InterruptedIOException) && !(e.getCause() instanceof InterruptedIOException)) {
+ handleException(e, serviceId);
+ }
+ } finally {
+ isRunning.set(false);
+ }
+ }
+
+ /**
+ * Here is the place that the heavy work is realized
+ *
+ * @param serviceId serviceId that was passed when created this object
+ *
+ * @throws Exception these exceptions are handled by the {@link #handleException(Exception, int)}
+ */
+ protected abstract void doWork(int serviceId) throws Exception;
+
+
+ /**
+ * Method that handle the exception thrown by the {@link #doWork(int)}.
+ *
+ * @param exception {@link Exception} that was thrown by {@link #doWork(int)}
+ */
+ protected abstract void handleException(Exception exception, int serviceId);
+
+ /**
+ * Return true if the extraction is not completed yet
+ *
+ * @return the value of the AtomicBoolean {@link #isRunning}
+ */
+ public boolean isRunning() {
+ return isRunning.get();
+ }
+
+ /**
+ * Cancel this ExtractorWorker, calling {@link #onDestroy()} and interrupting this thread.
+ *
+ * Note: Any I/O that is active in the moment that this method is called will be canceled and a Exception will be thrown, because of the {@link #interrupt()}.
+ * This is useful when you don't want the resulting {@link StreamInfo} anymore, but don't want to waste bandwidth, otherwise it'd run till it receives the StreamInfo.
+ */
+ public void cancel() {
+ onDestroy();
+ this.interrupt();
+ }
+
+ /**
+ * Method that discards everything that doesn't need anymore.
+ * Subclasses can override this method to destroy their garbage.
+ */
+ protected void onDestroy() {
+ this.isRunning.set(false);
+ this.context = null;
+ this.handler = null;
+ this.service = null;
+ }
+
+ public Handler getHandler() {
+ return handler;
+ }
+
+ public StreamingService getService() {
+ return service;
+ }
+
+ public int getServiceId() {
+ return serviceId;
+ }
+
+ public String getServiceName() {
+ return service == null ? "none" : service.getServiceInfo().name;
+ }
+
+ public Context getContext() {
+ return context;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java
index 5275688d4..bd4cf1139 100644
--- a/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java
+++ b/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java
@@ -31,7 +31,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
* Interface which will be called for result and errors
*/
public interface OnChannelInfoReceive {
- void onReceive(ChannelInfo info);
+ void onReceive(ChannelInfo info, boolean onlyVideos);
void onError(int messageId);
/**
* Called when an unrecoverable error has occurred.
@@ -44,12 +44,15 @@ public class ChannelExtractorWorker extends ExtractorWorker {
* @param context context for error reporting purposes
* @param serviceId id of the request service
* @param channelUrl channelUrl of the service (e.g. https://www.youtube.com/channel/UC_aEa8K-EOJ3D6gOs7HcyNg)
+ * @param pageNumber which page to extract
+ * @param onlyVideos flag that will be send by {@link OnChannelInfoReceive#onReceive(ChannelInfo, boolean)}
* @param callback listener that will be called-back when events occur (check {@link ChannelExtractorWorker.OnChannelInfoReceive})
*/
- public ChannelExtractorWorker(Context context, int serviceId, String channelUrl, int pageNumber, OnChannelInfoReceive callback) {
+ public ChannelExtractorWorker(Context context, int serviceId, String channelUrl, int pageNumber, boolean onlyVideos, OnChannelInfoReceive callback) {
super(context, channelUrl, serviceId);
this.pageNumber = pageNumber;
this.callback = callback;
+ this.onlyVideos = onlyVideos;
}
@Override
@@ -71,7 +74,7 @@ public class ChannelExtractorWorker extends ExtractorWorker {
public void run() {
if (isInterrupted() || callback == null) return;
- callback.onReceive(channelInfo);
+ callback.onReceive(channelInfo, onlyVideos);
onDestroy();
}
});
@@ -107,13 +110,5 @@ public class ChannelExtractorWorker extends ExtractorWorker {
});
}
}
-
- public boolean isOnlyVideos() {
- return onlyVideos;
- }
-
- public void setOnlyVideos(boolean onlyVideos) {
- this.onlyVideos = onlyVideos;
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java
index 442a9d8ab..57872b613 100644
--- a/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java
+++ b/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java
@@ -2,18 +2,12 @@ package org.schabi.newpipe.workers;
import android.app.Activity;
import android.content.Context;
-import android.os.Handler;
import android.util.Log;
import android.view.View;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.report.ErrorActivity;
-import java.io.InterruptedIOException;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Common properties of ExtractorWorkers
@@ -21,38 +15,18 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @author mauriciocolli
*/
@SuppressWarnings("WeakerAccess")
-public abstract class ExtractorWorker extends Thread {
-
- private final AtomicBoolean isRunning = new AtomicBoolean(false);
-
+public abstract class ExtractorWorker extends AbstractWorker {
private final String url;
- private final int serviceId;
- private Context context;
- private Handler handler;
- private StreamingService service;
public ExtractorWorker(Context context, String url, int serviceId) {
- this.context = context;
+ super(context, serviceId);
this.url = url;
- this.serviceId = serviceId;
- this.handler = new Handler(context.getMainLooper());
if (url.length() >= 40) setName("Thread-" + url.substring(url.length() - 11, url.length()));
}
@Override
- public void run() {
- try {
- isRunning.set(true);
- service = NewPipe.getService(serviceId);
- doWork(serviceId, url);
- } catch (Exception e) {
- // Handle the exception only if thread is not interrupted
- if (!isInterrupted() && !(e instanceof InterruptedIOException) && !(e.getCause() instanceof InterruptedIOException)) {
- handleException(e, serviceId, url);
- }
- } finally {
- isRunning.set(false);
- }
+ protected void doWork(int serviceId) throws Exception {
+ doWork(serviceId, url);
}
/**
@@ -65,6 +39,10 @@ public abstract class ExtractorWorker extends Thread {
*/
protected abstract void doWork(int serviceId, String url) throws Exception;
+ @Override
+ protected void handleException(Exception exception, int serviceId) {
+ handleException(exception, serviceId, url);
+ }
/**
* Method that handle the exception thrown by the {@link #doWork(int, String)}.
@@ -99,63 +77,12 @@ public abstract class ExtractorWorker extends Thread {
}
if (getContext() instanceof Activity) {
- View rootView = getContext() != null ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
+ View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
ErrorActivity.reportError(getHandler(), getContext(), errorsList, null, rootView, ErrorActivity.ErrorInfo.make(errorUserAction, getServiceName(), url, 0 /* no message for the user */));
}
}
- /**
- * Return true if the extraction is not completed yet
- *
- * @return the value of the AtomicBoolean {@link #isRunning}
- */
- public boolean isRunning() {
- return isRunning.get();
- }
-
- /**
- * Cancel this ExtractorWorker, calling {@link #onDestroy()} and interrupting this thread.
- *
- * Note: Any I/O that is active in the moment that this method is called will be canceled and a Exception will be thrown, because of the {@link #interrupt()}.
- * This is useful when you don't want the resulting {@link StreamInfo} anymore, but don't want to waste bandwidth, otherwise it'd run till it receives the StreamInfo.
- */
- public void cancel() {
- onDestroy();
- this.interrupt();
- }
-
- /**
- * Method that discards everything that doesn't need anymore.
- * Subclasses can override this method to destroy their garbage.
- */
- protected void onDestroy() {
- this.isRunning.set(false);
- this.context = null;
- this.handler = null;
- this.service = null;
- }
-
- public Handler getHandler() {
- return handler;
- }
-
public String getUrl() {
return url;
}
-
- public StreamingService getService() {
- return service;
- }
-
- public int getServiceId() {
- return serviceId;
- }
-
- public String getServiceName() {
- return service == null ? "none" : service.getServiceInfo().name;
- }
-
- public Context getContext() {
- return context;
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java b/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java
new file mode 100644
index 000000000..7d1e28441
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/workers/SearchWorker.java
@@ -0,0 +1,127 @@
+package org.schabi.newpipe.workers;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.view.View;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.extractor.search.SearchEngine;
+import org.schabi.newpipe.extractor.search.SearchResult;
+import org.schabi.newpipe.report.ErrorActivity;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+/**
+ * Return list of results based on a query
+ *
+ * @author mauriciocolli
+ */
+public class SearchWorker extends AbstractWorker {
+
+ private EnumSet filter;
+ private String query;
+ private int page;
+ private OnSearchResult callback;
+
+ /**
+ * Interface which will be called for result and errors
+ */
+ public interface OnSearchResult {
+ void onSearchResult(SearchResult result);
+ void onNothingFound(String message);
+ void onSearchError(int messageId);
+ void onReCaptchaChallenge();
+ }
+
+ public SearchWorker(Context context, int serviceId, String query, int page, EnumSet filter, OnSearchResult callback) {
+ super(context, serviceId);
+ this.callback = callback;
+ this.query = query;
+ this.page = page;
+ this.filter = filter;
+ }
+
+ public static SearchWorker startForQuery(Context context, int serviceId, @NonNull String query, int page, EnumSet filter, OnSearchResult callback) {
+ SearchWorker worker = new SearchWorker(context, serviceId, query, page, filter, callback);
+ worker.start();
+ return worker;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ this.callback = null;
+ }
+
+ @Override
+ protected void doWork(int serviceId) throws Exception {
+ SearchEngine searchEngine = getService().getSearchEngineInstance();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
+ String searchLanguageKey = getContext().getString(R.string.search_language_key);
+ String searchLanguage = sharedPreferences.getString(searchLanguageKey, getContext().getString(R.string.default_language_value));
+
+ final SearchResult searchResult = SearchResult.getSearchResult(searchEngine, query, page, searchLanguage, filter);
+ if (callback != null && searchResult != null && !isInterrupted()) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (isInterrupted() || callback == null) return;
+
+ callback.onSearchResult(searchResult);
+ onDestroy();
+ }
+ });
+ }
+
+ @Override
+ protected void handleException(final Exception exception, int serviceId) {
+ if (callback == null || getHandler() == null || isInterrupted()) return;
+
+ if (exception instanceof ReCaptchaException) {
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onReCaptchaChallenge();
+ }
+ });
+ } else if (exception instanceof IOException) {
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSearchError(R.string.network_error);
+ }
+ });
+ } else if (exception instanceof SearchEngine.NothingFoundException) {
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onNothingFound(exception.getMessage());
+ }
+ });
+ } else if (exception instanceof ExtractionException) {
+ View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
+ ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, getServiceName(), query, R.string.parsing_error));
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSearchError(R.string.parsing_error);
+ }
+ });
+ } else {
+ View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
+ ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, getServiceName(), query, R.string.general_error));
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSearchError(R.string.general_error);
+ }
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java b/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java
new file mode 100644
index 000000000..6bad06fce
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/workers/SuggestionWorker.java
@@ -0,0 +1,108 @@
+package org.schabi.newpipe.workers;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.view.View;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.SuggestionExtractor;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.report.ErrorActivity;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Worker that get suggestions based on the query
+ *
+ * @author mauriciocolli
+ */
+public class SuggestionWorker extends AbstractWorker {
+
+ private String query;
+ private OnSuggestionResult callback;
+
+ /**
+ * Interface which will be called for result and errors
+ */
+ public interface OnSuggestionResult {
+ void onSuggestionResult(@NonNull List suggestions);
+ void onSuggestionError(int messageId);
+ }
+
+ public SuggestionWorker(Context context, int serviceId, String query, OnSuggestionResult callback) {
+ super(context, serviceId);
+ this.callback = callback;
+ this.query = query;
+ }
+
+ public static SuggestionWorker startForQuery(Context context, int serviceId, @NonNull String query, OnSuggestionResult callback) {
+ SuggestionWorker worker = new SuggestionWorker(context, serviceId, query, callback);
+ worker.start();
+ return worker;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ this.callback = null;
+ this.query = null;
+ }
+
+ @Override
+ protected void doWork(int serviceId) throws Exception {
+ SuggestionExtractor suggestionExtractor = getService().getSuggestionExtractorInstance();
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
+ String searchLanguageKey = getContext().getString(R.string.search_language_key);
+ String searchLanguage = sharedPreferences.getString(searchLanguageKey, getContext().getString(R.string.default_language_value));
+
+ final List suggestions = suggestionExtractor.suggestionList(query, searchLanguage);
+
+ if (callback != null && suggestions != null && !isInterrupted()) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (isInterrupted() || callback == null) return;
+
+ callback.onSuggestionResult(suggestions);
+ onDestroy();
+ }
+ });
+ }
+
+ @Override
+ protected void handleException(final Exception exception, int serviceId) {
+ if (callback == null || getHandler() == null || isInterrupted()) return;
+
+ if (exception instanceof ExtractionException) {
+ View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
+ ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.GET_SUGGESTIONS, getServiceName(), query, R.string.parsing_error));
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSuggestionError(R.string.parsing_error);
+ }
+ });
+ } else if (exception instanceof IOException) {
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSuggestionError(R.string.network_error);
+ }
+ });
+ } else {
+ View rootView = getContext() instanceof Activity ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
+ ErrorActivity.reportError(getHandler(), getContext(), exception, null, rootView, ErrorActivity.ErrorInfo.make(ErrorActivity.GET_SUGGESTIONS, getServiceName(), query, R.string.general_error));
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSuggestionError(R.string.general_error);
+ }
+ });
+ }
+
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_close_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_black_24dp.png
new file mode 100644
index 000000000..1a9cd75a0
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png
new file mode 100644
index 000000000..ceb1a1eeb
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_expand_less_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 000000000..57139a78a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_expand_less_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_expand_less_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_expand_less_white_24dp.png
new file mode 100644
index 000000000..dea898838
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_expand_less_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_expand_more_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 000000000..9625f148f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_expand_more_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_expand_more_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_expand_more_white_24dp.png
new file mode 100644
index 000000000..022e05799
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_expand_more_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_filter_list_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_filter_list_black_24dp.png
new file mode 100644
index 000000000..a966cb9bd
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_filter_list_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_filter_list_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_filter_list_white_24dp.png
new file mode 100644
index 000000000..7e8a6b536
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_filter_list_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_picture_in_picture_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_picture_in_picture_black_24dp.png
new file mode 100644
index 000000000..54f824410
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_picture_in_picture_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_picture_in_picture_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_picture_in_picture_white_24dp.png
new file mode 100644
index 000000000..b4ec6bb70
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_picture_in_picture_white_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png
new file mode 100644
index 000000000..c593e7ad8
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png
new file mode 100644
index 000000000..bbfbc96cb
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_close_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_black_24dp.png
new file mode 100644
index 000000000..40a1a84e3
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png
new file mode 100644
index 000000000..af7f8288d
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_expand_less_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 000000000..08c16a328
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_expand_less_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_expand_less_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_expand_less_white_24dp.png
new file mode 100644
index 000000000..a2e4baad0
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_expand_less_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_expand_more_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 000000000..feb85a775
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_expand_more_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_expand_more_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_expand_more_white_24dp.png
new file mode 100644
index 000000000..910bb2a0a
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_expand_more_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_filter_list_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_filter_list_black_24dp.png
new file mode 100644
index 000000000..d86492b42
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_filter_list_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_filter_list_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_filter_list_white_24dp.png
new file mode 100644
index 000000000..59a2ec755
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_filter_list_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_picture_in_picture_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_picture_in_picture_black_24dp.png
new file mode 100644
index 000000000..e7a9be944
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_picture_in_picture_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_picture_in_picture_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_picture_in_picture_white_24dp.png
new file mode 100644
index 000000000..96b5ed3f4
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_picture_in_picture_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png
new file mode 100644
index 000000000..6b1634323
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png
new file mode 100644
index 000000000..faefc59c8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png differ
diff --git a/app/src/main/res/drawable-nodpi/ic_play_arrow_black.png b/app/src/main/res/drawable-nodpi/ic_play_arrow_black.png
deleted file mode 100644
index d12d49562..000000000
Binary files a/app/src/main/res/drawable-nodpi/ic_play_arrow_black.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_close_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_close_black_24dp.png
new file mode 100644
index 000000000..6bc437298
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_close_black_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_close_white.png b/app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png
similarity index 100%
rename from app/src/main/res/drawable-mdpi/ic_close_white.png
rename to app/src/main/res/drawable-xhdpi/ic_close_white_24dp.png
diff --git a/app/src/main/res/drawable-xhdpi/ic_expand_less_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 000000000..323360ead
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_expand_less_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_expand_less_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_expand_less_white_24dp.png
new file mode 100644
index 000000000..ae36d91e1
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_expand_less_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_expand_more_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 000000000..d3ee65e9a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_expand_more_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_expand_more_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_expand_more_white_24dp.png
new file mode 100644
index 000000000..c42e2a049
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_expand_more_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_filter_list_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_filter_list_black_24dp.png
new file mode 100644
index 000000000..b64df3612
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_filter_list_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_filter_list_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_filter_list_white_24dp.png
new file mode 100644
index 000000000..9416c70ec
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_filter_list_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_picture_in_picture_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_picture_in_picture_black_24dp.png
new file mode 100644
index 000000000..d85b80f84
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_picture_in_picture_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_picture_in_picture_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_picture_in_picture_white_24dp.png
new file mode 100644
index 000000000..8039bebcf
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_picture_in_picture_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png
new file mode 100644
index 000000000..638190268
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
new file mode 100644
index 000000000..bfc3e3939
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png
new file mode 100644
index 000000000..51b4401ca
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_white.png b/app/src/main/res/drawable-xxhdpi/ic_close_white.png
deleted file mode 100644
index 4927bc242..000000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_close_white.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_close_white.png b/app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png
similarity index 100%
rename from app/src/main/res/drawable-hdpi/ic_close_white.png
rename to app/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png
diff --git a/app/src/main/res/drawable-xxhdpi/ic_expand_less_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 000000000..ee92f4ecd
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_expand_less_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_expand_less_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_expand_less_white_24dp.png
new file mode 100644
index 000000000..62fc386c1
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_expand_less_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_expand_more_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 000000000..5cd142c1d
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_expand_more_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_expand_more_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_expand_more_white_24dp.png
new file mode 100644
index 000000000..dbc0b2032
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_expand_more_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_filter_list_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_filter_list_black_24dp.png
new file mode 100644
index 000000000..2314642f9
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_filter_list_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_filter_list_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_filter_list_white_24dp.png
new file mode 100644
index 000000000..1263ae82e
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_filter_list_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_picture_in_picture_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_picture_in_picture_black_24dp.png
new file mode 100644
index 000000000..c3892ff23
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_picture_in_picture_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_picture_in_picture_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_picture_in_picture_white_24dp.png
new file mode 100644
index 000000000..719a3fef8
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_picture_in_picture_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png
new file mode 100644
index 000000000..3ae490ef9
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
new file mode 100644
index 000000000..abbb98951
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png
new file mode 100644
index 000000000..df42feecb
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close_white.png b/app/src/main/res/drawable-xxxhdpi/ic_close_white.png
deleted file mode 100644
index 1ab231275..000000000
Binary files a/app/src/main/res/drawable-xxxhdpi/ic_close_white.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_close_white.png b/app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png
similarity index 100%
rename from app/src/main/res/drawable-xhdpi/ic_close_white.png
rename to app/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
new file mode 100644
index 000000000..99c6e3e1c
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_expand_less_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_expand_less_white_24dp.png
new file mode 100644
index 000000000..42615516b
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_expand_less_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
new file mode 100644
index 000000000..ad852e3e6
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_expand_more_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_expand_more_white_24dp.png
new file mode 100644
index 000000000..2859a6fec
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_expand_more_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_filter_list_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_filter_list_black_24dp.png
new file mode 100644
index 000000000..9319c4bb4
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_filter_list_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_filter_list_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_filter_list_white_24dp.png
new file mode 100644
index 000000000..cb2207f11
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_filter_list_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_picture_in_picture_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_picture_in_picture_black_24dp.png
new file mode 100644
index 000000000..bb43aa64a
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_picture_in_picture_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_picture_in_picture_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_picture_in_picture_white_24dp.png
new file mode 100644
index 000000000..b9d101119
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_picture_in_picture_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png
new file mode 100644
index 000000000..21be57299
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png
new file mode 100644
index 000000000..dd5adfc7f
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png differ
diff --git a/app/src/main/res/drawable/action_shadow.xml b/app/src/main/res/drawable/toolbar_shadow.xml
similarity index 87%
rename from app/src/main/res/drawable/action_shadow.xml
rename to app/src/main/res/drawable/toolbar_shadow.xml
index e76c1566f..f924df533 100644
--- a/app/src/main/res/drawable/action_shadow.xml
+++ b/app/src/main/res/drawable/toolbar_shadow.xml
@@ -4,7 +4,7 @@
android:shape="rectangle">
diff --git a/app/src/main/res/layout-land/channel_header.xml b/app/src/main/res/layout-land/channel_header.xml
index 17c2623e3..653379832 100644
--- a/app/src/main/res/layout-land/channel_header.xml
+++ b/app/src/main/res/layout-land/channel_header.xml
@@ -1,79 +1,87 @@
+ android:layout_marginBottom="16dp">
+ android:fitsSystemWindows="true"
+ android:scaleType="centerCrop"
+ android:src="@drawable/channel_banner"
+ tools:ignore="ContentDescription"/>
+ android:layout_alignTop="@id/channel_banner_image"
+ android:layout_marginLeft="12dp"
+ android:layout_marginTop="60dp"
+ tools:ignore="RtlHardcoded">
+ android:src="@drawable/white_circle"
+ tools:ignore="ContentDescription,RtlHardcoded"/>
+ android:layout_centerInParent="true"
+ android:src="@drawable/buddy"/>
+ tools:ignore="RtlHardcoded"
+ tools:text="Testing Title"/>
-
-
+ android:layout_alignLeft="@+id/channel_title_view"
+ android:layout_below="@+id/channel_title_view"
+ android:gravity="center"
+ android:visibility="gone"
+ tools:ignore="RtlHardcoded"
+ tools:text="123,141,411 subscribers"
+ tools:visibility="visible"/>
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_downloader.xml b/app/src/main/res/layout/activity_downloader.xml
index 05af4e47d..0c37f6813 100644
--- a/app/src/main/res/layout/activity_downloader.xml
+++ b/app/src/main/res/layout/activity_downloader.xml
@@ -1,7 +1,10 @@
-
+
+
+
+
+
+ android:layout_height="match_parent"
+ android:layout_marginTop="?attr/actionBarSize">
+ android:layout_height="match_parent"
+ android:layout_marginTop="?attr/actionBarSize"/>
+
+
diff --git a/app/src/main/res/layout/activity_exo_player.xml b/app/src/main/res/layout/activity_main_player.xml
similarity index 99%
rename from app/src/main/res/layout/activity_exo_player.xml
rename to app/src/main/res/layout/activity_main_player.xml
index b0e80b584..cd76d2f07 100644
--- a/app/src/main/res/layout/activity_exo_player.xml
+++ b/app/src/main/res/layout/activity_main_player.xml
@@ -236,7 +236,7 @@
-
+
-
\ No newline at end of file
+ android:layout_marginTop="?attr/actionBarSize"/>
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml
index 87a05c13c..3aa4e0634 100644
--- a/app/src/main/res/layout/channel_header.xml
+++ b/app/src/main/res/layout/channel_header.xml
@@ -1,78 +1,88 @@
+ android:layout_marginBottom="12dp">
+ android:fitsSystemWindows="true"
+ android:scaleType="centerCrop"
+ android:src="@drawable/channel_banner"
+ tools:ignore="ContentDescription"/>
+ android:layout_alignTop="@id/channel_banner_image"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="50dp"
+ tools:ignore="RtlHardcoded">
+ android:src="@drawable/white_circle"
+ tools:ignore="ContentDescription"/>
+ android:layout_centerInParent="true"
+ android:src="@drawable/buddy"/>
+ tools:ignore="RtlHardcoded"
+ tools:text="Testing Title"/>
-
-
+ android:layout_alignLeft="@+id/channel_title_view"
+ android:layout_below="@+id/channel_title_view"
+ android:gravity="center"
+ android:textSize="@dimen/channel_subscribers_text_size"
+ android:visibility="gone"
+ tools:ignore="RtlHardcoded"
+ tools:text="123,141,411 subscribers"
+ tools:visibility="visible"/>
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/channel_item.xml b/app/src/main/res/layout/channel_item.xml
index 3c74c9493..bbdf6779b 100644
--- a/app/src/main/res/layout/channel_item.xml
+++ b/app/src/main/res/layout/channel_item.xml
@@ -1,94 +1,76 @@
-
-
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:orientation="vertical"
+ android:padding="12dp">
+
+
+ android:layout_height="@dimen/video_item_search_thumbnail_image_height"
+ android:layout_toRightOf="@id/itemThumbnailView"
+ android:orientation="vertical"
+ tools:ignore="RtlHardcoded">
-
+
+
+
+
+ android:layout_alignParentBottom="true"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textSize="@dimen/video_item_search_upload_date_text_size"
+ tools:text="10M subscribers • "/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/dialog_url.xml b/app/src/main/res/layout/dialog_url.xml
index 33df882be..bb3fad414 100644
--- a/app/src/main/res/layout/dialog_url.xml
+++ b/app/src/main/res/layout/dialog_url.xml
@@ -1,115 +1,94 @@
-
+
-
+
-
+
-
+
-
+
+
-
-
-
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/app/src/main/res/layout/error_retry.xml b/app/src/main/res/layout/error_retry.xml
new file mode 100644
index 000000000..edd576e1c
--- /dev/null
+++ b/app/src/main/res/layout/error_retry.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_channel.xml b/app/src/main/res/layout/fragment_channel.xml
index 78388ee76..807a5ac4c 100644
--- a/app/src/main/res/layout/fragment_channel.xml
+++ b/app/src/main/res/layout/fragment_channel.xml
@@ -1,11 +1,9 @@
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ android:indeterminate="true"
+ android:visibility="gone"
+ tools:visibility="visible"/>
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
new file mode 100644
index 000000000..cd5846b56
--- /dev/null
+++ b/app/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml
index 2ed0ea0f7..80aa1ab4c 100644
--- a/app/src/main/res/layout/fragment_search.xml
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -1,27 +1,39 @@
-
-
-
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true">
+ tools:listitem="@layout/stream_item"/>
-
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml
index 4b34bcb49..aa29b822c 100644
--- a/app/src/main/res/layout/fragment_video_detail.xml
+++ b/app/src/main/res/layout/fragment_video_detail.xml
@@ -1,10 +1,12 @@
-
+
+
@@ -24,7 +27,6 @@
android:layout_height="wrap_content"
android:background="@android:color/black">
-
-
+
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ android:baselineAligned="false"
+ android:orientation="horizontal"
+ android:paddingTop="6dp">
-
-
-
-
+
+ android:layout_alignParentLeft="true"
+ android:layout_centerHorizontal="true"
+ android:orientation="vertical"
+ android:paddingTop="6dp"
+ tools:ignore="RtlHardcoded">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+ android:layout_below="@+id/detail_root">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:layout_below="@+id/detail_description_root_layout"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginTop="14dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/play_list_item.xml b/app/src/main/res/layout/play_list_item.xml
deleted file mode 100644
index d83591720..000000000
--- a/app/src/main/res/layout/play_list_item.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_notification.xml b/app/src/main/res/layout/player_notification.xml
index c7b70fb56..958b9bf3d 100644
--- a/app/src/main/res/layout/player_notification.xml
+++ b/app/src/main/res/layout/player_notification.xml
@@ -103,7 +103,7 @@
android:clickable="true"
android:padding="5dp"
android:scaleType="fitCenter"
- android:src="@drawable/ic_close_white"
+ android:src="@drawable/ic_close_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"/>
diff --git a/app/src/main/res/layout/player_notification_expanded.xml b/app/src/main/res/layout/player_notification_expanded.xml
index 46c22a451..22b0fd153 100644
--- a/app/src/main/res/layout/player_notification_expanded.xml
+++ b/app/src/main/res/layout/player_notification_expanded.xml
@@ -28,7 +28,7 @@
android:clickable="true"
android:padding="8dp"
android:scaleType="fitCenter"
- android:src="@drawable/ic_close_white"
+ android:src="@drawable/ic_close_white_24dp"
tools:ignore="ContentDescription,RtlHardcoded"/>
diff --git a/app/src/main/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml
index a3b2b80b6..a3be1511c 100644
--- a/app/src/main/res/layout/player_popup.xml
+++ b/app/src/main/res/layout/player_popup.xml
@@ -173,7 +173,7 @@
tools:visibility="visible"/>
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings_layout.xml b/app/src/main/res/layout/settings_layout.xml
new file mode 100644
index 000000000..1e56b5ce8
--- /dev/null
+++ b/app/src/main/res/layout/settings_layout.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/stream_item.xml b/app/src/main/res/layout/stream_item.xml
index 0fa478752..4c3c410d8 100644
--- a/app/src/main/res/layout/stream_item.xml
+++ b/app/src/main/res/layout/stream_item.xml
@@ -1,117 +1,96 @@
-
-
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:orientation="vertical"
+ android:padding="12dp">
+
+
+
+
+ android:layout_height="@dimen/video_item_search_thumbnail_image_height"
+ android:layout_toRightOf="@id/itemThumbnailView"
+ android:orientation="vertical"
+ tools:ignore="RtlHardcoded">
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_alignParentBottom="true"
+ android:maxLines="1"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textSize="@dimen/video_item_search_upload_date_text_size"
+ tools:text="2 years ago • "/>
+
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/toolbar_layout.xml b/app/src/main/res/layout/toolbar_layout.xml
new file mode 100644
index 000000000..8fef728fd
--- /dev/null
+++ b/app/src/main/res/layout/toolbar_layout.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/toolbar_search_layout.xml b/app/src/main/res/layout/toolbar_search_layout.xml
new file mode 100644
index 000000000..ceebcc427
--- /dev/null
+++ b/app/src/main/res/layout/toolbar_search_layout.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_fragment_menu.xml b/app/src/main/res/menu/main_fragment_menu.xml
new file mode 100644
index 000000000..8327e936d
--- /dev/null
+++ b/app/src/main/res/menu/main_fragment_menu.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/search_menu.xml b/app/src/main/res/menu/search_menu.xml
index d3df69b25..19d76a57b 100644
--- a/app/src/main/res/menu/search_menu.xml
+++ b/app/src/main/res/menu/search_menu.xml
@@ -1,21 +1,29 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/video_detail_menu.xml b/app/src/main/res/menu/video_detail_menu.xml
new file mode 100644
index 000000000..9ac1ce65c
--- /dev/null
+++ b/app/src/main/res/menu/video_detail_menu.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/videoitem_detail.xml b/app/src/main/res/menu/videoitem_detail.xml
deleted file mode 100644
index 6143ee974..000000000
--- a/app/src/main/res/menu/videoitem_detail.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/videoitem_two_pannel.xml b/app/src/main/res/menu/videoitem_two_pannel.xml
deleted file mode 100644
index 8b96a99d4..000000000
--- a/app/src/main/res/menu/videoitem_two_pannel.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
index 840a366bf..befa12970 100644
--- a/app/src/main/res/values-land/dimens.xml
+++ b/app/src/main/res/values-land/dimens.xml
@@ -13,15 +13,10 @@
142dp80dp
- 5dp
- 10dp
- 10dp10dp1sp7sp5sp
-
- 4dp
@@ -32,13 +27,16 @@
14sp14sp17sp
+ 14sp
+ 14sp70dp20sp20sp
+ 90dp
+ 94dp
- 10sp
- 10sp
- 20dp
+ 8dp
+ 4dp
\ No newline at end of file
diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml
index 105263e24..ed40aa04b 100644
--- a/app/src/main/res/values-sw600dp/dimens.xml
+++ b/app/src/main/res/values-sw600dp/dimens.xml
@@ -3,21 +3,19 @@
- 21sp
- 20sp
+ 18sp
+ 16sp16sp14sp16sp16sp16sp18sp
- 18sp100dp18sp18sp
- 10sp
- 10sp
+ 10dp
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 6460beaf6..29396ccfd 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -7,5 +7,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 01155e064..4e1e0e22c 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,11 +1,13 @@
+
#EEEEEE
- #CD322E
- #BC211D
+ #e53935
+ #c62828#000000#32000000
+ #48868686#222222
@@ -13,6 +15,7 @@
#BC211D#FFFFFF#0affffff
+ #48ffffff#1effffff
diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml
deleted file mode 100644
index e13926f59..000000000
--- a/app/src/main/res/values/constants.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
- 13sp
-
-
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 7505a84d2..b39348290 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -20,13 +20,15 @@
- 18sp
+ 16sp13sp12sp14sp13sp13sp15sp
+ 12sp
+ 12sp50dp18sp
@@ -34,12 +36,10 @@
70dp74dp
- 6sp
+ 5dp
+ 50dp16dp16dp
- 180dp
- 16dp
- 16dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 05f45e93c..bcfa65ed2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -26,6 +26,10 @@
Use external video playerUse external audio playerNewPipe Popup mode
+ RSS
+
+ Background
+ PopupVideo download pathPath to store downloaded videos in.
@@ -102,6 +106,9 @@
YesLaterDisabled
+ Filter
+ Refresh
+ ClearError
@@ -173,7 +180,6 @@
KMB
- RestartStart
@@ -202,7 +208,6 @@
Please wait…Copied to clipboard.Please select an available download directory.
- You have to restart the application to apply the theme.\n\nDo you want to restart now?This permission is needed to\nopen in popup mode
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 14ec77d89..3ef41885f 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -8,9 +8,7 @@
-
-
-
-
+
-
-
-
-
-
-
-
+
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
index ba4a40b6d..55b0c8196 100644
--- a/app/src/main/res/xml/settings.xml
+++ b/app/src/main/res/xml/settings.xml
@@ -24,6 +24,7 @@
android:title="@string/default_resolution_title"
android:entries="@array/resolution_list"
android:entryValues="@array/resolution_list"
+ android:summary="%s"
android:defaultValue="@string/default_resolution_value"/>