diff --git a/README.md b/README.md index b154fad58..348d297cf 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

ScreenshotsDescriptionFeaturesContributionDonateLicense

WebsiteBlogPress


-WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS. +**WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS.** ## Screenshots @@ -61,11 +61,11 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Queuing videos * Local playlists * Subtitles -* Multi-service support (eg. SoundCloud in NewPipe Beta) +* Multi-service support (eg. SoundCloud \[beta\]) +* Livestream support ### Coming Features -* Livestream support * Cast to UPnP and Cast * Show comments * ... and many more diff --git a/app/build.gradle b/app/build.gradle index 354dbaa0e..4ae9f0fb7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 27 - versionCode 66 - versionName "0.13.7" + versionCode 68 + versionName "0.14.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:fef71aeccc37' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:217d13b1028' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index f436a26b8..dfce8f100 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -106,7 +106,7 @@ public class App extends Application { // https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling RxJavaPlugins.setErrorHandler(new Consumer() { @Override - public void accept(@NonNull Throwable throwable) throws Exception { + public void accept(@NonNull Throwable throwable) { Log.e(TAG, "RxJavaPlugins.ErrorHandler called with -> : " + "throwable = [" + throwable.getClass().getName() + "]"); diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index b125ab02f..4e4cdcc0d 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -12,14 +12,12 @@ import android.view.View; import com.nostra13.universalimageloader.core.ImageLoader; import com.squareup.leakcanary.RefWatcher; -import org.schabi.newpipe.report.UserAction; - import icepick.Icepick; import icepick.State; public abstract class BaseFragment extends Fragment { protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); - protected boolean DEBUG = MainActivity.DEBUG; + protected final boolean DEBUG = MainActivity.DEBUG; protected AppCompatActivity activity; public static final ImageLoader imageLoader = ImageLoader.getInstance(); diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java index 17dc5859d..68f7e080f 100644 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ b/app/src/main/java/org/schabi/newpipe/Downloader.java @@ -43,7 +43,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { private static Downloader instance; private String mCookies; - private OkHttpClient client; + private final OkHttpClient client; private Downloader(OkHttpClient.Builder builder) { this.client = builder diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 7c77bff3d..a9f2e9622 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -23,7 +23,6 @@ package org.schabi.newpipe; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -66,8 +65,6 @@ import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.ThemeHelper; -import static org.schabi.newpipe.extractor.InfoItem.InfoType.PLAYLIST; - public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); diff --git a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java index a4e6730da..74c818bf9 100644 --- a/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ReCaptchaActivity.java @@ -85,7 +85,7 @@ public class ReCaptchaActivity extends AppCompatActivity { } private class ReCaptchaWebViewClient extends WebViewClient { - private Activity context; + private final Activity context; private String mCookies; ReCaptchaWebViewClient(Activity ctx) { diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 4f1fdeab2..e22e2f474 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -1,7 +1,6 @@ package org.schabi.newpipe; import android.annotation.SuppressLint; -import android.app.FragmentManager; import android.app.IntentService; import android.content.Context; import android.content.DialogInterface; @@ -13,7 +12,6 @@ import android.preference.PreferenceManager; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; import android.support.v4.app.NotificationCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; @@ -38,7 +36,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -51,14 +48,12 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ThemeHelper; -import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Observer; import icepick.Icepick; import icepick.State; @@ -86,7 +81,7 @@ public class RouterActivity extends AppCompatActivity { protected int selectedPreviously = -1; protected String currentUrl; - protected CompositeDisposable disposables = new CompositeDisposable(); + protected final CompositeDisposable disposables = new CompositeDisposable(); private boolean selectionIsDownload = false; @@ -184,12 +179,16 @@ public class RouterActivity extends AppCompatActivity { if (selectedChoiceKey.equals(alwaysAskKey)) { final List choices = getChoicesForService(currentService, currentLinkType); - if (choices.size() == 1) { - handleChoice(choices.get(0).key); - } else if (choices.size() == 0) { - handleChoice(showInfoKey); - } else { - showDialog(choices); + switch (choices.size()) { + case 1: + handleChoice(choices.get(0).key); + break; + case 0: + handleChoice(showInfoKey); + break; + default: + showDialog(choices); + break; } } else if (selectedChoiceKey.equals(showInfoKey)) { handleChoice(showInfoKey); diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java index 701e18cbf..09d33bd8a 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.java @@ -4,7 +4,6 @@ import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.os.AsyncTask; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.AlertDialog; import android.webkit.WebView; @@ -17,7 +16,7 @@ import java.lang.ref.WeakReference; public class LicenseFragmentHelper extends AsyncTask { - WeakReference weakReference; + final WeakReference weakReference; private License license; public LicenseFragmentHelper(@Nullable Activity activity) { @@ -78,18 +77,18 @@ public class LicenseFragmentHelper extends AsyncTask { throw new NullPointerException("license is null"); } - String licenseContent = ""; + StringBuilder licenseContent = new StringBuilder(); String webViewData; try { BufferedReader in = new BufferedReader(new InputStreamReader(context.getAssets().open(license.getFilename()), "UTF-8")); String str; while ((str = in.readLine()) != null) { - licenseContent += str; + licenseContent.append(str); } in.close(); // split the HTML file and insert the stylesheet into the HEAD of the file - String[] insert = licenseContent.split(""); + String[] insert = licenseContent.toString().split(""); webViewData = insert[0] + "" + insert[1]; diff --git a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java index 425c122ca..13117145a 100644 --- a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java @@ -30,7 +30,7 @@ public interface BasicDAO { /* Deletes */ @Delete - int delete(final Entity entity); + void delete(final Entity entity); @Delete int delete(final Collection entities); @@ -42,5 +42,5 @@ public interface BasicDAO { int update(final Entity entity); @Update - int update(final Collection entities); + void update(final Collection entities); } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index b0a3c3a3c..83e629e48 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -4,7 +4,6 @@ import android.arch.persistence.room.Dao; import android.arch.persistence.room.Query; import android.support.annotation.Nullable; -import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import java.util.List; diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java index fd7a1b96f..847153e12 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/StreamHistoryDAO.java @@ -5,7 +5,6 @@ import android.arch.persistence.room.Dao; import android.arch.persistence.room.Query; import android.support.annotation.Nullable; -import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.database.history.model.StreamHistoryEntity; diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java index 88d5645af..7a6282f96 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistDAO.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.database.playlist.dao; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Query; -import android.arch.persistence.room.Transaction; import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.playlist.model.PlaylistEntity; @@ -12,7 +11,6 @@ import java.util.List; import io.reactivex.Flowable; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; -import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; @Dao diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java index 8bf1ea696..8b6d62ca4 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/dao/PlaylistStreamDAO.java @@ -8,7 +8,6 @@ import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; -import org.schabi.newpipe.database.stream.model.StreamEntity; import java.util.List; diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java index a3ec1b5f2..bfda8eeec 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistEntity.java @@ -5,8 +5,6 @@ import android.arch.persistence.room.Entity; import android.arch.persistence.room.Index; import android.arch.persistence.room.PrimaryKey; -import java.util.Date; - import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; diff --git a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java index e2f2c8b08..ab917a22b 100644 --- a/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/playlist/model/PlaylistRemoteEntity.java @@ -6,7 +6,6 @@ import android.arch.persistence.room.Ignore; import android.arch.persistence.room.Index; import android.arch.persistence.room.PrimaryKey; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.util.Constants; diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.java b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.java index 63f9e5940..396a29fca 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/dao/StreamDAO.java @@ -10,7 +10,6 @@ import org.schabi.newpipe.database.BasicDAO; import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.database.history.model.StreamHistoryEntity; -import org.schabi.newpipe.database.stream.model.StreamStateEntity; import java.util.ArrayList; import java.util.List; @@ -23,7 +22,6 @@ import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_SERVI import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_TABLE; import static org.schabi.newpipe.database.stream.model.StreamEntity.STREAM_URL; import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; -import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; @Dao public abstract class StreamDAO implements BasicDAO { diff --git a/app/src/main/java/org/schabi/newpipe/download/DeleteDownloadManager.java b/app/src/main/java/org/schabi/newpipe/download/DeleteDownloadManager.java index f2912a6fa..5a2d4a486 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DeleteDownloadManager.java +++ b/app/src/main/java/org/schabi/newpipe/download/DeleteDownloadManager.java @@ -28,11 +28,11 @@ public class DeleteDownloadManager { private static final String KEY_STATE = "delete_manager_state"; - private View mView; - private HashSet mPendingMap; - private List mDisposableList; + private final View mView; + private final HashSet mPendingMap; + private final List mDisposableList; private DownloadManager mDownloadManager; - private PublishSubject publishSubject = PublishSubject.create(); + private final PublishSubject publishSubject = PublishSubject.create(); DeleteDownloadManager(Activity activity) { mPendingMap = new HashSet<>(); 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 75f05cd16..9bbda6032 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -55,7 +55,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck private StreamItemAdapter audioStreamsAdapter; private StreamItemAdapter videoStreamsAdapter; - private CompositeDisposable disposables = new CompositeDisposable(); + private final CompositeDisposable disposables = new CompositeDisposable(); private EditText nameEditText; private Spinner streamsSpinner; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 589d15bd4..acee1f111 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -32,7 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import icepick.State; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.functions.Consumer; import static org.schabi.newpipe.util.AnimationUtils.animateView; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java index 4ee90f083..948e9377d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.fragments; import android.os.Bundle; import android.support.annotation.Nullable; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index a920ecfe6..de14997ef 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -1,7 +1,5 @@ package org.schabi.newpipe.fragments; -import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -12,7 +10,6 @@ import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; -import android.support.v7.preference.PreferenceManager; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -23,42 +20,26 @@ import android.view.ViewGroup; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; -import org.schabi.newpipe.fragments.list.channel.ChannelFragment; -import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; -import org.schabi.newpipe.local.bookmark.BookmarkFragment; -import org.schabi.newpipe.local.feed.FeedFragment; -import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; -import org.schabi.newpipe.local.subscription.SubscriptionFragment; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.settings.tabs.Tab; +import org.schabi.newpipe.settings.tabs.TabsManager; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ServiceHelper; -import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener { - - public int currentServiceId = -1; private ViewPager viewPager; - private List tabs = new ArrayList<>(); - static PagerAdapter adapter; - TabLayout tabLayout; - private SharedPreferences prefs; - private Bundle savedInstanceStateBundle; + private SelectedTabsPagerAdapter pagerAdapter; + private TabLayout tabLayout; - private static final String TAB_NUMBER_BLANK = "0"; - private static final String TAB_NUMBER_KIOSK = "1"; - private static final String TAB_NUMBER_SUBSCIRPTIONS = "2"; - private static final String TAB_NUMBER_FEED = "3"; - private static final String TAB_NUMBER_BOOKMARKS = "4"; - private static final String TAB_NUMBER_HISTORY = "5"; - private static final String TAB_NUMBER_CHANNEL = "6"; + private List tabsList = new ArrayList<>(); + private TabsManager tabsManager; - SharedPreferences.OnSharedPreferenceChangeListener listener; + private boolean hasTabsChanged = false; /*////////////////////////////////////////////////////////////////////////// // Fragment's LifeCycle @@ -66,23 +47,24 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public void onCreate(Bundle savedInstanceState) { - savedInstanceStateBundle = savedInstanceState; super.onCreate(savedInstanceState); setHasOptionsMenu(true); - listener = (prefs, key) -> { - if(key.equals("saveUsedTabs")) { - mainPageChanged(); + + tabsManager = TabsManager.getManager(activity); + tabsManager.setSavedTabsListener(() -> { + if (DEBUG) { + Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed()); } - }; + if (isResumed()) { + updateTabs(); + } else { + hasTabsChanged = true; + } + }); } @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - currentServiceId = ServiceHelper.getSelectedServiceId(activity); - - prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - prefs.registerOnSharedPreferenceChangeListener(listener); - return inflater.inflate(R.layout.fragment_main, container, false); } @@ -94,110 +76,28 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte viewPager = rootView.findViewById(R.id.pager); /* Nested fragment, use child fragment here to maintain backstack in view pager. */ - adapter = new PagerAdapter(getChildFragmentManager()); - viewPager.setAdapter(adapter); + pagerAdapter = new SelectedTabsPagerAdapter(getChildFragmentManager()); + viewPager.setAdapter(pagerAdapter); tabLayout.setupWithViewPager(viewPager); - - mainPageChanged(); + tabLayout.addOnTabSelectedListener(this); + updateTabs(); } + @Override + public void onResume() { + super.onResume(); - public void mainPageChanged() { - getTabOrder(); - adapter.notifyDataSetChanged(); - viewPager.setOffscreenPageLimit(adapter.getCount()); - setIcons(); - setFirstTitle(); - } - - private void setFirstTitle() { - if((tabs.size() > 0) - && activity != null) { - String tabInformation = tabs.get(0); - if (tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) { - String kiosk[] = tabInformation.split("\t"); - if (kiosk.length == 3) { - setTitle(kiosk[1]); - } - } else if (tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) { - - String channelInfo[] = tabInformation.split("\t"); - if(channelInfo.length==4) { - setTitle(channelInfo[2]); - } - } else { - switch (tabInformation) { - case TAB_NUMBER_BLANK: - setTitle(getString(R.string.app_name)); - break; - case TAB_NUMBER_SUBSCIRPTIONS: - setTitle(getString(R.string.tab_subscriptions)); - break; - case TAB_NUMBER_FEED: - setTitle(getString(R.string.fragment_whats_new)); - break; - case TAB_NUMBER_BOOKMARKS: - setTitle(getString(R.string.tab_bookmarks)); - break; - case TAB_NUMBER_HISTORY: - setTitle(getString(R.string.title_activity_history)); - break; - } - } - - + if (hasTabsChanged) { + hasTabsChanged = false; + updateTabs(); } } - private void setIcons() { - for (int i = 0; i < tabs.size(); i++) { - String tabInformation = tabs.get(i); - - TabLayout.Tab tabToSet = tabLayout.getTabAt(i); - Context c = getContext(); - - if (tabToSet != null && c != null) { - - if (tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) { - String kiosk[] = tabInformation.split("\t"); - if (kiosk.length == 3) { - tabToSet.setIcon(KioskTranslator.getKioskIcons(kiosk[1], getContext())); - } - } else if (tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) { - tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_channel)); - } else { - switch (tabInformation) { - case TAB_NUMBER_BLANK: - tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_hot)); - break; - case TAB_NUMBER_SUBSCIRPTIONS: - tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_channel)); - break; - case TAB_NUMBER_FEED: - tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.rss)); - break; - case TAB_NUMBER_BOOKMARKS: - tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_bookmark)); - break; - case TAB_NUMBER_HISTORY: - tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.history)); - break; - } - } - - } - } - } - - - private void getTabOrder() { - tabs.clear(); - - String save = prefs.getString("saveUsedTabs", "1\tTrending\t0\n2\n4\n"); - String tabsArray[] = save.trim().split("\n"); - - Collections.addAll(tabs, tabsArray); + @Override + public void onDestroy() { + super.onDestroy(); + tabsManager.unsetSavedTabsListener(); } /*////////////////////////////////////////////////////////////////////////// @@ -237,9 +137,33 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte // Tabs //////////////////////////////////////////////////////////////////////////*/ + public void updateTabs() { + tabsList.clear(); + tabsList.addAll(tabsManager.getTabs()); + pagerAdapter.notifyDataSetChanged(); + + viewPager.setOffscreenPageLimit(pagerAdapter.getCount()); + updateTabsIcon(); + updateCurrentTitle(); + } + + private void updateTabsIcon() { + for (int i = 0; i < tabsList.size(); i++) { + final TabLayout.Tab tabToSet = tabLayout.getTabAt(i); + if (tabToSet != null) { + tabToSet.setIcon(tabsList.get(i).getTabIconRes(activity)); + } + } + } + + private void updateCurrentTitle() { + setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext())); + } + @Override - public void onTabSelected(TabLayout.Tab tab) { - viewPager.setCurrentItem(tab.getPosition()); + public void onTabSelected(TabLayout.Tab selectedTab) { + if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]"); + updateCurrentTitle(); } @Override @@ -248,68 +172,40 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public void onTabReselected(TabLayout.Tab tab) { + if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]"); + updateCurrentTitle(); } - private class PagerAdapter extends FragmentPagerAdapter { - PagerAdapter(FragmentManager fm) { - super(fm); + private class SelectedTabsPagerAdapter extends FragmentPagerAdapter { + private SelectedTabsPagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); } @Override public Fragment getItem(int position) { - String tabInformation = tabs.get(position); + final Tab tab = tabsList.get(position); - if(tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) { - String kiosk[] = tabInformation.split("\t"); - if(kiosk.length==3) { - KioskFragment fragment = null; - try { - fragment = KioskFragment.getInstance(Integer.parseInt(kiosk[2]), kiosk[1]); - fragment.useAsFrontPage(true); - return fragment; - } catch (Exception e) { - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); - } - } - } else if(tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) { - String channelInfo[] = tabInformation.split("\t"); - if(channelInfo.length==4) { - ChannelFragment fragment = ChannelFragment.getInstance(Integer.parseInt(channelInfo[3]), channelInfo[1], channelInfo[2]); - fragment.useAsFrontPage(true); - return fragment; - } else { - return new BlankFragment(); - } - } else { - switch (tabInformation) { - case TAB_NUMBER_BLANK: - return new BlankFragment(); - case TAB_NUMBER_SUBSCIRPTIONS: - SubscriptionFragment sFragment = new SubscriptionFragment(); - sFragment.useAsFrontPage(true); - return sFragment; - case TAB_NUMBER_FEED: - FeedFragment fFragment = new FeedFragment(); - fFragment.useAsFrontPage(true); - return fFragment; - case TAB_NUMBER_BOOKMARKS: - BookmarkFragment bFragment = new BookmarkFragment(); - bFragment.useAsFrontPage(true); - return bFragment; - case TAB_NUMBER_HISTORY: - StatisticsPlaylistFragment cFragment = new StatisticsPlaylistFragment(); - cFragment.useAsFrontPage(true); - return cFragment; - } - } - - return new BlankFragment(); + Throwable throwable = null; + Fragment fragment = null; + try { + fragment = tab.getFragment(); + } catch (ExtractionException e) { + throwable = e; } + if (throwable != null) { + ErrorActivity.reportError(activity, throwable, activity.getClass(), null, + ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + return new BlankFragment(); + } + + if (fragment instanceof BaseFragment) { + ((BaseFragment) fragment).useAsFrontPage(true); + } + + return fragment; + } + @Override public int getItemPosition(Object object) { // Causes adapter to reload all Fragments when @@ -319,14 +215,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public int getCount() { - return tabs.size(); + return tabsList.size(); } @Override public void destroyItem(ViewGroup container, int position, Object object) { - getFragmentManager() + getChildFragmentManager() .beginTransaction() - .remove((Fragment)object) + .remove((Fragment) object) .commitNowAllowingStateLoss(); } } 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 b8957f33c..f7f8ad702 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 @@ -3,9 +3,9 @@ package org.schabi.newpipe.fragments.detail; import java.io.Serializable; class StackItem implements Serializable { - private int serviceId; + private final int serviceId; private String title; - private String url; + private final String url; StackItem(int serviceId, String url, String title) { this.serviceId = serviceId; 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 7e0a013ca..9ab40e81c 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 @@ -768,7 +768,7 @@ public class VideoDetailFragment * Stack that contains the "navigation history".
* The peek is the current video. */ - protected LinkedList stack = new LinkedList<>(); + protected final LinkedList stack = new LinkedList<>(); public void clearHistory() { stack.clear(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index e702c602f..5d042c949 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -8,9 +8,6 @@ import android.view.View; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListInfo; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; -import org.schabi.newpipe.extractor.linkhandler.LinkHandler; import org.schabi.newpipe.util.Constants; import java.util.Queue; @@ -19,7 +16,6 @@ import icepick.State; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; public abstract class BaseListInfoFragment diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 4df5982f7..6a3b3eb50 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -33,15 +33,14 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; +import org.schabi.newpipe.local.subscription.SubscriptionService; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.local.subscription.SubscriptionService; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; @@ -69,7 +68,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; public class ChannelFragment extends BaseListInfoFragment { - private CompositeDisposable disposables = new CompositeDisposable(); + private final CompositeDisposable disposables = new CompositeDisposable(); private Disposable subscribeButtonMonitor; private SubscriptionService subscriptionService; @@ -91,8 +90,6 @@ public class ChannelFragment extends BaseListInfoFragment { private MenuItem menuRssButton; - private boolean mIsVisibleToUser = false; - public static ChannelFragment getInstance(int serviceId, String url, String name) { ChannelFragment instance = new ChannelFragment(); instance.setInitialData(serviceId, url, name); @@ -106,7 +103,6 @@ public class ChannelFragment extends BaseListInfoFragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - mIsVisibleToUser = isVisibleToUser; if(activity != null && useAsFrontPage && isVisibleToUser) { @@ -422,10 +418,12 @@ public class ChannelFragment extends BaseListInfoFragment { imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); - if (result.getSubscriberCount() != -1) { + headerSubscribersTextView.setVisibility(View.VISIBLE); + if (result.getSubscriberCount() >= 0) { headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount())); - headerSubscribersTextView.setVisibility(View.VISIBLE); - } else headerSubscribersTextView.setVisibility(View.GONE); + } else { + headerSubscribersTextView.setText(R.string.subscribers_count_not_available); + } if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java index 833d0f55e..92138f7db 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -5,7 +5,6 @@ import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.app.ActionBar; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -19,8 +18,6 @@ import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; -import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; -import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index b7a42791c..0019a3819 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -20,7 +20,6 @@ import android.widget.TextView; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; -import org.schabi.newpipe.App; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; @@ -30,7 +29,6 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index a699e28bc..19c7d463e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -122,12 +122,11 @@ public class SearchFragment private String nextPageUrl; private String contentCountry; private boolean isSuggestionsEnabled = true; - private boolean isSearchHistoryEnabled = true; - private PublishSubject suggestionPublisher = PublishSubject.create(); + private final PublishSubject suggestionPublisher = PublishSubject.create(); private Disposable searchDisposable; private Disposable suggestionDisposable; - private CompositeDisposable disposables = new CompositeDisposable(); + private final CompositeDisposable disposables = new CompositeDisposable(); private SuggestionListAdapter suggestionListAdapter; private HistoryRecordManager historyRecordManager; @@ -173,7 +172,7 @@ public class SearchFragment suggestionListAdapter = new SuggestionListAdapter(activity); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); - isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true); + boolean isSearchHistoryEnabled = preferences.getBoolean(getString(R.string.enable_search_history_key), true); suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled); historyRecordManager = new HistoryRecordManager(context); 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 78867c81f..f473e5d08 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 @@ -45,7 +45,7 @@ public class InfoItemBuilder { private static final String TAG = InfoItemBuilder.class.toString(); private final Context context; - private ImageLoader imageLoader = ImageLoader.getInstance(); + private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnClickGesture onStreamSelectedListener; private OnClickGesture onChannelSelectedListener; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java index 88aa76887..fd0e9f528 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemDialog.java @@ -5,7 +5,6 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 643886da8..ca783833a 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -47,6 +47,13 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemBuilder.getOnChannelSelectedListener().selected(item); } }); + + itemView.setOnLongClickListener(view -> { + if (itemBuilder.getOnChannelSelectedListener() != null) { + itemBuilder.getOnChannelSelectedListener().held(item); + } + return true; + }); } protected String getDetailLine(final ChannelInfoItem item) { diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java index 148f93075..0fbab0398 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemBuilder.java @@ -33,7 +33,7 @@ public class LocalItemBuilder { private static final String TAG = LocalItemBuilder.class.toString(); private final Context context; - private ImageLoader imageLoader = ImageLoader.getInstance(); + private final ImageLoader imageLoader = ImageLoader.getInstance(); private OnClickGesture onSelectedListener; diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index 15ba5d184..df98fb3eb 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -72,7 +72,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave @Override @SuppressWarnings("unchecked") - public void readFrom(@NonNull Queue savedObjects) throws Exception { + public void readFrom(@NonNull Queue savedObjects) { streamEntities = (List) savedObjects.poll(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java index 634bea62b..f1bb01734 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java @@ -36,8 +36,6 @@ import io.reactivex.MaybeObserver; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Predicate; public class FeedFragment extends BaseListFragment, Void> { diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java index 2cb1add5d..09549346b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryEntryAdapter.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.local.history; import android.content.Context; -import android.content.res.Resources; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index d3d824103..56453773a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -45,7 +45,6 @@ import java.util.List; import io.reactivex.Flowable; import io.reactivex.Maybe; -import io.reactivex.Scheduler; import io.reactivex.Single; import io.reactivex.schedulers.Schedulers; diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 95aeb09d7..32083fd42 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -21,7 +21,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.extractor.stream.StreamInfoItem; -import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -57,7 +56,7 @@ public class StatisticsPlaylistFragment /* Used for independent events */ private Subscription databaseSubscription; private HistoryRecordManager recordManager; - private CompositeDisposable disposables = new CompositeDisposable(); + private final CompositeDisposable disposables = new CompositeDisposable(); private enum StatisticSortMode { LAST_PLAYED, diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java index 584acfaf8..a304c2f0c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java @@ -1,8 +1,11 @@ package org.schabi.newpipe.local.subscription; +import android.annotation.SuppressLint; import android.app.Activity; +import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -45,10 +48,11 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.info_list.InfoListAdapter; -import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService; import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; @@ -89,7 +93,6 @@ public class SubscriptionFragment extends BaseStateFragment() { - @Override + public void selected(ChannelInfoItem selectedItem) { final FragmentManager fragmentManager = getFM(); NavigationHelper.openChannelFragment(fragmentManager, @@ -365,6 +369,11 @@ public class SubscriptionFragment extends BaseStateFragment importExportOptions.switchState()); } + private void showLongTapDialog(ChannelInfoItem selectedItem) { + final Context context = getContext(); + final Activity activity = getActivity(); + if (context == null || context.getResources() == null || getActivity() == null) return; + + final String[] commands = new String[]{ + context.getResources().getString(R.string.share), + context.getResources().getString(R.string.unsubscribe) + }; + + final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { + switch (i) { + case 0: + shareChannel(selectedItem); + break; + case 1: + deleteChannel(selectedItem); + break; + default: + break; + } + }; + + final View bannerView = View.inflate(activity, R.layout.dialog_title, null); + bannerView.setSelected(true); + + TextView titleView = bannerView.findViewById(R.id.itemTitleView); + titleView.setText(selectedItem.getName()); + + TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails); + detailsView.setVisibility(View.GONE); + + new AlertDialog.Builder(activity) + .setCustomTitle(bannerView) + .setItems(commands, actions) + .create() + .show(); + + } + + private void shareChannel (ChannelInfoItem selectedItem) { + shareUrl(selectedItem.getName(), selectedItem.getUrl()); + } + + @SuppressLint("CheckResult") + private void deleteChannel (ChannelInfoItem selectedItem) { + subscriptionService.subscriptionTable() + .getSubscription(selectedItem.getServiceId(), selectedItem.getUrl()) + .toObservable() + .observeOn(Schedulers.io()) + .subscribe(getDeleteObserver()); + + Toast.makeText(activity, getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show(); + } + + + + private Observer> getDeleteObserver(){ + return new Observer>() { + @Override + public void onSubscribe(Disposable d) { + disposables.add(d); + } + + @Override + public void onNext(List subscriptionEntities) { + subscriptionService.subscriptionTable().delete(subscriptionEntities); + } + + @Override + public void onError(Throwable exception) { + SubscriptionFragment.this.onError(exception); + } + + @Override + public void onComplete() { } + }; + } + private void resetFragment() { if (disposables != null) disposables.clear(); if (infoListAdapter != null) infoListAdapter.clearStreamItemList(); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionService.java index fc4230711..f9e2d9583 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionService.java @@ -55,10 +55,10 @@ public class SubscriptionService { private static final int SUBSCRIPTION_DEBOUNCE_INTERVAL = 500; private static final int SUBSCRIPTION_THREAD_POOL_SIZE = 4; - private AppDatabase db; - private Flowable> subscription; + private final AppDatabase db; + private final Flowable> subscription; - private Scheduler subscriptionScheduler; + private final Scheduler subscriptionScheduler; private SubscriptionService(Context context) { db = NewPipeDatabase.getInstance(context.getApplicationContext()); @@ -116,7 +116,7 @@ public class SubscriptionService { public Completable updateChannelInfo(final ChannelInfo info) { final Function, CompletableSource> update = new Function, CompletableSource>() { @Override - public CompletableSource apply(@NonNull List subscriptionEntities) throws Exception { + public CompletableSource apply(@NonNull List subscriptionEntities) { if (DEBUG) Log.d(TAG, "updateChannelInfo() called with: subscriptionEntities = [" + subscriptionEntities + "]"); if (subscriptionEntities.size() == 1) { SubscriptionEntity subscription = subscriptionEntities.get(0); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java index 9d055b82a..e3db6e12c 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/BaseImportExportService.java @@ -58,8 +58,8 @@ public abstract class BaseImportExportService extends Service { protected NotificationCompat.Builder notificationBuilder; protected SubscriptionService subscriptionService; - protected CompositeDisposable disposables = new CompositeDisposable(); - protected PublishProcessor notificationUpdater = PublishProcessor.create(); + protected final CompositeDisposable disposables = new CompositeDisposable(); + protected final PublishProcessor notificationUpdater = PublishProcessor.create(); @Nullable @Override @@ -90,9 +90,9 @@ public abstract class BaseImportExportService extends Service { private static final int NOTIFICATION_SAMPLING_PERIOD = 2500; - protected AtomicInteger currentProgress = new AtomicInteger(-1); - protected AtomicInteger maxProgress = new AtomicInteger(-1); - protected ImportExportEventListener eventListener = new ImportExportEventListener() { + protected final AtomicInteger currentProgress = new AtomicInteger(-1); + protected final AtomicInteger maxProgress = new AtomicInteger(-1); + protected final ImportExportEventListener eventListener = new ImportExportEventListener() { @Override public void onSizeReceived(int size) { maxProgress.set(size); @@ -187,13 +187,13 @@ public abstract class BaseImportExportService extends Service { protected Toast toast; protected void showToast(@StringRes int message) { - showToast(getString(message), Toast.LENGTH_SHORT); + showToast(getString(message)); } - protected void showToast(String message, int duration) { + protected void showToast(String message) { if (toast != null) toast.cancel(); - toast = Toast.makeText(this, message, duration); + toast = Toast.makeText(this, message, Toast.LENGTH_SHORT); toast.show(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java index 3fdc91358..48410205a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/services/SubscriptionsImportService.java @@ -144,12 +144,16 @@ public class SubscriptionsImportService extends BaseImportExportService { showToast(R.string.import_ongoing); Flowable> flowable = null; - if (currentMode == CHANNEL_URL_MODE) { - flowable = importFromChannelUrl(); - } else if (currentMode == INPUT_STREAM_MODE) { - flowable = importFromInputStream(); - } else if (currentMode == PREVIOUS_EXPORT_MODE) { - flowable = importFromPreviousExport(); + switch (currentMode) { + case CHANNEL_URL_MODE: + flowable = importFromChannelUrl(); + break; + case INPUT_STREAM_MODE: + flowable = importFromInputStream(); + break; + case PREVIOUS_EXPORT_MODE: + flowable = importFromPreviousExport(); + break; } if (flowable == null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index d87df3666..f5f843caa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -70,7 +70,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.MediaSourceTag; -import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.SerializedCache; @@ -88,7 +87,6 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; -import static org.schabi.newpipe.report.UserAction.PLAY_STREAM; /** * Base for the players, joining the common properties @@ -175,7 +173,6 @@ public abstract class BasePlayer implements }; this.intentFilter = new IntentFilter(); setupBroadcastReceiver(intentFilter); - context.registerReceiver(broadcastReceiver, intentFilter); this.recordManager = new HistoryRecordManager(context); @@ -212,6 +209,8 @@ public abstract class BasePlayer implements audioReactor = new AudioReactor(context, simpleExoPlayer); mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, new BasePlayerMediaSession(this)); + + registerBroadcastReceiver(); } public void initListeners() {} @@ -231,7 +230,8 @@ public abstract class BasePlayer implements int sizeBeforeAppend = playQueue.size(); playQueue.append(queue.getStreams()); - if (intent.getBooleanExtra(SELECT_ON_APPEND, false) && + if ((intent.getBooleanExtra(SELECT_ON_APPEND, false) || + getCurrentState() == STATE_COMPLETED) && queue.getStreams().size() > 0) { playQueue.setIndex(sizeBeforeAppend); } @@ -362,14 +362,17 @@ public abstract class BasePlayer implements } } - public void unregisterBroadcastReceiver() { + protected void registerBroadcastReceiver() { + // Try to unregister current first + unregisterBroadcastReceiver(); + context.registerReceiver(broadcastReceiver, intentFilter); + } + + protected void unregisterBroadcastReceiver() { try { context.unregisterReceiver(broadcastReceiver); } catch (final IllegalArgumentException unregisteredException) { - ErrorActivity.reportError(context, unregisteredException, null, null, - ErrorActivity.ErrorInfo.make(PLAY_STREAM, - "none", - "play stream", R.string.general_error)); + Log.w(TAG, "Broadcast receiver already unregistered (" + unregisteredException.getMessage() + ")"); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 41e7c305d..8ce18fa56 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -175,6 +175,10 @@ public final class MainVideoPlayer extends AppCompatActivity setLandscape(lastOrientationWasLandscape); } + final int lastResizeMode = defaultPreferences.getInt( + getString(R.string.last_resize_mode), AspectRatioFrameLayout.RESIZE_MODE_FIT); + playerImpl.setResizeMode(lastResizeMode); + // Upon going in or out of multiwindow mode, isInMultiWindow will always be false, // since the first onResume needs to restore the player. // Subsequent onResume calls while multiwindow mode remains the same and the player is @@ -460,7 +464,7 @@ public final class MainVideoPlayer extends AppCompatActivity public void initListeners() { super.initListeners(); - MySimpleOnGestureListener listener = new MySimpleOnGestureListener(); + PlayerGestureListener listener = new PlayerGestureListener(); gestureDetector = new GestureDetector(context, listener); gestureDetector.setIsLongpressEnabled(false); getRootView().setOnTouchListener(listener); @@ -489,6 +493,8 @@ public final class MainVideoPlayer extends AppCompatActivity volumeProgressBar.setMax(maxGestureLength); brightnessProgressBar.setMax(maxGestureLength); + + setInitialGestureValues(); } }); } @@ -703,14 +709,27 @@ public final class MainVideoPlayer extends AppCompatActivity @Override protected int nextResizeMode(int currentResizeMode) { + final int newResizeMode; switch (currentResizeMode) { case AspectRatioFrameLayout.RESIZE_MODE_FIT: - return AspectRatioFrameLayout.RESIZE_MODE_FILL; + newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL; + break; case AspectRatioFrameLayout.RESIZE_MODE_FILL: - return AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + break; default: - return AspectRatioFrameLayout.RESIZE_MODE_FIT; + newResizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; + break; } + + storeResizeMode(newResizeMode); + return newResizeMode; + } + + private void storeResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) { + defaultPreferences.edit() + .putInt(getString(R.string.last_resize_mode), resizeMode) + .apply(); } @Override @@ -799,6 +818,13 @@ public final class MainVideoPlayer extends AppCompatActivity // Utils //////////////////////////////////////////////////////////////////////////*/ + private void setInitialGestureValues() { + if (getAudioReactor() != null) { + final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume(); + volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized)); + } + } + @Override public void showControlsThenHide() { if (queueVisible) return; @@ -939,7 +965,7 @@ public final class MainVideoPlayer extends AppCompatActivity } } - private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { + private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { private boolean isMoving; @Override @@ -978,31 +1004,30 @@ public final class MainVideoPlayer extends AppCompatActivity return super.onDown(e); } - private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext()); + private static final int MOVEMENT_THRESHOLD = 40; + private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext()); private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume(); - private final int MOVEMENT_THRESHOLD = 40; - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) { if (!isPlayerGestureEnabled) return false; //noinspection PointlessBooleanExpression if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " + - ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" + - ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" + + ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + + ", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + ", distanceXy = [" + distanceX + ", " + distanceY + "]"); - if (!isMoving && ( - Math.abs(e2.getY() - e1.getY()) <= MOVEMENT_THRESHOLD - || Math.abs(distanceX) > Math.abs(distanceY) - ) || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) + final boolean insideThreshold = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD; + if (!isMoving && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY)) + || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) { return false; + } isMoving = true; - if (e1.getX() > playerImpl.getRootView().getWidth() / 2) { + if (initialEvent.getX() > playerImpl.getRootView().getWidth() / 2) { playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY); float currentProgressPercent = (float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength(); diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 0e7328020..a36a0576c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -114,7 +114,6 @@ public final class PopupVideoPlayer extends Service { private View closeOverlayView; private FloatingActionButton closeOverlayButton; - private WindowManager.LayoutParams closeOverlayLayoutParams; private int tossFlingVelocity; @@ -248,7 +247,7 @@ public final class PopupVideoPlayer extends Service { final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - closeOverlayLayoutParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, layoutParamType, flags, diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 679fc6645..d30d9b8be 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -137,16 +137,16 @@ public abstract class VideoPlayer extends BasePlayer private TextView captionTextView; private ValueAnimator controlViewAnimator; - private Handler controlsVisibilityHandler = new Handler(); + private final Handler controlsVisibilityHandler = new Handler(); boolean isSomePopupMenuVisible = false; - private int qualityPopupMenuGroupId = 69; + private final int qualityPopupMenuGroupId = 69; private PopupMenu qualityPopupMenu; - private int playbackSpeedPopupMenuGroupId = 79; + private final int playbackSpeedPopupMenuGroupId = 79; private PopupMenu playbackSpeedPopupMenu; - private int captionPopupMenuGroupId = 89; + private final int captionPopupMenuGroupId = 89; private PopupMenu captionPopupMenu; /////////////////////////////////////////////////////////////////////////// @@ -683,12 +683,17 @@ public abstract class VideoPlayer extends BasePlayer if (getAspectRatioFrameLayout() != null) { final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode(); final int newResizeMode = nextResizeMode(currentResizeMode); - getAspectRatioFrameLayout().setResizeMode(newResizeMode); - getResizeView().setText(PlayerHelper.resizeTypeOf(context, newResizeMode)); + setResizeMode(newResizeMode); } } + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { + getAspectRatioFrameLayout().setResizeMode(resizeMode); + getResizeView().setText(PlayerHelper.resizeTypeOf(context, resizeMode)); + } + protected abstract int nextResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode); + /*////////////////////////////////////////////////////////////////////////// // SeekBar Listener //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 5b9cce947..46d20c7e1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -116,7 +116,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, private void onAudioFocusGain() { Log.d(TAG, "onAudioFocusGain() called"); player.setVolume(DUCK_AUDIO_TO); - animateAudio(DUCK_AUDIO_TO, 1f, DUCK_DURATION); + animateAudio(DUCK_AUDIO_TO, 1f); if (PlayerHelper.isResumeAfterAudioFocusGain(context)) { player.setPlayWhenReady(true); @@ -131,13 +131,13 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, private void onAudioFocusLossCanDuck() { Log.d(TAG, "onAudioFocusLossCanDuck() called"); // Set the volume to 1/10 on ducking - animateAudio(player.getVolume(), DUCK_AUDIO_TO, DUCK_DURATION); + animateAudio(player.getVolume(), DUCK_AUDIO_TO); } - private void animateAudio(final float from, final float to, int duration) { + private void animateAudio(final float from, final float to) { ValueAnimator valueAnimator = new ValueAnimator(); valueAnimator.setFloatValues(from, to); - valueAnimator.setDuration(duration); + valueAnimator.setDuration(AudioReactor.DUCK_DURATION); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index ec7813056..b8d8dc12f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -4,12 +4,9 @@ import android.content.Context; import android.support.annotation.NonNull; import android.util.Log; -import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.cache.CacheDataSink; @@ -17,8 +14,6 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSource; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; -import org.schabi.newpipe.Downloader; - import java.io.File; /* package-private */ class CacheFactory implements DataSource.Factory { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index d6453f579..f49ca3330 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -66,25 +66,15 @@ public class PlaybackParameterDialog extends DialogFragment { private double stepSize = DEFAULT_STEP; @Nullable private SeekBar tempoSlider; - @Nullable private TextView tempoMinimumText; - @Nullable private TextView tempoMaximumText; @Nullable private TextView tempoCurrentText; @Nullable private TextView tempoStepDownText; @Nullable private TextView tempoStepUpText; @Nullable private SeekBar pitchSlider; - @Nullable private TextView pitchMinimumText; - @Nullable private TextView pitchMaximumText; @Nullable private TextView pitchCurrentText; @Nullable private TextView pitchStepDownText; @Nullable private TextView pitchStepUpText; - @Nullable private TextView stepSizeOnePercentText; - @Nullable private TextView stepSizeFivePercentText; - @Nullable private TextView stepSizeTenPercentText; - @Nullable private TextView stepSizeTwentyFivePercentText; - @Nullable private TextView stepSizeOneHundredPercentText; - @Nullable private CheckBox unhookingCheckbox; @Nullable private CheckBox skipSilenceCheckbox; @@ -181,8 +171,8 @@ public class PlaybackParameterDialog extends DialogFragment { private void setupTempoControl(@NonNull View rootView) { tempoSlider = rootView.findViewById(R.id.tempoSeekbar); - tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); - tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); + TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); + TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText); tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); @@ -203,8 +193,8 @@ public class PlaybackParameterDialog extends DialogFragment { private void setupPitchControl(@NonNull View rootView) { pitchSlider = rootView.findViewById(R.id.pitchSeekbar); - pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); - pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); + TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); + TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); @@ -247,11 +237,11 @@ public class PlaybackParameterDialog extends DialogFragment { } private void setupStepSizeSelector(@NonNull final View rootView) { - stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); - stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); - stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); - stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent); - stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent); + TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); + TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); + TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); + TextView stepSizeTwentyFivePercentText = rootView.findViewById(R.id.stepSizeTwentyFivePercent); + TextView stepSizeOneHundredPercentText = rootView.findViewById(R.id.stepSizeOneHundredPercent); if (stepSizeOnePercentText != null) { stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index ae187a834..05afe2859 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -203,7 +203,7 @@ public class PlayerHelper { @NonNull public static SeekParameters getSeekParameters(@NonNull final Context context) { - return isUsingInexactSeek(context, false) ? + return isUsingInexactSeek(context) ? SeekParameters.CLOSEST_SYNC : SeekParameters.EXACT; } @@ -318,8 +318,8 @@ public class PlayerHelper { return getPreferences(context).getBoolean(context.getString(R.string.popup_remember_size_pos_key), b); } - private static boolean isUsingInexactSeek(@NonNull final Context context, final boolean b) { - return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), b); + private static boolean isUsingInexactSeek(@NonNull final Context context) { + return getPreferences(context).getBoolean(context.getString(R.string.use_inexact_seek_key), false); } private static boolean isAutoQueueEnabled(@NonNull final Context context, final boolean b) { diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 429c26fd9..3d1fd171f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -79,7 +79,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator private void publishFloatingQueueWindow() { if (callback.getQueueSize() == 0) { - mediaSession.setQueue(Collections.emptyList()); + mediaSession.setQueue(Collections.emptyList()); activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; return; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java index 3365828d1..00604b236 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java @@ -8,7 +8,7 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItem; public class BasePlayerMediaSession implements MediaSessionCallback { - private BasePlayer player; + private final BasePlayer player; public BasePlayerMediaSession(final BasePlayer player) { this.player = player; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java index d80ea5bae..efe6f3a58 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/CustomTrackSelector.java @@ -4,7 +4,6 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; @@ -12,7 +11,6 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.Util; /** * This class allows irregular text language labels for use when selecting text captions and @@ -55,7 +53,7 @@ public class CustomTrackSelector extends DefaultTrackSelector { /** @see DefaultTrackSelector#selectTextTrack(TrackGroupArray, int[][], Parameters) */ @Override protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, - Parameters params) throws ExoPlaybackException { + Parameters params) { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index b27dc3dd6..3c5642d51 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -335,7 +335,7 @@ public class MediaSourceManager { private void loadImmediate() { if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called"); - final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue, WINDOW_SIZE); + final ItemsToLoad itemsToLoad = getItemsToLoad(playQueue); if (itemsToLoad == null) return; // Evict the previous items being loaded to free up memory, before start loading new ones @@ -472,8 +472,7 @@ public class MediaSourceManager { // Manager Helpers //////////////////////////////////////////////////////////////////////////*/ @Nullable - private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue, - final int windowSize) { + private static ItemsToLoad getItemsToLoad(@NonNull final PlayQueue playQueue) { // The current item has higher priority final int currentIndex = playQueue.getIndex(); final PlayQueueItem currentItem = playQueue.getItem(currentIndex); @@ -482,8 +481,8 @@ public class MediaSourceManager { // The rest are just for seamless playback // Although timeline is not updated prior to the current index, these sources are still // loaded into the cache for faster retrieval at a potentially later time. - final int leftBound = Math.max(0, currentIndex - windowSize); - final int rightLimit = currentIndex + windowSize + 1; + final int leftBound = Math.max(0, currentIndex - MediaSourceManager.WINDOW_SIZE); + final int rightLimit = currentIndex + MediaSourceManager.WINDOW_SIZE + 1; final int rightBound = Math.min(playQueue.size(), rightLimit); final Set neighbors = new ArraySet<>( playQueue.getStreams().subList(leftBound,rightBound)); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index 238bdfcd0..4e79c6d75 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -8,8 +8,6 @@ import com.google.android.exoplayer2.source.MediaSource; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import java.util.List; - public interface PlaybackListener { /** diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java index 2c08f4f92..676c0ca72 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/AbstractInfoPlayQueue.java @@ -19,14 +19,14 @@ abstract class AbstractInfoPlayQueue ext boolean isInitial; boolean isComplete; - int serviceId; - String baseUrl; + final int serviceId; + final String baseUrl; String nextUrl; transient Disposable fetchReactor; AbstractInfoPlayQueue(final U item) { - this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0); + this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0); } AbstractInfoPlayQueue(final int serviceId, diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java index 996d3ace3..c24eff81a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java @@ -5,10 +5,8 @@ import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java index 2483e4473..effb9aae9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemHolder.java @@ -6,8 +6,6 @@ import android.widget.ImageView; import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.info_list.holder.InfoItemHolder; /** * Created by Christian Schabesberger on 01.08.16. diff --git a/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java b/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java index 2d3226ab6..a6a81474f 100644 --- a/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java +++ b/app/src/main/java/org/schabi/newpipe/report/AcraReportSender.java @@ -5,7 +5,6 @@ import android.support.annotation.NonNull; import org.acra.collector.CrashReportData; import org.acra.sender.ReportSender; -import org.acra.sender.ReportSenderException; import org.schabi.newpipe.R; /* @@ -31,7 +30,7 @@ import org.schabi.newpipe.R; public class AcraReportSender implements ReportSender { @Override - public void send(@NonNull Context context, @NonNull CrashReportData report) throws ReportSenderException { + public void send(@NonNull Context context, @NonNull CrashReportData report) { ErrorActivity.reportError(context, report, ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,"none", "App crash, UI failure", R.string.app_ui_crash)); diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index 3ad08c3ec..f852e0134 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.report; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.net.Uri; @@ -46,7 +45,6 @@ import java.util.Date; import java.util.List; import java.util.TimeZone; import java.util.Vector; -import java.util.concurrent.atomic.AtomicBoolean; /* * Created by Christian Schabesberger on 24.10.15. @@ -81,12 +79,7 @@ public class ErrorActivity extends AppCompatActivity { private ErrorInfo errorInfo; private Class returnActivity; private String currentTimeStamp; - // views - private TextView errorView; private EditText userCommentBox; - private Button reportButton; - private TextView infoView; - private TextView errorMessageView; public static void reportUiError(final AppCompatActivity activity, final Throwable el) { reportError(activity, el, activity.getClass(), null, @@ -194,11 +187,11 @@ public class ErrorActivity extends AppCompatActivity { actionBar.setDisplayShowTitleEnabled(true); } - reportButton = findViewById(R.id.errorReportButton); + Button reportButton = findViewById(R.id.errorReportButton); userCommentBox = findViewById(R.id.errorCommentBox); - errorView = findViewById(R.id.errorView); - infoView = findViewById(R.id.errorInfosView); - errorMessageView = findViewById(R.id.errorMessageView); + TextView errorView = findViewById(R.id.errorView); + TextView infoView = findViewById(R.id.errorInfosView); + TextView errorMessageView = findViewById(R.id.errorMessageView); ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); returnActivity = ac.returnActivity; @@ -281,15 +274,14 @@ public class ErrorActivity extends AppCompatActivity { } private String formErrorText(String[] el) { - String text = ""; + StringBuilder text = new StringBuilder(); if (el != null) { for (String e : el) { - text += "-------------------------------------\n" - + e; + text.append("-------------------------------------\n").append(e); } } - text += "-------------------------------------"; - return text; + text.append("-------------------------------------"); + return text.toString(); } /** diff --git a/app/src/main/java/org/schabi/newpipe/settings/AddTabsDialog.java b/app/src/main/java/org/schabi/newpipe/settings/AddTabsDialog.java deleted file mode 100644 index 5130df3bf..000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/AddTabsDialog.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.schabi.newpipe.settings; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.widget.TextView; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfoItem; - -public class AddTabsDialog { - private final AlertDialog dialog; - - public AddTabsDialog(@NonNull final Context context, - @NonNull final String title, - @NonNull final String[] commands, - @NonNull final DialogInterface.OnClickListener actions) { - - final View bannerView = View.inflate(context, R.layout.dialog_title, null); - bannerView.setSelected(true); - - TextView titleView = bannerView.findViewById(R.id.itemTitleView); - titleView.setText(title); - - TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails); - detailsView.setVisibility(View.GONE); - - dialog = new AlertDialog.Builder(context) - .setCustomTitle(bannerView) - .setItems(commands, actions) - .create(); - } - - public void show() { - dialog.show(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java index 806b30a5b..821636ee5 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java @@ -6,7 +6,6 @@ import android.os.Bundle; import android.provider.Settings; import android.support.annotation.Nullable; import android.support.v7.preference.Preference; -import android.util.Log; import org.schabi.newpipe.R; import org.schabi.newpipe.util.Constants; @@ -49,7 +48,7 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment { return super.onPreferenceTreeClick(preference); } - private Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() { + private final Preference.OnPreferenceChangeListener themePreferenceChange = new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { defaultPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, true).apply(); diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java index e3c52cdad..e4fae3e1f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java @@ -13,7 +13,7 @@ import org.schabi.newpipe.MainActivity; public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { protected final String TAG = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()); - protected boolean DEBUG = MainActivity.DEBUG; + protected final boolean DEBUG = MainActivity.DEBUG; protected SharedPreferences defaultPreferences; diff --git a/app/src/main/java/org/schabi/newpipe/settings/ChoseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ChoseTabsFragment.java deleted file mode 100644 index d6238c7c4..000000000 --- a/app/src/main/java/org/schabi/newpipe/settings/ChoseTabsFragment.java +++ /dev/null @@ -1,291 +0,0 @@ -package org.schabi.newpipe.settings; - -import android.app.Dialog; -import android.content.SharedPreferences; -import android.content.res.ColorStateList; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.util.ThemeHelper; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class ChoseTabsFragment extends Fragment { - - public ChoseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; - - RecyclerView selectedTabsView; - - List selectedTabs = new ArrayList<>(); - private String saveString; - public String[] availableTabs = new String[7]; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - ((AppCompatActivity)getContext()).getSupportActionBar().setTitle(R.string.main_page_content); - return inflater.inflate(R.layout.fragment_chose_tabs, container, false); - } - - - @Override - public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { - super.onViewCreated(rootView, savedInstanceState); - - tabNames(); - initUsedTabs(); - initButton(rootView); - - selectedTabsView = rootView.findViewById(R.id.usedTabs); - selectedTabsView.setLayoutManager(new LinearLayoutManager(getContext())); - selectedTabsAdapter = new ChoseTabsFragment.SelectedTabsAdapter(); - - - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(selectedTabsView); - selectedTabsAdapter.setOnItemSelectedListener(itemTouchHelper); - - selectedTabsView.setAdapter(selectedTabsAdapter); - } - - private void saveChanges() { - StringBuilder save = new StringBuilder(); - if(selectedTabs.size()==0) { - save = new StringBuilder("0"); - } else { - for(String s: selectedTabs) { - save.append(s); - save.append("\n"); - } - } - saveString = save.toString(); - } - - @Override - public void onPause() { - saveChanges(); - SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(getContext()); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString("saveUsedTabs", saveString); - editor.commit(); - super.onPause(); - } - - private void initUsedTabs() { - String save = android.preference.PreferenceManager.getDefaultSharedPreferences(getContext()).getString("saveUsedTabs", "1\tTrending\t0\n2\n4\n"); - String tabs[] = save.trim().split("\n"); - selectedTabs.addAll(Arrays.asList(tabs)); - } - - private void tabNames() { - availableTabs[0] = getString(R.string.blank_page_summary); - availableTabs[1] = getString(R.string.kiosk_page_summary); - availableTabs[2] = getString(R.string.subscription_page_summary); - availableTabs[3] = getString(R.string.feed_page_summary); - availableTabs[4] = getString(R.string.tab_bookmarks); - availableTabs[5] = getString(R.string.title_activity_history); - availableTabs[6] = getString(R.string.channel_page_summary); - } - - private void initButton(View rootView) { - FloatingActionButton fab = rootView.findViewById(R.id.floatingActionButton); - fab.setImageResource(ThemeHelper.getIconByAttr(R.attr.ic_add, getContext())); - fab.setOnClickListener(v -> { - Dialog.OnClickListener onClickListener = (dialog, which) -> addTab(which); - - new AddTabsDialog(getContext(), - getString(R.string.tab_chose), - availableTabs, - onClickListener) - .show(); - }); - - TypedValue typedValue = new TypedValue(); - getActivity().getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true); - int color = typedValue.data; - fab.setBackgroundTintList(ColorStateList.valueOf(color)); - } - - - private void addTab(int position) { - if(position==6) { - SelectChannelFragment selectChannelFragment = new SelectChannelFragment(); - selectChannelFragment.setOnSelectedLisener((String url, String name, int service) -> { - selectedTabs.add(position+"\t"+url+"\t"+name+"\t"+service); - selectedTabsAdapter.notifyDataSetChanged(); - saveChanges(); - }); - selectChannelFragment.show(getFragmentManager(), "select_channel"); - } else if(position==1) { - SelectKioskFragment selectKioskFragment = new SelectKioskFragment(); - selectKioskFragment.setOnSelectedLisener((String kioskId, int service_id) -> { - selectedTabs.add(position+"\t"+kioskId+"\t"+service_id); - selectedTabsAdapter.notifyDataSetChanged(); - saveChanges(); - }); - selectKioskFragment.show(getFragmentManager(), "select_kiosk"); - } else { - selectedTabs.add(String.valueOf(position)); - selectedTabsAdapter.notifyDataSetChanged(); - saveChanges(); - } - } - - public class SelectedTabsAdapter extends RecyclerView.Adapter{ - private ItemTouchHelper itemTouchHelper; - - public void setOnItemSelectedListener(ItemTouchHelper mItemTouchHelper) { - itemTouchHelper = mItemTouchHelper; - } - - public void swapItems(int fromPosition, int toPosition) { - String temp = selectedTabs.get(fromPosition); - selectedTabs.set(fromPosition, selectedTabs.get(toPosition)); - selectedTabs.set(toPosition, temp); - notifyItemMoved(fromPosition, toPosition); - saveChanges(); - } - - @Override - public ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - - LayoutInflater inflater = LayoutInflater.from(getContext()); - View view = inflater.inflate(R.layout.viewholder_chose_tabs, parent, false); - return new ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) { - holder.bind(position, holder); - } - - @Override - public int getItemCount() { - return selectedTabs.size(); - } - - class TabViewHolder extends RecyclerView.ViewHolder { - - TextView text; - View view; - CardView cardView; - ImageView handle; - - public TabViewHolder(View itemView) { - super(itemView); - - text = itemView.findViewById(R.id.tabName); - cardView = itemView.findViewById(R.id.layoutCard); - handle = itemView.findViewById(R.id.handle); - view = itemView; - } - - void bind(int position, TabViewHolder holder) { - handle.setImageResource(ThemeHelper.getIconByAttr(R.attr.drag_handle, getContext())); - handle.setOnTouchListener(getOnTouchListener(holder)); - - view.setOnLongClickListener(getOnLongClickListener(holder)); - - if(selectedTabs.get(position).startsWith("6\t")) { - String channelInfo[] = selectedTabs.get(position).split("\t"); - String channelName = ""; - if (channelInfo.length == 4) channelName = channelInfo[2]; - String textToSet = availableTabs[6] + ": " + channelName; - text.setText(textToSet); - } else if(selectedTabs.get(position).startsWith("1\t")) { - String kioskInfo[] = selectedTabs.get(position).split("\t"); - String kioskName = ""; - if (kioskInfo.length == 3) kioskName = kioskInfo[1]; - String textToSet = availableTabs[1] + ": " + kioskName; - text.setText(textToSet); - } else { - text.setText(availableTabs[Integer.parseInt(selectedTabs.get(position))]); - } - } - - private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { - return (view, motionEvent) -> { - view.performClick(); - if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { - if(itemTouchHelper != null) itemTouchHelper.startDrag(item); - } - return false; - }; - } - - private View.OnLongClickListener getOnLongClickListener(TabViewHolder holder) { - return (view) -> { - if(itemTouchHelper != null) itemTouchHelper.startSwipe(holder); - return false; - }; - } - - } - } - - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, - ItemTouchHelper.START | ItemTouchHelper.END) { - @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, - int viewSizeOutOfBounds, int totalSize, - long msSinceStartScroll) { - final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, - viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int minimumAbsVelocity = Math.max(12, - Math.abs(standardSpeed)); - return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, - RecyclerView.ViewHolder target) { - if (source.getItemViewType() != target.getItemViewType() || - selectedTabsAdapter == null) { - return false; - } - - final int sourceIndex = source.getAdapterPosition(); - final int targetIndex = target.getAdapterPosition(); - selectedTabsAdapter.swapItems(sourceIndex, targetIndex); - return true; - } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - - @Override - public boolean isItemViewSwipeEnabled() { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - int position = viewHolder.getAdapterPosition(); - selectedTabs.remove(position); - selectedTabsAdapter.notifyItemRemoved(position); - saveChanges(); - } - }; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 0ca78b34a..5c54fa735 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -9,8 +9,6 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.util.Log; import android.widget.Toast; @@ -19,12 +17,9 @@ import com.nononsenseapps.filepicker.Utils; import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.FilePickerActivityHelper; -import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.ZipHelper; import java.io.BufferedOutputStream; @@ -47,7 +42,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private static final int REQUEST_IMPORT_PATH = 8945; private static final int REQUEST_EXPORT_PATH = 30945; - private String homeDir; private File databasesDir; private File newpipe_db; private File newpipe_db_journal; @@ -81,7 +75,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - homeDir = getActivity().getApplicationInfo().dataDir; + String homeDir = getActivity().getApplicationInfo().dataDir; databasesDir = new File(homeDir + "/databases"); newpipe_db = new File(homeDir + "/databases/newpipe.db"); newpipe_db_journal = new File(homeDir + "/databases/newpipe.db-journal"); @@ -193,7 +187,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { } finally { try { zipFile.close(); - } catch (Exception e){} + } catch (Exception ignored){} } try { @@ -254,17 +248,17 @@ public class ContentSettingsFragment extends BasePreferenceFragment { String key = entry.getKey(); if (v instanceof Boolean) - prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); + prefEdit.putBoolean(key, (Boolean) v); else if (v instanceof Float) - prefEdit.putFloat(key, ((Float) v).floatValue()); + prefEdit.putFloat(key, (Float) v); else if (v instanceof Integer) - prefEdit.putInt(key, ((Integer) v).intValue()); + prefEdit.putInt(key, (Integer) v); else if (v instanceof Long) - prefEdit.putLong(key, ((Long) v).longValue()); + prefEdit.putLong(key, (Long) v); else if (v instanceof String) prefEdit.putString(key, ((String) v)); } - prefEdit.commit(); + prefEdit.apply(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { @@ -286,13 +280,12 @@ public class ContentSettingsFragment extends BasePreferenceFragment { // Error //////////////////////////////////////////////////////////////////////////*/ - protected boolean onError(Throwable e) { + protected void onError(Throwable e) { final Activity activity = getActivity(); ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); - return true; } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java index cc1f408b7..d28c3179c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -1,29 +1,20 @@ package org.schabi.newpipe.settings; -import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; -import android.util.Log; import android.widget.Toast; -import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.InfoCache; -import java.util.ArrayList; -import java.util.Collection; - -import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; -import io.reactivex.disposables.Disposables; public class HistorySettingsFragment extends BasePreferenceFragment { private String cacheWipeKey; diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index e961de969..61bd4077e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -51,9 +51,7 @@ import io.reactivex.schedulers.Schedulers; */ public class SelectChannelFragment extends DialogFragment { - private SelectChannelAdapter channelAdapter; - private SubscriptionService subscriptionService; - private ImageLoader imageLoader = ImageLoader.getInstance(); + private final ImageLoader imageLoader = ImageLoader.getInstance(); private ProgressBar progressBar; private TextView emptyView; @@ -66,7 +64,7 @@ public class SelectChannelFragment extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ public interface OnSelectedLisener { - void onChannelSelected(String url, String name, int service); + void onChannelSelected(int serviceId, String url, String name); } OnSelectedLisener onSelectedLisener = null; public void setOnSelectedLisener(OnSelectedLisener listener) { @@ -89,9 +87,9 @@ public class SelectChannelFragment extends DialogFragment { @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.select_channel_fragment, container, false); - recyclerView = (RecyclerView) v.findViewById(R.id.items_list); + recyclerView = v.findViewById(R.id.items_list); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - channelAdapter = new SelectChannelAdapter(); + SelectChannelAdapter channelAdapter = new SelectChannelAdapter(); recyclerView.setAdapter(channelAdapter); progressBar = v.findViewById(R.id.progressBar); @@ -101,7 +99,7 @@ public class SelectChannelFragment extends DialogFragment { emptyView.setVisibility(View.GONE); - subscriptionService = SubscriptionService.getInstance(getContext()); + SubscriptionService subscriptionService = SubscriptionService.getInstance(getContext()); subscriptionService.getSubscription().toObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -126,7 +124,7 @@ public class SelectChannelFragment extends DialogFragment { private void clickedItem(int position) { if(onSelectedLisener != null) { SubscriptionEntity entry = subscriptions.get(position); - onSelectedLisener.onChannelSelected(entry.getUrl(), entry.getName(), entry.getServiceId()); + onSelectedLisener.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName()); } dismiss(); } @@ -203,9 +201,9 @@ public class SelectChannelFragment extends DialogFragment { thumbnailView = v.findViewById(R.id.itemThumbnailView); titleView = v.findViewById(R.id.itemTitleView); } - public View view; - public CircleImageView thumbnailView; - public TextView titleView; + public final View view; + public final CircleImageView thumbnailView; + public final TextView titleView; } } @@ -213,14 +211,13 @@ public class SelectChannelFragment extends DialogFragment { // Error //////////////////////////////////////////////////////////////////////////*/ - protected boolean onError(Throwable e) { + protected void onError(Throwable e) { final Activity activity = getActivity(); ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); - return true; } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index 00b618889..8c3bd56e7 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -56,7 +56,7 @@ public class SelectKioskFragment extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ public interface OnSelectedLisener { - void onKioskSelected(String kioskId, int service_id); + void onKioskSelected(int serviceId, String kioskId, String kioskName); } OnSelectedLisener onSelectedLisener = null; @@ -75,7 +75,7 @@ public class SelectKioskFragment extends DialogFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false); - recyclerView = (RecyclerView) v.findViewById(R.id.items_list); + recyclerView = v.findViewById(R.id.items_list); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); try { selectKioskAdapter = new SelectKioskAdapter(); @@ -101,7 +101,7 @@ public class SelectKioskFragment extends DialogFragment { private void clickedItem(SelectKioskAdapter.Entry entry) { if(onSelectedLisener != null) { - onSelectedLisener.onKioskSelected(entry.kioskId, entry.serviceId); + onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName); } dismiss(); } @@ -112,13 +112,13 @@ public class SelectKioskFragment extends DialogFragment { public Entry (int i, int si, String ki, String kn){ icon = i; serviceId=si; kioskId=ki; kioskName = kn; } - int icon; - int serviceId; - String kioskId; - String kioskName; + final int icon; + final int serviceId; + final String kioskId; + final String kioskName; } - private List kioskList = new Vector<>(); + private final List kioskList = new Vector<>(); public SelectKioskAdapter() throws Exception { @@ -157,9 +157,9 @@ public class SelectKioskFragment extends DialogFragment { thumbnailView = v.findViewById(R.id.itemThumbnailView); titleView = v.findViewById(R.id.itemTitleView); } - public View view; - public ImageView thumbnailView; - public TextView titleView; + public final View view; + public final ImageView thumbnailView; + public final TextView titleView; } public void onBindViewHolder(SelectKioskItemHolder holder, final int position) { @@ -179,13 +179,12 @@ public class SelectKioskFragment extends DialogFragment { // Error //////////////////////////////////////////////////////////////////////////*/ - protected boolean onError(Throwable e) { + protected void onError(Throwable e) { final Activity activity = getActivity(); ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); - return true; } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index 7d6f8d633..a8482e0eb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -77,7 +77,8 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc finish(); } else getSupportFragmentManager().popBackStack(); } - return true; + + return super.onOptionsItemSelected(item); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java new file mode 100644 index 000000000..695f81ff5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/AddTabDialog.java @@ -0,0 +1,94 @@ +package org.schabi.newpipe.settings.tabs; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.v7.widget.AppCompatImageView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.util.ThemeHelper; + +public class AddTabDialog { + private final AlertDialog dialog; + + AddTabDialog(@NonNull final Context context, + @NonNull final ChooseTabListItem[] items, + @NonNull final DialogInterface.OnClickListener actions) { + + dialog = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.tab_choose)) + .setAdapter(new DialogListAdapter(context, items), actions) + .create(); + } + + public void show() { + dialog.show(); + } + + public static final class ChooseTabListItem { + final int tabId; + final String itemName; + @DrawableRes final int itemIcon; + + ChooseTabListItem(Context context, Tab tab) { + this(tab.getTabId(), tab.getTabName(context), tab.getTabIconRes(context)); + } + + ChooseTabListItem(int tabId, String itemName, @DrawableRes int itemIcon) { + this.tabId = tabId; + this.itemName = itemName; + this.itemIcon = itemIcon; + } + } + + private static class DialogListAdapter extends BaseAdapter { + private final LayoutInflater inflater; + private final ChooseTabListItem[] items; + + @DrawableRes private final int fallbackIcon; + + private DialogListAdapter(Context context, ChooseTabListItem[] items) { + this.inflater = LayoutInflater.from(context); + this.items = items; + this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot); + } + + @Override + public int getCount() { + return items.length; + } + + @Override + public ChooseTabListItem getItem(int position) { + return items[position]; + } + + @Override + public long getItemId(int position) { + return getItem(position).tabId; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = inflater.inflate(R.layout.list_choose_tabs_dialog, parent, false); + } + + final ChooseTabListItem item = getItem(position); + final AppCompatImageView tabIconView = convertView.findViewById(R.id.tabIcon); + final TextView tabNameView = convertView.findViewById(R.id.tabName); + + tabIconView.setImageResource(item.itemIcon > 0 ? item.itemIcon : fallbackIcon); + tabNameView.setText(item.itemName); + + return convertView; + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java new file mode 100644 index 000000000..b86f13d14 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -0,0 +1,386 @@ +package org.schabi.newpipe.settings.tabs; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.Fragment; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.content.res.AppCompatResources; +import android.support.v7.widget.AppCompatImageView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.settings.SelectChannelFragment; +import org.schabi.newpipe.settings.SelectKioskFragment; +import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem; +import org.schabi.newpipe.util.ThemeHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.schabi.newpipe.settings.tabs.Tab.typeFrom; + +public class ChooseTabsFragment extends Fragment { + + private TabsManager tabsManager; + private List tabList = new ArrayList<>(); + public ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; + + /*////////////////////////////////////////////////////////////////////////// + // Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + tabsManager = TabsManager.getManager(requireContext()); + updateTabList(); + + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_choose_tabs, container, false); + } + + @Override + public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { + super.onViewCreated(rootView, savedInstanceState); + + initButton(rootView); + + RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs); + listSelectedTabs.setLayoutManager(new LinearLayoutManager(requireContext())); + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + itemTouchHelper.attachToRecyclerView(listSelectedTabs); + + selectedTabsAdapter = new SelectedTabsAdapter(requireContext(), itemTouchHelper); + listSelectedTabs.setAdapter(selectedTabsAdapter); + } + + @Override + public void onResume() { + super.onResume(); + updateTitle(); + } + + @Override + public void onPause() { + super.onPause(); + saveChanges(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + + private final int MENU_ITEM_RESTORE_ID = 123456; + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + + final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults); + restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + + final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults); + restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon)); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == MENU_ITEM_RESTORE_ID) { + restoreDefaults(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + + private void updateTabList() { + tabList.clear(); + tabList.addAll(tabsManager.getTabs()); + } + + private void updateTitle() { + if (getActivity() instanceof AppCompatActivity) { + ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null) actionBar.setTitle(R.string.main_page_content); + } + } + + private void saveChanges() { + tabsManager.saveTabs(tabList); + } + + private void restoreDefaults() { + new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext())) + .setTitle(R.string.restore_defaults) + .setMessage(R.string.restore_defaults_confirmation) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.yes, (dialog, which) -> { + tabsManager.resetTabs(); + updateTabList(); + selectedTabsAdapter.notifyDataSetChanged(); + }) + .show(); + } + + private void initButton(View rootView) { + final FloatingActionButton fab = rootView.findViewById(R.id.addTabsButton); + fab.setOnClickListener(v -> { + final ChooseTabListItem[] availableTabs = getAvailableTabs(requireContext()); + + if (availableTabs.length == 0) { + //Toast.makeText(requireContext(), "No available tabs", Toast.LENGTH_SHORT).show(); + return; + } + + Dialog.OnClickListener actionListener = (dialog, which) -> { + final ChooseTabListItem selected = availableTabs[which]; + addTab(selected.tabId); + }; + + new AddTabDialog(requireContext(), availableTabs, actionListener) + .show(); + }); + } + + private void addTab(final Tab tab) { + tabList.add(tab); + selectedTabsAdapter.notifyDataSetChanged(); + } + + private void addTab(int tabId) { + final Tab.Type type = typeFrom(tabId); + + if (type == null) { + ErrorActivity.reportError(requireContext(), new IllegalStateException("Tab id not found: " + tabId), null, null, + ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Choosing tabs on settings", 0)); + return; + } + + switch (type) { + case KIOSK: { + SelectKioskFragment selectFragment = new SelectKioskFragment(); + selectFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) -> + addTab(new Tab.KioskTab(serviceId, kioskId))); + selectFragment.show(requireFragmentManager(), "select_kiosk"); + return; + } + case CHANNEL: { + SelectChannelFragment selectFragment = new SelectChannelFragment(); + selectFragment.setOnSelectedLisener((serviceId, url, name) -> + addTab(new Tab.ChannelTab(serviceId, url, name))); + selectFragment.show(requireFragmentManager(), "select_channel"); + return; + } + default: + addTab(type.getTab()); + break; + } + } + + public ChooseTabListItem[] getAvailableTabs(Context context) { + final ArrayList returnList = new ArrayList<>(); + + for (Tab.Type type : Tab.Type.values()) { + final Tab tab = type.getTab(); + switch (type) { + case BLANK: + if (!tabList.contains(tab)) { + returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.blank_page_summary), + tab.getTabIconRes(context))); + } + break; + case KIOSK: + returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.kiosk_page_summary), + ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot))); + break; + case CHANNEL: + returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.channel_page_summary), + tab.getTabIconRes(context))); + break; + default: + if (!tabList.contains(tab)) { + returnList.add(new ChooseTabListItem(context, tab)); + } + break; + } + } + + return returnList.toArray(new ChooseTabListItem[0]); + } + + /*////////////////////////////////////////////////////////////////////////// + // List Handling + //////////////////////////////////////////////////////////////////////////*/ + + private class SelectedTabsAdapter extends RecyclerView.Adapter { + private ItemTouchHelper itemTouchHelper; + private final LayoutInflater inflater; + + SelectedTabsAdapter(Context context, ItemTouchHelper itemTouchHelper) { + this.itemTouchHelper = itemTouchHelper; + this.inflater = LayoutInflater.from(context); + } + + public void swapItems(int fromPosition, int toPosition) { + Collections.swap(tabList, fromPosition, toPosition); + notifyItemMoved(fromPosition, toPosition); + } + + @NonNull + @Override + public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = inflater.inflate(R.layout.list_choose_tabs, parent, false); + return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) { + holder.bind(position, holder); + } + + @Override + public int getItemCount() { + return tabList.size(); + } + + class TabViewHolder extends RecyclerView.ViewHolder { + private AppCompatImageView tabIconView; + private TextView tabNameView; + private ImageView handle; + + TabViewHolder(View itemView) { + super(itemView); + + tabNameView = itemView.findViewById(R.id.tabName); + tabIconView = itemView.findViewById(R.id.tabIcon); + handle = itemView.findViewById(R.id.handle); + } + + @SuppressLint("ClickableViewAccessibility") + void bind(int position, TabViewHolder holder) { + handle.setOnTouchListener(getOnTouchListener(holder)); + + final Tab tab = tabList.get(position); + final Tab.Type type = Tab.typeFrom(tab.getTabId()); + + if (type == null) { + return; + } + + String tabName = tab.getTabName(requireContext()); + switch (type) { + case BLANK: + tabName = requireContext().getString(R.string.blank_page_summary); + break; + case KIOSK: + tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab).getKioskServiceId()) + "/" + tabName; + break; + case CHANNEL: + tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tabName; + break; + } + + + tabNameView.setText(tabName); + tabIconView.setImageResource(tab.getTabIconRes(requireContext())); + } + + @SuppressLint("ClickableViewAccessibility") + private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { + return (view, motionEvent) -> { + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (itemTouchHelper != null && getItemCount() > 1) { + itemTouchHelper.startDrag(item); + return true; + } + } + return false; + }; + } + } + } + + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, + int viewSizeOutOfBounds, int totalSize, + long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int minimumAbsVelocity = Math.max(12, + Math.abs(standardSpeed)); + return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() || + selectedTabsAdapter == null) { + return false; + } + + final int sourceIndex = source.getAdapterPosition(); + final int targetIndex = target.getAdapterPosition(); + selectedTabsAdapter.swapItems(sourceIndex, targetIndex); + return true; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + int position = viewHolder.getAdapterPosition(); + tabList.remove(position); + selectedTabsAdapter.notifyItemRemoved(position); + + if (tabList.isEmpty()) { + tabList.add(Tab.Type.BLANK.getTab()); + selectedTabsAdapter.notifyItemInserted(0); + } + } + }; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java new file mode 100644 index 000000000..d7c249a3e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -0,0 +1,416 @@ +package org.schabi.newpipe.settings.tabs; + +import android.content.Context; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; + +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonSink; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.fragments.BlankFragment; +import org.schabi.newpipe.fragments.list.channel.ChannelFragment; +import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; +import org.schabi.newpipe.local.bookmark.BookmarkFragment; +import org.schabi.newpipe.local.feed.FeedFragment; +import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; +import org.schabi.newpipe.local.subscription.SubscriptionFragment; +import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.util.ThemeHelper; + +public abstract class Tab { + Tab() { + } + + Tab(@NonNull JsonObject jsonObject) { + readDataFromJson(jsonObject); + } + + public abstract int getTabId(); + public abstract String getTabName(Context context); + @DrawableRes public abstract int getTabIconRes(Context context); + + /** + * Return a instance of the fragment that this tab represent. + */ + public abstract Fragment getFragment() throws ExtractionException; + + @Override + public boolean equals(Object obj) { + return obj instanceof Tab && obj.getClass().equals(this.getClass()) + && ((Tab) obj).getTabId() == this.getTabId(); + } + + /*////////////////////////////////////////////////////////////////////////// + // JSON Handling + //////////////////////////////////////////////////////////////////////////*/ + + private static final String JSON_TAB_ID_KEY = "tab_id"; + + public void writeJsonOn(JsonSink jsonSink) { + jsonSink.object(); + + jsonSink.value(JSON_TAB_ID_KEY, getTabId()); + writeDataToJson(jsonSink); + + jsonSink.end(); + } + + protected void writeDataToJson(JsonSink writerSink) { + // No-op + } + + protected void readDataFromJson(JsonObject jsonObject) { + // No-op + } + + /*////////////////////////////////////////////////////////////////////////// + // Tab Handling + //////////////////////////////////////////////////////////////////////////*/ + + @Nullable + public static Tab from(@NonNull JsonObject jsonObject) { + final int tabId = jsonObject.getInt(Tab.JSON_TAB_ID_KEY, -1); + + if (tabId == -1) { + return null; + } + + return from(tabId, jsonObject); + } + + @Nullable + public static Tab from(final int tabId) { + return from(tabId, null); + } + + @Nullable + public static Type typeFrom(int tabId) { + for (Type available : Type.values()) { + if (available.getTabId() == tabId) { + return available; + } + } + return null; + } + + @Nullable + private static Tab from(final int tabId, @Nullable JsonObject jsonObject) { + final Type type = typeFrom(tabId); + + if (type == null) { + return null; + } + + if (jsonObject != null) { + switch (type) { + case KIOSK: + return new KioskTab(jsonObject); + case CHANNEL: + return new ChannelTab(jsonObject); + } + } + + return type.getTab(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Implementations + //////////////////////////////////////////////////////////////////////////*/ + + public enum Type { + BLANK(new BlankTab()), + SUBSCRIPTIONS(new SubscriptionsTab()), + FEED(new FeedTab()), + BOOKMARKS(new BookmarksTab()), + HISTORY(new HistoryTab()), + KIOSK(new KioskTab()), + CHANNEL(new ChannelTab()); + + private Tab tab; + + Type(Tab tab) { + this.tab = tab; + } + + public int getTabId() { + return tab.getTabId(); + } + + public Tab getTab() { + return tab; + } + } + + public static class BlankTab extends Tab { + public static final int ID = 0; + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return "NewPipe"; //context.getString(R.string.blank_page_summary); + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_blank_page); + } + + @Override + public BlankFragment getFragment() { + return new BlankFragment(); + } + } + + public static class SubscriptionsTab extends Tab { + public static final int ID = 1; + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return context.getString(R.string.tab_subscriptions); + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel); + } + + @Override + public SubscriptionFragment getFragment() { + return new SubscriptionFragment(); + } + + } + + public static class FeedTab extends Tab { + public static final int ID = 2; + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return context.getString(R.string.fragment_whats_new); + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss); + } + + @Override + public FeedFragment getFragment() { + return new FeedFragment(); + } + } + + public static class BookmarksTab extends Tab { + public static final int ID = 3; + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return context.getString(R.string.tab_bookmarks); + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark); + } + + @Override + public BookmarkFragment getFragment() { + return new BookmarkFragment(); + } + } + + public static class HistoryTab extends Tab { + public static final int ID = 4; + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return context.getString(R.string.title_activity_history); + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history); + } + + @Override + public StatisticsPlaylistFragment getFragment() { + return new StatisticsPlaylistFragment(); + } + } + + public static class KioskTab extends Tab { + public static final int ID = 5; + + private int kioskServiceId; + private String kioskId; + + private static final String JSON_KIOSK_SERVICE_ID_KEY = "service_id"; + private static final String JSON_KIOSK_ID_KEY = "kiosk_id"; + + private KioskTab() { + this(-1, ""); + } + + public KioskTab(int kioskServiceId, String kioskId) { + this.kioskServiceId = kioskServiceId; + this.kioskId = kioskId; + } + + public KioskTab(JsonObject jsonObject) { + super(jsonObject); + } + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return KioskTranslator.getTranslatedKioskName(kioskId, context); + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context); + + if (kioskIcon <= 0) { + throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\""); + } + + return kioskIcon; + } + + @Override + public KioskFragment getFragment() throws ExtractionException { + return KioskFragment.getInstance(kioskServiceId, kioskId); + } + + @Override + protected void writeDataToJson(JsonSink writerSink) { + writerSink.value(JSON_KIOSK_SERVICE_ID_KEY, kioskServiceId) + .value(JSON_KIOSK_ID_KEY, kioskId); + } + + @Override + protected void readDataFromJson(JsonObject jsonObject) { + kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1); + kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, ""); + } + + public int getKioskServiceId() { + return kioskServiceId; + } + + public String getKioskId() { + return kioskId; + } + } + + public static class ChannelTab extends Tab { + public static final int ID = 6; + + private int channelServiceId; + private String channelUrl; + private String channelName; + + private static final String JSON_CHANNEL_SERVICE_ID_KEY = "channel_service_id"; + private static final String JSON_CHANNEL_URL_KEY = "channel_url"; + private static final String JSON_CHANNEL_NAME_KEY = "channel_name"; + + private ChannelTab() { + this(-1, "", ""); + } + + public ChannelTab(int channelServiceId, String channelUrl, String channelName) { + this.channelServiceId = channelServiceId; + this.channelUrl = channelUrl; + this.channelName = channelName; + } + + public ChannelTab(JsonObject jsonObject) { + super(jsonObject); + } + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(Context context) { + return channelName; + } + + @DrawableRes + @Override + public int getTabIconRes(Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel); + } + + @Override + public ChannelFragment getFragment() { + return ChannelFragment.getInstance(channelServiceId, channelUrl, channelName); + } + + @Override + protected void writeDataToJson(JsonSink writerSink) { + writerSink.value(JSON_CHANNEL_SERVICE_ID_KEY, channelServiceId) + .value(JSON_CHANNEL_URL_KEY, channelUrl) + .value(JSON_CHANNEL_NAME_KEY, channelName); + } + + @Override + protected void readDataFromJson(JsonObject jsonObject) { + channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1); + channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, ""); + channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, ""); + } + + public int getChannelServiceId() { + return channelServiceId; + } + + public String getChannelUrl() { + return channelUrl; + } + + public String getChannelName() { + return channelName; + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java new file mode 100644 index 000000000..332e244c8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -0,0 +1,114 @@ +package org.schabi.newpipe.settings.tabs; + +import android.support.annotation.Nullable; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; +import com.grack.nanojson.JsonStringWriter; +import com.grack.nanojson.JsonWriter; + +import org.schabi.newpipe.settings.tabs.Tab.Type; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.schabi.newpipe.extractor.ServiceList.YouTube; + +/** + * Class to get a JSON representation of a list of tabs, and the other way around. + */ +public class TabsJsonHelper { + private static final String JSON_TABS_ARRAY_KEY = "tabs"; + + protected static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList(Arrays.asList( + new Tab.KioskTab(YouTube.getServiceId(), "Trending"), + Type.SUBSCRIPTIONS.getTab(), + Type.BOOKMARKS.getTab() + )); + + public static class InvalidJsonException extends Exception { + private InvalidJsonException() { + super(); + } + + private InvalidJsonException(String message) { + super(message); + } + + private InvalidJsonException(Throwable cause) { + super(cause); + } + } + + /** + * Try to reads the passed JSON and returns the list of tabs if no error were encountered. + *

+ * If the JSON is null or empty, or the list of tabs that it represents is empty, the + * {@link #FALLBACK_INITIAL_TABS_LIST fallback list} will be returned. + *

+ * Tabs with invalid ids (i.e. not in the {@link Tab.Type} enum) will be ignored. + * + * @param tabsJson a JSON string got from {@link #getJsonToSave(List)}. + * @return a list of {@link Tab tabs}. + * @throws InvalidJsonException if the JSON string is not valid + */ + public static List getTabsFromJson(@Nullable String tabsJson) throws InvalidJsonException { + if (tabsJson == null || tabsJson.isEmpty()) { + return FALLBACK_INITIAL_TABS_LIST; + } + + final List returnTabs = new ArrayList<>(); + + final JsonObject outerJsonObject; + try { + outerJsonObject = JsonParser.object().from(tabsJson); + final JsonArray tabsArray = outerJsonObject.getArray(JSON_TABS_ARRAY_KEY); + + if (tabsArray == null) { + throw new InvalidJsonException("JSON doesn't contain \"" + JSON_TABS_ARRAY_KEY + "\" array"); + } + + for (Object o : tabsArray) { + if (!(o instanceof JsonObject)) continue; + + final Tab tab = Tab.from((JsonObject) o); + + if (tab != null) { + returnTabs.add(tab); + } + } + } catch (JsonParserException e) { + throw new InvalidJsonException(e); + } + + if (returnTabs.isEmpty()) { + return FALLBACK_INITIAL_TABS_LIST; + } + + return returnTabs; + } + + /** + * Get a JSON representation from a list of tabs. + * + * @param tabList a list of {@link Tab tabs}. + * @return a JSON string representing the list of tabs + */ + public static String getJsonToSave(@Nullable List tabList) { + final JsonStringWriter jsonWriter = JsonWriter.string(); + jsonWriter.object(); + + jsonWriter.array(JSON_TABS_ARRAY_KEY); + if (tabList != null) for (Tab tab : tabList) { + tab.writeJsonOn(jsonWriter); + } + jsonWriter.end(); + + jsonWriter.end(); + return jsonWriter.done(); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java new file mode 100644 index 000000000..a7d8dffa4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsManager.java @@ -0,0 +1,93 @@ +package org.schabi.newpipe.settings.tabs; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.widget.Toast; + +import org.schabi.newpipe.R; + +import java.util.List; + +public class TabsManager { + private final SharedPreferences sharedPreferences; + private final String savedTabsKey; + private final Context context; + + public static TabsManager getManager(Context context) { + return new TabsManager(context); + } + + private TabsManager(Context context) { + this.context = context; + this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + this.savedTabsKey = context.getString(R.string.saved_tabs_key); + } + + public List getTabs() { + final String savedJson = sharedPreferences.getString(savedTabsKey, null); + try { + return TabsJsonHelper.getTabsFromJson(savedJson); + } catch (TabsJsonHelper.InvalidJsonException e) { + Toast.makeText(context, R.string.saved_tabs_invalid_json, Toast.LENGTH_SHORT).show(); + return getDefaultTabs(); + } + } + + public void saveTabs(List tabList) { + final String jsonToSave = TabsJsonHelper.getJsonToSave(tabList); + sharedPreferences.edit().putString(savedTabsKey, jsonToSave).apply(); + } + + public void resetTabs() { + sharedPreferences.edit().remove(savedTabsKey).apply(); + } + + public List getDefaultTabs() { + return TabsJsonHelper.FALLBACK_INITIAL_TABS_LIST; + } + + /*////////////////////////////////////////////////////////////////////////// + // Listener + //////////////////////////////////////////////////////////////////////////*/ + + public interface SavedTabsChangeListener { + void onTabsChanged(); + } + + private SavedTabsChangeListener savedTabsChangeListener; + private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; + + public void setSavedTabsListener(SavedTabsChangeListener listener) { + if (preferenceChangeListener != null) { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener); + } + savedTabsChangeListener = listener; + preferenceChangeListener = getPreferenceChangeListener(); + sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener); + } + + public void unsetSavedTabsListener() { + if (preferenceChangeListener != null) { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener); + } + preferenceChangeListener = null; + savedTabsChangeListener = null; + } + + private SharedPreferences.OnSharedPreferenceChangeListener getPreferenceChangeListener() { + return (sharedPreferences, key) -> { + if (key.equals(savedTabsKey)) { + if (savedTabsChangeListener != null) savedTabsChangeListener.onTabsChanged(); + } + }; + } + +} + + + + + + + diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index e445233c3..31ab1a845 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -32,6 +32,7 @@ import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; @@ -183,7 +184,7 @@ public final class ExtractorHelper { cache.removeInfo(serviceId, url); load = loadFromNetwork; } else { - load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url), + load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url), loadFromNetwork.toMaybe()) .firstElement() //Take the first valid .toSingle(); diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java index ecc66bb40..318db37a1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java @@ -28,9 +28,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.Info; import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; public final class InfoCache { @@ -58,7 +55,7 @@ public final class InfoCache { public Info getFromKey(int serviceId, @NonNull String url) { if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); synchronized (lruCache) { - return getInfo(lruCache, keyOf(serviceId, url)); + return getInfo(keyOf(serviceId, url)); } } @@ -89,7 +86,7 @@ public final class InfoCache { public void trimCache() { if (DEBUG) Log.d(TAG, "trimCache() called"); synchronized (lruCache) { - removeStaleCache(lruCache); + removeStaleCache(); lruCache.trimToSize(TRIM_CACHE_TO); } } @@ -105,23 +102,22 @@ public final class InfoCache { return serviceId + url; } - private static void removeStaleCache(@NonNull final LruCache cache) { - for (Map.Entry entry : cache.snapshot().entrySet()) { + private static void removeStaleCache() { + for (Map.Entry entry : InfoCache.lruCache.snapshot().entrySet()) { final CacheData data = entry.getValue(); if (data != null && data.isExpired()) { - cache.remove(entry.getKey()); + InfoCache.lruCache.remove(entry.getKey()); } } } @Nullable - private static Info getInfo(@NonNull final LruCache cache, - @NonNull final String key) { - final CacheData data = cache.get(key); + private static Info getInfo(@NonNull final String key) { + final CacheData data = InfoCache.lruCache.get(key); if (data == null) return null; if (data.isExpired()) { - cache.remove(key); + InfoCache.lruCache.remove(key); return null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 1a5bf14f7..871d0578f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -204,7 +204,7 @@ public final class ListHelper { */ private static void sortStreamList(List videoStreams, final boolean ascendingOrder) { Collections.sort(videoStreams, (o1, o2) -> { - int result = compareVideoStreamResolution(o1, o2, VIDEO_FORMAT_QUALITY_RANKING); + int result = compareVideoStreamResolution(o1, o2); return result == 0 ? 0 : (ascendingOrder ? result : -result); }); } @@ -399,8 +399,7 @@ public final class ListHelper { } // Compares the quality of two video streams. - private static int compareVideoStreamResolution(VideoStream streamA, VideoStream streamB, - List formatRanking) { + private static int compareVideoStreamResolution(VideoStream streamA, VideoStream streamB) { if (streamA == null) { return -1; } @@ -414,7 +413,7 @@ public final class ListHelper { } // Same bitrate and format - return formatRanking.indexOf(streamA.getFormat()) - formatRanking.indexOf(streamB.getFormat()); + return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat()); } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 13767125d..5b953697d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -26,7 +26,6 @@ import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.StreamInfo; diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java index 51dceddf3..3115862e0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java +++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java @@ -21,7 +21,6 @@ package org.schabi.newpipe.util; import android.content.Context; -import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java index e3fe4a679..e100a447b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/util/StreamItemAdapter.java @@ -31,7 +31,7 @@ import us.shandian.giga.util.Utility; public class StreamItemAdapter extends BaseAdapter { private final Context context; - private StreamSizeWrapper streamsWrapper; + private final StreamSizeWrapper streamsWrapper; private final boolean showIconNoAudio; public StreamItemAdapter(Context context, StreamSizeWrapper streamsWrapper, boolean showIconNoAudio) { @@ -124,7 +124,7 @@ public class StreamItemAdapter extends BaseAdapter { public static class StreamSizeWrapper implements Serializable { private static final StreamSizeWrapper EMPTY = new StreamSizeWrapper<>(Collections.emptyList()); private final List streamsList; - private long[] streamSizes; + private final long[] streamSizes; public StreamSizeWrapper(List streamsList) { this.streamsList = streamsList; diff --git a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java index 3578f34ac..3142ad8dc 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java @@ -56,7 +56,6 @@ public class ZipHelper { /** * This will extract data from Zipfiles. * Caution this will override the original file. - * @param inZip The ZipOutputStream where the data is stored in * @param file The path of the file on the disk where the data should be extracted to. * @param name The path of the file inside the zip. * @return will return true if the file was found within the zip file diff --git a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java index adef7e76f..88d525625 100644 --- a/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java +++ b/app/src/main/java/org/schabi/newpipe/views/CollapsibleView.java @@ -81,7 +81,7 @@ public class CollapsibleView extends LinearLayout { private int targetHeight = -1; private ValueAnimator currentAnimator; - private List listeners = new ArrayList<>(); + private final List listeners = new ArrayList<>(); /** * This method recalculates the height of this view so it must be called when diff --git a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java index 3294f5164..a377d861c 100755 --- a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java @@ -123,7 +123,7 @@ public class DownloadManagerImpl implements DownloadManager { Collections.sort(missions, new Comparator() { @Override public int compare(DownloadMission o1, DownloadMission o2) { - return Long.valueOf(o1.timestamp).compareTo(o2.timestamp); + return Long.compare(o1.timestamp, o2.timestamp); } }); } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMission.java b/app/src/main/java/us/shandian/giga/get/DownloadMission.java index f02eaae28..79c4baf05 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMission.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMission.java @@ -67,8 +67,8 @@ public class DownloadMission implements Serializable { public long done; public int threadCount = 3; public int finishCount; - private List threadPositions = new ArrayList(); - public final Map blockState = new HashMap(); + private final List threadPositions = new ArrayList<>(); + public final Map blockState = new HashMap<>(); public boolean running; public boolean finished; public boolean fallback; @@ -77,7 +77,7 @@ public class DownloadMission implements Serializable { public transient boolean recovered; - private transient ArrayList> mListeners = new ArrayList>(); + private transient ArrayList> mListeners = new ArrayList<>(); private transient boolean mWritingToFile; private static final int NO_IDENTIFIER = -1; @@ -232,7 +232,7 @@ public class DownloadMission implements Serializable { public synchronized void addListener(MissionListener listener) { Handler handler = new Handler(Looper.getMainLooper()); MissionListener.handlerStore.put(listener, handler); - mListeners.add(new WeakReference(listener)); + mListeners.add(new WeakReference<>(listener)); } public synchronized void removeListener(MissionListener listener) { diff --git a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java index b53f8aea9..6ad8626c3 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadRunnable.java @@ -92,7 +92,7 @@ public class DownloadRunnable implements Runnable { // A server may be ignoring the range request if (conn.getResponseCode() != 206) { mMission.errCode = DownloadMission.ERROR_SERVER_UNSUPPORTED; - notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); + notifyError(); if (DEBUG) { Log.e(TAG, mId + ":Unsupported " + conn.getResponseCode()); @@ -161,9 +161,9 @@ public class DownloadRunnable implements Runnable { } } - private void notifyError(final int err) { + private void notifyError() { synchronized (mMission) { - mMission.notifyError(err); + mMission.notifyError(DownloadMission.ERROR_SERVER_UNSUPPORTED); mMission.pause(); } } diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 59f5e2225..ff410a79a 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -56,7 +56,7 @@ public class DownloadManagerService extends Service { private DownloadDataSource mDataSource; - private MissionListener missionListener = new MissionListener(); + private final MissionListener missionListener = new MissionListener(); private void notifyMediaScanner(DownloadMission mission) { diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 8127c3467..d5555c2be 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -306,12 +306,12 @@ public class MissionAdapter extends RecyclerView.Adapter { ProgressDialog prog; - WeakReference weakReference; + final WeakReference weakReference; ChecksumTask(@NonNull Activity activity) { weakReference = new WeakReference<>(activity); diff --git a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java index 6a0e35cff..955ce4c65 100644 --- a/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java +++ b/app/src/main/java/us/shandian/giga/ui/common/ProgressDrawable.java @@ -12,7 +12,8 @@ import android.support.v4.content.ContextCompat; public class ProgressDrawable extends Drawable { private float mProgress; - private int mBackgroundColor, mForegroundColor; + private final int mBackgroundColor; + private final int mForegroundColor; public ProgressDrawable(Context context, @ColorRes int background, @ColorRes int foreground) { this(ContextCompat.getColor(context, background), ContextCompat.getColor(context, foreground)); diff --git a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java index 14439f6c8..5241415b2 100644 --- a/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java +++ b/app/src/main/java/us/shandian/giga/ui/fragment/MissionsFragment.java @@ -16,6 +16,8 @@ import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -44,7 +46,7 @@ public abstract class MissionsFragment extends Fragment { private DeleteDownloadManager mDeleteDownloadManager; private Disposable mDeleteDisposable; - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder binder) { @@ -144,17 +146,21 @@ public abstract class MissionsFragment extends Fragment { } @Override - public boolean onOptionsItemSelected(MenuItem item) { - return super.onOptionsItemSelected(item); + public void onPrepareOptionsMenu(Menu menu) { + mSwitch = menu.findItem(R.id.switch_mode); + super.onPrepareOptionsMenu(menu); + } - /*switch (item.getItemId()) { + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { case R.id.switch_mode: mLinear = !mLinear; updateList(); return true; default: return super.onOptionsItemSelected(item); - }*/ + } } public void notifyChange() { diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index de9a16a1b..163ac2b14 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -11,9 +11,7 @@ import android.widget.Toast; import org.schabi.newpipe.R; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -198,7 +196,7 @@ public class Utility { while ((len = i.read(buf)) != -1) { md.update(buf, 0, len); } - } catch (IOException e) { + } catch (IOException ignored) { } diff --git a/app/src/main/res/drawable/ic_blank_page_black_24dp.xml b/app/src/main/res/drawable/ic_blank_page_black_24dp.xml new file mode 100644 index 000000000..e8c60a1a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_blank_page_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_blank_page_white_24dp.xml b/app/src/main/res/drawable/ic_blank_page_white_24dp.xml new file mode 100644 index 000000000..86a68484f --- /dev/null +++ b/app/src/main/res/drawable/ic_blank_page_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_backup_restore_black_24dp.xml b/app/src/main/res/drawable/ic_settings_backup_restore_black_24dp.xml new file mode 100644 index 000000000..aa424c0d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_backup_restore_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_backup_restore_white_24dp.xml b/app/src/main/res/drawable/ic_settings_backup_restore_white_24dp.xml new file mode 100644 index 000000000..03a26f550 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_backup_restore_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-v21/drawer_header.xml b/app/src/main/res/layout-v21/drawer_header.xml index 918bbaf43..4474ee4ed 100644 --- a/app/src/main/res/layout-v21/drawer_header.xml +++ b/app/src/main/res/layout-v21/drawer_header.xml @@ -16,7 +16,8 @@ android:layout_height="match_parent" android:background="?attr/colorPrimary" android:scaleType="centerCrop" - android:src="@drawable/background_header" /> + android:src="@drawable/background_header" + android:contentDescription="TODO" /> + android:src="@drawable/np_logo_nude_shadow" + android:contentDescription="TODO" /> + android:src="@drawable/ic_arrow_down_white" + android:contentDescription="TODO" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_error.xml b/app/src/main/res/layout/activity_error.xml index 7752dc7cb..c47077c73 100644 --- a/app/src/main/res/layout/activity_error.xml +++ b/app/src/main/res/layout/activity_error.xml @@ -114,7 +114,8 @@ + android:layout_height="wrap_content" + android:inputType="" />