From a8fe3296785e66ede2d928ec4ab9df4a2653a985 Mon Sep 17 00:00:00 2001 From: Mauricio Colli Date: Wed, 26 Apr 2017 16:32:04 -0300 Subject: [PATCH] Improve fragments - They save the state now, that means, no more reloading after rotating the screen or switching between apps --- .../java/org/schabi/newpipe/MainActivity.java | 87 +- .../newpipe/download/DownloadActivity.java | 23 +- .../newpipe/download/DownloadDialog.java | 39 +- .../newpipe/fragments/BaseFragment.java | 243 ++++++ .../newpipe/fragments/MainFragment.java | 76 ++ .../fragments/channel/ChannelFragment.java | 382 ++++----- .../fragments/detail/ActionBarHandler.java | 118 +-- .../newpipe/fragments/detail/StackItem.java | 11 + .../fragments/detail/StreamInfoCache.java | 85 ++ .../fragments/detail/VideoDetailFragment.java | 797 ++++++++++-------- .../fragments/search/SearchFragment.java | 729 ++++++++++------ .../search/SuggestionListAdapter.java | 5 + .../info_list/ChannelInfoItemHolder.java | 6 +- .../newpipe/info_list/InfoItemBuilder.java | 100 +-- .../newpipe/info_list/InfoListAdapter.java | 18 +- .../info_list/StreamInfoItemHolder.java | 15 +- .../newpipe/settings/SettingsActivity.java | 116 +-- .../newpipe/settings/SettingsFragment.java | 321 ++----- 18 files changed, 1793 insertions(+), 1378 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/BaseFragment.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/detail/StreamInfoCache.java 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/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. - *

- * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class ChannelFragment extends Fragment implements ChannelExtractorWorker.OnChannelInfoReceive { - private static final String TAG = "ChannelFragment"; - - private AppCompatActivity activity; - private OnItemSelectedListener onItemSelectedListener; private InfoListAdapter infoListAdapter; - private ChannelExtractorWorker currentExtractorWorker; + private ChannelExtractorWorker currentChannelWorker; private ChannelInfo currentChannelInfo; private int serviceId = -1; private String channelName = ""; private String channelUrl = ""; - - private boolean isLoading = false; private int pageNumber = 0; private boolean hasNextPage = true; - private ImageLoader imageLoader = ImageLoader.getInstance(); - /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ - private View rootView = null; - private RecyclerView channelVideosList; - private LinearLayoutManager layoutManager; - private ProgressBar loadingProgressBar; private View headerRootLayout; private ImageView headerChannelBanner; private ImageView headerAvatarView; private TextView headerTitleView; - private TextView headerSubscriberView; - private Button headerSubscriberButton; - private View headerSubscriberLayout; + private TextView headerSubscribersTextView; + private Button headerRssButton; /*////////////////////////////////////////////////////////////////////////*/ public ChannelFragment() { } - public static ChannelFragment newInstance(int serviceId, String url, String name) { - ChannelFragment instance = newInstance(); - - Bundle bundle = new Bundle(); - bundle.putString(Constants.KEY_URL, url); - bundle.putString(Constants.KEY_TITLE, name); - bundle.putInt(Constants.KEY_SERVICE_ID, serviceId); - - instance.setArguments(bundle); + public static Fragment getInstance(int serviceId, String channelUrl, String name) { + ChannelFragment instance = new ChannelFragment(); + instance.setChannel(serviceId, channelUrl, name); return instance; } - public static ChannelFragment newInstance() { - return new ChannelFragment(); - } - /*////////////////////////////////////////////////////////////////////////// // Fragment's LifeCycle //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onAttach(Context context) { - super.onAttach(context); - activity = ((AppCompatActivity) context); - onItemSelectedListener = ((OnItemSelectedListener) context); - } - @Override public void onCreate(Bundle savedInstanceState) { + if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); super.onCreate(savedInstanceState); setHasOptionsMenu(true); - isLoading = false; if (savedInstanceState != null) { channelUrl = savedInstanceState.getString(Constants.KEY_URL); channelName = savedInstanceState.getString(Constants.KEY_TITLE); serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, -1); - } else { - try { - Bundle args = getArguments(); - if (args != null) { - channelUrl = args.getString(Constants.KEY_URL); - channelName = args.getString(Constants.KEY_TITLE); - serviceId = args.getInt(Constants.KEY_SERVICE_ID, 0); - } - } catch (Exception e) { - e.printStackTrace(); - ErrorActivity.reportError(getActivity(), e, null, - getActivity().findViewById(android.R.id.content), - ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, - NewPipe.getNameOfService(serviceId), - "", R.string.general_error)); - } + + pageNumber = savedInstanceState.getInt(PAGE_NUMBER_KEY, 0); + Serializable serializable = savedInstanceState.getSerializable(CHANNEL_INFO_KEY); + if (serializable instanceof ChannelInfo) currentChannelInfo = (ChannelInfo) serializable; } } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - rootView = inflater.inflate(R.layout.fragment_channel, container, false); - return rootView; + if (DEBUG) Log.d(TAG, "onCreateView() called with: inflater = [" + inflater + "], container = [" + container + "], savedInstanceState = [" + savedInstanceState + "]"); + return inflater.inflate(R.layout.fragment_channel, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - loadingProgressBar = (ProgressBar) view.findViewById(R.id.loading_progress_bar); - channelVideosList = (RecyclerView) view.findViewById(R.id.channel_streams_view); + super.onViewCreated(view, savedInstanceState); - infoListAdapter = new InfoListAdapter(activity, rootView); - layoutManager = new LinearLayoutManager(activity); - channelVideosList.setLayoutManager(layoutManager); - - headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, channelVideosList, false); - infoListAdapter.setHeader(headerRootLayout); - infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, channelVideosList, false)); - channelVideosList.setAdapter(infoListAdapter); - - headerChannelBanner = (ImageView) headerRootLayout.findViewById(R.id.channel_banner_image); - headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.channel_avatar_view); - headerTitleView = (TextView) headerRootLayout.findViewById(R.id.channel_title_view); - headerSubscriberView = (TextView) headerRootLayout.findViewById(R.id.channel_subscriber_view); - headerSubscriberButton = (Button) headerRootLayout.findViewById(R.id.channel_subscribe_button); - headerSubscriberLayout = headerRootLayout.findViewById(R.id.channel_subscriber_layout); - - initListeners(); - - isLoading = true; + if (currentChannelInfo == null) loadPage(0); + else handleChannelInfo(currentChannelInfo, false, false); } @Override public void onDestroyView() { - super.onDestroyView(); + if (DEBUG) Log.d(TAG, "onDestroyView() called"); headerAvatarView.setImageBitmap(null); headerChannelBanner.setImageBitmap(null); channelVideosList.removeAllViews(); - rootView = null; channelVideosList = null; - layoutManager = null; - loadingProgressBar = null; headerRootLayout = null; headerChannelBanner = null; headerAvatarView = null; headerTitleView = null; - headerSubscriberView = null; - headerSubscriberButton = null; - headerSubscriberLayout = null; + headerSubscribersTextView = null; + headerRssButton = null; + + super.onDestroyView(); } @Override public void onResume() { + if (DEBUG) Log.d(TAG, "onResume() called"); super.onResume(); - if (isLoading) { - requestData(false); + if (wasLoading.getAndSet(false) && (currentChannelWorker == null || !currentChannelWorker.isRunning())) { + loadPage(pageNumber); } } @Override public void onStop() { + if (DEBUG) Log.d(TAG, "onStop() called"); super.onStop(); - if (currentExtractorWorker != null) currentExtractorWorker.cancel(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - imageLoader.clearMemoryCache(); + wasLoading.set(currentChannelWorker != null && currentChannelWorker.isRunning()); + if (currentChannelWorker != null && currentChannelWorker.isRunning()) currentChannelWorker.cancel(); } @Override public void onSaveInstanceState(Bundle outState) { + if (DEBUG) Log.d(TAG, "onSaveInstanceState() called with: outState = [" + outState + "]"); super.onSaveInstanceState(outState); outState.putString(Constants.KEY_URL, channelUrl); outState.putString(Constants.KEY_TITLE, channelName); outState.putInt(Constants.KEY_SERVICE_ID, serviceId); + + outState.putSerializable(INFO_LIST_KEY, ((ArrayList) infoListAdapter.getItemsList())); + outState.putSerializable(CHANNEL_INFO_KEY, currentChannelInfo); + outState.putInt(PAGE_NUMBER_KEY, pageNumber); } /*////////////////////////////////////////////////////////////////////////// @@ -243,6 +164,7 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker. //////////////////////////////////////////////////////////////////////////*/ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_channel, menu); @@ -250,20 +172,18 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker. if (supportActionBar != null) { supportActionBar.setDisplayShowTitleEnabled(true); supportActionBar.setDisplayHomeAsUpEnabled(true); - //noinspection deprecation - supportActionBar.setNavigationMode(0); } } @Override public boolean onOptionsItemSelected(MenuItem item) { + if (DEBUG) Log.d(TAG, "onOptionsItemSelected() called with: item = [" + item + "]"); super.onOptionsItemSelected(item); switch (item.getItemId()) { case R.id.menu_item_openInBrowser: { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setData(Uri.parse(channelUrl)); - startActivity(Intent.createChooser(intent, getString(R.string.choose_browser))); return true; } @@ -283,79 +203,185 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker. // Init's //////////////////////////////////////////////////////////////////////////*/ - private void initListeners() { + @Override + protected void initViews(View rootView, Bundle savedInstanceState) { + super.initViews(rootView, savedInstanceState); + + channelVideosList = (RecyclerView) rootView.findViewById(R.id.channel_streams_view); + + channelVideosList.setLayoutManager(new LinearLayoutManager(activity)); + if (infoListAdapter == null) { + infoListAdapter = new InfoListAdapter(activity, rootView); + if (savedInstanceState != null) { + //noinspection unchecked + ArrayList serializable = (ArrayList) savedInstanceState.getSerializable(INFO_LIST_KEY); + infoListAdapter.addInfoItemList(serializable); + } + } + + channelVideosList.setAdapter(infoListAdapter); + headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, channelVideosList, false); + infoListAdapter.setHeader(headerRootLayout); + infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, channelVideosList, false)); + + headerChannelBanner = (ImageView) headerRootLayout.findViewById(R.id.channel_banner_image); + headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.channel_avatar_view); + headerTitleView = (TextView) headerRootLayout.findViewById(R.id.channel_title_view); + headerSubscribersTextView = (TextView) headerRootLayout.findViewById(R.id.channel_subscriber_view); + headerRssButton = (Button) headerRootLayout.findViewById(R.id.channel_rss_button); + } + + protected void initListeners() { + super.initListeners(); + infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { @Override public void selected(int serviceId, String url, String title) { + if (DEBUG) Log.d(TAG, "selected() called with: serviceId = [" + serviceId + "], url = [" + url + "], title = [" + title + "]"); NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title); } }); - // detect if list has ben scrolled to the bottom - channelVideosList.setOnScrollListener(new RecyclerView.OnScrollListener() { + channelVideosList.clearOnScrollListeners(); + channelVideosList.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) channelVideosList.getLayoutManager(); + visibleItemCount = layoutManager.getChildCount(); totalItemCount = layoutManager.getItemCount(); pastVisiblesItems = layoutManager.findFirstVisibleItemPosition(); - if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !currentExtractorWorker.isRunning() && hasNextPage) { + if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && (currentChannelWorker == null || !currentChannelWorker.isRunning()) && hasNextPage && !isLoading.get()) { pageNumber++; - requestData(true); + loadMoreVideos(); } } } }); - headerSubscriberButton.setOnClickListener(new View.OnClickListener() { + headerRssButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - Log.d(TAG, currentChannelInfo.feed_url); + if (DEBUG) Log.d(TAG, "onClick() called with: view = [" + view + "] feed url > " + currentChannelInfo.feed_url); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(currentChannelInfo.feed_url)); startActivity(i); } }); } + @Override + protected void reloadContent() { + if (DEBUG) Log.d(TAG, "reloadContent() called"); + currentChannelInfo = null; + infoListAdapter.clearStreamItemList(); + loadPage(0); + } + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ + private String buildSubscriberString(long count) { String out = NumberFormat.getNumberInstance().format(count); out += " " + getString(count > 1 ? R.string.subscriber_plural : R.string.subscriber); return out; } - private void requestData(boolean onlyVideos) { - if (currentExtractorWorker != null && currentExtractorWorker.isRunning()) currentExtractorWorker.cancel(); + private void loadPage(int page) { + if (DEBUG) Log.d(TAG, "loadPage() called with: page = [" + page + "]"); + if (currentChannelWorker != null && currentChannelWorker.isRunning()) currentChannelWorker.cancel(); + isLoading.set(true); + pageNumber = page; + infoListAdapter.showFooter(false); - isLoading = true; - if (!onlyVideos) { - //delete already displayed content - loadingProgressBar.setVisibility(View.VISIBLE); - infoListAdapter.clearSteamItemList(); - pageNumber = 0; - headerSubscriberLayout.setVisibility(View.GONE); - headerTitleView.setText(channelName != null ? channelName : ""); - if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(channelName != null ? channelName : ""); - if (SDK_INT >= 21) { - headerChannelBanner.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.channel_banner)); - headerAvatarView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy)); - } - infoListAdapter.showFooter(false); - } + animateView(loadingProgressBar, true, 200); + animateView(errorPanel, false, 200); - currentExtractorWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, pageNumber, this); - currentExtractorWorker.setOnlyVideos(onlyVideos); - currentExtractorWorker.start(); + imageLoader.cancelDisplayTask(headerChannelBanner); + imageLoader.cancelDisplayTask(headerAvatarView); + + headerRssButton.setVisibility(View.GONE); + headerSubscribersTextView.setVisibility(View.GONE); + + headerTitleView.setText(channelName != null ? channelName : ""); + headerChannelBanner.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.channel_banner)); + headerAvatarView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy)); + if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(channelName != null ? channelName : ""); + + currentChannelWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, page, false, this); + currentChannelWorker.start(); } - private void addVideos(ChannelInfo info) { - infoListAdapter.addInfoItemList(info.related_streams); + private void loadMoreVideos() { + if (DEBUG) Log.d(TAG, "loadMoreVideos() called"); + if (currentChannelWorker != null && currentChannelWorker.isRunning()) currentChannelWorker.cancel(); + isLoading.set(true); + currentChannelWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, pageNumber, true, this); + currentChannelWorker.start(); + } + + private void setChannel(int serviceId, String channelUrl, String name) { + this.serviceId = serviceId; + this.channelUrl = channelUrl; + this.channelName = name; + } + + private void handleChannelInfo(ChannelInfo info, boolean onlyVideos, boolean addVideos) { + currentChannelInfo = info; + + animateView(errorPanel, false, 300); + animateView(channelVideosList, true, 200); + animateView(loadingProgressBar, false, 200); + + if (!onlyVideos) { + headerRootLayout.setVisibility(View.VISIBLE); + //animateView(loadingProgressBar, false, 200, null); + + if (!TextUtils.isEmpty(info.channel_name)) { + if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(info.channel_name); + headerTitleView.setText(info.channel_name); + channelName = info.channel_name; + } else channelName = ""; + + if (!TextUtils.isEmpty(info.banner_url)) { + imageLoader.displayImage(info.banner_url, headerChannelBanner, displayImageOptions, new ImageErrorLoadingListener(activity, getView(), info.service_id)); + } + + if (!TextUtils.isEmpty(info.avatar_url)) { + headerAvatarView.setVisibility(View.VISIBLE); + imageLoader.displayImage(info.avatar_url, headerAvatarView, displayImageOptions, new ImageErrorLoadingListener(activity, getView(), info.service_id)); + } + + if (info.subscriberCount != -1) { + headerSubscribersTextView.setText(buildSubscriberString(info.subscriberCount)); + headerSubscribersTextView.setVisibility(View.VISIBLE); + } else headerSubscribersTextView.setVisibility(View.GONE); + + if (!TextUtils.isEmpty(info.feed_url)) headerRssButton.setVisibility(View.VISIBLE); + else headerRssButton.setVisibility(View.INVISIBLE); + + infoListAdapter.showFooter(true); + } + + hasNextPage = info.hasNextPage; + if (!hasNextPage) infoListAdapter.showFooter(false); + + //if (!listRestored) { + if (addVideos) infoListAdapter.addInfoItemList(info.related_streams); + //} + } + + @Override + protected void setErrorMessage(String message, boolean showRetryButton) { + super.setErrorMessage(message, showRetryButton); + + animateView(channelVideosList, false, 200); + currentChannelInfo = null; } /*////////////////////////////////////////////////////////////////////////// @@ -363,59 +389,23 @@ public class ChannelFragment extends Fragment implements ChannelExtractorWorker. //////////////////////////////////////////////////////////////////////////*/ @Override - public void onReceive(ChannelInfo info) { + public void onReceive(ChannelInfo info, boolean onlyVideos) { + if (DEBUG) Log.d(TAG, "onReceive() called with: info = [" + info + "]"); if (info == null || isRemoving() || !isVisible()) return; - currentChannelInfo = info; - - if (!currentExtractorWorker.isOnlyVideos()) { - headerRootLayout.setVisibility(View.VISIBLE); - loadingProgressBar.setVisibility(View.GONE); - - if (info.channel_name != null && !info.channel_name.isEmpty()) { - if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(info.channel_name); - headerTitleView.setText(info.channel_name); - channelName = info.channel_name; - } else channelName = ""; - - if (info.banner_url != null && !info.banner_url.isEmpty()) { - imageLoader.displayImage(info.banner_url, headerChannelBanner, - new ImageErrorLoadingListener(activity, rootView, info.service_id)); - } - - if (info.avatar_url != null && !info.avatar_url.isEmpty()) { - headerAvatarView.setVisibility(View.VISIBLE); - imageLoader.displayImage(info.avatar_url, headerAvatarView, - new ImageErrorLoadingListener(activity, rootView, info.service_id)); - } - - if (info.subscriberCount != -1) { - headerSubscriberView.setText(buildSubscriberString(info.subscriberCount)); - } - - if ((info.feed_url != null && !info.feed_url.isEmpty()) || (info.subscriberCount != -1)) { - headerSubscriberLayout.setVisibility(View.VISIBLE); - } - - if (info.feed_url == null || info.feed_url.isEmpty()) { - headerSubscriberButton.setVisibility(View.INVISIBLE); - } - - infoListAdapter.showFooter(true); - } - hasNextPage = info.hasNextPage; - if (!hasNextPage) infoListAdapter.showFooter(false); - addVideos(info); - isLoading = false; + handleChannelInfo(info, onlyVideos, true); + isLoading.set(false); } @Override public void onError(int messageId) { - Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show(); + if (DEBUG) Log.d(TAG, "onError() called with: messageId = [" + messageId + "]"); + setErrorMessage(getString(messageId), true); } @Override public void onUnrecoverableError(Exception exception) { + if (DEBUG) Log.d(TAG, "onUnrecoverableError() called with: exception = [" + exception + "]"); activity.finish(); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java index 007cb0082..3805c30e7 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java @@ -2,13 +2,15 @@ package org.schabi.newpipe.fragments.detail; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.Spinner; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.MediaFormat; @@ -19,27 +21,27 @@ import java.util.List; /** * Created by Christian Schabesberger on 18.08.15. - * + *

* 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..f7469e8fd 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,19 @@ 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.NonNull; import android.support.v4.content.ContextCompat; 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 +24,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 +47,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 +61,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 +93,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,6 +119,9 @@ 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; @@ -143,6 +139,7 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork private TextView nextStreamTitle; private RelativeLayout relatedStreamRootLayout; private LinearLayout relatedStreamsView; + private ImageButton relatedStreamExpandButton; /*////////////////////////////////////////////////////////////////////////*/ @@ -159,24 +156,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,65 +178,30 @@ 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); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - thumbnailImageView.setImageBitmap(null); - relatedStreamsView.removeAllViews(); - - loadingProgressBar = null; - - parallaxScrollRootView = null; - contentRootLayout = null; - - thumbnailBackgroundButton = null; - thumbnailImageView = null; - thumbnailPlayButton = null; - - videoTitleRoot = null; - videoTitleTextView = null; - videoTitleToggleArrow = null; - videoCountView = null; - - videoDescriptionRootLayout = null; - videoUploadDateView = null; - videoDescriptionView = null; - - uploaderButton = null; - uploaderTextView = null; - uploaderThumb = null; - - thumbsUpTextView = null; - thumbsUpImageView = null; - thumbsDownTextView = null; - thumbsDownImageView = null; - thumbsDisabledTextView = null; - - nextStreamTitle = null; - relatedStreamRootLayout = null; - relatedStreamsView = null; + super.onViewCreated(rootView, savedInstanceState); + if (currentStreamInfo == null) selectAndLoadVideo(serviceId, videoUrl, videoTitle); + else prepareAndLoad(currentStreamInfo); } @Override @@ -267,23 +226,79 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork @Override public void onStop() { super.onStop(); - wasLoading.set(curExtractorWorker.isRunning()); + 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); - imageLoader.clearMemoryCache(); + } + + @Override + public void onDestroyView() { + if (DEBUG) Log.d(TAG, "onDestroyView() called"); + thumbnailImageView.setImageBitmap(null); + relatedStreamsView.removeAllViews(); + spinnerToolbar.setOnItemSelectedListener(null); + + spinnerToolbar = null; + + parallaxScrollRootView = null; + contentRootLayoutHiding = null; + + thumbnailBackgroundButton = null; + thumbnailImageView = null; + thumbnailPlayButton = null; + + videoTitleRoot = null; + videoTitleTextView = null; + videoTitleToggleArrow = null; + videoCountView = null; + + detailControlsBackground = null; + detailControlsPopup = null; + + videoDescriptionRootLayout = null; + videoUploadDateView = null; + videoDescriptionView = null; + + uploaderButton = null; + uploaderTextView = null; + uploaderThumb = null; + + thumbsUpTextView = null; + thumbsUpImageView = null; + thumbsDownTextView = null; + thumbsDownImageView = null; + thumbsDisabledTextView = null; + + nextStreamTitle = null; + relatedStreamRootLayout = null; + relatedStreamsView = null; + relatedStreamExpandButton = null; + + 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 +328,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); + } + + 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); @@ -353,6 +505,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 +515,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 +525,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 +560,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 +587,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 +600,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 +627,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 +701,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 +718,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 +731,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 +748,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 +768,145 @@ 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) { + if (DEBUG) Log.d(TAG, "selectAndHandleInfo() called with: info = [" + info + "]"); + selectVideo(info.service_id, info.webpage_url, info.title); + prepareAndLoad(info); } - public void loadSelectedVideo() { + public void selectAndLoadVideo(int serviceId, String videoUrl, String videoTitle) { + if (DEBUG) Log.d(TAG, "selectAndLoadVideo() called with: serviceId = [" + serviceId + "], videoUrl = [" + videoUrl + "], videoTitle = [" + videoTitle + "]"); + selectVideo(serviceId, videoUrl, videoTitle); + prepareAndLoad(null); + } + + /** + * 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 + */ + public void prepareAndLoad(StreamInfo info) { + 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(); - - if (activity.getSupportActionBar() != null) { - //noinspection deprecation - activity.getSupportActionBar().setNavigationMode(0); - } - - animateView(contentRootLayout, false, 50, null); - - videoTitleTextView.setMaxLines(1); - int scrollY = parallaxScrollRootView.getScrollY(); - if (scrollY < 30) animateView(videoTitleTextView, false, 200, new Runnable() { + animateView(spinnerToolbar, false, 200); + animateView(errorPanel, false, 200); + animateView(videoTitleTextView, false, 100, new Runnable() { @Override public void run() { if (videoTitleTextView == null) return; videoTitleTextView.setText(videoTitle != null ? videoTitle : ""); - animateView(videoTitleTextView, true, 400, null); + videoTitleTextView.setMaxLines(1); + animateView(videoTitleTextView, true, 100); } }); - else videoTitleTextView.setText(videoTitle != null ? videoTitle : ""); - //videoTitleTextView.setText(videoTitle != null ? videoTitle : ""); 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; + animateView(contentRootLayoutHiding, false, 300, new Runnable() { + @Override + public void run() { + if (contentRootLayoutHiding == null) return; + handleStreamInfo(infoFinal, false); + isLoading.set(false); + animateView(contentRootLayoutHiding, true, 400, 200); + } + }); + } else { + 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); + parallaxScrollRootView.scrollTo(0, 0); + + + // 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 +974,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,51 +989,15 @@ 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); } } @@ -879,66 +1005,33 @@ public class VideoDetailFragment extends Fragment implements StreamExtractorWork // 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); - - // 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); + handleStreamInfo(info, true); + animateView(contentRootLayoutHiding, true, 400); + animateView(loadingProgressBar, false, 200); if (autoPlayEnabled) { playVideo(info); @@ -946,15 +1039,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 +1057,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 +1074,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/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/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/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); } }