mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-26 04:47:38 +00:00 
			
		
		
		
	Merge branch 'dev' into tablet_ui
This commit is contained in:
		| @@ -15,7 +15,7 @@ | ||||
| <p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p> | ||||
| <p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a>  • <a href="https://newpipe.schabi.org/press/">Press</a></p> | ||||
| <hr /> | ||||
| 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 | ||||
|   | ||||
| @@ -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' | ||||
|   | ||||
| @@ -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<Throwable>() { | ||||
|             @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() + "]"); | ||||
|  | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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"); | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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<AdapterChoiceItem> 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); | ||||
|   | ||||
| @@ -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<Object, Void, Integer> { | ||||
|  | ||||
|     WeakReference<Activity> weakReference; | ||||
|     final WeakReference<Activity> weakReference; | ||||
|     private License license; | ||||
|  | ||||
|     public LicenseFragmentHelper(@Nullable Activity activity) { | ||||
| @@ -78,18 +77,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> { | ||||
|             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("</head>"); | ||||
|             String[] insert = licenseContent.toString().split("</head>"); | ||||
|             webViewData = insert[0] + "<style type=\"text/css\">" | ||||
|                     + getLicenseStylesheet(context) + "</style></head>" | ||||
|                     + insert[1]; | ||||
|   | ||||
| @@ -30,7 +30,7 @@ public interface BasicDAO<Entity> { | ||||
|  | ||||
|     /* Deletes */ | ||||
|     @Delete | ||||
|     int delete(final Entity entity); | ||||
|     void delete(final Entity entity); | ||||
|  | ||||
|     @Delete | ||||
|     int delete(final Collection<Entity> entities); | ||||
| @@ -42,5 +42,5 @@ public interface BasicDAO<Entity> { | ||||
|     int update(final Entity entity); | ||||
|  | ||||
|     @Update | ||||
|     int update(final Collection<Entity> entities); | ||||
|     void update(final Collection<Entity> entities); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<StreamEntity> { | ||||
|   | ||||
| @@ -28,11 +28,11 @@ public class DeleteDownloadManager { | ||||
|  | ||||
|     private static final String KEY_STATE = "delete_manager_state"; | ||||
|  | ||||
|     private View mView; | ||||
|     private HashSet<String> mPendingMap; | ||||
|     private List<Disposable> mDisposableList; | ||||
|     private final View mView; | ||||
|     private final HashSet<String> mPendingMap; | ||||
|     private final List<Disposable> mDisposableList; | ||||
|     private DownloadManager mDownloadManager; | ||||
|     private PublishSubject<DownloadMission> publishSubject = PublishSubject.create(); | ||||
|     private final PublishSubject<DownloadMission> publishSubject = PublishSubject.create(); | ||||
|  | ||||
|     DeleteDownloadManager(Activity activity) { | ||||
|         mPendingMap = new HashSet<>(); | ||||
|   | ||||
| @@ -55,7 +55,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | ||||
|     private StreamItemAdapter<AudioStream> audioStreamsAdapter; | ||||
|     private StreamItemAdapter<VideoStream> videoStreamsAdapter; | ||||
|  | ||||
|     private CompositeDisposable disposables = new CompositeDisposable(); | ||||
|     private final CompositeDisposable disposables = new CompositeDisposable(); | ||||
|  | ||||
|     private EditText nameEditText; | ||||
|     private Spinner streamsSpinner; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<String> 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<Tab> 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(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -768,7 +768,7 @@ public class VideoDetailFragment | ||||
|      * Stack that contains the "navigation history".<br> | ||||
|      * The peek is the current video. | ||||
|      */ | ||||
|     protected LinkedList<StackItem> stack = new LinkedList<>(); | ||||
|     protected final LinkedList<StackItem> stack = new LinkedList<>(); | ||||
|  | ||||
|     public void clearHistory() { | ||||
|         stack.clear(); | ||||
|   | ||||
| @@ -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<I extends ListInfo> | ||||
|   | ||||
| @@ -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<ChannelInfo> { | ||||
|  | ||||
|     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<ChannelInfo> { | ||||
|  | ||||
|     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<ChannelInfo> { | ||||
|     @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<ChannelInfo> { | ||||
|         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())); | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -122,12 +122,11 @@ public class SearchFragment | ||||
|     private String nextPageUrl; | ||||
|     private String contentCountry; | ||||
|     private boolean isSuggestionsEnabled = true; | ||||
|     private boolean isSearchHistoryEnabled = true; | ||||
|  | ||||
|     private PublishSubject<String> suggestionPublisher = PublishSubject.create(); | ||||
|     private final PublishSubject<String> 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); | ||||
|   | ||||
| @@ -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<StreamInfoItem> onStreamSelectedListener; | ||||
|     private OnClickGesture<ChannelInfoItem> onChannelSelectedListener; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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<LocalItem> onSelectedListener; | ||||
|  | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave | ||||
|  | ||||
|     @Override | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public void readFrom(@NonNull Queue<Object> savedObjects) throws Exception { | ||||
|     public void readFrom(@NonNull Queue<Object> savedObjects) { | ||||
|         streamEntities = (List<StreamEntity>) savedObjects.poll(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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<List<SubscriptionEntity>, Void> { | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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<List<SubscriptionEnt | ||||
|  | ||||
|     private static final int LIST_MODE_UPDATE_FLAG = 0x32; | ||||
|  | ||||
|     private View headerRootLayout; | ||||
|     private View whatsNewItemListHeader; | ||||
|     private View importExportListHeader; | ||||
|  | ||||
| @@ -327,6 +330,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | ||||
|         itemsList = rootView.findViewById(R.id.items_list); | ||||
|         itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); | ||||
|  | ||||
|         View headerRootLayout; | ||||
|         infoListAdapter.setHeader(headerRootLayout = activity.getLayoutInflater().inflate(R.layout.subscription_header, itemsList, false)); | ||||
|         whatsNewItemListHeader = headerRootLayout.findViewById(R.id.whats_new); | ||||
|         importExportListHeader = headerRootLayout.findViewById(R.id.import_export); | ||||
| @@ -357,7 +361,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | ||||
|         super.initListeners(); | ||||
|  | ||||
|         infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<ChannelInfoItem>() { | ||||
|             @Override | ||||
|  | ||||
|             public void selected(ChannelInfoItem selectedItem) { | ||||
|                 final FragmentManager fragmentManager = getFM(); | ||||
|                 NavigationHelper.openChannelFragment(fragmentManager, | ||||
| @@ -365,6 +369,11 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | ||||
|                         selectedItem.getUrl(), | ||||
|                         selectedItem.getName()); | ||||
|             } | ||||
|  | ||||
|             public void held(ChannelInfoItem selectedItem) { | ||||
|                 showLongTapDialog(selectedItem); | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         //noinspection ConstantConditions | ||||
| @@ -375,6 +384,85 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | ||||
|         importExportListHeader.setOnClickListener(v -> 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<List<SubscriptionEntity>> getDeleteObserver(){ | ||||
|         return new Observer<List<SubscriptionEntity>>() { | ||||
|             @Override | ||||
|             public void onSubscribe(Disposable d) { | ||||
|                 disposables.add(d); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             public void onNext(List<SubscriptionEntity> 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(); | ||||
|   | ||||
| @@ -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<List<SubscriptionEntity>> subscription; | ||||
|     private final AppDatabase db; | ||||
|     private final Flowable<List<SubscriptionEntity>> 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<List<SubscriptionEntity>, CompletableSource> update = new Function<List<SubscriptionEntity>, CompletableSource>() { | ||||
|             @Override | ||||
|             public CompletableSource apply(@NonNull List<SubscriptionEntity> subscriptionEntities) throws Exception { | ||||
|             public CompletableSource apply(@NonNull List<SubscriptionEntity> subscriptionEntities) { | ||||
|                 if (DEBUG) Log.d(TAG, "updateChannelInfo() called with: subscriptionEntities = [" + subscriptionEntities + "]"); | ||||
|                 if (subscriptionEntities.size() == 1) { | ||||
|                     SubscriptionEntity subscription = subscriptionEntities.get(0); | ||||
|   | ||||
| @@ -58,8 +58,8 @@ public abstract class BaseImportExportService extends Service { | ||||
|     protected NotificationCompat.Builder notificationBuilder; | ||||
|  | ||||
|     protected SubscriptionService subscriptionService; | ||||
|     protected CompositeDisposable disposables = new CompositeDisposable(); | ||||
|     protected PublishProcessor<String> notificationUpdater = PublishProcessor.create(); | ||||
|     protected final CompositeDisposable disposables = new CompositeDisposable(); | ||||
|     protected final PublishProcessor<String> 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(); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -144,12 +144,16 @@ public class SubscriptionsImportService extends BaseImportExportService { | ||||
|         showToast(R.string.import_ongoing); | ||||
|  | ||||
|         Flowable<List<SubscriptionItem>> 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) { | ||||
|   | ||||
| @@ -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() + ")"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
|     //////////////////////////////////////////////////////////////////////////*/ | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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)); | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -79,7 +79,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator | ||||
|  | ||||
|     private void publishFloatingQueueWindow() { | ||||
|         if (callback.getQueueSize() == 0) { | ||||
|             mediaSession.setQueue(Collections.<MediaSessionCompat.QueueItem>emptyList()); | ||||
|             mediaSession.setQueue(Collections.emptyList()); | ||||
|             activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<PlayQueueItem> neighbors = new ArraySet<>( | ||||
|                 playQueue.getStreams().subList(leftBound,rightBound)); | ||||
|   | ||||
| @@ -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 { | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -19,14 +19,14 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> 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.<StreamInfoItem>emptyList(), 0); | ||||
|         this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0); | ||||
|     } | ||||
|  | ||||
|     AbstractInfoPlayQueue(final int serviceId, | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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)); | ||||
|   | ||||
| @@ -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(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -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(); | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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<String> 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<ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder>{ | ||||
|         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(); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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<Entry> kioskList = new Vector<>(); | ||||
|         private final List<Entry> 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; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -77,7 +77,8 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc | ||||
|                 finish(); | ||||
|             } else getSupportFragmentManager().popBackStack(); | ||||
|         } | ||||
|         return true; | ||||
|  | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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<Tab> 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<ChooseTabListItem> 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<ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder> { | ||||
|         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); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										416
									
								
								app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
									
									
									
									
									
										Normal file
									
								
							| @@ -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, "<no-id>"); | ||||
|         } | ||||
|  | ||||
|         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, "<no-id>"); | ||||
|         } | ||||
|  | ||||
|         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, "<no-url>", "<no-name>"); | ||||
|         } | ||||
|  | ||||
|         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, "<no-url>"); | ||||
|             channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, "<no-name>"); | ||||
|         } | ||||
|  | ||||
|         public int getChannelServiceId() { | ||||
|             return channelServiceId; | ||||
|         } | ||||
|  | ||||
|         public String getChannelUrl() { | ||||
|             return channelUrl; | ||||
|         } | ||||
|  | ||||
|         public String getChannelName() { | ||||
|             return channelName; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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<Tab> 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. | ||||
|      * <p> | ||||
|      * 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. | ||||
|      * <p> | ||||
|      * 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<Tab> getTabsFromJson(@Nullable String tabsJson) throws InvalidJsonException { | ||||
|         if (tabsJson == null || tabsJson.isEmpty()) { | ||||
|             return FALLBACK_INITIAL_TABS_LIST; | ||||
|         } | ||||
|  | ||||
|         final List<Tab> 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<Tab> 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(); | ||||
|     } | ||||
| } | ||||
| @@ -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<Tab> 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<Tab> tabList) { | ||||
|         final String jsonToSave = TabsJsonHelper.getJsonToSave(tabList); | ||||
|         sharedPreferences.edit().putString(savedTabsKey, jsonToSave).apply(); | ||||
|     } | ||||
|  | ||||
|     public void resetTabs() { | ||||
|         sharedPreferences.edit().remove(savedTabsKey).apply(); | ||||
|     } | ||||
|  | ||||
|     public List<Tab> 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(); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -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.<I>loadFromCache(serviceId, url), | ||||
|             load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url), | ||||
|                     loadFromNetwork.toMaybe()) | ||||
|                     .firstElement() //Take the first valid | ||||
|                     .toSingle(); | ||||
|   | ||||
| @@ -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<String, CacheData> cache) { | ||||
|         for (Map.Entry<String, CacheData> entry : cache.snapshot().entrySet()) { | ||||
|     private static void removeStaleCache() { | ||||
|         for (Map.Entry<String, CacheData> 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<String, CacheData> 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; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -204,7 +204,7 @@ public final class ListHelper { | ||||
|      */ | ||||
|     private static void sortStreamList(List<VideoStream> 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<MediaFormat> 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()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -31,7 +31,7 @@ import us.shandian.giga.util.Utility; | ||||
| public class StreamItemAdapter<T extends Stream> extends BaseAdapter { | ||||
|     private final Context context; | ||||
|  | ||||
|     private StreamSizeWrapper<T> streamsWrapper; | ||||
|     private final StreamSizeWrapper<T> streamsWrapper; | ||||
|     private final boolean showIconNoAudio; | ||||
|  | ||||
|     public StreamItemAdapter(Context context, StreamSizeWrapper<T> streamsWrapper, boolean showIconNoAudio) { | ||||
| @@ -124,7 +124,7 @@ public class StreamItemAdapter<T extends Stream> extends BaseAdapter { | ||||
|     public static class StreamSizeWrapper<T extends Stream> implements Serializable { | ||||
|         private static final StreamSizeWrapper<Stream> EMPTY = new StreamSizeWrapper<>(Collections.emptyList()); | ||||
|         private final List<T> streamsList; | ||||
|         private long[] streamSizes; | ||||
|         private final long[] streamSizes; | ||||
|  | ||||
|         public StreamSizeWrapper(List<T> streamsList) { | ||||
|             this.streamsList = streamsList; | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -81,7 +81,7 @@ public class CollapsibleView extends LinearLayout { | ||||
|  | ||||
|     private int targetHeight = -1; | ||||
|     private ValueAnimator currentAnimator; | ||||
|     private List<StateListener> listeners = new ArrayList<>(); | ||||
|     private final List<StateListener> listeners = new ArrayList<>(); | ||||
|  | ||||
|     /** | ||||
|      * This method recalculates the height of this view so it <b>must</b> be called when | ||||
|   | ||||
| @@ -123,7 +123,7 @@ public class DownloadManagerImpl implements DownloadManager { | ||||
|         Collections.sort(missions, new Comparator<DownloadMission>() { | ||||
|             @Override | ||||
|             public int compare(DownloadMission o1, DownloadMission o2) { | ||||
|                 return Long.valueOf(o1.timestamp).compareTo(o2.timestamp); | ||||
|                 return Long.compare(o1.timestamp, o2.timestamp); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -67,8 +67,8 @@ public class DownloadMission implements Serializable { | ||||
|     public long done; | ||||
|     public int threadCount = 3; | ||||
|     public int finishCount; | ||||
|     private List<Long> threadPositions = new ArrayList<Long>(); | ||||
|     public final Map<Long, Boolean> blockState = new HashMap<Long, Boolean>(); | ||||
|     private final List<Long> threadPositions = new ArrayList<>(); | ||||
|     public final Map<Long, Boolean> 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<WeakReference<MissionListener>> mListeners = new ArrayList<WeakReference<MissionListener>>(); | ||||
|     private transient ArrayList<WeakReference<MissionListener>> 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<MissionListener>(listener)); | ||||
|         mListeners.add(new WeakReference<>(listener)); | ||||
|     } | ||||
|  | ||||
|     public synchronized void removeListener(MissionListener listener) { | ||||
|   | ||||
| @@ -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(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -306,12 +306,12 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold | ||||
|         public DownloadMission mission; | ||||
|         public int position; | ||||
|  | ||||
|         public TextView status; | ||||
|         public ImageView icon; | ||||
|         public TextView name; | ||||
|         public TextView size; | ||||
|         public View bkg; | ||||
|         public ImageView menu; | ||||
|         public final TextView status; | ||||
|         public final ImageView icon; | ||||
|         public final TextView name; | ||||
|         public final TextView size; | ||||
|         public final View bkg; | ||||
|         public final ImageView menu; | ||||
|         public ProgressDrawable progress; | ||||
|         public MissionObserver observer; | ||||
|  | ||||
| @@ -332,8 +332,8 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold | ||||
|     } | ||||
|  | ||||
|     static class MissionObserver implements DownloadMission.MissionListener { | ||||
|         private MissionAdapter mAdapter; | ||||
|         private ViewHolder mHolder; | ||||
|         private final MissionAdapter mAdapter; | ||||
|         private final ViewHolder mHolder; | ||||
|  | ||||
|         public MissionObserver(MissionAdapter adapter, ViewHolder holder) { | ||||
|             mAdapter = adapter; | ||||
| @@ -365,7 +365,7 @@ public class MissionAdapter extends RecyclerView.Adapter<MissionAdapter.ViewHold | ||||
|  | ||||
|     private static class ChecksumTask extends AsyncTask<String, Void, String> { | ||||
|         ProgressDialog prog; | ||||
|         WeakReference<Activity> weakReference; | ||||
|         final WeakReference<Activity> weakReference; | ||||
|  | ||||
|         ChecksumTask(@NonNull Activity activity) { | ||||
|             weakReference = new WeakReference<>(activity); | ||||
|   | ||||
| @@ -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)); | ||||
|   | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -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) { | ||||
|  | ||||
|         } | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								app/src/main/res/drawable/ic_blank_page_black_24dp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/drawable/ic_blank_page_black_24dp.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FF000000" | ||||
|         android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,5c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z"/> | ||||
| </vector> | ||||
							
								
								
									
										9
									
								
								app/src/main/res/drawable/ic_blank_page_white_24dp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/drawable/ic_blank_page_white_24dp.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFFFF" | ||||
|         android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,5c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z"/> | ||||
| </vector> | ||||
| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FF000000" | ||||
|         android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/> | ||||
| </vector> | ||||
| @@ -0,0 +1,9 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFFFF" | ||||
|         android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/> | ||||
| </vector> | ||||
| @@ -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" /> | ||||
|  | ||||
|     <ImageView | ||||
|         android:id="@+id/drawer_header_np_nude_view" | ||||
| @@ -26,7 +27,8 @@ | ||||
|  | ||||
|         android:layout_marginStart="30dp" | ||||
|         android:layout_marginTop="30dp" | ||||
|         android:src="@drawable/np_logo_nude_shadow" /> | ||||
|         android:src="@drawable/np_logo_nude_shadow" | ||||
|         android:contentDescription="TODO" /> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/drawer_header_np_text_view" | ||||
| @@ -38,7 +40,8 @@ | ||||
|         android:layout_toRightOf="@id/drawer_header_np_nude_view" | ||||
|         android:gravity="center" | ||||
|         android:text="@string/app_name" | ||||
|         android:textSize="30dp" | ||||
|         android:textSize="30sp" | ||||
|         android:textColor="@color/drawer_header_font_color" | ||||
|         android:textStyle="bold|italic" /> | ||||
|  | ||||
|     <TextView | ||||
| @@ -49,7 +52,8 @@ | ||||
|         android:layout_alignStart="@id/drawer_header_np_text_view" | ||||
|         android:layout_below="@id/drawer_header_np_text_view" | ||||
|         android:text="YouTube" | ||||
|         android:textSize="18dp" | ||||
|         android:textSize="18sp" | ||||
|         android:textColor="@color/drawer_header_font_color" | ||||
|         android:textStyle="italic" /> | ||||
|  | ||||
|     <ImageView | ||||
| @@ -64,6 +68,7 @@ | ||||
|         android:paddingBottom="20dp" | ||||
|         android:paddingEnd="20dp" | ||||
|         android:paddingRight="20dp" | ||||
|         android:src="@drawable/ic_arrow_down_white" /> | ||||
|         android:src="@drawable/ic_arrow_down_white" | ||||
|         android:contentDescription="TODO" /> | ||||
|  | ||||
| </RelativeLayout> | ||||
| @@ -114,7 +114,8 @@ | ||||
|             <EditText | ||||
|                 android:id="@+id/errorCommentBox" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content"/> | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:inputType="" /> | ||||
|  | ||||
|             <Button | ||||
|                 android:id="@+id/errorReportButton" | ||||
|   | ||||
| @@ -61,7 +61,7 @@ | ||||
|             android:layout_below="@+id/channel_title_view" | ||||
|             android:ellipsize="end" | ||||
|             android:gravity="left|center" | ||||
|             android:lines="1" | ||||
|             android:maxLines="2" | ||||
|             android:textSize="@dimen/channel_subscribers_text_size" | ||||
|             android:visibility="gone" | ||||
|             tools:ignore="RtlHardcoded" | ||||
|   | ||||
| @@ -16,7 +16,8 @@ android:focusable="true"> | ||||
|         android:layout_height="match_parent" | ||||
|         android:background="?attr/colorPrimary" | ||||
|         android:src="@drawable/background_header" | ||||
|         android:scaleType="centerCrop"/> | ||||
|         android:scaleType="centerCrop" | ||||
|         android:contentDescription="TODO" /> | ||||
|  | ||||
|     <ImageView | ||||
|         android:id="@+id/drawer_header_np_nude_view" | ||||
| @@ -26,7 +27,8 @@ android:focusable="true"> | ||||
|  | ||||
|         android:layout_width="70dp" | ||||
|         android:layout_height="70dp" | ||||
|         android:src="@drawable/np_logo_nude_shadow"/> | ||||
|         android:src="@drawable/np_logo_nude_shadow" | ||||
|         android:contentDescription="TODO" /> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/drawer_header_np_text_view" | ||||
| @@ -38,7 +40,8 @@ android:focusable="true"> | ||||
|         android:layout_alignTop="@id/drawer_header_np_nude_view" | ||||
|         android:layout_alignBottom="@id/drawer_header_np_nude_view" | ||||
|         android:gravity="center" | ||||
|         android:textSize="30dp" | ||||
|         android:textSize="30sp" | ||||
|         android:textColor="@color/drawer_header_font_color" | ||||
|         android:textStyle="bold|italic"/> | ||||
|  | ||||
|     <TextView | ||||
| @@ -49,7 +52,8 @@ android:focusable="true"> | ||||
|         android:layout_below="@id/drawer_header_np_text_view" | ||||
|         android:layout_alignLeft="@id/drawer_header_np_text_view" | ||||
|         android:layout_alignStart="@id/drawer_header_np_text_view" | ||||
|         android:textSize="18dp" | ||||
|         android:textSize="18sp" | ||||
|         android:textColor="@color/drawer_header_font_color" | ||||
|         android:textStyle="italic"/> | ||||
|  | ||||
|     <ImageView | ||||
| @@ -64,6 +68,7 @@ android:focusable="true"> | ||||
|         android:paddingBottom="20dp" | ||||
|         android:paddingRight="20dp" | ||||
|         android:src="@drawable/ic_arrow_down_white" | ||||
|         android:paddingEnd="20dp" /> | ||||
|         android:paddingEnd="20dp" | ||||
|         android:contentDescription="TODO" /> | ||||
|  | ||||
| </RelativeLayout> | ||||
| @@ -21,7 +21,8 @@ | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="center_horizontal" | ||||
|             android:layout_marginBottom="8dp" | ||||
|             app:srcCompat="@mipmap/ic_launcher" /> | ||||
|             app:srcCompat="@mipmap/ic_launcher" | ||||
|             android:contentDescription="TODO" /> | ||||
|  | ||||
|         <TextView | ||||
|             android:id="@+id/app_name" | ||||
|   | ||||
| @@ -1,32 +1,32 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <RelativeLayout | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     android:id="@+id/relLay" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:orientation="vertical"> | ||||
| 
 | ||||
|     <android.support.v7.widget.RecyclerView | ||||
|         android:id="@+id/usedTabs" | ||||
|         android:id="@+id/selectedTabs" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_margin="0dp" | ||||
|         android:paddingBottom="0dp" | ||||
|         android:paddingTop="0dp" > | ||||
| 
 | ||||
|     </android.support.v7.widget.RecyclerView> | ||||
|         tools:listitem="@layout/list_choose_tabs"/> | ||||
| 
 | ||||
|     <android.support.design.widget.FloatingActionButton | ||||
|         android:id="@+id/floatingActionButton" | ||||
|         android:id="@+id/addTabsButton" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_alignParentBottom="true" | ||||
|         android:layout_alignParentEnd="true" | ||||
|         android:layout_alignParentRight="true" | ||||
|         android:layout_marginBottom="16dp" | ||||
|         android:layout_marginEnd="16dp" | ||||
|         android:clickable="true" | ||||
|         android:layout_alignParentRight="true" | ||||
|         android:layout_marginRight="16dp" | ||||
|         android:focusable="true" /> | ||||
|         android:clickable="true" | ||||
|         android:focusable="true" | ||||
|         app:backgroundTint="?attr/colorPrimary" | ||||
|         app:fabSize="auto" | ||||
|         app:srcCompat="?attr/ic_add"/> | ||||
| 
 | ||||
| </RelativeLayout> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Christian Schabesberger
					Christian Schabesberger