diff --git a/app/build.gradle b/app/build.gradle index 13f422afa..86d6542e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 27 - versionCode 46 - versionName "0.11.5" + versionCode 47 + versionName "0.11.6" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:1c97da8b51b3610' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:7fd21ec08581d' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:1.10.19' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dab6fb2ec..f0a8d45e0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:logo="@mipmap/ic_launcher" - android:theme="@style/DarkTheme" + android:theme="@style/OpeningTheme" tools:ignore="AllowBackup"> + android:launchMode="singleTask"/> + android:theme="@style/RouterActivityThemeDark"> @@ -175,17 +174,21 @@ - - + + + + android:theme="@style/RouterActivityThemeDark"> @@ -204,6 +207,11 @@ + + + + + diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 93b4becde..49f73853b 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -1,9 +1,12 @@ package org.schabi.newpipe; +import android.app.AlarmManager; import android.app.Application; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.os.Build; import android.util.Log; @@ -116,7 +119,6 @@ public class App extends Application { }); } - private void initACRA() { try { final ACRAConfiguration acraConfig = new ConfigurationBuilder(this) @@ -149,4 +151,5 @@ public class App extends Application { NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.createNotificationChannel(mChannel); } + } diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index 186b4adc2..6cd79e2c9 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -77,17 +77,6 @@ public abstract class BaseFragment extends Fragment { protected void initListeners() { } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - protected final int resolveResourceIdFromAttr(@AttrRes int attr) { - TypedArray a = activity.getTheme().obtainStyledAttributes(new int[]{attr}); - int attributeResourceId = a.getResourceId(0, 0); - a.recycle(); - return attributeResourceId; - } - /*////////////////////////////////////////////////////////////////////////// // DisplayImageOptions default configurations //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index f5a43fa63..9e8f3fa76 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -30,7 +30,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; @@ -50,17 +49,18 @@ import org.schabi.newpipe.database.history.dao.WatchHistoryDAO; import org.schabi.newpipe.database.history.model.HistoryEntry; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; import org.schabi.newpipe.database.history.model.WatchHistoryEntry; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.BackPressable; +import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; import org.schabi.newpipe.history.HistoryListener; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; import org.schabi.newpipe.util.ThemeHelper; @@ -73,7 +73,8 @@ import io.reactivex.subjects.PublishSubject; public class MainActivity extends AppCompatActivity implements HistoryListener { private static final String TAG = "MainActivity"; - public static final boolean DEBUG = false; + public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); + private SharedPreferences sharedPreferences; private ActionBarDrawerToggle toggle = null; @@ -83,9 +84,11 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { @Override protected void onCreate(Bundle savedInstanceState) { - if (DEBUG) - Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); - ThemeHelper.setTheme(this); + if (DEBUG) Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); + + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); + super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); @@ -93,62 +96,51 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { initFragments(); } + setSupportActionBar(findViewById(R.id.toolbar)); + setupDrawer(); + initHistory(); + } + + private void setupDrawer() { final Toolbar toolbar = findViewById(R.id.toolbar); final DrawerLayout drawer = findViewById(R.id.drawer_layout); final NavigationView drawerItems = findViewById(R.id.navigation); - setSupportActionBar(toolbar); - drawerItems.getMenu().getItem(NewPipe.getIdOfService(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("service", "YouTube"))).setChecked(true); + //drawerItems.setItemIconTintList(null); // Set null to use the original icon + drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); if (!BuildConfig.BUILD_TYPE.equals("release")) { toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close); toggle.syncState(); drawer.addDrawerListener(toggle); + drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { + private int lastService; - drawerItems.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { - public boolean onNavigationItemSelected(@NonNull MenuItem item) { - drawerItems.getMenu().getItem(NewPipe.getIdOfService(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("service", "YouTube"))).setChecked(false); - SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putString("service", item.getTitle().toString()); - editor.apply(); - drawer.closeDrawers(); - drawerItems.getMenu().getItem(NewPipe.getIdOfService(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("service", "YouTube"))).setChecked(true); - return true; + @Override + public void onDrawerOpened(View drawerView) { + lastService = ServiceHelper.getSelectedServiceId(MainActivity.this); } + + @Override + public void onDrawerClosed(View drawerView) { + if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) { + new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate); + } + } + }); + + drawerItems.setNavigationItemSelectedListener(item -> { + if (item.getGroupId() == R.id.menu_services_group) { + drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false); + ServiceHelper.setSelectedServiceId(this, item.getTitle().toString()); + drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); + } + drawer.closeDrawers(); + return true; }); } else { drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); } - - getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { - @Override - public void onBackStackChanged() { - if (getSupportFragmentManager().getBackStackEntryCount() > 1) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - toolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - onBackPressed(); - } - }); - } else { - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - if (toggle != null) { - toggle.syncState(); - toolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - drawer.openDrawer(GravityCompat.START); - } - }); - } - } - } - }); - - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - initHistory(); } @Override @@ -171,20 +163,14 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { sharedPreferences.edit().putBoolean(Constants.KEY_THEME_CHANGE, false).apply(); // https://stackoverflow.com/questions/10844112/runtimeexception-performing-pause-of-activity-that-is-not-resumed // Briefly, let the activity resume properly posting the recreate call to end of the message queue - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - MainActivity.this.recreate(); - } - }); + new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate); } - if(sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) { + if (sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) { if (DEBUG) Log.d(TAG, "main page has changed, recreating main fragment..."); sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply(); NavigationHelper.openMainActivity(this); } - } @Override @@ -218,6 +204,38 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { } else super.onBackPressed(); } + /** + * Implement the following diagram behavior for the up button: + *
+     *              +---------------+
+     *              |  Main Screen  +----+
+     *              +-------+-------+    |
+     *                      |            |
+     *                      ▲ Up         | Search Button
+     *                      |            |
+     *                 +----+-----+      |
+     *    +------------+  Search  |◄-----+
+     *    |            +----+-----+
+     *    |   Open          |
+     *    |  something      ▲ Up
+     *    |                 |
+     *    |    +------------+-------------+
+     *    |    |                          |
+     *    |    |  Video    <->  Channel   |
+     *    +---►|  Channel  <->  Playlist  |
+     *         |  Video    <->  ....      |
+     *         |                          |
+     *         +--------------------------+
+     * 
+ */ + private void onHomeButtonPressed() { + // If search fragment wasn't found in the backstack... + if (!NavigationHelper.tryGotoSearchFragment(getSupportFragmentManager())) { + // ...go to the main fragment + NavigationHelper.gotoMainFragment(getSupportFragmentManager()); + } + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -243,6 +261,9 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(false); } + + updateDrawerNavigation(); + return true; } @@ -253,7 +274,7 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { switch (id) { case android.R.id.home: - NavigationHelper.gotoMainFragment(getSupportFragmentManager()); + onHomeButtonPressed(); return true; case R.id.action_settings: NavigationHelper.openSettings(this); @@ -287,6 +308,27 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { // Utils //////////////////////////////////////////////////////////////////////////*/ + private void updateDrawerNavigation() { + if (getSupportActionBar() == null) return; + + final Toolbar toolbar = findViewById(R.id.toolbar); + final DrawerLayout drawer = findViewById(R.id.drawer_layout); + + final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); + if (fragment instanceof MainFragment) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + if (toggle != null) { + toggle.syncState(); + toolbar.setNavigationOnClickListener(v -> drawer.openDrawer(GravityCompat.START)); + drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNDEFINED); + } + } else { + drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + toolbar.setNavigationOnClickListener(v -> onHomeButtonPressed()); + } + } + private void handleIntent(Intent intent) { if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 41e557b52..8aaa248dd 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -3,14 +3,25 @@ package org.schabi.newpipe; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; import android.widget.Toast; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.NavigationHelper; import java.util.Collection; import java.util.HashSet; -/** +import icepick.Icepick; +import icepick.State; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +/* * Copyright (C) Christian Schabesberger 2017 * RouterActivity.java is part of NewPipe. * @@ -34,23 +45,71 @@ import java.util.HashSet; */ public class RouterActivity extends AppCompatActivity { + @State + protected String currentUrl; + protected CompositeDisposable disposables = new CompositeDisposable(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Icepick.restoreInstanceState(this, savedInstanceState); - String videoUrl = getUrl(getIntent()); - handleUrl(videoUrl); + if (TextUtils.isEmpty(currentUrl)) { + currentUrl = getUrl(getIntent()); + + if (TextUtils.isEmpty(currentUrl)) { + Toast.makeText(this, R.string.invalid_url_toast, Toast.LENGTH_LONG).show(); + finish(); + } + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Icepick.saveInstanceState(this, outState); + } + + @Override + protected void onStart() { + super.onStart(); + handleUrl(currentUrl); } protected void handleUrl(String url) { - boolean success = NavigationHelper.openByLink(this, url); - if (!success) { + disposables.add(Observable + .fromCallable(() -> NavigationHelper.getIntentByLink(this, url)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(intent -> { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + + finish(); + }, this::handleError) + ); + } + + protected void handleError(Throwable error) { + error.printStackTrace(); + + if (error instanceof ExtractionException) { Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); + } else { + ExtractorHelper.handleGeneralException(this, -1, null, error, UserAction.SOMETHING_ELSE, null); } finish(); } + @Override + protected void onDestroy() { + super.onDestroy(); + + disposables.clear(); + } + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -71,7 +130,8 @@ public class RouterActivity extends AppCompatActivity { } else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) { //this means that vidoe was called through share menu String extraText = intent.getStringExtra(Intent.EXTRA_TEXT); - videoUrl = getUris(extraText)[0]; + final String[] uris = getUris(extraText); + videoUrl = uris.length > 0 ? uris[0] : null; } return videoUrl; diff --git a/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java new file mode 100644 index 000000000..7196e413d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/RouterPlayerActivity.java @@ -0,0 +1,413 @@ +package org.schabi.newpipe; + +import android.app.IntentService; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.preference.PreferenceManager; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Toast; + +import org.schabi.newpipe.extractor.Info; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.StreamingService.LinkType; +import org.schabi.newpipe.extractor.channel.ChannelInfo; +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.player.helper.PlayerHelper; +import org.schabi.newpipe.playlist.ChannelPlayQueue; +import org.schabi.newpipe.playlist.PlayQueue; +import org.schabi.newpipe.playlist.PlaylistPlayQueue; +import org.schabi.newpipe.playlist.SinglePlayQueue; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.PermissionHelper; +import org.schabi.newpipe.util.ThemeHelper; + +import java.io.Serializable; +import java.util.Arrays; + +import icepick.State; +import io.reactivex.Observable; +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; + +import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; + +/** + * Get the url from the intent and open it in the chosen preferred player + */ +public class RouterPlayerActivity extends RouterActivity { + + @State + protected int currentServiceId = -1; + private StreamingService currentService; + @State + protected LinkType currentLinkType; + @State + protected int selectedRadioPosition = -1; + protected int selectedPreviously = -1; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { + super.onCreate(savedInstanceState, persistentState); + setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); + } + + @Override + protected void handleUrl(String url) { + disposables.add(Observable + .fromCallable(() -> { + if (currentServiceId == -1) { + currentService = NewPipe.getServiceByUrl(url); + currentServiceId = currentService.getServiceId(); + currentLinkType = currentService.getLinkTypeByUrl(url); + currentUrl = NavigationHelper.getCleanUrl(currentService, url, currentLinkType); + } else { + currentService = NewPipe.getService(currentServiceId); + } + + return currentLinkType != LinkType.NONE; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> { + if (result) { + onSuccess(); + } else { + onError(); + } + }, this::handleError)); + } + + protected void onError() { + Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); + finish(); + } + + protected void onSuccess() { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); + boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); + + if ((isExtAudioEnabled || isExtVideoEnabled) && currentLinkType != LinkType.STREAM) { + Toast.makeText(this, R.string.external_player_unsupported_link_type, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + // TODO: Add some sort of "capabilities" field to services (audio only, video and audio, etc.) + if (currentService == ServiceList.SoundCloud.getService()) { + handleChoice(getString(R.string.background_player_key)); + return; + } + + final String playerChoiceKey = preferences.getString(getString(R.string.preferred_player_key), getString(R.string.preferred_player_default)); + final String alwaysAskKey = getString(R.string.always_ask_player_key); + + if (playerChoiceKey.equals(alwaysAskKey)) { + showDialog(); + } else { + handleChoice(playerChoiceKey); + } + } + + private void showDialog() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(this, + ThemeHelper.isLightThemeSelected(this) ? R.style.LightTheme : R.style.DarkTheme); + + LayoutInflater inflater = LayoutInflater.from(themeWrapper); + final LinearLayout rootLayout = (LinearLayout) inflater.inflate(R.layout.preferred_player_dialog_view, null, false); + final RadioGroup radioGroup = rootLayout.findViewById(android.R.id.list); + + final AdapterChoiceItem[] choices = { + new AdapterChoiceItem(getString(R.string.video_player_key), getString(R.string.video_player), + resolveResourceIdFromAttr(themeWrapper, R.attr.play)), + new AdapterChoiceItem(getString(R.string.background_player_key), getString(R.string.background_player), + resolveResourceIdFromAttr(themeWrapper, R.attr.audio)), + new AdapterChoiceItem(getString(R.string.popup_player_key), getString(R.string.popup_player), + resolveResourceIdFromAttr(themeWrapper, R.attr.popup)) + }; + + final DialogInterface.OnClickListener dialogButtonsClickListener = (dialog, which) -> { + final int indexOfChild = radioGroup.indexOfChild(radioGroup.findViewById(radioGroup.getCheckedRadioButtonId())); + final AdapterChoiceItem choice = choices[indexOfChild]; + + handleChoice(choice.key); + + if (which == DialogInterface.BUTTON_POSITIVE) { + preferences.edit().putString(getString(R.string.preferred_player_key), choice.key).apply(); + } + }; + + final AlertDialog alertDialog = new AlertDialog.Builder(themeWrapper) + .setTitle(R.string.preferred_player_share_menu_title) + .setView(radioGroup) + .setCancelable(true) + .setNegativeButton(R.string.just_once, dialogButtonsClickListener) + .setPositiveButton(R.string.always, dialogButtonsClickListener) + .setOnDismissListener((dialog) -> finish()) + .create(); + + alertDialog.setOnShowListener(dialog -> { + setDialogButtonsState(alertDialog, radioGroup.getCheckedRadioButtonId() != -1); + }); + + radioGroup.setOnCheckedChangeListener((group, checkedId) -> setDialogButtonsState(alertDialog, true)); + final View.OnClickListener radioButtonsClickListener = v -> { + final int indexOfChild = radioGroup.indexOfChild(v); + if (indexOfChild == -1) return; + + selectedPreviously = selectedRadioPosition; + selectedRadioPosition = indexOfChild; + + if (selectedPreviously == selectedRadioPosition) { + handleChoice(choices[selectedRadioPosition].key); + } + }; + + int id = 12345; + for (AdapterChoiceItem item : choices) { + final RadioButton radioButton = (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null); + radioButton.setText(item.description); + radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0); + radioButton.setChecked(false); + radioButton.setId(id++); + radioButton.setLayoutParams(new RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + radioButton.setOnClickListener(radioButtonsClickListener); + radioGroup.addView(radioButton); + } + + if (selectedRadioPosition == -1) { + final String lastSelectedPlayer = preferences.getString(getString(R.string.preferred_player_last_selected_key), null); + if (!TextUtils.isEmpty(lastSelectedPlayer)) { + for (int i = 0; i < choices.length; i++) { + AdapterChoiceItem c = choices[i]; + if (lastSelectedPlayer.equals(c.key)) { + selectedRadioPosition = i; + break; + } + } + } + } + + selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.length - 1); + if (selectedRadioPosition != -1) { + ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true); + } + selectedPreviously = selectedRadioPosition; + + alertDialog.show(); + } + + private void setDialogButtonsState(AlertDialog dialog, boolean state) { + final Button negativeButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); + final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (negativeButton == null || positiveButton == null) return; + + negativeButton.setEnabled(state); + positiveButton.setEnabled(state); + } + + private void handleChoice(final String playerChoiceKey) { + if (Arrays.asList(getResources().getStringArray(R.array.preferred_player_values_list)).contains(playerChoiceKey)) { + PreferenceManager.getDefaultSharedPreferences(this).edit() + .putString(getString(R.string.preferred_player_last_selected_key), playerChoiceKey).apply(); + } + + if (playerChoiceKey.equals(getString(R.string.popup_player_key)) && !PermissionHelper.isPopupEnabled(this)) { + PermissionHelper.showPopupEnablementToast(this); + finish(); + return; + } + + final Intent intent = new Intent(this, FetcherService.class); + intent.putExtra(FetcherService.KEY_CHOICE, new Choice(currentService.getServiceId(), currentLinkType, currentUrl, playerChoiceKey)); + startService(intent); + + finish(); + } + + private static class AdapterChoiceItem { + final String description, key; + @DrawableRes + final int icon; + + AdapterChoiceItem(String key, String description, int icon) { + this.description = description; + this.key = key; + this.icon = icon; + } + } + + private static class Choice implements Serializable { + final int serviceId; + final String url, playerChoice; + final LinkType linkType; + + Choice(int serviceId, LinkType linkType, String url, String playerChoice) { + this.serviceId = serviceId; + this.linkType = linkType; + this.url = url; + this.playerChoice = playerChoice; + } + + @Override + public String toString() { + return serviceId + ":" + url + " > " + linkType + " ::: " + playerChoice; + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Service Fetcher + //////////////////////////////////////////////////////////////////////////*/ + + public static class FetcherService extends IntentService { + + private static final int ID = 456; + public static final String KEY_CHOICE = "key_choice"; + private Disposable fetcher; + + public FetcherService() { + super(FetcherService.class.getSimpleName()); + } + + @Override + public void onCreate() { + super.onCreate(); + startForeground(ID, createNotification().build()); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + if (intent == null) return; + + final Serializable serializable = intent.getSerializableExtra(KEY_CHOICE); + if (!(serializable instanceof Choice)) return; + Choice playerChoice = (Choice) serializable; + handleChoice(playerChoice); + } + + public void handleChoice(Choice choice) { + Single single = null; + UserAction userAction = UserAction.SOMETHING_ELSE; + + switch (choice.linkType) { + case STREAM: + single = ExtractorHelper.getStreamInfo(choice.serviceId, choice.url, false); + userAction = UserAction.REQUESTED_STREAM; + break; + case CHANNEL: + single = ExtractorHelper.getChannelInfo(choice.serviceId, choice.url, false); + userAction = UserAction.REQUESTED_CHANNEL; + break; + case PLAYLIST: + single = ExtractorHelper.getPlaylistInfo(choice.serviceId, choice.url, false); + userAction = UserAction.REQUESTED_PLAYLIST; + break; + } + + + if (single != null) { + final UserAction finalUserAction = userAction; + final Consumer resultHandler = getResultHandler(choice); + fetcher = single + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(info -> { + resultHandler.accept(info); + if (fetcher != null) fetcher.dispose(); + }, throwable -> ExtractorHelper.handleGeneralException(this, + choice.serviceId, choice.url, throwable, finalUserAction, ", opened with " + choice.playerChoice)); + } + } + + public Consumer getResultHandler(Choice choice) { + return info -> { + final String videoPlayerKey = getString(R.string.video_player_key); + final String backgroundPlayerKey = getString(R.string.background_player_key); + final String popupPlayerKey = getString(R.string.popup_player_key); + + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); + boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); + boolean useOldVideoPlayer = PlayerHelper.isUsingOldPlayer(this); + + PlayQueue playQueue; + String playerChoice = choice.playerChoice; + + if (info instanceof StreamInfo) { + if (playerChoice.equals(backgroundPlayerKey) && isExtAudioEnabled) { + NavigationHelper.playOnExternalAudioPlayer(this, (StreamInfo) info); + + } else if (playerChoice.equals(videoPlayerKey) && isExtVideoEnabled) { + NavigationHelper.playOnExternalVideoPlayer(this, (StreamInfo) info); + + } else if (playerChoice.equals(videoPlayerKey) && useOldVideoPlayer) { + NavigationHelper.playOnOldVideoPlayer(this, (StreamInfo) info); + + } else { + playQueue = new SinglePlayQueue((StreamInfo) info); + + if (playerChoice.equals(videoPlayerKey)) { + NavigationHelper.playOnMainPlayer(this, playQueue); + } else if (playerChoice.equals(backgroundPlayerKey)) { + NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); + } else if (playerChoice.equals(popupPlayerKey)) { + NavigationHelper.enqueueOnPopupPlayer(this, playQueue, true); + } + } + } + + if (info instanceof ChannelInfo || info instanceof PlaylistInfo) { + playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); + + if (playerChoice.equals(videoPlayerKey)) { + NavigationHelper.playOnMainPlayer(this, playQueue); + } else if (playerChoice.equals(backgroundPlayerKey)) { + NavigationHelper.playOnBackgroundPlayer(this, playQueue); + } else if (playerChoice.equals(popupPlayerKey)) { + NavigationHelper.playOnPopupPlayer(this, playQueue); + } + } + }; + } + + @Override + public void onDestroy() { + super.onDestroy(); + stopForeground(true); + if (fetcher != null) fetcher.dispose(); + } + + private NotificationCompat.Builder createNotification() { + return new NotificationCompat.Builder(this, getString(R.string.notification_channel_id)) + .setOngoing(true) + .setSmallIcon(R.drawable.ic_newpipe_triangle_white) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContentTitle(getString(R.string.preferred_player_fetcher_notification_title)) + .setContentText(getString(R.string.preferred_player_fetcher_notification_message)); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java b/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java deleted file mode 100644 index fe0b6e907..000000000 --- a/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.schabi.newpipe; - -import android.content.Intent; -import android.os.Build; -import android.widget.Toast; - -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.player.PopupVideoPlayer; -import org.schabi.newpipe.util.Constants; -import org.schabi.newpipe.util.PermissionHelper; - -/** - * Get the url from the intent and open a popup player - */ -public class RouterPopupActivity extends RouterActivity { - - @Override - protected void handleUrl(String url) { - if (!PermissionHelper.isPopupEnabled(this)) { - PermissionHelper.showPopupEnablementToast(this); - finish(); - return; - } - StreamingService service; - try { - service = NewPipe.getServiceByUrl(url); - } catch (ExtractionException e) { - Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); - return; - } - - Intent callIntent = new Intent(this, PopupVideoPlayer.class); - switch (service.getLinkTypeByUrl(url)) { - case STREAM: - break; - default: - Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); - return; - } - - callIntent.putExtra(Constants.KEY_URL, url); - callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId()); - startService(callIntent); - - finish(); - } -} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 645049078..a75c8561f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -19,7 +19,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; @@ -247,6 +246,12 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC // Utils //////////////////////////////////////////////////////////////////////////*/ + public void setTitle(String title) { + if (DEBUG) Log.d(TAG, "setTitle() called with: title = [" + title + "]"); + if (activity != null && activity.getSupportActionBar() != null) { + activity.getSupportActionBar().setTitle(title); + } + } protected void openUrlInBrowser(String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java index be3422706..3a8c7569c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -23,6 +23,7 @@ import android.view.ViewGroup; import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.kiosk.KioskList; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; @@ -33,23 +34,20 @@ import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener { - private ViewPager viewPager; - private boolean showBlankTab = false; public int currentServiceId = -1; - + private ViewPager viewPager; /*////////////////////////////////////////////////////////////////////////// // Constants //////////////////////////////////////////////////////////////////////////*/ - - private static final int FALLBACK_SERVICE_ID = 0; // Youtube - private static final String FALLBACK_CHANNEL_URL = - "https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ"; + private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getId(); + private static final String FALLBACK_CHANNEL_URL = "https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ"; private static final String FALLBACK_CHANNEL_NAME = "Music"; private static final String FALLBACK_KIOSK_ID = "Trending"; private static final int KIOSK_MENU_OFFSET = 2000; @@ -66,8 +64,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - currentServiceId = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getString(getString(R.string.current_service_key), "0")); + currentServiceId = ServiceHelper.getSelectedServiceId(activity); return inflater.inflate(R.layout.fragment_main, container, false); } @@ -85,22 +82,10 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte tabLayout.setupWithViewPager(viewPager); - int channelIcon; - int whatsHotIcon; + int channelIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_channel); + int whatsHotIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_hot); - if (ThemeHelper.isLightThemeSelected(getActivity())) { - tabLayout.setBackgroundColor(getResources().getColor(R.color.light_youtube_primary_color)); - channelIcon = R.drawable.ic_channel_black_24dp; - whatsHotIcon = R.drawable.ic_whatshot_black_24dp; - } else { - channelIcon = R.drawable.ic_channel_white_24dp; - whatsHotIcon = R.drawable.ic_whatshot_white_24dp; - } - - - if (PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key)) - .equals(getString(R.string.subscription_page_key))) { + if (isSubscriptionsPageOnlySelected()) { tabLayout.getTabAt(0).setIcon(channelIcon); } else { tabLayout.getTabAt(0).setIcon(whatsHotIcon); @@ -138,7 +123,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_search: - NavigationHelper.openSearchFragment(getFragmentManager(), NewPipe.getIdOfService(PreferenceManager.getDefaultSharedPreferences(getActivity()).getString("service", "YouTube")), ""); + NavigationHelper.openSearchFragment(getFragmentManager(), ServiceHelper.getSelectedServiceId(activity), ""); return true; } return super.onOptionsItemSelected(item); @@ -163,11 +148,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte private class PagerAdapter extends FragmentPagerAdapter { - private int[] tabTitles = new int[]{ - R.string.tab_main, - R.string.tab_subscriptions - }; - PagerAdapter(FragmentManager fm) { super(fm); } @@ -176,13 +156,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte public Fragment getItem(int position) { switch (position) { case 0: - if(PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key)) - .equals(getString(R.string.subscription_page_key))) { - return new SubscriptionFragment(); - } else { - return getMainPageFragment(); - } + return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment(); case 1: return new SubscriptionFragment(); default: @@ -198,13 +172,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public int getCount() { - if(PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key)) - .equals(getString(R.string.subscription_page_key))) { - return 1; - } else { - return 2; - } + return isSubscriptionsPageOnlySelected() ? 1 : 2; } } @@ -212,28 +180,33 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte // Main page content //////////////////////////////////////////////////////////////////////////*/ + private boolean isSubscriptionsPageOnlySelected() { + return PreferenceManager.getDefaultSharedPreferences(activity) + .getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key)) + .equals(getString(R.string.subscription_page_key)); + } + private Fragment getMainPageFragment() { try { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); final String setMainPage = preferences.getString(getString(R.string.main_page_content_key), - getString(R.string.main_page_selectd_kiosk_id)); - if(setMainPage.equals(getString(R.string.blank_page_key))) { + getString(R.string.main_page_selectd_kiosk_id)); + if (setMainPage.equals(getString(R.string.blank_page_key))) { return new BlankFragment(); - } else if(setMainPage.equals(getString(R.string.kiosk_page_key))) { + } else if (setMainPage.equals(getString(R.string.kiosk_page_key))) { int serviceId = preferences.getInt(getString(R.string.main_page_selected_service), FALLBACK_SERVICE_ID); String kioskId = preferences.getString(getString(R.string.main_page_selectd_kiosk_id), FALLBACK_KIOSK_ID); - KioskFragment fragment = KioskFragment.getInstance(serviceId, kioskId - ); + KioskFragment fragment = KioskFragment.getInstance(serviceId, kioskId); fragment.useAsFrontPage(true); return fragment; - } else if(setMainPage.equals(getString(R.string.feed_page_key))) { + } else if (setMainPage.equals(getString(R.string.feed_page_key))) { FeedFragment fragment = new FeedFragment(); fragment.useAsFrontPage(true); return fragment; - } else if(setMainPage.equals(getString(R.string.channel_page_key))) { + } else if (setMainPage.equals(getString(R.string.channel_page_key))) { int serviceId = preferences.getInt(getString(R.string.main_page_selected_service), FALLBACK_SERVICE_ID); String url = preferences.getString(getString(R.string.main_page_selected_channel_url), @@ -266,7 +239,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte StreamingService service = NewPipe.getService(currentServiceId); KioskList kl = service.getKioskList(); int i = 0; - for(final String ks : kl.getAvailableKiosks()) { + for (final String ks : kl.getAvailableKiosks()) { menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE, KioskTranslator.getTranslatedKioskName(ks, getContext())) .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 665a8f7f9..c7b61eceb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -13,7 +13,6 @@ import android.support.annotation.DrawableRes; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; -import android.support.v4.text.TextUtilsCompat; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; @@ -25,7 +24,6 @@ import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -37,7 +35,6 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.PopupMenu; import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.TextView; @@ -51,7 +48,6 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.download.DownloadDialog; import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ParsingException; @@ -80,6 +76,7 @@ import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; +import org.schabi.newpipe.util.ThemeHelper; import java.io.Serializable; import java.util.ArrayList; @@ -394,7 +391,7 @@ public class VideoDetailFragment extends BaseStateFragment implement if (relatedStreamsView.getChildCount() > initialCount) { relatedStreamsView.removeViews(initialCount, relatedStreamsView.getChildCount() - (initialCount)); - relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand))); + relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); return; } @@ -404,7 +401,7 @@ public class VideoDetailFragment extends BaseStateFragment implement //Log.d(TAG, "i = " + i); relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item)); } - relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.collapse))); + relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); } /*////////////////////////////////////////////////////////////////////////// @@ -579,7 +576,7 @@ public class VideoDetailFragment extends BaseStateFragment implement relatedStreamRootLayout.setVisibility(View.VISIBLE); relatedStreamExpandButton.setVisibility(View.VISIBLE); - relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, resolveResourceIdFromAttr(R.attr.expand))); + relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable(activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); } else { if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE); relatedStreamExpandButton.setVisibility(View.GONE); @@ -807,7 +804,7 @@ public class VideoDetailFragment extends BaseStateFragment implement if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 16) { openNormalBackgroundPlayer(append); } else { - openExternalBackgroundPlayer(audioStream); + NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), audioStream); } } @@ -841,13 +838,12 @@ public class VideoDetailFragment extends BaseStateFragment implement } if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(this.getString(R.string.use_external_video_player_key), false)) { - openExternalVideoPlayer(selectedVideoStream); + NavigationHelper.playOnExternalPlayer(activity, currentInfo.getName(), currentInfo.getUploaderName(), selectedVideoStream); } else { openNormalPlayer(selectedVideoStream); } } - private void openNormalBackgroundPlayer(final boolean append) { final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); if (append) { @@ -857,40 +853,6 @@ public class VideoDetailFragment extends BaseStateFragment implement } } - private void openExternalBackgroundPlayer(AudioStream audioStream) { - Intent intent; - intent = new Intent(); - try { - intent.setAction(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(audioStream.getUrl()), audioStream.getFormat().getMimeType()); - intent.putExtra(Intent.EXTRA_TITLE, currentInfo.getName()); - intent.putExtra("title", currentInfo.getName()); - activity.startActivity(intent); - } catch (Exception e) { - e.printStackTrace(); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setMessage(R.string.no_player_found) - .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url))); - activity.startActivity(intent); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Log.i(TAG, "You unlocked a secret unicorn."); - } - }); - builder.create().show(); - Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:"); - e.printStackTrace(); - } - } - private void openNormalPlayer(VideoStream selectedVideoStream) { Intent mIntent; boolean useOldPlayer = PlayerHelper.isUsingOldPlayer(activity) || (Build.VERSION.SDK_INT < 16); @@ -909,33 +871,6 @@ public class VideoDetailFragment extends BaseStateFragment implement startActivity(mIntent); } - private void openExternalVideoPlayer(VideoStream selectedVideoStream) { - // External Player - Intent intent = new Intent(); - try { - intent.setAction(Intent.ACTION_VIEW) - .setDataAndType(Uri.parse(selectedVideoStream.getUrl()), selectedVideoStream.getFormat().getMimeType()) - .putExtra(Intent.EXTRA_TITLE, currentInfo.getName()) - .putExtra("title", currentInfo.getName()); - this.startActivity(intent); - } catch (Exception e) { - e.printStackTrace(); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setMessage(R.string.no_player_found) - .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent() - .setAction(Intent.ACTION_VIEW) - .setData(Uri.parse(getString(R.string.fdroid_vlc_url))); - startActivity(intent); - } - }) - .setNegativeButton(R.string.cancel, null); - builder.create().show(); - } - } - /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -1212,4 +1147,4 @@ public class VideoDetailFragment extends BaseStateFragment implement showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema); } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 57e77d97a..8a26d81aa 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -84,17 +84,6 @@ public abstract class BaseListInfoFragment extends BaseListF currentNextItemsUrl = (String) savedObjects.poll(); } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - public void setTitle(String title) { - Log.d(TAG, "setTitle() called with: title = [" + title + "]"); - if (activity.getSupportActionBar() != null) { - activity.getSupportActionBar().setTitle(title); - } - } - /*////////////////////////////////////////////////////////////////////////// // Load and handle //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 0cc5cabf3..1b24a5dce 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -43,7 +43,6 @@ import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.PermissionHelper; import java.util.List; import java.util.concurrent.TimeUnit; @@ -102,11 +101,7 @@ public class ChannelFragment extends BaseListInfoFragment { if(activity != null && useAsFrontPage && isVisibleToUser) { - try { - activity.getSupportActionBar().setTitle(currentInfo.getName()); - } catch (Exception e) { - onError(e); - } + setTitle(currentInfo != null ? currentInfo.getName() : name); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java index 424d28276..830471b73 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -57,6 +57,7 @@ public class KioskFragment extends BaseListInfoFragment { @State protected String kioskId = ""; + protected String kioskTranslatedName; /*////////////////////////////////////////////////////////////////////////// // Views @@ -87,16 +88,11 @@ public class KioskFragment extends BaseListInfoFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onActivityCreated(Bundle savedState) { - super.onActivityCreated(savedState); - try { - activity.getSupportActionBar() - .setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity())); - } catch (Exception e) { - onUnrecoverableError(e, UserAction.UI_ERROR, - "none", - "none", R.string.app_ui_crash); - } + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + kioskTranslatedName = KioskTranslator.getTranslatedKioskName(kioskId, activity); + name = kioskTranslatedName; } @Override @@ -104,8 +100,7 @@ public class KioskFragment extends BaseListInfoFragment { super.setUserVisibleHint(isVisibleToUser); if(useAsFrontPage && isVisibleToUser && activity != null) { try { - activity.getSupportActionBar() - .setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity())); + setTitle(kioskTranslatedName); } catch (Exception e) { onUnrecoverableError(e, UserAction.UI_ERROR, "none", @@ -115,11 +110,8 @@ public class KioskFragment extends BaseListInfoFragment { } @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_kiosk, container, false); - activity.getSupportActionBar() - .setTitle(KioskTranslator.getTranslatedKioskName(kioskId, getActivity())); - return view; + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_kiosk, container, false); } /*////////////////////////////////////////////////////////////////////////// @@ -171,9 +163,8 @@ public class KioskFragment extends BaseListInfoFragment { public void handleResult(@NonNull final KioskInfo result) { super.handleResult(result); - String title = KioskTranslator.getTranslatedKioskName(result.id, getActivity()); - ActionBar supportActionBar = activity.getSupportActionBar(); - supportActionBar.setTitle(title); + name = kioskTranslatedName; + setTitle(kioskTranslatedName); if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java index de711f9ac..761b50d85 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayerActivity.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.view.MenuItem; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.PermissionHelper; import static org.schabi.newpipe.player.BackgroundPlayer.ACTION_CLOSE; @@ -48,6 +49,12 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity { @Override public boolean onPlayerOptionSelected(MenuItem item) { if (item.getItemId() == R.id.action_switch_popup) { + + if (!PermissionHelper.isPopupEnabled(this)) { + PermissionHelper.showPopupEnablementToast(this); + return true; + } + this.player.setRecovery(); getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); getApplicationContext().startService(getSwitchIntent(PopupVideoPlayer.class)); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 427c97741..55a73d484 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -111,6 +111,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen public static final String PLAYBACK_QUALITY = "playback_quality"; public static final String PLAY_QUEUE = "play_queue"; public static final String APPEND_ONLY = "append_only"; + public static final String SELECT_ON_APPEND = "select_on_append"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -218,7 +219,13 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen // Resolve append intents if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) { + int sizeBeforeAppend = playQueue.size(); playQueue.append(queue.getStreams()); + + if (intent.getBooleanExtra(SELECT_ON_APPEND, false) && queue.getStreams().size() > 0) { + playQueue.setIndex(sizeBeforeAppend); + } + return; } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 2ec0dc14f..8081dcad7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Color; import android.media.AudioManager; import android.os.Build; @@ -65,8 +64,6 @@ import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PopupMenuIconHacker; import org.schabi.newpipe.util.ThemeHelper; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.List; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -400,9 +397,8 @@ public final class MainVideoPlayer extends Activity { if (DEBUG) Log.d(TAG, "onFullScreenButtonClicked() called"); if (simpleExoPlayer == null) return; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && !PermissionHelper.checkSystemAlertWindowPermission(MainVideoPlayer.this)) { - Toast.makeText(MainVideoPlayer.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show(); + if (!PermissionHelper.isPopupEnabled(context)) { + PermissionHelper.showPopupEnablementToast(context); return; } diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 0f0d8d785..c3803f0d5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -31,7 +31,6 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.os.Build; -import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.annotation.NonNull; @@ -49,20 +48,12 @@ import android.widget.PopupMenu; import android.widget.RemoteViews; import android.widget.SeekBar; import android.widget.TextView; -import android.widget.Toast; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import org.schabi.newpipe.BuildConfig; -import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.ReCaptchaActivity; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; -import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.player.event.PlayerEventListener; @@ -70,22 +61,12 @@ import org.schabi.newpipe.player.helper.LockManager; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.playlist.PlayQueueItem; -import org.schabi.newpipe.playlist.SinglePlayQueue; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.report.UserAction; -import org.schabi.newpipe.util.Constants; -import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; -import java.io.IOException; import java.util.List; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - import static org.schabi.newpipe.player.helper.PlayerHelper.isUsingOldPlayer; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -125,8 +106,8 @@ public final class PopupVideoPlayer extends Service { private RemoteViews notRemoteView; private VideoPlayerImpl playerImpl; - private Disposable currentWorker; private LockManager lockManager; + /*////////////////////////////////////////////////////////////////////////// // Service-Activity Binder //////////////////////////////////////////////////////////////////////////*/ @@ -157,21 +138,8 @@ public final class PopupVideoPlayer extends Service { if (playerImpl.getPlayer() == null) initPopup(); if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true); - if (intent != null && intent.getStringExtra(Constants.KEY_URL) != null) { - final int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0); - final String url = intent.getStringExtra(Constants.KEY_URL); + playerImpl.handleIntent(intent); - playerImpl.setStartedFromNewPipe(false); - - final FetcherHandler fetcherRunnable = new FetcherHandler(this, serviceId, url); - currentWorker = ExtractorHelper.getStreamInfo(serviceId,url,false) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(fetcherRunnable::onReceive, fetcherRunnable::onError); - } else { - playerImpl.setStartedFromNewPipe(true); - playerImpl.handleIntent(intent); - } return START_NOT_STICKY; } @@ -302,7 +270,6 @@ public final class PopupVideoPlayer extends Service { } if (lockManager != null) lockManager.releaseWifiAndCpu(); if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); - if (currentWorker != null) currentWorker.dispose(); mBinder = null; playerImpl = null; @@ -452,7 +419,6 @@ public final class PopupVideoPlayer extends Service { this.getPlaybackPitch(), this.getPlaybackQuality() ); - if (!isStartedFromNewPipe()) intent.putExtra(VideoPlayer.STARTED_FROM_NEWPIPE, false); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } else { intent = new Intent(PopupVideoPlayer.this, PlayVideoActivity.class) @@ -862,63 +828,4 @@ public final class PopupVideoPlayer extends Service { return true; } } - - /** - * Fetcher handler used if open by a link out of NewPipe - */ - private class FetcherHandler { - private final int serviceId; - private final String url; - - private final Context context; - private final Handler mainHandler; - - private FetcherHandler(Context context, int serviceId, String url) { - this.mainHandler = new Handler(PopupVideoPlayer.this.getMainLooper()); - this.context = context; - this.url = url; - this.serviceId = serviceId; - } - - private void onReceive(final StreamInfo info) { - mainHandler.post(() -> { - final Intent intent = NavigationHelper.getPlayerIntent(getApplicationContext(), - PopupVideoPlayer.class, new SinglePlayQueue(info)); - playerImpl.handleIntent(intent); - }); - } - - private void onError(final Throwable exception) { - if (DEBUG) Log.d(TAG, "onError() called with: exception = [" + exception + "]"); - exception.printStackTrace(); - mainHandler.post(() -> { - if (exception instanceof ReCaptchaException) { - onReCaptchaException(); - } else if (exception instanceof IOException) { - Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); - } else if (exception instanceof YoutubeStreamExtractor.GemaException) { - Toast.makeText(context, R.string.blocked_by_gema, Toast.LENGTH_LONG).show(); - } else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) { - Toast.makeText(context, R.string.live_streams_not_supported, Toast.LENGTH_LONG).show(); - } else if (exception instanceof ContentNotAvailableException) { - Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); - } else { - int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error : - exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error; - ErrorActivity.reportError(mainHandler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(UserAction.REQUESTED_STREAM, NewPipe.getNameOfService(serviceId), url, errorId)); - } - }); - stopSelf(); - } - - private void onReCaptchaException() { - Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); - // Starting ReCaptcha Challenge Activity - Intent intent = new Intent(context, ReCaptchaActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - stopSelf(); - } - } - } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 58c117017..4165dc087 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -183,7 +183,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity this.player.getPlaybackSpeed(), this.player.getPlaybackPitch(), null - ); + ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } //////////////////////////////////////////////////////////////////////////// // Service Connection diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index a3c8b53dc..5399ff047 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -39,7 +39,6 @@ import android.view.Menu; import android.view.MenuItem; import android.view.SurfaceView; import android.view.View; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.ProgressBar; @@ -85,12 +84,6 @@ public abstract class VideoPlayer extends BasePlayer public static final boolean DEBUG = BasePlayer.DEBUG; public final String TAG; - /*////////////////////////////////////////////////////////////////////////// - // Intent - //////////////////////////////////////////////////////////////////////////*/ - - public static final String STARTED_FROM_NEWPIPE = "started_from_newpipe"; - /*////////////////////////////////////////////////////////////////////////// // Player //////////////////////////////////////////////////////////////////////////*/ @@ -102,7 +95,6 @@ public abstract class VideoPlayer extends BasePlayer protected String playbackQuality; - private boolean startedFromNewPipe = true; protected boolean wasPlaying = false; /*////////////////////////////////////////////////////////////////////////// @@ -695,14 +687,6 @@ public abstract class VideoPlayer extends BasePlayer return availableStreams.get(selectedStreamIndex); } - public boolean isStartedFromNewPipe() { - return startedFromNewPipe; - } - - public void setStartedFromNewPipe(boolean startedFromNewPipe) { - this.startedFromNewPipe = startedFromNewPipe; - } - public Handler getControlsVisibilityHandler() { return controlsVisibilityHandler; } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java index 3c615608c..d3e31982a 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java @@ -15,6 +15,10 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue el, final Class returnActivity, final View rootView, final ErrorInfo errorInfo) { - handler.post(new Runnable() { - @Override - public void run() { - reportError(context, el, returnActivity, rootView, errorInfo); - } - }); + handler.post(() -> reportError(context, el, returnActivity, rootView, errorInfo)); } public static void reportError(final Context context, final CrashReportData report, final ErrorInfo errorInfo) { @@ -218,17 +214,13 @@ public class ErrorActivity extends AppCompatActivity { addGuruMeditaion(); currentTimeStamp = getCurrentTimeStamp(); - reportButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { + reportButton.setOnClickListener((View v) -> { + Intent i = new Intent(Intent.ACTION_SENDTO); + i.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS)) + .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) + .putExtra(Intent.EXTRA_TEXT, buildJson()); - Intent intent = new Intent(Intent.ACTION_SENDTO); - intent.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS)) - .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) - .putExtra(Intent.EXTRA_TEXT, buildJson()); - - startActivity(Intent.createChooser(intent, "Send Email")); - } + startActivity(Intent.createChooser(i, "Send Email")); }); reportButton.setEnabled(false); diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 2cda95987..4161f96c1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -1,9 +1,14 @@ package org.schabi.newpipe.settings; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; +import android.util.Log; +import android.widget.Toast; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; @@ -12,91 +17,208 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; +import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.util.ZipHelper; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import javax.annotation.Nonnull; 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; + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + 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"); + addPreferencesFromResource(R.xml.content_settings); final ListPreference mainPageContentPref = (ListPreference) findPreference(getString(R.string.main_page_content_key)); - mainPageContentPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValueO) { - final String newValue = newValueO.toString(); + mainPageContentPref.setOnPreferenceChangeListener((Preference preference, Object newValueO) -> { + final String newValue = newValueO.toString(); - final String mainPrefOldValue = - defaultPreferences.getString(getString(R.string.main_page_content_key), "blank_page"); - final String mainPrefOldSummary = getMainPagePrefSummery(mainPrefOldValue, mainPageContentPref); + final String mainPrefOldValue = + defaultPreferences.getString(getString(R.string.main_page_content_key), "blank_page"); + final String mainPrefOldSummary = getMainPagePrefSummery(mainPrefOldValue, mainPageContentPref); - if(newValue.equals(getString(R.string.kiosk_page_key))) { - SelectKioskFragment selectKioskFragment = new SelectKioskFragment(); - selectKioskFragment.setOnSelectedLisener(new SelectKioskFragment.OnSelectedLisener() { - @Override - public void onKioskSelected(String kioskId, int service_id) { - defaultPreferences.edit() - .putInt(getString(R.string.main_page_selected_service), service_id).apply(); - defaultPreferences.edit() - .putString(getString(R.string.main_page_selectd_kiosk_id), kioskId).apply(); - String serviceName = ""; - try { - serviceName = NewPipe.getService(service_id).getServiceInfo().name; - } catch (ExtractionException e) { - onError(e); - } - String kioskName = KioskTranslator.getTranslatedKioskName(kioskId, - getContext()); - - String summary = - String.format(getString(R.string.service_kiosk_string), - serviceName, - kioskName); - - mainPageContentPref.setSummary(summary); - } - }); - selectKioskFragment.setOnCancelListener(new SelectKioskFragment.OnCancelListener() { - @Override - public void onCancel() { - mainPageContentPref.setSummary(mainPrefOldSummary); - mainPageContentPref.setValue(mainPrefOldValue); - } - }); - selectKioskFragment.show(getFragmentManager(), "select_kiosk"); - } else if(newValue.equals(getString(R.string.channel_page_key))) { - SelectChannelFragment selectChannelFragment = new SelectChannelFragment(); - selectChannelFragment.setOnSelectedLisener(new SelectChannelFragment.OnSelectedLisener() { - @Override - public void onChannelSelected(String url, String name, int service) { - defaultPreferences.edit() - .putInt(getString(R.string.main_page_selected_service), service).apply(); - defaultPreferences.edit() - .putString(getString(R.string.main_page_selected_channel_url), url).apply(); - defaultPreferences.edit() - .putString(getString(R.string.main_page_selected_channel_name), name).apply(); - - mainPageContentPref.setSummary(name); - } - }); - selectChannelFragment.setOnCancelListener(new SelectChannelFragment.OnCancelListener() { - @Override - public void onCancel() { - mainPageContentPref.setSummary(mainPrefOldSummary); - mainPageContentPref.setValue(mainPrefOldValue); - } - }); - selectChannelFragment.show(getFragmentManager(), "select_channel"); - } else { - mainPageContentPref.setSummary(getMainPageSummeryByKey(newValue)); - } - - defaultPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); - - return true; + if(newValue.equals(getString(R.string.kiosk_page_key))) { + SelectKioskFragment selectKioskFragment = new SelectKioskFragment(); + selectKioskFragment.setOnSelectedLisener((String kioskId, int service_id) -> { + defaultPreferences.edit() + .putInt(getString(R.string.main_page_selected_service), service_id).apply(); + defaultPreferences.edit() + .putString(getString(R.string.main_page_selectd_kiosk_id), kioskId).apply(); + String serviceName = ""; + try { + serviceName = NewPipe.getService(service_id).getServiceInfo().name; + } catch (ExtractionException e) { + onError(e); } + String kioskName = KioskTranslator.getTranslatedKioskName(kioskId, + getContext()); + + String summary = + String.format(getString(R.string.service_kiosk_string), + serviceName, + kioskName); + + mainPageContentPref.setSummary(summary); }); + selectKioskFragment.setOnCancelListener(() -> { + mainPageContentPref.setSummary(mainPrefOldSummary); + mainPageContentPref.setValue(mainPrefOldValue); + }); + selectKioskFragment.show(getFragmentManager(), "select_kiosk"); + } else if(newValue.equals(getString(R.string.channel_page_key))) { + SelectChannelFragment selectChannelFragment = new SelectChannelFragment(); + selectChannelFragment.setOnSelectedLisener((String url, String name, int service) -> { + defaultPreferences.edit() + .putInt(getString(R.string.main_page_selected_service), service).apply(); + defaultPreferences.edit() + .putString(getString(R.string.main_page_selected_channel_url), url).apply(); + defaultPreferences.edit() + .putString(getString(R.string.main_page_selected_channel_name), name).apply(); + + mainPageContentPref.setSummary(name); + }); + selectChannelFragment.setOnCancelListener(() -> { + mainPageContentPref.setSummary(mainPrefOldSummary); + mainPageContentPref.setValue(mainPrefOldValue); + }); + selectChannelFragment.show(getFragmentManager(), "select_channel"); + } else { + mainPageContentPref.setSummary(getMainPageSummeryByKey(newValue)); + } + + defaultPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); + + return true; + }); + + Preference importDataPreference = findPreference(getString(R.string.import_data)); + importDataPreference.setOnPreferenceClickListener((Preference p) -> { + Intent i = new Intent(getActivity(), FilePickerActivityHelper.class) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, false) + .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_FILE); + startActivityForResult(i, REQUEST_IMPORT_PATH); + return true; + }); + + Preference exportDataPreference = findPreference(getString(R.string.export_data)); + exportDataPreference.setOnPreferenceClickListener((Preference p) -> { + Intent i = new Intent(getActivity(), FilePickerActivityHelper.class) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) + .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR); + startActivityForResult(i, REQUEST_EXPORT_PATH); + return true; + }); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nonnull Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (DEBUG) { + Log.d(TAG, "onActivityResult() called with: requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]"); + } + + if ((requestCode == REQUEST_IMPORT_PATH || requestCode == REQUEST_EXPORT_PATH) + && resultCode == Activity.RESULT_OK) { + String path = data.getData().getPath(); + if (requestCode == REQUEST_EXPORT_PATH) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); + exportDatabase(path + "/NewPipeData-" + sdf.format(new Date()) + ".zip"); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.override_current_data) + .setPositiveButton(android.R.string.ok, + (DialogInterface d, int id) -> importDatabase(path)) + .setNegativeButton(android.R.string.cancel, + (DialogInterface d, int id) -> d.cancel()); + builder.create().show(); + } + } + } + + private void exportDatabase(String path) { + try { + ZipOutputStream outZip = new ZipOutputStream( + new BufferedOutputStream( + new FileOutputStream(path))); + ZipHelper.addFileToZip(outZip, newpipe_db.getPath(), "newpipe.db"); + ZipHelper.addFileToZip(outZip, newpipe_db_journal.getPath(), "newpipe.db-journal"); + + outZip.close(); + + Toast.makeText(getContext(), R.string.export_complete_toast, Toast.LENGTH_SHORT) + .show(); + } catch (Exception e) { + onError(e); + } + } + + private void importDatabase(String filePath) { + // check if file is supported + ZipFile zipFile = null; + try { + zipFile = new ZipFile(filePath); + } catch (IOException ioe) { + Toast.makeText(getContext(), R.string.no_valid_zip_file, Toast.LENGTH_SHORT) + .show(); + return; + } finally { + try { + zipFile.close(); + } catch (Exception e){} + } + + try { + ZipInputStream zipIn = new ZipInputStream( + new BufferedInputStream( + new FileInputStream(filePath))); + + if (!databasesDir.exists() && !databasesDir.mkdir()) { + throw new Exception("Could not create databases dir"); + } + + if(!(ZipHelper.extractFileFromZip(zipIn, newpipe_db.getPath(), "newpipe.db") + && ZipHelper.extractFileFromZip(zipIn, newpipe_db_journal.getPath(), "newpipe.db-journal"))) { + Toast.makeText(getContext(), R.string.could_not_import_all_files, Toast.LENGTH_LONG) + .show(); + } + + zipIn.close(); + + // restart app to properly load db + //App.restart(getContext()); + System.exit(0); + } catch (Exception e) { + onError(e); + } } @Override @@ -117,8 +239,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { getString(R.string.main_page_selected_service), 0)); String kioskName = KioskTranslator.getTranslatedKioskName( - defaultPreferences.getString( - getString(R.string.main_page_selectd_kiosk_id), "Trending"), + defaultPreferences.getString( + getString(R.string.main_page_selectd_kiosk_id), "Trending"), getContext()); String summary = diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index 9a43374a5..9a065d9d8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -46,7 +46,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { Log.d(TAG, "onPreferenceTreeClick() called with: preference = [" + preference + "]"); } - if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { + if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) + || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { Intent i = new Intent(getActivity(), FilePickerActivityHelper.class) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index 9e5420b6e..167b6f31b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -14,21 +14,17 @@ import android.widget.ImageView; import android.widget.TextView; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.subscription.SubscriptionEntity; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.fragments.subscription.SubscriptionService; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.KioskTranslator; -import org.schabi.newpipe.util.ServiceIconMapper; +import org.schabi.newpipe.util.ServiceHelper; import java.util.List; import java.util.Vector; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - /** * Created by Christian Schabesberger on 09.10.17. * SelectKioskFragment.java is part of NewPipe. @@ -125,13 +121,15 @@ public class SelectKioskFragment extends DialogFragment { throws Exception { for(StreamingService service : NewPipe.getServices()) { + //TODO: Multi-service support + if (service.getServiceId() != ServiceList.YouTube.getId()) continue; + for(String kioskId : service.getKioskList().getAvailableKiosks()) { String name = String.format(getString(R.string.service_kiosk_string), service.getServiceInfo().name, KioskTranslator.getTranslatedKioskName(kioskId, getContext())); kioskList.add(new Entry( - //ServiceIconMapper.getIconResource(service.getServiceId()), - ServiceIconMapper.getIconResource(-1), + ServiceHelper.getIcon(service.getServiceId()), service.getServiceId(), kioskId, name)); @@ -140,9 +138,7 @@ public class SelectKioskFragment extends DialogFragment { } public int getItemCount() { - //todo: uncommend this line on multyservice support - //return kioskList.size(); - return 1; + return kioskList.size(); } public SelectKioskItemHolder onCreateViewHolder(ViewGroup parent, int type) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java index bfb19c23b..7d6f8d633 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java @@ -43,7 +43,8 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc @Override protected void onCreate(Bundle savedInstanceBundle) { - ThemeHelper.setTheme(this); + setTheme(ThemeHelper.getSettingsThemeStyle(this)); + super.onCreate(savedInstanceBundle); setContentView(R.layout.settings_layout); @@ -72,7 +73,9 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == android.R.id.home) { - finish(); + if (getSupportFragmentManager().getBackStackEntryCount() == 0) { + finish(); + } else getSupportFragmentManager().popBackStack(); } return true; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 0f1c568e7..8e755b922 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -19,29 +19,38 @@ package org.schabi.newpipe.util; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; import android.util.Log; +import android.widget.Toast; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; +import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchResult; +import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; +import java.io.IOException; import java.io.InterruptedIOException; import java.util.List; -import java.util.concurrent.Callable; import io.reactivex.Maybe; -import io.reactivex.MaybeSource; import io.reactivex.Single; import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; public final class ExtractorHelper { private static final String TAG = ExtractorHelper.class.getSimpleName(); @@ -198,6 +207,37 @@ public final class ExtractorHelper { }); } + /** + * A simple and general error handler that show a Toast for known exceptions, and for others, opens the report error activity with the (optional) error message. + */ + public static void handleGeneralException(Context context, int serviceId, String url, Throwable exception, UserAction userAction, String optionalErrorMessage) { + final Handler handler = new Handler(context.getMainLooper()); + + handler.post(() -> { + if (exception instanceof ReCaptchaException) { + Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); + // Starting ReCaptcha Challenge Activity + Intent intent = new Intent(context, ReCaptchaActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else if (exception instanceof IOException) { + Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show(); + } else if (exception instanceof YoutubeStreamExtractor.GemaException) { + Toast.makeText(context, R.string.blocked_by_gema, Toast.LENGTH_LONG).show(); + } else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) { + Toast.makeText(context, R.string.live_streams_not_supported, Toast.LENGTH_LONG).show(); + } else if (exception instanceof ContentNotAvailableException) { + Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show(); + } else { + int errorId = exception instanceof YoutubeStreamExtractor.DecryptException ? R.string.youtube_signature_decryption_error : + exception instanceof ParsingException ? R.string.parsing_error : R.string.general_error; + ErrorActivity.reportError(handler, context, exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(userAction, + serviceId == -1 ? "none" : NewPipe.getNameOfService(serviceId), url + (optionalErrorMessage == null ? "" : optionalErrorMessage), errorId)); + } + }); + + } + /** * Check if throwable have the cause that can be assignable from the causes to check. * @@ -263,17 +303,4 @@ public final class ExtractorHelper { InterruptedIOException.class, InterruptedException.class); } - - public static String toUpperCase(String value) { - StringBuilder sb = new StringBuilder(value); - for (int index = 0; index < sb.length(); index++) { - char c = sb.charAt(index); - if (Character.isLowerCase(c)) { - sb.setCharAt(index, Character.toUpperCase(c)); - } else { - sb.setCharAt(index, Character.toLowerCase(c)); - } - } - return sb.toString(); - } } diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java index 46c08b01b..0f082cc11 100644 --- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2017 Mauricio Colli * InfoCache.java is part of NewPipe * diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 3a9125bdf..8894af9df 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -9,6 +9,8 @@ import android.os.Build; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; +import android.support.v7.app.AlertDialog; +import android.util.Log; import android.widget.Toast; import com.nostra13.universalimageloader.core.ImageLoader; @@ -21,6 +23,10 @@ import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.stream.AudioStream; +import org.schabi.newpipe.extractor.stream.Stream; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; @@ -36,16 +42,21 @@ import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayerActivity; import org.schabi.newpipe.player.VideoPlayer; +import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.settings.SettingsActivity; +import java.util.ArrayList; + @SuppressWarnings({"unused", "WeakerAccess"}) public class NavigationHelper { public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag"; + public static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag"; /*////////////////////////////////////////////////////////////////////////// // Players //////////////////////////////////////////////////////////////////////////*/ + public static Intent getPlayerIntent(final Context context, final Class targetClazz, final PlayQueue playQueue, @@ -65,9 +76,11 @@ public class NavigationHelper { public static Intent getPlayerEnqueueIntent(final Context context, final Class targetClazz, - final PlayQueue playQueue) { + final PlayQueue playQueue, + final boolean selectOnAppend) { return getPlayerIntent(context, targetClazz, playQueue) - .putExtra(BasePlayer.APPEND_ONLY, true); + .putExtra(BasePlayer.APPEND_ONLY, true) + .putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend); } public static Intent getPlayerIntent(final Context context, @@ -84,16 +97,39 @@ public class NavigationHelper { } public static void playOnMainPlayer(final Context context, final PlayQueue queue) { - context.startActivity(getPlayerIntent(context, MainVideoPlayer.class, queue)); + final Intent playerIntent = getPlayerIntent(context, MainVideoPlayer.class, queue); + playerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(playerIntent); } - public static void playOnPopupPlayer(final Activity activity, final PlayQueue queue) { - if (!PermissionHelper.isPopupEnabled(activity)) { - PermissionHelper.showPopupEnablementToast(activity); + public static void playOnOldVideoPlayer(Context context, StreamInfo info) { + ArrayList videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false)); + int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList); + + if (index == -1) { + Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show(); return; } - Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - activity.startService(getPlayerIntent(activity, PopupVideoPlayer.class, queue)); + + VideoStream videoStream = videoStreamsList.get(index); + Intent intent = new Intent(context, PlayVideoActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(PlayVideoActivity.VIDEO_TITLE, info.getName()) + .putExtra(PlayVideoActivity.STREAM_URL, videoStream.getUrl()) + .putExtra(PlayVideoActivity.VIDEO_URL, info.getUrl()) + .putExtra(PlayVideoActivity.START_POSITION, info.getStartPosition()); + + context.startActivity(intent); + } + + public static void playOnPopupPlayer(final Context context, final PlayQueue queue) { + if (!PermissionHelper.isPopupEnabled(context)) { + PermissionHelper.showPopupEnablementToast(context); + return; + } + + Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerIntent(context, PopupVideoPlayer.class, queue)); } public static void playOnBackgroundPlayer(final Context context, final PlayQueue queue) { @@ -101,19 +137,92 @@ public class NavigationHelper { context.startService(getPlayerIntent(context, BackgroundPlayer.class, queue)); } - public static void enqueueOnPopupPlayer(final Activity activity, final PlayQueue queue) { - if (!PermissionHelper.isPopupEnabled(activity)) { - PermissionHelper.showPopupEnablementToast(activity); + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue) { + enqueueOnPopupPlayer(context, queue, false); + } + + public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) { + if (!PermissionHelper.isPopupEnabled(context)) { + PermissionHelper.showPopupEnablementToast(context); return; } - Toast.makeText(activity, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); - activity.startService(getPlayerEnqueueIntent(activity, PopupVideoPlayer.class, queue)); + + Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerEnqueueIntent(context, PopupVideoPlayer.class, queue, selectOnAppend)); } public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue) { - Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); - context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue)); + enqueueOnBackgroundPlayer(context, queue, false); } + + public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, boolean selectOnAppend) { + Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); + context.startService(getPlayerEnqueueIntent(context, BackgroundPlayer.class, queue, selectOnAppend)); + } + + /*////////////////////////////////////////////////////////////////////////// + // External Players + //////////////////////////////////////////////////////////////////////////*/ + + public static void playOnExternalAudioPlayer(Context context, StreamInfo info) { + final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); + + if (index == -1) { + Toast.makeText(context, R.string.audio_streams_empty, Toast.LENGTH_SHORT).show(); + return; + } + + AudioStream audioStream = info.getAudioStreams().get(index); + playOnExternalPlayer(context, info.getName(), info.getUploaderName(), audioStream); + } + + public static void playOnExternalVideoPlayer(Context context, StreamInfo info) { + ArrayList videoStreamsList = new ArrayList<>(ListHelper.getSortedStreamVideosList(context, info.getVideoStreams(), null, false)); + int index = ListHelper.getDefaultResolutionIndex(context, videoStreamsList); + + if (index == -1) { + Toast.makeText(context, R.string.video_streams_empty, Toast.LENGTH_SHORT).show(); + return; + } + + VideoStream videoStream = videoStreamsList.get(index); + playOnExternalPlayer(context, info.getName(), info.getUploaderName(), videoStream); + } + + public static void playOnExternalPlayer(Context context, String name, String artist, Stream stream) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(stream.getUrl()), stream.getFormat().getMimeType()); + intent.putExtra(Intent.EXTRA_TITLE, name); + intent.putExtra("title", name); + intent.putExtra("artist", artist); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + resolveActivityOrAskToInstall(context, intent); + } + + public static void resolveActivityOrAskToInstall(Context context, Intent intent) { + if (intent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(intent); + } else { + if (context instanceof Activity) { + new AlertDialog.Builder(context) + .setMessage(R.string.no_player_found) + .setPositiveButton(R.string.install, (dialog, which) -> { + Intent i = new Intent(); + i.setAction(Intent.ACTION_VIEW); + i.setData(Uri.parse(context.getString(R.string.fdroid_vlc_url))); + context.startActivity(i); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> Log.i("NavigationHelper", "You unlocked a secret unicorn.")) + .show(); + //Log.e("NavigationHelper", "Either no Streaming player for audio was installed, or something important crashed:"); + } else { + Toast.makeText(context, R.string.no_player_found_toast, Toast.LENGTH_LONG).show(); + } + } + } + /*////////////////////////////////////////////////////////////////////////// // Through FragmentManager //////////////////////////////////////////////////////////////////////////*/ @@ -136,11 +245,21 @@ public class NavigationHelper { .commit(); } + public static boolean tryGotoSearchFragment(FragmentManager fragmentManager) { + if (MainActivity.DEBUG) { + for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) { + Log.d("NavigationHelper", "tryGoToSearchFragment() [" + i + "] = [" + fragmentManager.getBackStackEntryAt(i) + "]"); + } + } + + return fragmentManager.popBackStackImmediate(SEARCH_FRAGMENT_TAG, 0); + } + public static void openSearchFragment(FragmentManager fragmentManager, int serviceId, String query) { fragmentManager.beginTransaction() .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out) .replace(R.id.fragment_holder, SearchFragment.getInstance(serviceId, query)) - .addToBackStack(null) + .addToBackStack(SEARCH_FRAGMENT_TAG) .commit(); } @@ -287,19 +406,6 @@ public class NavigationHelper { // Link handling //////////////////////////////////////////////////////////////////////////*/ - public static boolean openByLink(Context context, String url) { - Intent intentByLink; - try { - intentByLink = getIntentByLink(context, url); - } catch (ExtractionException e) { - return false; - } - intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivity(intentByLink); - return true; - } - private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) { Intent mIntent = new Intent(context, MainActivity.class); mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId); @@ -317,15 +423,14 @@ public class NavigationHelper { throw new ExtractionException("Service not supported at the moment"); } - int serviceId = service.getServiceId(); StreamingService.LinkType linkType = service.getLinkTypeByUrl(url); if (linkType == StreamingService.LinkType.NONE) { - throw new ExtractionException("Url not known to service. service=" + serviceId + " url=" + url); + throw new ExtractionException("Url not known to service. service=" + service + " url=" + url); } url = getCleanUrl(service, url, linkType); - Intent rIntent = getOpenIntent(context, url, serviceId, linkType); + Intent rIntent = getOpenIntent(context, url, service.getServiceId(), linkType); switch (linkType) { case STREAM: @@ -337,7 +442,7 @@ public class NavigationHelper { return rIntent; } - private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException { + public static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException { switch (linkType) { case STREAM: return service.getStreamUrlIdHandler().cleanUrl(dirtyUrl); @@ -351,7 +456,6 @@ public class NavigationHelper { return null; } - private static Uri openMarketUrl(String packageName) { return Uri.parse("market://details") .buildUpon() diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index 7cf804401..a33348934 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -20,7 +20,6 @@ import org.schabi.newpipe.R; public class PermissionHelper { public static final int PERMISSION_WRITE_STORAGE = 778; public static final int PERMISSION_READ_STORAGE = 777; - public static final int PERMISSION_SYSTEM_ALERT_WINDOW = 779; public static boolean checkStoragePermissions(Activity activity) { @@ -80,27 +79,25 @@ public class PermissionHelper { * In order to be able to draw over other apps, the permission android.permission.SYSTEM_ALERT_WINDOW have to be granted. *

* On < API 23 (MarshMallow) the permission was granted when the user installed the application (via AndroidManifest), - * on > 23, however, it have to start a activity asking the user if he agree. + * on > 23, however, it have to start a activity asking the user if he agrees. *

- * This method just return if canDraw over other apps, if it doesn't, try to get the permission, - * it does not get the result of the startActivityForResult, if the user accept, the next time that he tries to open - * it will return true. + * This method just return if the app has permission to draw over other apps, and if it doesn't, it will try to get the permission. * - * @param activity context to startActivityForResult * @return returns {@link Settings#canDrawOverlays(Context)} **/ @RequiresApi(api = Build.VERSION_CODES.M) - public static boolean checkSystemAlertWindowPermission(Activity activity) { - if (!Settings.canDrawOverlays(activity)) { - Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + activity.getPackageName())); - activity.startActivityForResult(i, PERMISSION_SYSTEM_ALERT_WINDOW); + public static boolean checkSystemAlertWindowPermission(Context context) { + if (!Settings.canDrawOverlays(context)) { + Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName())); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); return false; }else return true; } - public static boolean isPopupEnabled(Activity activity) { + public static boolean isPopupEnabled(Context context) { return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || - PermissionHelper.checkSystemAlertWindowPermission(activity); + PermissionHelper.checkSystemAlertWindowPermission(context); } public static void showPopupEnablementToast(Context context) { diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java new file mode 100644 index 000000000..ce1491ba4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -0,0 +1,67 @@ +package org.schabi.newpipe.util; + +import android.content.Context; +import android.preference.PreferenceManager; +import android.support.annotation.DrawableRes; + +import org.schabi.newpipe.BuildConfig; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; + +public class ServiceHelper { + private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube.getService(); + + @DrawableRes + public static int getIcon(int serviceId) { + switch (serviceId) { + case 0: + return R.drawable.place_holder_youtube; + case 1: + return R.drawable.place_holder_circle; + default: + return R.drawable.service; + } + } + + public static int getSelectedServiceId(Context context) { + if (BuildConfig.BUILD_TYPE.equals("release")) return DEFAULT_FALLBACK_SERVICE.getServiceId(); + + final String serviceName = PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.current_service_key), context.getString(R.string.default_service_value)); + + int serviceId; + try { + serviceId = NewPipe.getService(serviceName).getServiceId(); + } catch (ExtractionException e) { + serviceId = DEFAULT_FALLBACK_SERVICE.getServiceId(); + } + + return serviceId; + } + + public static void setSelectedServiceId(Context context, int serviceId) { + String serviceName; + try { + serviceName = NewPipe.getService(serviceId).getServiceInfo().name; + } catch (ExtractionException e) { + serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().name; + } + + setSelectedServicePreferences(context, serviceName); + } + + public static void setSelectedServiceId(Context context, String serviceName) { + int serviceId = NewPipe.getIdOfService(serviceName); + if (serviceId == -1) serviceName = DEFAULT_FALLBACK_SERVICE.getServiceInfo().name; + + setSelectedServicePreferences(context, serviceName); + } + + private static void setSelectedServicePreferences(Context context, String serviceName) { + PreferenceManager.getDefaultSharedPreferences(context).edit(). + putString(context.getString(R.string.current_service_key), serviceName).apply(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceIconMapper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceIconMapper.java deleted file mode 100644 index 060013dd2..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceIconMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.schabi.newpipe.util; - -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.NewPipe; - -/** - * Created by Chrsitian Schabesberger on 09.10.17. - * ServiceIconMapper.java is part of NewPipe. - * - * NewPipe is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * NewPipe is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with NewPipe. If not, see . - */ - -public class ServiceIconMapper { - public static int getIconResource(int service_id) { - switch(service_id) { - case 0: - return R.drawable.youtube; - case 1: - return R.drawable.soud_cloud; - default: - return R.drawable.service; - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 6fdf035f4..b0e00465a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -1,29 +1,38 @@ package org.schabi.newpipe.util; import android.content.Context; +import android.content.res.TypedArray; import android.preference.PreferenceManager; +import android.support.annotation.AttrRes; +import android.support.annotation.StyleRes; import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; public class ThemeHelper { /** * Apply the selected theme (on NewPipe settings) in the context + * with the default style (see {@link #setTheme(Context, int)}). * * @param context context that the theme will be applied */ public static void setTheme(Context context) { - String lightTheme = context.getResources().getString(R.string.light_theme_key); - String darkTheme = context.getResources().getString(R.string.dark_theme_key); - String blackTheme = context.getResources().getString(R.string.black_theme_key); + setTheme(context, -1); + } - String selectedTheme = getSelectedTheme(context); - - if (selectedTheme.equals(lightTheme)) context.setTheme(R.style.LightTheme); - else if (selectedTheme.equals(blackTheme)) context.setTheme(R.style.BlackTheme); - else if (selectedTheme.equals(darkTheme)) context.setTheme(R.style.DarkTheme); - // Fallback - else context.setTheme(R.style.DarkTheme); + /** + * Apply the selected theme (on NewPipe settings) in the context, + * themed according with the styles defined for the service . + * + * @param context context that the theme will be applied + * @param serviceId the theme will be styled to the service with this id, + * pass -1 to get the default style + */ + public static void setTheme(Context context, int serviceId) { + context.setTheme(getThemeForService(context, serviceId)); } /** @@ -35,9 +44,73 @@ public class ThemeHelper { return getSelectedTheme(context).equals(context.getResources().getString(R.string.light_theme_key)); } + @StyleRes + public static int getThemeForService(Context context, int serviceId) { + String lightTheme = context.getResources().getString(R.string.light_theme_key); + String darkTheme = context.getResources().getString(R.string.dark_theme_key); + String blackTheme = context.getResources().getString(R.string.black_theme_key); + + String selectedTheme = getSelectedTheme(context); + + int defaultTheme = R.style.DarkTheme; + if (selectedTheme.equals(lightTheme)) defaultTheme = R.style.LightTheme; + else if (selectedTheme.equals(blackTheme)) defaultTheme = R.style.BlackTheme; + else if (selectedTheme.equals(darkTheme)) defaultTheme = R.style.DarkTheme; + + if (serviceId <= -1) { + return defaultTheme; + } + + final StreamingService service; + try { + service = NewPipe.getService(serviceId); + } catch (ExtractionException ignored) { + return defaultTheme; + } + + String themeName = "DarkTheme"; + if (selectedTheme.equals(lightTheme)) themeName = "LightTheme"; + else if (selectedTheme.equals(blackTheme)) themeName = "BlackTheme"; + else if (selectedTheme.equals(darkTheme)) themeName = "DarkTheme"; + + themeName += "." + service.getServiceInfo().name; + int resourceId = context.getResources().getIdentifier(themeName, "style", context.getPackageName()); + + if (resourceId > 0) { + return resourceId; + } + + return defaultTheme; + } + public static String getSelectedTheme(Context context) { String themeKey = context.getString(R.string.theme_key); String defaultTheme = context.getResources().getString(R.string.default_theme_value); return PreferenceManager.getDefaultSharedPreferences(context).getString(themeKey, defaultTheme); } + + @StyleRes + public static int getSettingsThemeStyle(Context context) { + String lightTheme = context.getResources().getString(R.string.light_theme_key); + String darkTheme = context.getResources().getString(R.string.dark_theme_key); + String blackTheme = context.getResources().getString(R.string.black_theme_key); + + String selectedTheme = getSelectedTheme(context); + + if (selectedTheme.equals(lightTheme)) return R.style.LightSettingsTheme; + else if (selectedTheme.equals(blackTheme)) return R.style.BlackSettingsTheme; + else if (selectedTheme.equals(darkTheme)) return R.style.DarkSettingsTheme; + // Fallback + else return R.style.DarkSettingsTheme; + } + + /** + * Get a resource id from a resource styled according to the the context's theme. + */ + public static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) { + TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); + int attributeResourceId = a.getResourceId(0, 0); + a.recycle(); + return attributeResourceId; + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java new file mode 100644 index 000000000..c3cf3f815 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ZipHelper.java @@ -0,0 +1,94 @@ +package org.schabi.newpipe.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Created by Christian Schabesberger on 28.01.18. + * Copyright 2018 Christian Schabesberger + * ZipHelper.java is part of NewPipe + * + * License: GPL-3.0+ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +public class ZipHelper { + + private static final int BUFFER_SIZE = 2048; + + /** + * This function helps to create zip files. + * Caution this will override the original file. + * @param outZip The ZipOutputStream where the data should be stored in + * @param file The path of the file that should be added to zip. + * @param name The path of the file inside the zip. + * @throws Exception + */ + public static void addFileToZip(ZipOutputStream outZip, String file, String name) throws Exception { + byte data[] = new byte[BUFFER_SIZE]; + FileInputStream fi = new FileInputStream(file); + BufferedInputStream inputStream = new BufferedInputStream(fi, BUFFER_SIZE); + ZipEntry entry = new ZipEntry(name); + outZip.putNextEntry(entry); + int count; + while((count = inputStream.read(data, 0, BUFFER_SIZE)) != -1) { + outZip.write(data, 0, count); + } + inputStream.close(); + } + + /** + * 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 + * @throws Exception + */ + public static boolean extractFileFromZip(ZipInputStream inZip, String file, String name) throws Exception { + byte data[] = new byte[BUFFER_SIZE]; + + boolean found = false; + + ZipEntry ze; + while((ze = inZip.getNextEntry()) != null) { + if(ze.getName().equals(name)) { + found = true; + // delete old file first + File oldFile = new File(file); + if(oldFile.exists()) { + if(!oldFile.delete()) { + throw new Exception("Could not delete " + file); + } + } + + FileOutputStream outFile = new FileOutputStream(file); + int count = 0; + while((count = inZip.read(data)) != -1) { + outFile.write(data, 0, count); + } + + outFile.close(); + inZip.closeEntry(); + } + } + return true; + } +} diff --git a/app/src/main/res/anim/switch_service_in.xml b/app/src/main/res/anim/switch_service_in.xml new file mode 100644 index 000000000..a49d1daba --- /dev/null +++ b/app/src/main/res/anim/switch_service_in.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/anim/switch_service_out.xml b/app/src/main/res/anim/switch_service_out.xml new file mode 100644 index 000000000..635d1630e --- /dev/null +++ b/app/src/main/res/anim/switch_service_out.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png new file mode 100644 index 000000000..e9c288c99 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_arrow_black_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 000000000..57c9fa546 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_arrow_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png new file mode 100644 index 000000000..d78c57bad Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_arrow_black_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 000000000..c61e948bb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_arrow_white_24dp.png differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_circle.png b/app/src/main/res/drawable-nodpi/place_holder_circle.png new file mode 100644 index 000000000..704729e8f Binary files /dev/null and b/app/src/main/res/drawable-nodpi/place_holder_circle.png differ diff --git a/app/src/main/res/drawable-nodpi/place_holder_youtube.png b/app/src/main/res/drawable-nodpi/place_holder_youtube.png new file mode 100644 index 000000000..c4113e005 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/place_holder_youtube.png differ diff --git a/app/src/main/res/drawable-nodpi/soud_cloud.png b/app/src/main/res/drawable-nodpi/soundcloud.png similarity index 100% rename from app/src/main/res/drawable-nodpi/soud_cloud.png rename to app/src/main/res/drawable-nodpi/soundcloud.png diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png new file mode 100644 index 000000000..f208795fc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 000000000..a3c80e73d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png new file mode 100644 index 000000000..5345ee3c4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 000000000..547ef30aa Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png new file mode 100644 index 000000000..d12d49562 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png new file mode 100644 index 000000000..be5c062b5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png differ diff --git a/app/src/main/res/drawable/dark_checked_selector.xml b/app/src/main/res/drawable/dark_checked_selector.xml new file mode 100644 index 000000000..59019470f --- /dev/null +++ b/app/src/main/res/drawable/dark_checked_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/light_checked_selector.xml b/app/src/main/res/drawable/light_checked_selector.xml new file mode 100644 index 000000000..b782a3688 --- /dev/null +++ b/app/src/main/res/drawable/light_checked_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml index b1dd3e20b..abbe69ff7 100644 --- a/app/src/main/res/layout/fragment_main.xml +++ b/app/src/main/res/layout/fragment_main.xml @@ -11,7 +11,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" - android:background="@color/dark_youtube_primary_color" + android:background="?attr/colorPrimary" app:tabGravity="fill"/> + diff --git a/app/src/main/res/layout/preferred_player_dialog_view.xml b/app/src/main/res/layout/preferred_player_dialog_view.xml new file mode 100644 index 000000000..83e1031a5 --- /dev/null +++ b/app/src/main/res/layout/preferred_player_dialog_view.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/drawer_items.xml b/app/src/main/res/menu/drawer_items.xml index 2f82327c3..ae4598edd 100644 --- a/app/src/main/res/menu/drawer_items.xml +++ b/app/src/main/res/menu/drawer_items.xml @@ -1,5 +1,14 @@

- - + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 6508052a7..24edb6043 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -176,13 +176,13 @@ صفر لا تقم با الإختيار (في بعض اللغات) لأنها ليست \"حالة خاصة\" للأندرويد - صفر - واحد - اثنان - قليل - كثير - أخرى - + صفر + %s مشترك + اثنان + قليل + كثير + %s مشتركون + لاتوجد مشاهدات لاتوجد فديوهات @@ -226,13 +226,13 @@ التراخيص واجهة أمامية لليوتوب مجانية مفتوحة المصدر و خفيفة الوزن لنظام التشغيل أندرويد. ساهم - إذا كان لديك أفكار؛ او ترجمة، او تغييرات على التصميم، او تنظيف وتحسين الكود البرمجي ، أو تغييرات ثقيلة على الكود البرمجي، مساعدتك دائما موضع ترحيب. وكلما تم ذلك كلما كان ذلك أفضل! + إذا كانت لديك أفكار؛ أو ترجمة، أو تغييرات تخص التصميم، أو تنظيف و تحسين الشفرة البرمجية ، أو تعديلات عميقة عليها، فتذكر أنّ مساعدتك دائما موضع ترحيب. وكلما أتممنا شيئا كلما كان ذلك أفضل ! عرض على GitHub تبرع يتم تطوير NewPipe من قبل المتطوعين الذين يقضون وقت فراغهم لتقديم أفضل تجربة لك. الآن حان الوقت لإعطاء مرة أخرى للتأكد من المطورين لدينا يمكن أن تجعل NewPipe أكثر و أفضل بينما نتمتع بكوب من جافا! تبرع الموقع - للحصول على مزيد من المعلومات وآخر الأخبار حول NewPipe الرجاء زيارة موقعنا على الانترنت. + للحصول على مزيد من المعلومات و آخر الأخبار حول NewPipe الرجاء زيارة موقعنا على الانترنت. تراخيص NewPipe قراءة الترخيص @@ -266,20 +266,20 @@ اضغط للإدراج بقائمة الانتظار صفر - واحد + %s مشاهدة اثنان قليل كثير - أخرى + %s مشاهدات صفر - واحد + %s فيديو اثنان قليل كثير - أخرى + %s فيديوهات إعادة طلب كلمة التحقق @@ -292,4 +292,18 @@ إدراج بقائمة الانتظار على خلفية إدراج بقائمة الانتظار على المنبثقة ابدأ هنا على خلفية المصدر - +المحتوى الإفتراضي حسب البلد + تغيير الإتجاه + الإنتقال إلى الخلفية + الإنتقال إلى نافذة منبثقة + التحول إلى الرئيسية + + الخدمة + فتح الدرج + إغلاق الدرج + دائمًا + مرة واحدة فقط + + العنوان خاطئ + \@string/preferred_player_settings_title + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 961a6fa47..b4c6c5845 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -172,7 +172,7 @@ otevření ve vyskakovacím okně Zobrazovat návrhy při vyhledávání Historie vyhledávání Hledané výrazy lokálně uchovávat - Historie + Historie sledování Evidovat zhlédnutá videa Přehrávat po přechodu do popředí Pokračovat v přehrávání po přerušení (např. hovor) @@ -298,4 +298,13 @@ otevření ve vyskakovacím okně Daruj Webová stránka Pro další informace a poslední novinky o NewPipe navštivte naši stránku. + Země výchozího obsahu + Služba + Změna orientaci + Na pozadí + Do okna + Přepnout na hlavní + + Otevřít Drawer + Zavřít Drawer diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 283dd1d3a..6c13ca985 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -285,9 +285,9 @@ Kiosk Tipp anzeigen, wenn der Hintergrundwiedergabe- oder Pop-up-Button auf der Videodetailseite gedrückt gehalten wird In der Warteschlange der Hintergrundwiedergabe - Neu und brandheiß + Neu & Heiß Halten zum Hinzufügen zur Warteschleife -‚Gedrückt halten, um Tipp hinzuzufügen‘ anzeigen +\"Gedrückt halten, um Tipp hinzuzufügen\" anzeigen [Unbekannt] In Warteschlange für Hintergrundwiedergabe @@ -300,4 +300,32 @@ Website Um mehr Informationen und die aktuellsten Nachrichten über NewPipe zu bekommen, besuche unsere Website. NewPipe wird von Freiwilligen entwickelt, die ihre Freizeit damit verbringen, dir das beste Erlebnis zu bieten. Jetzt ist es an der Zeit, etwas zurückzugeben, um sicherzustellen, dass unsere Entwickler NewPipe noch besser machen können, während sie eine Tasse Java genießen! - + Service + Kein Streamplayer gefunden (Du kannst VLC installieren, um ihn abzuspielen) + Standard-Land des Inhalts + Immer + Nur einmal + + Ausrichtung umschalten + In den Hintergrund wechseln + Zu Popup wechseln + Zur Hauptseite wechseln + + Externe Player unterstützen diese Art von Links nicht + Ungültige URL + Keine Video-Streams gefunden + Keine Audio-Streams gefunden + + Navigationsleiste öffnen + Navigationsleiste schließen + Mit bevorzugtem Player öffnen + Bevorzugter Player + + Video-Player + Hintergrund-Player + Popup-Player + Immer fragen + + Informationen werden abgerufen… + Der angeforderte Inhalt wird geladen + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 35847fdb3..af9ca97b3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -59,7 +59,7 @@ No se pudo descifrar la URL del vídeo No se pudo analizar el sitio web Mostrar vídeos siguientes y similares - Idioma por defecto del contenido + Idioma del contenido por defecto Vista previa del vídeo Vista previa del vídeo Me gusta @@ -284,7 +284,7 @@ abrir en modo popup Reproductor de fondo Reproductor popup - Remover + Quitar Detalles Ajustes de audio [Desconocido] @@ -294,9 +294,9 @@ abrir en modo popup Comenzar a reproducir aquí Comenzar aquí en segundo plano Comenzar aquí en popup -Desplegar consejo \"Mantener para poner en la fila\" +Mostrar consejo \"Mantener para poner en la cola\" Nuevo y popular - Mantener para poner en la fila + Mantener para poner en la cola Donar NewPipe es desarrollado por voluntarios que emplean su tiempo libre en mejorar tu experiencia. ¡ Ahora puedes devolver el favor para asegurar que los desarrolladores puedan crear un NewPipe aún mejor mientras saborean una taza de Java ! Donar @@ -308,4 +308,27 @@ abrir en modo popup Cambiar a popup Cambiar a principal - + Servicio + Abrir cajón + Cerrar cajón + No se ha encontrado ningún reproductor de streaming (puede instalar VLC para reproducirlo) + Siempre + Sólo una vez + + Los reproductores externos no soportan este tipo de enlaces + URL no válida + No se encontraron transmisiones de vídeo + No se encontraron transmisiones de audio + + \@string/preferred_player_settings_title + Abrir con el reproductor preferido + Reproductor preferido + + Reproductor de vídeo + Reproductor de fondo + Reproductor de popup + Preguntar siempre + + Obteniendo información… + El contenido solicitado se está cargando + diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 11f0ec40c..29ee0a755 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -139,7 +139,7 @@ Hasi Pausatu - Ikusi + Jo Ezabatu Egiaztaketa-batura @@ -179,7 +179,7 @@ NewPipe Lizentzia Ideiak, itzulpenak, diseinu aldaketak, kode garbiketak, kode aldaketa sakonak badituzu, laguntza beti da ongi etorria. Eginaz hobetzen da! Irakurri lizentzia - Parte hartzea + Hartu parte Harpidetu Harpidetuta Kanaletik harpidetza kenduta @@ -244,4 +244,77 @@ Historiala hutsik dago Historiala garbitu da Elementua ezabatuta +Erakutsi \"Mantendu eransteko\" aholkua + Erakutsi aholkua bigarren planoko eta laster-leihoko botoia sakatzean bideoaren xehetasunen orrian + Lehenetsitako edukiaren herrialdea + Zerbitzua + Bigarren planoko erreproduzigailuaren ilaran + Laster-leiho erreproduzigailuaren ilaran + Jo denak + + [Ezezaguna] + + Txandakatu orientazioa + Aldatu bigarren planora + Aldatu laster-leihora + Aldatu nagusira + + Huts egin du jario hau erreproduzitzean + Erreproduzigailuaren errore berreskuraezina gertatu da + Erreproduzigailuaren erroretik berreskuratzen + + Dohaintza + NewPipe bere denbora librea zuri esperientziarik onena ekartzeko ematen duten boluntarioek garatzen dute. Orain zerbait atzera emateko unea da, garatzaileek NewPipe hobetu ahal izan dezaten Javako kafe bat hartzen duten bitartean! + Egin dohaintza + Webgunea + NewPipe aplikazioari buruzko informazio gehiago eta azken berriak jasotzeko bisitatu gure webgunea. + Elementu hau bilaketen historialetik ezabatu nahi duzu? + + Orri nagusiko edukia + Orri hutsa + Kioskoaren orria + Harpidetza orria + Jario-orria + Kanal-orria + Hautatu kanal bat + Ez zara inolako kanalera harpidetu oraindik + Hautatu kiosko bat + + Kioskoa + Joerak + Lehen 50ak + Berria eta arrakastatsua + Bigarren planoko erreproduzigailua + Laster-leiho erreproduzigailua + Kendu + Xehetasunak + Audio ezarpenak + Mantendu ilaran jartzeko + Jarri ilaran bigarren planoan + Jarri ilaran laster-leihoan + Hasi hemen erreproduzitzen + Hasi hemen bigarren planoan + Hasi hemen laster-leihoan + + "Ireki tiradera " + Itxi tiradera + Ez da jarioen erreproduzigailurik aurkitu (VLC instalatu dezakezu) + Beti + Behin besterik ez + + Kanpo erreproduzigailuek ez dituzte mota honetako estekak onartzen + URL baliogabea + Ez da bideo jariorik aurkitu + Ez da audio jariorik aurkitu + + Ireki gogoko erreproduzigailuarekin + Gogoko erreproduzigailua + + Bideo erreproduzigailua + Bigarren planoko erreproduzigailua + Laster-leiho erreproduzigailua + Galdetu beti + + Informazioa eskuratzen… + Eskatutako edukia kargatzen ari da diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f2fb59a5f..e7a459c07 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -5,8 +5,8 @@ Résolution par défaut Vouliez-vous dire : %1$s ? Télécharger - Chemin de téléchargement - Entrer le chemin de téléchargement des vidéos + Chemin de téléchargement vidéo + Entrez le chemin de téléchargement des vidéos Chemin de stockage des vidéos téléchargées Installer L’application Kore est introuvable. L\'installer ? @@ -29,7 +29,7 @@ Télécharger Vidéo suivante Afficher les vidéos suivantes et similaires - URL non pris en charge + Lien non pris en charge Vidéo et audio Autre @@ -37,7 +37,7 @@ Miniature d’aperçu vidéo Je n’aime pas J’aime - Langue par défaut du contenu + Langue du contenu par défaut Miniature de l’avatar de l’utilisateur Utiliser un lecteur vidéo externe Utiliser un lecteur audio externe @@ -53,7 +53,7 @@ Apparence Erreur réseau - Chemin de téléchargement + Chemin de téléchargement audio Chemin de stockage des fichiers audio téléchargés Entrez le chemin de téléchargement des fichiers audio @@ -70,7 +70,7 @@ Direct Impossible de charger toutes les miniatures - Erreur lors du décryptage du lien + Impossible de déchiffrer la signature du lien Impossible d\'analyser complètement le site web Il s\'agit d\'un direct, non supporté pour le moment. Désolé, une erreur inattendue s\'est produite. @@ -109,15 +109,15 @@ OK Nom du fichier - Discussions + Threads Erreur Serveur non supporté Fichier déjà existant - URL malformée ou internet indisponible + Lien malformé ou internet indisponible Téléchargement NewPipe Appuyer pour plus de détails Veuillez patienter… - Copié dans le presse-papier + Copié dans le presse-papiers Sélectionner un dossier de téléchargement disponible Impossible de charger l\'image @@ -136,7 +136,7 @@ Ouvrir en mode fenêtré Mode fenêtré NewPipe - Lire en mode fenêtré + Lecture en mode fenêtré Oui Plus tard Désactivé @@ -153,7 +153,7 @@ Arrière-plan Fenêtre - Résolution par défaut de la fenêtre + Résolution de la fenêtre par défaut Afficher des résolutions plus élevées Certains appareils uniquement supportent la lecture 2K/4K Format vidéo par défaut @@ -212,7 +212,7 @@ Caractère de remplacement Historique de recherche - Sauvegarder les recherches sur l\'appareil + Conserver les recherches sur l\'appareil Historique Historique Recherché @@ -268,17 +268,17 @@ Top 50 Nouveau & populaire Mis en file d\'attente du lecteur en arrière-plan - Mis en file d\'attente du lecteur fenêtré + Mis en file d\'attente du lecteur en fenêtré Tout lire Échec de la lecture de ce flux Une erreur irrécupérable du lecteur s\'est produite Encore aucune chaîne souscrite Lecteur en arrière-plan - Lecteur fenêtré + Lecteur en fenêtré Retirer Détails - Réglages audio + Paramètres audio Afficher l\'aide \"Appui long pour mettre en file d\'attente\" Afficher l\'aide en appuyant sur les boutons \"Arrière-plan\" et \"Fenêtre\" sur la page de détails d\'une vidéo [Inconnu] @@ -291,7 +291,7 @@ Kiosque Appui long pour mettre en file d\'attente Mettre en file d\'attente en arrière-plan - Mettre en file d\'attente du lecteur fenêtré + Mettre en file d\'attente en fenêtré Démarrer ici Démarrer ici en arrière-plan Démarrer ici en fenêtré @@ -301,9 +301,31 @@ Pour obtenir plus d\'informations et les dernières nouvelles à propos de NewPipe, visitez notre site Internet. Donner en retour Pays du contenu par défaut - Orientation + Rotation Basculer en arrière-plan Basculer en fenêtré Basculer en normal + Service + Ouvrir le menu + Fermer le menu + Aucun lecteur de flux trouvé (vous pouvez installer VLC pour le lire) + Toujours + Une seule fois + + Les lecteurs externes ne supportent pas ces types de liens + Lien non valide + Aucun flux vidéo trouvé + Aucun flux audio trouvé + + Ouvrir avec le lecteur préféré + Lecteur préféré + + Lecteur vidéo + Lecteur en arrière-plan + Lecteur en fenêtré + Toujours demander + + Obtention des infos… + Le contenu demandé est en chargement diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c498db814..354c413a9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -313,4 +313,27 @@ Cambia in visualizzazione a comparsa Cambia al menù principale - + Servizio + "Apri il menù " + Chiudi il menù + Nessun riproduttore multimediale trovato (puoi installare VLC per riprodurlo) + Sempre + Solo una volta + + I riproduttori esterni non supportano questa tipologia di collegamenti + URL invalido + Nessun flusso video trovato + Nessun flusso audio trovato + + Riproduttore preferito + Apri con il riproduttore preferito + Riproduttore preferito + + Riproduttore video + Riproduttore di fondo + Riproduttore a comparsa + Chiedi sempre + + Raccogliendo informazioni… + Il contenuto richiesto sta caricando + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 40cd7809b..8d28fa00e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1,17 +1,17 @@ %1$s に公開 - ストリームのプレイヤーが見つかりません。VLC を入手しますか。 + 動画プレイヤーが見つかりません。VLC を入手しますか? 入手 取り消し - ブラウザーで開く + ブラウザで開く 共有 保存 検索 設定 %1$s の間違いではありませんか 共有 - ブラウザーを選択 + ブラウザを選択 回転 動画を保存する場所 動画を保存する場所 @@ -35,8 +35,8 @@ 動画 プレビュー サムネイル 動画 プレビュー サムネイル アップローダー サムネイル - 不適切 - 好ましい + 低評価 + 高評価 外部プレイヤーを使用する 外部プレイヤーを使用する バックグラウンドで再生中 @@ -59,7 +59,7 @@ 保存場所 \'%1$s\' を作成できません 保存場所 \'%1$s\' を作成しました - 異常 + エラー 全てのサムネイルを読み込むことができません 動画のURL署名を復号できませんでした Webサイトを解析できませんでした @@ -273,4 +273,9 @@ 削除 詳細 音声の設定 + 画面を回転 + バックグラウンド再生に変更 + ポップアップ再生に変更 + メイン再生に変更 + diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index e51e71e46..2a2d6dfbb 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -126,12 +126,15 @@ Senas įtaisytas media grotuvas - %s prenumeratorius - %s prenumeratoriai + % prenumeratorius + % prenumeratoriai + % prenumeratorių - Vaizdo įrašai + % vaizdo įrašas + % vaizdo įrašai + % vaizdo įrašų Pradėti @@ -208,7 +211,9 @@ Nėra prenumeratorių Nėra peržiūrų - %a peržiūra + % peržiūra + % peržiūros + % peržiūrų Nėra vaizdo įrašų diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index b10255a93..95c4d63e2 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -296,4 +296,32 @@ Bidra Nettside Mer informasjon og siste nytt om NewPipe er å finne på vår nettside. - + Forvalgt innholdsland + Tjeneste + Veksle skjermretning + Bytt til bakgrunnsmodus + Bytt til oppsprettsmodus + Bytt til hovedmodus + + Åpne skuff + Lukk skuff + Ingen strømmespiller installert (du kan installere VLC for å spille den) + Alltid + Kun én gang + + Eksterne avspillere kan ikke spille lenker av disse typene + Ugyldig nettadresse + Fant ingen videostrømmer + Fant ingen lydstrømmer + + Åpne med foretrukket avspiller + Foretrukket avpsiller + + Videoavspiller + Bakgrunnsavspiller + Oppsprettsavspiller + Spør alltid + + Henter informasjon… + Forespurt innhold innlastes + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e3f320df5..9b06d8888 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -303,4 +303,33 @@ te openen in pop-upmodus Teruggeven Website Bezoek onze website voor meer informatie en het laatste nieuws over NewPipe. - + Standaardinhoudsland + Dienst + Oriëntatie wijzigen + Verplaatsen naar achtergrond + Verplaatsen naar pop-up + Verplaatsen naar normaal + + Menu openen + Menu sluiten + Geen speler met streamondersteuning gevonden (je kan VLC installeren om het af te spelen) + Altijd + Eenmalig + + Externe spelers ondersteunen deze soorten koppelingen niet + Ongeldige URL + Geen videostreams gevonden + Geen audiostreams gevonden + + Voorkeursspeler + Openen met voorkeursspeler + Voorkeursspeler + + Videospeler + Achtergrondspeler + Pop-upspeler + Altijd vragen + + Info ophalen… + De gevraagde inhoud is aan het laden + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c04d557e8..4ed07fbb1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -42,7 +42,7 @@ Jasny Pobrane - Następne video + Następne wideo Pokaż następne i podobne filmy URL nie obsługiwany Preferowany język zawartości @@ -296,4 +296,27 @@ Zacznij Odtwarzanie Tutaj Zacznij Odwarzanie Tutaj — w Tle Zacznij Odtwarzanie Tutaj — w Okienku +Nie znaleziono odtwarzacza strumieniowego (żeby odtworzyć możesz zainstalować VLC) + Domyślny kraj zawartości + Usługa + Zawsze + Tylko raz + + Przełącz orientację + Odtwarzaj w tle + Zewnętrzne odtwarzacze nie obsługują linków tego typu + Nieprawidłowy URL + Nie znaleziono strumieni wideo + Nie znaleziono strumieni audio + + Preferowany odtwarzacz + Otwórz za pomocą preferowanego odtwarzacza + Preferowany odtwarzacz + + Odtwarzacz wideo + Odtwarzacz w tle + Pytaj za każdym razem + + Informacje… + Ładuję żądaną zawartość diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 86add704f..b207ae3ac 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -284,4 +284,30 @@ abrir em modo popup Retribuir Site oficial Para obter mais informações e as últimas notícias sobre o NewPipe visite nosso Site. - + Nenhum reprodudor de stream encontrado (você pode instalar VLC para reproduzir isto) + País do conteúdo padrão + Serviço + Sempre + Apenas esta vez + + Alterar a orientação + Alterar para plano de fundo + Alterar para Popup + Aletar para principal + + Reprodutores externos não suportam estes tipos de links + URL inválida + Nenhum stream de vídeo encontrado + Nenhum stream de áudio encontrado + + Abrir com reprodutor padrão + Reprodutor padrão + + Reprodutor de vídeo + Reprodutor em plano de fundo + Reprodutor popup + Sempre perguntar + + Obtendo informações… + O conteúdo solicitado está carregando + diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index e3a29073a..a7dce00ca 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -76,7 +76,7 @@ Apăsați căutare pentru a începe Redă automat Redă automat un videoclip atunci când NewPipe este deschis din altă aplicație - în direct + În direct Descărcări Descărcări Raport de erori @@ -168,7 +168,7 @@ pentru a deschide în mod pop-up Arată sugestii Arată sugestii în timpul căutării - Pop-up + Popup Filtrează Reîmprospătare Șterge @@ -192,7 +192,7 @@ pentru a deschide în mod pop-up Memorează videourile văzute Reia la câștigarea focalizării Continuă redarea după întreruperi (ex. după apeluri) - Player + "Player " Comportament Istoric Playlist @@ -273,4 +273,7 @@ pentru a deschide în mod pop-up Trenduri Top 50 Tendințe - +Serviciu + Adăugaţi în playlist fundal + Adăugaţi în playlist pop-up + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 93d3628f1..43f07e947 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -270,15 +270,15 @@ Ana sayfanın içeriği Boş Sayfa - Büfe Sayfası + Köşk Sayfası Abonelik Sayfası Besleme Sayfası Kanal Sayfası Kanal seç Henüz abone olunan kanal yok - Büfe seç + Köşk seç - Büfe + Köşk Eğilimler En Üst 50 Yeni ve sıcak @@ -291,11 +291,39 @@ Arka Planda Kuyruğa Al Açılır Pencerede Kuyruğa Al Burayı Oynatmaya Başla - Arka Planda Burayı Başlat - Açılır Pencerede Burayı Başlat + Burayı Arka Planda Başlat + Burayı Açılır Pencerede Başlat Bağış yapın NewPipe, size en iyi deneyimi sunmak için boş zamanını harcayan gönüllüler tarafından geliştirilmektedir. Geliştiricilerimizin bir fincan kahvenin tadını çıkarırken NewPipe\'ı daha da çok iyileştirebilmelerinden emin olmak için karşılığını vermenin tam zamanı! Karşılığını ver Web sitesi NewPipe hakkında daha çok bilgiyi ve son haberleri almak için web sitemize uğrayın. - + Öntanımlı içerik ülkesi + Hizmet + Yönelimi Değiştir + Arka Plana Geç + Açılır Pencereye Geç + Ana Görünüme Geç + + Çekmeceyi Aç + Çekmeceyi Kapat + Akış oynatıcı bulunamadı (oynatmak için VLC\'yi kurabilirsiniz) + Her Zaman + Yalnızca Bir Kez + + Harici oynatıcılar bu türdeki bağlantıları desteklemiyor + Geçersiz URL + Video akışı bulunamadı + Ses akışı bulunamadı + + Yeğlenen oynatıcıyla aç + Yeğlenen oynatıcı + + Video oynatıcı + Arka plan oynatıcı + Açılır pencere oynatıcı + Her zaman sor + + Bilgi alınıyor… + İstenen içerik yükleniyor + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3f9865c47..8d95ff204 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -86,7 +86,7 @@ 錯誤回報 全部 頻道 - + 是的 稍後 已停用 篩選器 @@ -240,4 +240,24 @@ 已清除歷史紀錄 項目已刪除 確定要刪除此項搜尋紀錄嗎? - +沒有找到串流播放器(你可以安裝 VLC播放器 來播放) + 顯示鎖定到附加指引上 + 預設內容國家 + 服務 + 在背景播放器上等候 + 在懸浮視窗播放器上等候 + 全部播放 + 總是 + 僅一次 + + [未知] + + 切換方向 + 切換到背景 + 切換到懸浮視窗 + 切換到主介面 + + 無法播放此串流 + 發生無法復原的播放器錯誤 + 從播放器錯誤中恢復 + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 86b9644ae..61bc5e520 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,5 +1,6 @@ + @@ -22,10 +23,14 @@ + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b77a2d229..21f19fc7b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,13 +1,13 @@ + #CD201F #EEEEEE - #e53935 - #d32f2f - #000000 + #e53935 #32000000 #48868686 + #2a868686 #1fa6a6a6 #5a000000 #ffffff @@ -16,11 +16,10 @@ #222222 - #CD322E - #BC211D - #FFFFFF + #ff5252 #0affffff #48ffffff + #2affffff #1f717171 #82000000 #424242 @@ -29,6 +28,7 @@ #000 + @color/dark_settings_accent_color #1effffff #23454545 diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml new file mode 100644 index 000000000..36a292453 --- /dev/null +++ b/app/src/main/res/values/colors_services.xml @@ -0,0 +1,21 @@ + + + + #e53935 + #d32f2f + #000000 + + #CD322E + #BC211D + #FFFFFF + + + #f57c00 + #ef6c00 + #000000 + + #f57c00 + #ef6c00 + #FFFFFF + + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index 1633e193a..000000000 --- a/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #CD201F - diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index f0c496aa2..372b917e0 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1,7 +1,12 @@ - current_service + + @string/youtube + @string/soundcloud + + service + @string/youtube download_path @@ -46,12 +51,6 @@ 144p - - @string/youtube - @string/soundcloud - - service - @string/youtube video_mp4 video_webm @@ -131,6 +130,8 @@ main_page_selected_channel_name main_page_selected_channel_url main_page_selectd_kiosk_id + import_data + export_data file_rename @@ -149,6 +150,29 @@ @string/charset_most_special_characters_value + + preferred_player_key + @string/always_ask_player_key + preferred_player_last_selected + + video_player + background_player + popup_player + always_ask_player + + + @string/video_player + @string/background_player + @string/popup_player + @string/always_ask_player + + + @string/video_player_key + @string/background_player_key + @string/popup_player_key + @string/always_ask_player_key + + af diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a6014a29..5d05d088d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ %1$s views Published on %1$s No stream player found. Do you want to install VLC? + No stream player found (you can install VLC to play it) Install Cancel https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc @@ -119,6 +120,8 @@ Best resolution Undo Play All + Always + Just Once newpipe NewPipe Notification @@ -131,6 +134,10 @@ Switch to Popup Switch to Main + Import database + Export database + Will override your current history and subscriptions + Export history, subscriptions and playlists. Error Network error @@ -148,6 +155,10 @@ Failed to play this stream Unrecoverable player error occurred Recovering from player error + External players don\'t support these types of links + Invalid URL + No video streams found + No audio streams found Sorry, that should not have happened. @@ -310,6 +321,11 @@ Select a channel No channel subscribed yet Select a kiosk + Export complete + Import complete + No valid Zip file + WARNING: Could not import all files. + This will override your current setup. Kiosk @@ -336,4 +352,17 @@ Close Drawer YouTube SoundCloud + + + @string/preferred_player_settings_title + Open with preferred player + Preferred player + + Video player + Background player + Popup player + Always ask + + Getting info… + "The requested content is loading" diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c0c16e30f..ee526ca41 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,6 +1,15 @@ - + + + + - + + + + - + + + + + + + + + - - - - - - - - - - - - - - - - - - + + diff --git a/app/src/main/res/values/styles_misc.xml b/app/src/main/res/values/styles_misc.xml new file mode 100644 index 000000000..4d177228d --- /dev/null +++ b/app/src/main/res/values/styles_misc.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml new file mode 100644 index 000000000..6ed8c29e9 --- /dev/null +++ b/app/src/main/res/values/styles_services.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 22269eef6..20099d5c0 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -37,4 +37,14 @@ android:key="@string/main_page_content_key" android:title="@string/main_page_content" android:summary="%s"/> + + + + diff --git a/app/src/main/res/xml/video_audio_settings.xml b/app/src/main/res/xml/video_audio_settings.xml index 6685f1a25..ceacfb142 100644 --- a/app/src/main/res/xml/video_audio_settings.xml +++ b/app/src/main/res/xml/video_audio_settings.xml @@ -1,7 +1,6 @@ + +