diff --git a/app/build.gradle b/app/build.gradle index f22492aef..354dbaa0e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,12 +49,13 @@ ext { icepickLibVersion = '3.2.0' stethoLibVersion = '1.5.0' } + dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:1eff8c5708' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:fef71aeccc37' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' @@ -93,6 +94,9 @@ dependencies { debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryLibVersion" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion" + implementation "com.squareup.okhttp3:okhttp:$okHttpLibVersion" debugImplementation "com.facebook.stetho:stetho-okhttp3:$stethoLibVersion" + implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support:cardview-v7:27.1.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 33e0651e5..e4d448184 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -76,10 +76,6 @@ android:name=".about.AboutActivity" android:label="@string/title_activity_about"/> - - @@ -122,6 +118,7 @@ + = Build.VERSION_CODES.LOLLIPOP) { + Window w = getWindow(); + w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + if (getSupportFragmentManager() != null && getSupportFragmentManager().getBackStackEntryCount() == 0) { initFragments(); } setSupportActionBar(findViewById(R.id.toolbar)); - setupDrawer(); + try { + setupDrawer(); + } catch (Exception e) { + ErrorActivity.reportUiError(this, e); + } } - private void setupDrawer() { + private void setupDrawer() throws Exception { final Toolbar toolbar = findViewById(R.id.toolbar); drawer = findViewById(R.id.drawer_layout); drawerItems = findViewById(R.id.navigation); - for(StreamingService s : NewPipe.getServices()) { - final String title = s.getServiceInfo().getName() + - (ServiceHelper.isBeta(s) ? " (beta)" : ""); - final MenuItem item = drawerItems.getMenu() - .add(R.id.menu_services_group, s.getServiceId(), 0, title); - item.setIcon(ServiceHelper.getIcon(s.getServiceId())); + //Tabs + int currentServiceId = ServiceHelper.getSelectedServiceId(this); + StreamingService service = NewPipe.getService(currentServiceId); + + int kioskId = 0; + + for (final String ks : service.getKioskList().getAvailableKiosks()) { + drawerItems.getMenu() + .add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator.getTranslatedKioskName(ks, this)) + .setIcon(KioskTranslator.getKioskIcons(ks, this)); + kioskId ++; } - drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_whats_new) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history)); - toggle = new ActionBarDrawerToggle(this, drawer, toolbar, - R.string.drawer_open, R.string.drawer_close) { - @Override - public void onDrawerClosed(View view) { super.onDrawerClosed(view); } + //Settings and About + drawerItems.getMenu() + .add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings)); + drawerItems.getMenu() + .add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info)); - @Override - public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); } - - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - super.onDrawerSlide(drawerView, 0); - } - }; + toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close); toggle.syncState(); drawer.addDrawerListener(toggle); drawer.addDrawerListener(new DrawerLayout.SimpleDrawerListener() { @@ -133,51 +175,179 @@ public class MainActivity extends AppCompatActivity { @Override public void onDrawerClosed(View drawerView) { + if(servicesShown) { + toggleServices(); + } if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) { new Handler(Looper.getMainLooper()).post(MainActivity.this::recreate); } } }); - drawerItems.setNavigationItemSelectedListener(this::changeService); - - setupDrawerFooter(); + drawerItems.setNavigationItemSelectedListener(this::drawerItemSelected); setupDrawerHeader(); } + private boolean drawerItemSelected(MenuItem item) { + switch (item.getGroupId()) { + case R.id.menu_services_group: + changeService(item); + break; + case R.id.menu_tabs_group: + try { + tabSelected(item); + } catch (Exception e) { + ErrorActivity.reportUiError(this, e); + } + break; + case R.id.menu_options_about_group: + optionsAboutSelected(item); + break; + default: + return false; + } - private boolean changeService(MenuItem item) { - if (item.getGroupId() != R.id.menu_services_group) - return false; - drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false); - ServiceHelper.setSelectedServiceId(this, item.getItemId()); - drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); drawer.closeDrawers(); return true; } - private void setupDrawerFooter() { - ImageButton settings = findViewById(R.id.drawer_settings); - ImageButton downloads = findViewById(R.id.drawer_downloads); - ImageButton history = findViewById(R.id.drawer_history); + private void changeService(MenuItem item) { + drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(false); + ServiceHelper.setSelectedServiceId(this, item.getItemId()); + drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); + } - settings.setOnClickListener(view -> NavigationHelper.openSettings(this)); - downloads.setOnClickListener(view ->NavigationHelper.openDownloads(this)); - history.setOnClickListener(view -> - NavigationHelper.openStatisticFragment(getSupportFragmentManager())); + private void tabSelected(MenuItem item) throws ExtractionException { + switch(item.getItemId()) { + case ITEM_ID_SUBSCRIPTIONS: + NavigationHelper.openSubscriptionFragment(getSupportFragmentManager()); + break; + case ITEM_ID_FEED: + NavigationHelper.openWhatsNewFragment(getSupportFragmentManager()); + break; + case ITEM_ID_BOOKMARKS: + NavigationHelper.openBookmarksFragment(getSupportFragmentManager()); + break; + case ITEM_ID_DOWNLOADS: + NavigationHelper.openDownloads(this); + break; + case ITEM_ID_HISTORY: + NavigationHelper.openStatisticFragment(getSupportFragmentManager()); + break; + default: + int currentServiceId = ServiceHelper.getSelectedServiceId(this); + StreamingService service = NewPipe.getService(currentServiceId); + String serviceName = ""; + + int kioskId = 0; + for (final String ks : service.getKioskList().getAvailableKiosks()) { + if(kioskId == item.getItemId()) { + serviceName = ks; + } + kioskId ++; + } + + NavigationHelper.openKioskFragment(getSupportFragmentManager(), currentServiceId, serviceName); + break; + } + } + + private void optionsAboutSelected(MenuItem item) { + switch(item.getItemId()) { + case ITEM_ID_SETTINGS: + NavigationHelper.openSettings(this); + break; + case ITEM_ID_ABOUT: + NavigationHelper.openAbout(this); + break; + } } private void setupDrawerHeader() { - headerServiceView = findViewById(R.id.drawer_header_service_view); - Button action = findViewById(R.id.drawer_header_action_button); + NavigationView navigationView = findViewById(R.id.navigation); + View hView = navigationView.getHeaderView(0); + + serviceArrow = hView.findViewById(R.id.drawer_arrow); + headerServiceView = hView.findViewById(R.id.drawer_header_service_view); + Button action = hView.findViewById(R.id.drawer_header_action_button); action.setOnClickListener(view -> { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("https://newpipe.schabi.org/blog/")); - startActivity(intent); - drawer.closeDrawers(); + toggleServices(); }); } + private void toggleServices() { + servicesShown = !servicesShown; + + drawerItems.getMenu().removeGroup(R.id.menu_services_group); + drawerItems.getMenu().removeGroup(R.id.menu_tabs_group); + drawerItems.getMenu().removeGroup(R.id.menu_options_about_group); + + if(servicesShown) { + showServices(); + } else { + try { + showTabs(); + } catch (Exception e) { + ErrorActivity.reportUiError(this, e); + } + } + } + + private void showServices() { + serviceArrow.setImageResource(R.drawable.ic_arrow_up_white); + + for(StreamingService s : NewPipe.getServices()) { + final String title = s.getServiceInfo().getName() + + (ServiceHelper.isBeta(s) ? " (beta)" : ""); + + drawerItems.getMenu() + .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) + .setIcon(ServiceHelper.getIcon(s.getServiceId())); + } + drawerItems.getMenu().getItem(ServiceHelper.getSelectedServiceId(this)).setChecked(true); + } + + private void showTabs() throws ExtractionException { + serviceArrow.setImageResource(R.drawable.ic_arrow_down_white); + + //Tabs + int currentServiceId = ServiceHelper.getSelectedServiceId(this); + StreamingService service = NewPipe.getService(currentServiceId); + + int kioskId = 0; + + for (final String ks : service.getKioskList().getAvailableKiosks()) { + drawerItems.getMenu() + .add(R.id.menu_tabs_group, kioskId, ORDER, KioskTranslator.getTranslatedKioskName(ks, this)) + .setIcon(KioskTranslator.getKioskIcons(ks, this)); + kioskId ++; + } + + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_whats_new) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download)); + drawerItems.getMenu() + .add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history)); + + //Settings and About + drawerItems.getMenu() + .add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings)); + drawerItems.getMenu() + .add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about) + .setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info)); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -341,16 +511,13 @@ public class MainActivity extends AppCompatActivity { onHomeButtonPressed(); return true; case R.id.action_show_downloads: - return NavigationHelper.openDownloads(this); + return NavigationHelper.openDownloads(this); case R.id.action_history: - NavigationHelper.openStatisticFragment(getSupportFragmentManager()); - return true; - case R.id.action_about: - NavigationHelper.openAbout(this); - return true; + NavigationHelper.openStatisticFragment(getSupportFragmentManager()); + return true; case R.id.action_settings: - NavigationHelper.openSettings(this); - return true; + NavigationHelper.openSettings(this); + return true; default: return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java b/app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java new file mode 100644 index 000000000..c02ef92eb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/download/ExtSDDownloadFailedActivity.java @@ -0,0 +1,38 @@ +package org.schabi.newpipe.download; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.settings.NewPipeSettings; +import org.schabi.newpipe.util.ServiceHelper; +import org.schabi.newpipe.util.ThemeHelper; + +public class ExtSDDownloadFailedActivity extends AppCompatActivity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this)); + } + + @Override + protected void onStart() { + super.onStart(); + new AlertDialog.Builder(this) + .setTitle(R.string.download_to_sdcard_error_title) + .setMessage(R.string.download_to_sdcard_error_message) + .setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> { + NewPipeSettings.resetDownloadFolders(this); + finish(); + }) + .setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> { + dialogInterface.dismiss(); + finish(); + }) + .create() + .show(); + } +} 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 5707716bf..589d15bd4 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -51,9 +51,6 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC protected Button errorButtonRetry; protected TextView errorTextView; - @State - protected boolean useAsFrontPage = false; - @Override public void onViewCreated(View rootView, Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); @@ -66,9 +63,6 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC wasLoading.set(isLoading.get()); } - public void useAsFrontPage(boolean value) { - useAsFrontPage = value; - } /*////////////////////////////////////////////////////////////////////////// // Init @@ -93,12 +87,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC RxView.clicks(errorButtonRetry) .debounce(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer() { - @Override - public void accept(Object o) throws Exception { - onRetryButtonClicked(); - } - }); + .subscribe(o -> onRetryButtonClicked()); } protected void onRetryButtonClicked() { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java index e81645202..4ee90f083 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BlankFragment.java @@ -14,24 +14,16 @@ public class BlankFragment extends BaseFragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - if(activity != null && activity.getSupportActionBar() != null) { - activity.getSupportActionBar() - .setTitle("NewPipe"); - } + setTitle("NewPipe"); return inflater.inflate(R.layout.fragment_blank, container, false); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - if(isVisibleToUser) { - if(activity != null && activity.getSupportActionBar() != null) { - activity.getSupportActionBar() - .setTitle("NewPipe"); - } - // leave this inline. Will make it harder for copy cats. - // If you are a Copy cat FUCK YOU. - // I WILL FIND YOU, AND I WILL ... - } + setTitle("NewPipe"); + // leave this inline. Will make it harder for copy cats. + // If you are a Copy cat FUCK YOU. + // I WILL FIND YOU, AND I WILL ... } } 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 0e3312403..a920ecfe6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.fragments; +import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.NonNull; @@ -17,20 +18,16 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.SubMenu; import android.view.View; 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; -import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.local.bookmark.BookmarkFragment; +import org.schabi.newpipe.local.feed.FeedFragment; +import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; @@ -39,20 +36,29 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.ThemeHelper; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener { public int currentServiceId = -1; private ViewPager viewPager; + private List tabs = new ArrayList<>(); + static PagerAdapter adapter; + TabLayout tabLayout; + private SharedPreferences prefs; + private Bundle savedInstanceStateBundle; - /*////////////////////////////////////////////////////////////////////////// - // Constants - //////////////////////////////////////////////////////////////////////////*/ + private static final String TAB_NUMBER_BLANK = "0"; + private static final String TAB_NUMBER_KIOSK = "1"; + private static final String TAB_NUMBER_SUBSCIRPTIONS = "2"; + private static final String TAB_NUMBER_FEED = "3"; + private static final String TAB_NUMBER_BOOKMARKS = "4"; + private static final String TAB_NUMBER_HISTORY = "5"; + private static final String TAB_NUMBER_CHANNEL = "6"; - private static final int FALLBACK_SERVICE_ID = ServiceList.YouTube.getServiceId(); - 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; + SharedPreferences.OnSharedPreferenceChangeListener listener; /*////////////////////////////////////////////////////////////////////////// // Fragment's LifeCycle @@ -60,13 +66,23 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public void onCreate(Bundle savedInstanceState) { + savedInstanceStateBundle = savedInstanceState; super.onCreate(savedInstanceState); setHasOptionsMenu(true); + listener = (prefs, key) -> { + if(key.equals("saveUsedTabs")) { + mainPageChanged(); + } + }; } @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { currentServiceId = ServiceHelper.getSelectedServiceId(activity); + + prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + prefs.registerOnSharedPreferenceChangeListener(listener); + return inflater.inflate(R.layout.fragment_main, container, false); } @@ -74,30 +90,116 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte protected void initViews(View rootView, Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - TabLayout tabLayout = rootView.findViewById(R.id.main_tab_layout); + tabLayout = rootView.findViewById(R.id.main_tab_layout); viewPager = rootView.findViewById(R.id.pager); /* Nested fragment, use child fragment here to maintain backstack in view pager. */ - PagerAdapter adapter = new PagerAdapter(getChildFragmentManager()); + adapter = new PagerAdapter(getChildFragmentManager()); viewPager.setAdapter(adapter); - viewPager.setOffscreenPageLimit(adapter.getCount()); tabLayout.setupWithViewPager(viewPager); - int channelIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_channel); - int whatsHotIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_hot); - int bookmarkIcon = ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.ic_bookmark); + mainPageChanged(); + } + + + public void mainPageChanged() { + getTabOrder(); + adapter.notifyDataSetChanged(); + viewPager.setOffscreenPageLimit(adapter.getCount()); + setIcons(); + setFirstTitle(); + } + + private void setFirstTitle() { + if((tabs.size() > 0) + && activity != null) { + String tabInformation = tabs.get(0); + if (tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) { + String kiosk[] = tabInformation.split("\t"); + if (kiosk.length == 3) { + setTitle(kiosk[1]); + } + } else if (tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) { + + String channelInfo[] = tabInformation.split("\t"); + if(channelInfo.length==4) { + setTitle(channelInfo[2]); + } + } else { + switch (tabInformation) { + case TAB_NUMBER_BLANK: + setTitle(getString(R.string.app_name)); + break; + case TAB_NUMBER_SUBSCIRPTIONS: + setTitle(getString(R.string.tab_subscriptions)); + break; + case TAB_NUMBER_FEED: + setTitle(getString(R.string.fragment_whats_new)); + break; + case TAB_NUMBER_BOOKMARKS: + setTitle(getString(R.string.tab_bookmarks)); + break; + case TAB_NUMBER_HISTORY: + setTitle(getString(R.string.title_activity_history)); + break; + } + } + - if (isSubscriptionsPageOnlySelected()) { - tabLayout.getTabAt(0).setIcon(channelIcon); - tabLayout.getTabAt(1).setIcon(bookmarkIcon); - } else { - tabLayout.getTabAt(0).setIcon(whatsHotIcon); - tabLayout.getTabAt(1).setIcon(channelIcon); - tabLayout.getTabAt(2).setIcon(bookmarkIcon); } } + private void setIcons() { + for (int i = 0; i < tabs.size(); i++) { + String tabInformation = tabs.get(i); + + TabLayout.Tab tabToSet = tabLayout.getTabAt(i); + Context c = getContext(); + + if (tabToSet != null && c != null) { + + if (tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) { + String kiosk[] = tabInformation.split("\t"); + if (kiosk.length == 3) { + tabToSet.setIcon(KioskTranslator.getKioskIcons(kiosk[1], getContext())); + } + } else if (tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) { + tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_channel)); + } else { + switch (tabInformation) { + case TAB_NUMBER_BLANK: + tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_hot)); + break; + case TAB_NUMBER_SUBSCIRPTIONS: + tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_channel)); + break; + case TAB_NUMBER_FEED: + tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.rss)); + break; + case TAB_NUMBER_BOOKMARKS: + tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_bookmark)); + break; + case TAB_NUMBER_HISTORY: + tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.history)); + break; + } + } + + } + } + } + + + private void getTabOrder() { + tabs.clear(); + + String save = prefs.getString("saveUsedTabs", "1\tTrending\t0\n2\n4\n"); + String tabsArray[] = save.trim().split("\n"); + + Collections.addAll(tabs, tabsArray); + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -107,16 +209,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte super.onCreateOptionsMenu(menu, inflater); if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); inflater.inflate(R.menu.main_fragment_menu, menu); - SubMenu kioskMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, 200, getString(R.string.kiosk)); - try { - createKioskMenu(kioskMenu, inflater); - } catch (Exception e) { - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); - } ActionBar supportActionBar = activity.getSupportActionBar(); if (supportActionBar != null) { @@ -165,115 +257,77 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public Fragment getItem(int position) { - switch (position) { - case 0: - return isSubscriptionsPageOnlySelected() ? new SubscriptionFragment() : getMainPageFragment(); - case 1: - 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 BookmarkFragment(); - } else { - return new SubscriptionFragment(); + String tabInformation = tabs.get(position); + + if(tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) { + String kiosk[] = tabInformation.split("\t"); + if(kiosk.length==3) { + KioskFragment fragment = null; + try { + fragment = KioskFragment.getInstance(Integer.parseInt(kiosk[2]), kiosk[1]); + fragment.useAsFrontPage(true); + return fragment; + } catch (Exception e) { + ErrorActivity.reportError(activity, e, + activity.getClass(), + null, + ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, + "none", "", R.string.app_ui_crash)); } - case 2: - return new BookmarkFragment(); - default: + } + } else if(tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) { + String channelInfo[] = tabInformation.split("\t"); + if(channelInfo.length==4) { + ChannelFragment fragment = ChannelFragment.getInstance(Integer.parseInt(channelInfo[3]), channelInfo[1], channelInfo[2]); + fragment.useAsFrontPage(true); + return fragment; + } else { return new BlankFragment(); + } + } else { + switch (tabInformation) { + case TAB_NUMBER_BLANK: + return new BlankFragment(); + case TAB_NUMBER_SUBSCIRPTIONS: + SubscriptionFragment sFragment = new SubscriptionFragment(); + sFragment.useAsFrontPage(true); + return sFragment; + case TAB_NUMBER_FEED: + FeedFragment fFragment = new FeedFragment(); + fFragment.useAsFrontPage(true); + return fFragment; + case TAB_NUMBER_BOOKMARKS: + BookmarkFragment bFragment = new BookmarkFragment(); + bFragment.useAsFrontPage(true); + return bFragment; + case TAB_NUMBER_HISTORY: + StatisticsPlaylistFragment cFragment = new StatisticsPlaylistFragment(); + cFragment.useAsFrontPage(true); + return cFragment; + } + } + + return new BlankFragment(); } - } @Override - public CharSequence getPageTitle(int position) { - //return getString(this.tabTitles[position]); - return ""; + public int getItemPosition(Object object) { + // Causes adapter to reload all Fragments when + // notifyDataSetChanged is called + return POSITION_NONE; } @Override public int getCount() { - return isSubscriptionsPageOnlySelected() ? 2 : 3; + return tabs.size(); } - } - /*////////////////////////////////////////////////////////////////////////// - // 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() { - if (getActivity() == null) return new BlankFragment(); - - 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))) { - return new BlankFragment(); - } 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); - fragment.useAsFrontPage(true); - return fragment; - } 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))) { - 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), - FALLBACK_CHANNEL_URL); - String name = preferences.getString(getString(R.string.main_page_selected_channel_name), - FALLBACK_CHANNEL_NAME); - ChannelFragment fragment = ChannelFragment.getInstance(serviceId, - url, - name); - fragment.useAsFrontPage(true); - return fragment; - } else { - return new BlankFragment(); - } - - } catch (Exception e) { - ErrorActivity.reportError(activity, e, - activity.getClass(), - null, - ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, - "none", "", R.string.app_ui_crash)); - return new BlankFragment(); - } - } - - /*////////////////////////////////////////////////////////////////////////// - // Select Kiosk - //////////////////////////////////////////////////////////////////////////*/ - - private void createKioskMenu(Menu menu, MenuInflater menuInflater) - throws Exception { - StreamingService service = NewPipe.getService(currentServiceId); - KioskList kl = service.getKioskList(); - int i = 0; - for (final String ks : kl.getAvailableKiosks()) { - menu.add(0, KIOSK_MENU_OFFSET + i, Menu.NONE, - KioskTranslator.getTranslatedKioskName(ks, getContext())) - .setOnMenuItemClickListener(menuItem -> { - try { - NavigationHelper.openKioskFragment(getFragmentManager(), currentServiceId, ks); - } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); - } - return true; - }); - i++; + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + getFragmentManager() + .beginTransaction() + .remove((Fragment)object) + .commitNowAllowingStateLoss(); } } } 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 d91502cdd..c726f8cee 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 @@ -1227,10 +1227,10 @@ public class VideoDetailFragment spinnerToolbar.setVisibility(View.GONE); break; default: + if(info.getAudioStreams().isEmpty()) detailControlsBackground.setVisibility(View.GONE); if (!info.getVideoStreams().isEmpty() || !info.getVideoOnlyStreams().isEmpty()) break; - detailControlsBackground.setVisibility(View.GONE); detailControlsPopup.setVisibility(View.GONE); spinnerToolbar.setVisibility(View.GONE); thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 1db12bba9..c70ea2b19 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -156,9 +156,7 @@ public abstract class BaseListFragment extends BaseStateFragment implem public void selected(ChannelInfoItem selectedItem) { try { onItemSelected(selectedItem); - NavigationHelper.openChannelFragment(useAsFrontPage ? - getParentFragment().getFragmentManager() - : getFragmentManager(), + NavigationHelper.openChannelFragment(getFM(), selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName()); @@ -173,10 +171,7 @@ public abstract class BaseListFragment extends BaseStateFragment implem public void selected(PlaylistInfoItem selectedItem) { try { onItemSelected(selectedItem); - NavigationHelper.openPlaylistFragment( - useAsFrontPage - ? getParentFragment().getFragmentManager() - : getFragmentManager(), + NavigationHelper.openPlaylistFragment(getFM(), selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName()); @@ -197,9 +192,7 @@ public abstract class BaseListFragment extends BaseStateFragment implem private void onStreamSelected(StreamInfoItem selectedItem) { onItemSelected(selectedItem); - NavigationHelper.openVideoDetailFragment(useAsFrontPage - ? getParentFragment().getFragmentManager() - : getFragmentManager(), + NavigationHelper.openVideoDetailFragment(getFM(), selectedItem.getServiceId(), selectedItem.getUrl(), selectedItem.getName()); } 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 42ba8e0ac..4df5982f7 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 @@ -91,6 +91,8 @@ public class ChannelFragment extends BaseListInfoFragment { private MenuItem menuRssButton; + private boolean mIsVisibleToUser = false; + public static ChannelFragment getInstance(int serviceId, String url, String name) { ChannelFragment instance = new ChannelFragment(); instance.setInitialData(serviceId, url, name); @@ -104,6 +106,7 @@ public class ChannelFragment extends BaseListInfoFragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); + mIsVisibleToUser = isVisibleToUser; if(activity != null && useAsFrontPage && isVisibleToUser) { @@ -166,38 +169,35 @@ public class ChannelFragment extends BaseListInfoFragment { context.getResources().getString(R.string.share) }; - final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0); - switch (i) { - case 0: - NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); - break; - case 1: - NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); - break; - case 2: - NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); - break; - case 3: - NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); - break; - case 4: - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); - break; - case 5: - if (getFragmentManager() != null) { - PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) - .show(getFragmentManager(), TAG); - } - break; - case 6: - shareUrl(item.getName(), item.getUrl()); - break; - default: - break; - } + final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> { + final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0); + switch (i) { + case 0: + NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); + break; + case 1: + NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); + break; + case 2: + NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); + break; + case 3: + NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); + break; + case 4: + NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); + break; + case 5: + if (getFragmentManager() != null) { + PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) + .show(getFragmentManager(), TAG); + } + break; + case 6: + shareUrl(item.getName(), item.getUrl()); + break; + default: + break; } }; @@ -255,12 +255,12 @@ public class ChannelFragment extends BaseListInfoFragment { private static final int BUTTON_DEBOUNCE_INTERVAL = 100; private void monitorSubscription(final ChannelInfo info) { - final Consumer onError = new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { + final Consumer onError = (Throwable throwable) -> { animateView(headerSubscribeButton, false, 100); - showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Get subscription status", 0); - } + showSnackBarError(throwable, UserAction.SUBSCRIPTION, + NewPipe.getNameOfService(currentInfo.getServiceId()), + "Get subscription status", + 0); }; final Observable> observable = subscriptionService.subscriptionTable() @@ -276,50 +276,38 @@ public class ChannelFragment extends BaseListInfoFragment { // so only update the UI for the latest emission ("sync" the subscribe button's state) .debounce(100, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer>() { - @Override - public void accept(List subscriptionEntities) throws Exception { - updateSubscribeButton(!subscriptionEntities.isEmpty()); - } - }, onError)); + .subscribe((List subscriptionEntities) -> + updateSubscribeButton(!subscriptionEntities.isEmpty()) + , onError)); } private Function mapOnSubscribe(final SubscriptionEntity subscription) { - return new Function() { - @Override - public Object apply(@NonNull Object o) throws Exception { - subscriptionService.subscriptionTable().insert(subscription); - return o; - } + return (@NonNull Object o) -> { + subscriptionService.subscriptionTable().insert(subscription); + return o; }; } private Function mapOnUnsubscribe(final SubscriptionEntity subscription) { - return new Function() { - @Override - public Object apply(@NonNull Object o) throws Exception { - subscriptionService.subscriptionTable().delete(subscription); - return o; - } + return (@NonNull Object o) -> { + subscriptionService.subscriptionTable().delete(subscription); + return o; }; } private void updateSubscription(final ChannelInfo info) { if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]"); - final Action onComplete = new Action() { - @Override - public void run() throws Exception { + final Action onComplete = () -> { if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl()); - } }; - final Consumer onError = new Consumer() { - @Override - public void accept(@NonNull Throwable throwable) throws Exception { - onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.getServiceId()), "Updating Subscription for " + info.getUrl(), R.string.subscription_update_failed); - } - }; + final Consumer onError = (@NonNull Throwable throwable) -> + onUnrecoverableError(throwable, + UserAction.SUBSCRIPTION, + NewPipe.getNameOfService(info.getServiceId()), + "Updating Subscription for " + info.getUrl(), + R.string.subscription_update_failed); disposables.add(subscriptionService.updateChannelInfo(info) .subscribeOn(Schedulers.io()) @@ -328,19 +316,16 @@ public class ChannelFragment extends BaseListInfoFragment { } private Disposable monitorSubscribeButton(final Button subscribeButton, final Function action) { - final Consumer onNext = new Consumer() { - @Override - public void accept(@NonNull Object o) throws Exception { + final Consumer onNext = (@NonNull Object o) -> { if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!"); - } }; - final Consumer onError = new Consumer() { - @Override - public void accept(@NonNull Throwable throwable) throws Exception { - onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Subscription Change", R.string.subscription_change_failed); - } - }; + final Consumer onError = (@NonNull Throwable throwable) -> + onUnrecoverableError(throwable, + UserAction.SUBSCRIPTION, + NewPipe.getNameOfService(currentInfo.getServiceId()), + "Subscription Change", + R.string.subscription_change_failed); /* Emit clicks from main thread unto io thread */ return RxView.clicks(subscribeButton) @@ -352,25 +337,25 @@ public class ChannelFragment extends BaseListInfoFragment { } private Consumer> getSubscribeUpdateMonitor(final ChannelInfo info) { - return new Consumer>() { - @Override - public void accept(List subscriptionEntities) throws Exception { - if (DEBUG) - Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]"); - if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); + return (List subscriptionEntities) -> { + if (DEBUG) + Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]"); + if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose(); - if (subscriptionEntities.isEmpty()) { - if (DEBUG) Log.d(TAG, "No subscription to this channel!"); - SubscriptionEntity channel = new SubscriptionEntity(); - channel.setServiceId(info.getServiceId()); - channel.setUrl(info.getUrl()); - channel.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount()); - subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel)); - } else { - if (DEBUG) Log.d(TAG, "Found subscription to this channel!"); - final SubscriptionEntity subscription = subscriptionEntities.get(0); - subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription)); - } + if (subscriptionEntities.isEmpty()) { + if (DEBUG) Log.d(TAG, "No subscription to this channel!"); + SubscriptionEntity channel = new SubscriptionEntity(); + channel.setServiceId(info.getServiceId()); + channel.setUrl(info.getUrl()); + channel.setData(info.getName(), + info.getAvatarUrl(), + info.getDescription(), + info.getSubscriberCount()); + subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel)); + } else { + if (DEBUG) Log.d(TAG, "Found subscription to this channel!"); + final SubscriptionEntity subscription = subscriptionEntities.get(0); + subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription)); } }; } @@ -488,8 +473,11 @@ public class ChannelFragment extends BaseListInfoFragment { super.handleNextItems(result); if (!result.getErrors().isEmpty()) { - showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId), - "Get next page of: " + url, R.string.general_error); + showSnackBarError(result.getErrors(), + UserAction.REQUESTED_CHANNEL, + NewPipe.getNameOfService(serviceId), + "Get next page of: " + url, + R.string.general_error); } } @@ -517,6 +505,6 @@ public class ChannelFragment extends BaseListInfoFragment { @Override public void setTitle(String title) { super.setTitle(title); - headerTitleView.setText(title); + if (!useAsFrontPage) headerTitleView.setText(title); } } 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 114e92e43..833d0f55e 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 { protected String kioskId = ""; protected String kioskTranslatedName; + /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ @@ -167,7 +168,9 @@ public class KioskFragment extends BaseListInfoFragment { super.handleResult(result); name = kioskTranslatedName; - setTitle(kioskTranslatedName); + if(!useAsFrontPage) { + setTitle(kioskTranslatedName); + } if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 411379963..a699e28bc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -365,7 +365,7 @@ public class SearchFragment int itemId = 0; boolean isFirstItem = true; final Context c = getContext(); - for(String filter : service.getSearchQIHFactory().getAvailableContentFilter()) { + for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) { menuItemToFilterName.put(itemId, filter); MenuItem item = menu.add(1, itemId++, @@ -575,8 +575,7 @@ public class SearchFragment .onNext(searchEditText.getText().toString()), throwable -> showSnackBarError(throwable, UserAction.DELETE_FROM_HISTORY, "none", - "Deleting item failed", R.string.general_error) - ); + "Deleting item failed", R.string.general_error)); disposables.add(onDelete); }) .show(); @@ -837,7 +836,10 @@ public class SearchFragment @Override public void handleResult(@NonNull SearchInfo result) { - if (!result.getErrors().isEmpty()) { + final List exceptions = result.getErrors(); + if (!exceptions.isEmpty() + && !(exceptions.size() == 1 + && exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){ showSnackBarError(result.getErrors(), UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchString, 0); } @@ -864,6 +866,7 @@ public class SearchFragment showListFooter(false); currentPageUrl = result.getNextPageUrl(); infoListAdapter.addInfoItemList(result.getItems()); + nextPageUrl = result.getNextPageUrl(); if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), UserAction.SEARCHED, diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index 02eabd9ef..99bd70f5b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -6,7 +6,6 @@ import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentManager; -import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -20,11 +19,9 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistLocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; @@ -69,11 +66,10 @@ public final class BookmarkFragment public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - if (activity != null && activity.getSupportActionBar() != null) { - activity.getSupportActionBar().setDisplayShowTitleEnabled(true); - activity.setTitle(R.string.tab_subscriptions); - } + if(!useAsFrontPage) { + setTitle(activity.getString(R.string.tab_bookmarks)); + } return inflater.inflate(R.layout.fragment_bookmarks, container, false); } @@ -102,26 +98,20 @@ public final class BookmarkFragment itemListAdapter.setSelectedListener(new OnClickGesture() { @Override public void selected(LocalItem selectedItem) { - try { - // Requires the parent fragment to find holder for fragment replacement - if (getParentFragment() == null) return; - final FragmentManager fragmentManager = getParentFragment().getFragmentManager(); + final FragmentManager fragmentManager = getFM(); - if (selectedItem instanceof PlaylistMetadataEntry) { - final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); - NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid, - entry.name); + if (selectedItem instanceof PlaylistMetadataEntry) { + final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem); + NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.uid, + entry.name); - } else if (selectedItem instanceof PlaylistRemoteEntity) { - final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); - NavigationHelper.openPlaylistFragment( - fragmentManager, - entry.getServiceId(), - entry.getUrl(), - entry.getName()); - } - } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + } else if (selectedItem instanceof PlaylistRemoteEntity) { + final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem); + NavigationHelper.openPlaylistFragment( + fragmentManager, + entry.getServiceId(), + entry.getUrl(), + entry.getName()); } } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java index 4937bb094..634bea62b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.java @@ -71,6 +71,10 @@ public class FeedFragment extends BaseListFragment, Voi @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { + + if(!useAsFrontPage) { + setTitle(activity.getString(R.string.fragment_whats_new)); + } return inflater.inflate(R.layout.fragment_feed, container, false); } @@ -105,20 +109,19 @@ public class FeedFragment extends BaseListFragment, Voi super.onDestroyView(); } - /*@Override - protected RecyclerView.LayoutManager getListLayoutManager() { - boolean isPortrait = getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels; - return new GridLayoutManager(activity, isPortrait ? 1 : 2); - }*/ + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (activity != null && isVisibleToUser) { + setTitle(activity.getString(R.string.fragment_whats_new)); + } + } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); ActionBar supportActionBar = activity.getSupportActionBar(); - if (supportActionBar != null) { - supportActionBar.setTitle(R.string.fragment_whats_new); - } if(useAsFrontPage) { supportActionBar.setDisplayShowTitleEnabled(true); @@ -176,19 +179,9 @@ public class FeedFragment extends BaseListFragment, Voi showLoading(); showListFooter(true); subscriptionObserver = subscriptionService.getSubscription() - .onErrorReturnItem(Collections.emptyList()) + .onErrorReturnItem(Collections.emptyList()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer>() { - @Override - public void accept(List subscriptionEntities) throws Exception { - handleResult(subscriptionEntities); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - onError(throwable); - } - }); + .subscribe(this::handleResult, this::onError); } @Override @@ -239,13 +232,12 @@ public class FeedFragment extends BaseListFragment, Voi if (!itemsLoaded.contains(subscriptionEntity.getServiceId() + subscriptionEntity.getUrl())) { subscriptionService.getChannelInfo(subscriptionEntity) .observeOn(AndroidSchedulers.mainThread()) - .onErrorComplete(new Predicate() { - @Override - public boolean test(@io.reactivex.annotations.NonNull Throwable throwable) throws Exception { - return FeedFragment.super.onError(throwable); - } - }) - .subscribe(getChannelInfoObserver(subscriptionEntity.getServiceId(), subscriptionEntity.getUrl())); + .onErrorComplete( + (@io.reactivex.annotations.NonNull Throwable throwable) -> + FeedFragment.super.onError(throwable)) + .subscribe( + getChannelInfoObserver(subscriptionEntity.getServiceId(), + subscriptionEntity.getUrl())); } else { requestFeed(1); } @@ -316,7 +308,10 @@ public class FeedFragment extends BaseListFragment, Voi @Override public void onError(Throwable exception) { - showSnackBarError(exception, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(serviceId), url, 0); + showSnackBarError(exception, + UserAction.SUBSCRIPTION, + NewPipe.getNameOfService(serviceId), + url, 0); requestFeed(1); onDone(); } @@ -361,12 +356,7 @@ public class FeedFragment extends BaseListFragment, Voi delayHandler.removeCallbacksAndMessages(null); // Add a little of a delay when requesting more items because the cache is so fast, // that the view seems stuck to the user when he scroll to the bottom - delayHandler.postDelayed(new Runnable() { - @Override - public void run() { - requestFeed(FEED_LOAD_COUNT); - } - }, 300); + delayHandler.postDelayed(() -> requestFeed(FEED_LOAD_COUNT), 300); } @Override @@ -423,7 +413,9 @@ public class FeedFragment extends BaseListFragment, Voi int heightPixels = getResources().getDisplayMetrics().heightPixels; int itemHeightPixels = activity.getResources().getDimensionPixelSize(R.dimen.video_item_search_height); - int items = itemHeightPixels > 0 ? heightPixels / itemHeightPixels + OFF_SCREEN_ITEMS_COUNT : MIN_ITEMS_INITIAL_LOAD; + int items = itemHeightPixels > 0 + ? heightPixels / itemHeightPixels + OFF_SCREEN_ITEMS_COUNT + : MIN_ITEMS_INITIAL_LOAD; return Math.max(MIN_ITEMS_INITIAL_LOAD, items); } @@ -441,8 +433,14 @@ public class FeedFragment extends BaseListFragment, Voi protected boolean onError(Throwable exception) { if (super.onError(exception)) return true; - int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error; - onUnrecoverableError(exception, UserAction.SOMETHING_ELSE, "none", "Requesting feed", errorId); + int errorId = exception instanceof ExtractionException + ? R.string.parsing_error + : R.string.general_error; + onUnrecoverableError(exception, + UserAction.SOMETHING_ELSE, + "none", + "Requesting feed", + errorId); return true; } } diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index c2c813a4d..95aeb09d7 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -21,6 +21,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -73,7 +74,7 @@ public class StatisticsPlaylistFragment return results; case MOST_PLAYED: Collections.sort(results, (left, right) -> - ((Long) right.watchCount).compareTo(left.watchCount)); + Long.compare(right.watchCount, left.watchCount)); return results; default: return null; } @@ -96,6 +97,14 @@ public class StatisticsPlaylistFragment return inflater.inflate(R.layout.fragment_playlist, container, false); } + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (activity != null && isVisibleToUser) { + setTitle(activity.getString(R.string.title_activity_history)); + } + } + /////////////////////////////////////////////////////////////////////////// // Fragment LifeCycle - Views /////////////////////////////////////////////////////////////////////////// @@ -103,7 +112,9 @@ public class StatisticsPlaylistFragment @Override protected void initViews(View rootView, Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - setTitle(getString(R.string.title_last_played)); + if(!useAsFrontPage) { + setTitle(getString(R.string.title_last_played)); + } } @Override @@ -129,8 +140,10 @@ public class StatisticsPlaylistFragment public void selected(LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { final StreamStatisticsEntry item = (StreamStatisticsEntry) selectedItem; - NavigationHelper.openVideoDetailFragment(getFragmentManager(), - item.serviceId, item.url, item.title); + NavigationHelper.openVideoDetailFragment(getFM(), + item.serviceId, + item.url, + item.title); } } @@ -341,7 +354,7 @@ public class StatisticsPlaylistFragment final Disposable onDelete = recordManager.deleteStreamHistory(entry.streamId) .observeOn(AndroidSchedulers.mainThread()) .subscribe( - howManyDelted -> { + howManyDeleted -> { if(getView() != null) { Snackbar.make(getView(), R.string.one_item_deleted, Snackbar.LENGTH_SHORT).show(); diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java index 7b7f43047..2c24f7f17 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.java @@ -16,6 +16,7 @@ import android.os.Parcelable; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.FragmentManager; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; @@ -212,7 +213,8 @@ public class SubscriptionFragment extends BaseStateFragment onImportPreviousSelected()); final int iconColor = ThemeHelper.isLightThemeSelected(getContext()) ? Color.BLACK : Color.WHITE; @@ -244,8 +246,8 @@ public class SubscriptionFragment extends BaseStateFragment() { public void selected(ChannelInfoItem selectedItem) { - try { - // Requires the parent fragment to find holder for fragment replacement - NavigationHelper.openChannelFragment(getParentFragment().getFragmentManager(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); - } + final FragmentManager fragmentManager = getFM(); + NavigationHelper.openChannelFragment(fragmentManager, + selectedItem.getServiceId(), + selectedItem.getUrl(), + selectedItem.getName()); } public void held(ChannelInfoItem selectedItem) { @@ -341,8 +339,10 @@ public class SubscriptionFragment extends BaseStateFragment - NavigationHelper.openWhatsNewFragment(getParentFragment().getFragmentManager())); + whatsNewItemListHeader.setOnClickListener(v -> { + FragmentManager fragmentManager = getFM(); + NavigationHelper.openWhatsNewFragment(fragmentManager); + }); importExportListHeader.setOnClickListener(v -> importExportOptions.switchState()); } @@ -492,10 +492,13 @@ public class SubscriptionFragment extends BaseStateFragment getSubscriptionItems(List subscriptions) { List items = new ArrayList<>(); - for (final SubscriptionEntity subscription : subscriptions) items.add(subscription.toChannelInfoItem()); + for (final SubscriptionEntity subscription : subscriptions) { + items.add(subscription.toChannelInfoItem()); + } Collections.sort(items, - (InfoItem o1, InfoItem o2) -> o1.getName().compareToIgnoreCase(o2.getName())); + (InfoItem o1, InfoItem o2) -> + o1.getName().compareToIgnoreCase(o2.getName())); return items; } @@ -524,7 +527,11 @@ public class SubscriptionFragment extends BaseStateFragment= 26 /*Oreo*/) updateNotificationThumbnail(); if (bigNotRemoteView != null) { bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false); bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration)); 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 7339dd50f..d87df3666 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -51,6 +51,7 @@ import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; +import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -69,6 +70,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.MediaSourceTag; +import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.SerializedCache; @@ -86,6 +88,7 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK; import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT; +import static org.schabi.newpipe.report.UserAction.PLAY_STREAM; /** * Base for the players, joining the common properties @@ -96,7 +99,7 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJ public abstract class BasePlayer implements Player.EventListener, PlaybackListener, ImageLoadingListener { - public static final boolean DEBUG = true; + public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); @NonNull public static final String TAG = "BasePlayer"; @NonNull final protected Context context; @@ -363,7 +366,10 @@ public abstract class BasePlayer implements try { context.unregisterReceiver(broadcastReceiver); } catch (final IllegalArgumentException unregisteredException) { - Log.e(TAG, "Broadcast receiver already unregistered.", unregisteredException); + ErrorActivity.reportError(context, unregisteredException, null, null, + ErrorActivity.ErrorInfo.make(PLAY_STREAM, + "none", + "play stream", R.string.general_error)); } } @@ -1001,6 +1007,8 @@ public abstract class BasePlayer implements try { metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag(); } catch (IndexOutOfBoundsException | ClassCastException error) { + if(DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage()); + if(DEBUG) error.printStackTrace(); return; } @@ -1087,6 +1095,9 @@ public abstract class BasePlayer implements return simpleExoPlayer.isCurrentWindowDynamic(); } catch (@NonNull IndexOutOfBoundsException ignored) { // Why would this even happen =( + // But lets log it anyway. Save is save + if(DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage()); + if(DEBUG) ignored.printStackTrace(); return false; } } 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 9f3b5d020..41e7c305d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -36,6 +36,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; +import android.support.v7.content.res.AppCompatResources; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.DisplayMetrics; @@ -46,7 +47,9 @@ import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.PopupMenu; +import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; @@ -82,6 +85,7 @@ import java.util.UUID; import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING; import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION; import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME; +import static org.schabi.newpipe.util.AnimationUtils.Type.SCALE_AND_ALPHA; import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA; import static org.schabi.newpipe.util.AnimationUtils.animateRotation; import static org.schabi.newpipe.util.AnimationUtils.animateView; @@ -365,10 +369,16 @@ public final class MainVideoPlayer extends AppCompatActivity @SuppressWarnings({"unused", "WeakerAccess"}) private class VideoPlayerImpl extends VideoPlayer { + private final float MAX_GESTURE_LENGTH = 0.75f; + private TextView titleTextView; private TextView channelTextView; - private TextView volumeTextView; - private TextView brightnessTextView; + private RelativeLayout volumeRelativeLayout; + private ProgressBar volumeProgressBar; + private ImageView volumeImageView; + private RelativeLayout brightnessRelativeLayout; + private ProgressBar brightnessProgressBar; + private ImageView brightnessImageView; private ImageButton queueButton; private ImageButton repeatButton; private ImageButton shuffleButton; @@ -392,6 +402,8 @@ public final class MainVideoPlayer extends AppCompatActivity private RelativeLayout windowRootLayout; private View secondaryControls; + private int maxGestureLength; + VideoPlayerImpl(final Context context) { super("VideoPlayerImpl" + MainVideoPlayer.TAG, context); } @@ -401,8 +413,12 @@ public final class MainVideoPlayer extends AppCompatActivity super.initViews(rootView); this.titleTextView = rootView.findViewById(R.id.titleTextView); this.channelTextView = rootView.findViewById(R.id.channelTextView); - this.volumeTextView = rootView.findViewById(R.id.volumeTextView); - this.brightnessTextView = rootView.findViewById(R.id.brightnessTextView); + this.volumeRelativeLayout = rootView.findViewById(R.id.volumeRelativeLayout); + this.volumeProgressBar = rootView.findViewById(R.id.volumeProgressBar); + this.volumeImageView = rootView.findViewById(R.id.volumeImageView); + this.brightnessRelativeLayout = rootView.findViewById(R.id.brightnessRelativeLayout); + this.brightnessProgressBar = rootView.findViewById(R.id.brightnessProgressBar); + this.brightnessImageView = rootView.findViewById(R.id.brightnessImageView); this.queueButton = rootView.findViewById(R.id.queueButton); this.repeatButton = rootView.findViewById(R.id.repeatButton); this.shuffleButton = rootView.findViewById(R.id.shuffleButton); @@ -461,6 +477,20 @@ public final class MainVideoPlayer extends AppCompatActivity toggleOrientationButton.setOnClickListener(this); switchBackgroundButton.setOnClickListener(this); switchPopupButton.setOnClickListener(this); + + getRootView().addOnLayoutChangeListener((view, l, t, r, b, ol, ot, or, ob) -> { + if (l != ol || t != ot || r != or || b != ob) { + // Use smaller value to be consistent between screen orientations + // (and to make usage easier) + int width = r - l, height = b - t; + maxGestureLength = (int) (Math.min(width, height) * MAX_GESTURE_LENGTH); + + if (DEBUG) Log.d(TAG, "maxGestureLength = " + maxGestureLength); + + volumeProgressBar.setMax(maxGestureLength); + brightnessProgressBar.setMax(maxGestureLength); + } + }); } public void minimize() { @@ -872,12 +902,28 @@ public final class MainVideoPlayer extends AppCompatActivity return channelTextView; } - public TextView getVolumeTextView() { - return volumeTextView; + public RelativeLayout getVolumeRelativeLayout() { + return volumeRelativeLayout; } - public TextView getBrightnessTextView() { - return brightnessTextView; + public ProgressBar getVolumeProgressBar() { + return volumeProgressBar; + } + + public ImageView getVolumeImageView() { + return volumeImageView; + } + + public RelativeLayout getBrightnessRelativeLayout() { + return brightnessRelativeLayout; + } + + public ProgressBar getBrightnessProgressBar() { + return brightnessProgressBar; + } + + public ImageView getBrightnessImageView() { + return brightnessImageView; } public ImageButton getRepeatButton() { @@ -887,6 +933,10 @@ public final class MainVideoPlayer extends AppCompatActivity public ImageButton getPlayPauseButton() { return playPauseButton; } + + public int getMaxGestureLength() { + return maxGestureLength; + } } private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { @@ -930,23 +980,10 @@ public final class MainVideoPlayer extends AppCompatActivity private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext()); - private final float stepsBrightness = 15, stepBrightness = (1f / stepsBrightness), minBrightness = .01f; - private float currentBrightness = getWindow().getAttributes().screenBrightness > 0 - ? getWindow().getAttributes().screenBrightness - : 0.5f; - - private int currentVolume, maxVolume = playerImpl.getAudioReactor().getMaxVolume(); - private final float stepsVolume = 15, stepVolume = (float) Math.ceil(maxVolume / stepsVolume), minVolume = 0; - - private final String brightnessUnicode = new String(Character.toChars(0x2600)); - private final String volumeUnicode = new String(Character.toChars(0x1F508)); + private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume(); private final int MOVEMENT_THRESHOLD = 40; - private final int eventsThreshold = 8; - private boolean triggered = false; - private int eventsNum; - // TODO: Improve video gesture controls @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (!isPlayerGestureEnabled) return false; @@ -956,63 +993,77 @@ public final class MainVideoPlayer extends AppCompatActivity ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" + ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" + ", distanceXy = [" + distanceX + ", " + distanceY + "]"); - float abs = Math.abs(e2.getY() - e1.getY()); - if (!triggered) { - triggered = abs > MOVEMENT_THRESHOLD; + + if (!isMoving && ( + Math.abs(e2.getY() - e1.getY()) <= MOVEMENT_THRESHOLD + || Math.abs(distanceX) > Math.abs(distanceY) + ) || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false; - } - if (eventsNum++ % eventsThreshold != 0 || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) return false; isMoving = true; -// boolean up = !((e2.getY() - e1.getY()) > 0) && distanceY > 0; // Android's origin point is on top - boolean up = distanceY > 0; - if (e1.getX() > playerImpl.getRootView().getWidth() / 2) { - double floor = Math.floor(up ? stepVolume : -stepVolume); - currentVolume = (int) (playerImpl.getAudioReactor().getVolume() + floor); - if (currentVolume >= maxVolume) currentVolume = maxVolume; - if (currentVolume <= minVolume) currentVolume = (int) minVolume; + playerImpl.getVolumeProgressBar().incrementProgressBy((int) distanceY); + float currentProgressPercent = + (float) playerImpl.getVolumeProgressBar().getProgress() / playerImpl.getMaxGestureLength(); + int currentVolume = (int) (maxVolume * currentProgressPercent); playerImpl.getAudioReactor().setVolume(currentVolume); - currentVolume = playerImpl.getAudioReactor().getVolume(); if (DEBUG) Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); - final String volumeText = volumeUnicode + " " + Math.round((((float) currentVolume) / maxVolume) * 100) + "%"; - playerImpl.getVolumeTextView().setText(volumeText); - if (playerImpl.getVolumeTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getVolumeTextView(), true, 200); - if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE); + final int resId = + currentProgressPercent <= 0 ? R.drawable.ic_volume_off_white_72dp + : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute_white_72dp + : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down_white_72dp + : R.drawable.ic_volume_up_white_72dp; + + playerImpl.getVolumeImageView().setImageDrawable( + AppCompatResources.getDrawable(getApplicationContext(), resId) + ); + + if (playerImpl.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { + animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, true, 200); + } + if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { + playerImpl.getBrightnessRelativeLayout().setVisibility(View.GONE); + } } else { - WindowManager.LayoutParams lp = getWindow().getAttributes(); - currentBrightness += up ? stepBrightness : -stepBrightness; - if (currentBrightness >= 1f) currentBrightness = 1f; - if (currentBrightness <= minBrightness) currentBrightness = minBrightness; + playerImpl.getBrightnessProgressBar().incrementProgressBy((int) distanceY); + float currentProgressPercent = + (float) playerImpl.getBrightnessProgressBar().getProgress() / playerImpl.getMaxGestureLength(); + WindowManager.LayoutParams layoutParams = getWindow().getAttributes(); + layoutParams.screenBrightness = currentProgressPercent; + getWindow().setAttributes(layoutParams); - lp.screenBrightness = currentBrightness; - getWindow().setAttributes(lp); - if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentBrightness); - int brightnessNormalized = Math.round(currentBrightness * 100); + if (DEBUG) Log.d(TAG, "onScroll().brightnessControl, currentBrightness = " + currentProgressPercent); - final String brightnessText = brightnessUnicode + " " + (brightnessNormalized == 1 ? 0 : brightnessNormalized) + "%"; - playerImpl.getBrightnessTextView().setText(brightnessText); + final int resId = + currentProgressPercent < 0.25 ? R.drawable.ic_brightness_low_white_72dp + : currentProgressPercent < 0.75 ? R.drawable.ic_brightness_medium_white_72dp + : R.drawable.ic_brightness_high_white_72dp; - if (playerImpl.getBrightnessTextView().getVisibility() != View.VISIBLE) animateView(playerImpl.getBrightnessTextView(), true, 200); - if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE); + playerImpl.getBrightnessImageView().setImageDrawable( + AppCompatResources.getDrawable(getApplicationContext(), resId) + ); + + if (playerImpl.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) { + animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, true, 200); + } + if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { + playerImpl.getVolumeRelativeLayout().setVisibility(View.GONE); + } } return true; } private void onScrollEnd() { if (DEBUG) Log.d(TAG, "onScrollEnd() called"); - triggered = false; - eventsNum = 0; - /* if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) playerImpl.getVolumeTextView().setVisibility(View.GONE); - if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) playerImpl.getBrightnessTextView().setVisibility(View.GONE);*/ - if (playerImpl.getVolumeTextView().getVisibility() == View.VISIBLE) { - animateView(playerImpl.getVolumeTextView(), false, 200, 200); + + if (playerImpl.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { + animateView(playerImpl.getVolumeRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200); } - if (playerImpl.getBrightnessTextView().getVisibility() == View.VISIBLE) { - animateView(playerImpl.getBrightnessTextView(), false, 200, 200); + if (playerImpl.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { + animateView(playerImpl.getBrightnessRelativeLayout(), SCALE_AND_ALPHA, false, 200, 200); } if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { 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 86998e0ea..0e7328020 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -19,6 +19,8 @@ package org.schabi.newpipe.player; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; import android.app.NotificationManager; import android.app.PendingIntent; @@ -34,6 +36,7 @@ import android.os.Build; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; import android.support.v4.app.NotificationCompat; import android.util.DisplayMetrics; import android.util.Log; @@ -41,7 +44,9 @@ import android.view.GestureDetector; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; +import android.view.animation.AnticipateInterpolator; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.PopupMenu; @@ -104,10 +109,13 @@ public final class PopupVideoPlayer extends Service { WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; private WindowManager windowManager; - private WindowManager.LayoutParams windowLayoutParams; - private GestureDetector gestureDetector; + private WindowManager.LayoutParams popupLayoutParams; + private GestureDetector popupGestureDetector; + + private View closeOverlayView; + private FloatingActionButton closeOverlayButton; + private WindowManager.LayoutParams closeOverlayLayoutParams; - private int shutdownFlingVelocity; private int tossFlingVelocity; private float screenWidth, screenHeight; @@ -122,6 +130,7 @@ public final class PopupVideoPlayer extends Service { private VideoPlayerImpl playerImpl; private LockManager lockManager; + private boolean isPopupClosing = false; /*////////////////////////////////////////////////////////////////////////// // Service-Activity Binder @@ -150,7 +159,10 @@ public final class PopupVideoPlayer extends Service { public int onStartCommand(final Intent intent, int flags, int startId) { if (DEBUG) Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]"); - if (playerImpl.getPlayer() == null) initPopup(); + if (playerImpl.getPlayer() == null) { + initPopup(); + initPopupCloseOverlay(); + } if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true); playerImpl.handleIntent(intent); @@ -160,15 +172,16 @@ public final class PopupVideoPlayer extends Service { @Override public void onConfigurationChanged(Configuration newConfig) { + if (DEBUG) Log.d(TAG, "onConfigurationChanged() called with: newConfig = [" + newConfig + "]"); updateScreenSize(); - updatePopupSize(windowLayoutParams.width, -1); - checkPositionBounds(); + updatePopupSize(popupLayoutParams.width, -1); + checkPopupPositionBounds(); } @Override public void onDestroy() { if (DEBUG) Log.d(TAG, "onDestroy() called"); - onClose(); + closePopup(); } @Override @@ -186,7 +199,6 @@ public final class PopupVideoPlayer extends Service { View rootView = View.inflate(this, R.layout.player_popup, null); playerImpl.setup(rootView); - shutdownFlingVelocity = PlayerHelper.getShutdownFlingVelocity(this); tossFlingVelocity = PlayerHelper.getTossFlingVelocity(this); updateScreenSize(); @@ -200,27 +212,52 @@ public final class PopupVideoPlayer extends Service { WindowManager.LayoutParams.TYPE_PHONE : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - windowLayoutParams = new WindowManager.LayoutParams( + popupLayoutParams = new WindowManager.LayoutParams( (int) popupWidth, (int) getMinimumVideoHeight(popupWidth), layoutParamType, IDLE_WINDOW_FLAGS, PixelFormat.TRANSLUCENT); - windowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - windowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; int centerX = (int) (screenWidth / 2f - popupWidth / 2f); int centerY = (int) (screenHeight / 2f - popupHeight / 2f); - windowLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX; - windowLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY; + popupLayoutParams.x = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX; + popupLayoutParams.y = popupRememberSizeAndPos ? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY; - checkPositionBounds(); + checkPopupPositionBounds(); - MySimpleOnGestureListener listener = new MySimpleOnGestureListener(); - gestureDetector = new GestureDetector(this, listener); + PopupWindowGestureListener listener = new PopupWindowGestureListener(); + popupGestureDetector = new GestureDetector(this, listener); rootView.setOnTouchListener(listener); - playerImpl.getLoadingPanel().setMinimumWidth(windowLayoutParams.width); - playerImpl.getLoadingPanel().setMinimumHeight(windowLayoutParams.height); - windowManager.addView(rootView, windowLayoutParams); + + playerImpl.getLoadingPanel().setMinimumWidth(popupLayoutParams.width); + playerImpl.getLoadingPanel().setMinimumHeight(popupLayoutParams.height); + windowManager.addView(rootView, popupLayoutParams); + } + + @SuppressLint("RtlHardcoded") + private void initPopupCloseOverlay() { + if (DEBUG) Log.d(TAG, "initPopupCloseOverlay() called"); + closeOverlayView = View.inflate(this, R.layout.player_popup_close_overlay, null); + closeOverlayButton = closeOverlayView.findViewById(R.id.closeButton); + + final int layoutParamType = Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O ? + WindowManager.LayoutParams.TYPE_PHONE : + WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + + closeOverlayLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + layoutParamType, + flags, + PixelFormat.TRANSLUCENT); + closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + + closeOverlayButton.setVisibility(View.GONE); + windowManager.addView(closeOverlayView, closeOverlayLayoutParams); } /*////////////////////////////////////////////////////////////////////////// @@ -280,44 +317,105 @@ public final class PopupVideoPlayer extends Service { // Misc //////////////////////////////////////////////////////////////////////////*/ - public void onClose() { - if (DEBUG) Log.d(TAG, "onClose() called"); + public void closePopup() { + if (DEBUG) Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); + if (isPopupClosing) return; + isPopupClosing = true; if (playerImpl != null) { if (playerImpl.getRootView() != null) { windowManager.removeView(playerImpl.getRootView()); - playerImpl.setRootView(null); } + playerImpl.setRootView(null); playerImpl.stopActivityBinding(); playerImpl.destroy(); + playerImpl = null; } + + mBinder = null; if (lockManager != null) lockManager.releaseWifiAndCpu(); if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID); - mBinder = null; - playerImpl = null; - stopForeground(true); - stopSelf(); + animateOverlayAndFinishService(); + } + + private void animateOverlayAndFinishService() { + final int targetTranslationY = (int) (closeOverlayButton.getRootView().getHeight() - closeOverlayButton.getY()); + + closeOverlayButton.animate().setListener(null).cancel(); + closeOverlayButton.animate() + .setInterpolator(new AnticipateInterpolator()) + .translationY(targetTranslationY) + .setDuration(400) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(Animator animation) { + end(); + } + + @Override + public void onAnimationEnd(Animator animation) { + end(); + } + + private void end() { + windowManager.removeView(closeOverlayView); + + stopForeground(true); + stopSelf(); + } + }).start(); } /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ - private void checkPositionBounds() { - if (windowLayoutParams.x > screenWidth - windowLayoutParams.width) - windowLayoutParams.x = (int) (screenWidth - windowLayoutParams.width); - if (windowLayoutParams.x < 0) windowLayoutParams.x = 0; - if (windowLayoutParams.y > screenHeight - windowLayoutParams.height) - windowLayoutParams.y = (int) (screenHeight - windowLayoutParams.height); - if (windowLayoutParams.y < 0) windowLayoutParams.y = 0; + /** + * @see #checkPopupPositionBounds(float, float) + */ + @SuppressWarnings("UnusedReturnValue") + private boolean checkPopupPositionBounds() { + return checkPopupPositionBounds(screenWidth, screenHeight); + } + + /** + * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary that goes from (0,0) to (boundaryWidth,boundaryHeight). + *

+ * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed and {@code true} is returned + * to represent this change. + * + * @return if the popup was out of bounds and have been moved back to it + */ + private boolean checkPopupPositionBounds(final float boundaryWidth, final float boundaryHeight) { + if (DEBUG) { + Log.d(TAG, "checkPopupPositionBounds() called with: boundaryWidth = [" + boundaryWidth + "], boundaryHeight = [" + boundaryHeight + "]"); + } + + if (popupLayoutParams.x < 0) { + popupLayoutParams.x = 0; + return true; + } else if (popupLayoutParams.x > boundaryWidth - popupLayoutParams.width) { + popupLayoutParams.x = (int) (boundaryWidth - popupLayoutParams.width); + return true; + } + + if (popupLayoutParams.y < 0) { + popupLayoutParams.y = 0; + return true; + } else if (popupLayoutParams.y > boundaryHeight - popupLayoutParams.height) { + popupLayoutParams.y = (int) (boundaryHeight - popupLayoutParams.height); + return true; + } + + return false; } private void savePositionAndSize() { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(PopupVideoPlayer.this); - sharedPreferences.edit().putInt(POPUP_SAVED_X, windowLayoutParams.x).apply(); - sharedPreferences.edit().putInt(POPUP_SAVED_Y, windowLayoutParams.y).apply(); - sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, windowLayoutParams.width).apply(); + sharedPreferences.edit().putInt(POPUP_SAVED_X, popupLayoutParams.x).apply(); + sharedPreferences.edit().putInt(POPUP_SAVED_Y, popupLayoutParams.y).apply(); + sharedPreferences.edit().putFloat(POPUP_SAVED_WIDTH, popupLayoutParams.width).apply(); } private float getMinimumVideoHeight(float width) { @@ -352,13 +450,13 @@ public final class PopupVideoPlayer extends Service { if (height == -1) height = (int) getMinimumVideoHeight(width); else height = (int) (height > maximumHeight ? maximumHeight : height < minimumHeight ? minimumHeight : height); - windowLayoutParams.width = width; - windowLayoutParams.height = height; + popupLayoutParams.width = width; + popupLayoutParams.height = height; popupWidth = width; popupHeight = height; if (DEBUG) Log.d(TAG, "updatePopupSize() updated values: width = [" + width + "], height = [" + height + "]"); - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); } protected void setRepeatModeRemote(final RemoteViews remoteViews, final int repeatMode) { @@ -380,10 +478,10 @@ public final class PopupVideoPlayer extends Service { } private void updateWindowFlags(final int flags) { - if (windowLayoutParams == null || windowManager == null || playerImpl == null) return; + if (popupLayoutParams == null || windowManager == null || playerImpl == null) return; - windowLayoutParams.flags = flags; - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); } /////////////////////////////////////////////////////////////////////////// @@ -393,6 +491,7 @@ public final class PopupVideoPlayer extends Service { private ImageView videoPlayPause; private View extraOptionsView; + private View closingOverlayView; @Override public void handleIntent(Intent intent) { @@ -413,12 +512,18 @@ public final class PopupVideoPlayer extends Service { fullScreenButton = rootView.findViewById(R.id.fullScreenButton); fullScreenButton.setOnClickListener(v -> onFullScreenButtonClicked()); videoPlayPause = rootView.findViewById(R.id.videoPlayPause); - videoPlayPause.setOnClickListener(this::onPlayPauseButtonPressed); extraOptionsView = rootView.findViewById(R.id.extraOptionsView); + closingOverlayView = rootView.findViewById(R.id.closingOverlay); rootView.addOnLayoutChangeListener(this); } + @Override + public void initListeners() { + super.initListeners(); + videoPlayPause.setOnClickListener(v -> onPlayPause()); + } + @Override protected void setupSubtitleView(@NonNull SubtitleView view, final float captionScale, @@ -429,10 +534,6 @@ public final class PopupVideoPlayer extends Service { view.setStyle(captionStyle); } - private void onPlayPauseButtonPressed(View ib) { - onPlayPause(); - } - @Override public void onLayoutChange(final View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { @@ -476,7 +577,7 @@ public final class PopupVideoPlayer extends Service { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } context.startActivity(intent); - onClose(); + closePopup(); } @Override @@ -634,7 +735,7 @@ public final class PopupVideoPlayer extends Service { @Override public void onPlaybackShutdown() { super.onPlaybackShutdown(); - onClose(); + closePopup(); } /*////////////////////////////////////////////////////////////////////////// @@ -660,7 +761,7 @@ public final class PopupVideoPlayer extends Service { if (DEBUG) Log.d(TAG, "onBroadcastReceived() called with: intent = [" + intent + "]"); switch (intent.getAction()) { case ACTION_CLOSE: - onClose(); + closePopup(); break; case ACTION_PLAY_PAUSE: onPlayPause(); @@ -791,12 +892,15 @@ public final class PopupVideoPlayer extends Service { public TextView getResizingIndicator() { return resizingIndicator; } + + public View getClosingOverlayView() { + return closingOverlayView; + } } - private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { + private class PopupWindowGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { private int initialPopupX, initialPopupY; private boolean isMoving; - private boolean isResizing; @Override @@ -832,10 +936,15 @@ public final class PopupVideoPlayer extends Service { @Override public boolean onDown(MotionEvent e) { if (DEBUG) Log.d(TAG, "onDown() called with: e = [" + e + "]"); - initialPopupX = windowLayoutParams.x; - initialPopupY = windowLayoutParams.y; - popupWidth = windowLayoutParams.width; - popupHeight = windowLayoutParams.height; + + // Fix popup position when the user touch it, it may have the wrong one + // because the soft input is visible (the draggable area is currently resized). + checkPopupPositionBounds(closeOverlayView.getWidth(), closeOverlayView.getHeight()); + + initialPopupX = popupLayoutParams.x; + initialPopupY = popupLayoutParams.y; + popupWidth = popupLayoutParams.width; + popupHeight = popupLayoutParams.height; return super.onDown(e); } @@ -843,20 +952,22 @@ public final class PopupVideoPlayer extends Service { public void onLongPress(MotionEvent e) { if (DEBUG) Log.d(TAG, "onLongPress() called with: e = [" + e + "]"); updateScreenSize(); - checkPositionBounds(); + checkPopupPositionBounds(); updatePopupSize((int) screenWidth, -1); } @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (isResizing || playerImpl == null) return super.onScroll(e1, e2, distanceX, distanceY); + public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) { + if (isResizing || playerImpl == null) return super.onScroll(initialEvent, movingEvent, distanceX, distanceY); + + if (!isMoving) { + animateView(closeOverlayButton, true, 200); + } - if (playerImpl.getCurrentState() != BasePlayer.STATE_BUFFERING - && (!isMoving || playerImpl.getControlsRoot().getAlpha() != 1f)) playerImpl.showControls(0); isMoving = true; - float diffX = (int) (e2.getRawX() - e1.getRawX()), posX = (int) (initialPopupX + diffX); - float diffY = (int) (e2.getRawY() - e1.getRawY()), posY = (int) (initialPopupY + diffY); + float diffX = (int) (movingEvent.getRawX() - initialEvent.getRawX()), posX = (int) (initialPopupX + diffX); + float diffY = (int) (movingEvent.getRawY() - initialEvent.getRawY()), posY = (int) (initialPopupY + diffY); if (posX > (screenWidth - popupWidth)) posX = (int) (screenWidth - popupWidth); else if (posX < 0) posX = 0; @@ -864,26 +975,49 @@ public final class PopupVideoPlayer extends Service { if (posY > (screenHeight - popupHeight)) posY = (int) (screenHeight - popupHeight); else if (posY < 0) posY = 0; - windowLayoutParams.x = (int) posX; - windowLayoutParams.y = (int) posY; + popupLayoutParams.x = (int) posX; + popupLayoutParams.y = (int) posY; + + final View closingOverlayView = playerImpl.getClosingOverlayView(); + if (isInsideClosingRadius(movingEvent)) { + if (closingOverlayView.getVisibility() == View.GONE) { + animateView(closingOverlayView, true, 250); + } + } else { + if (closingOverlayView.getVisibility() == View.VISIBLE) { + animateView(closingOverlayView, false, 0); + } + } //noinspection PointlessBooleanExpression - if (DEBUG && false) Log.d(TAG, "PopupVideoPlayer.onScroll = " + - ", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" + - ", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" + - ", distanceXy = [" + distanceX + ", " + distanceY + "]" + - ", posXy = [" + posX + ", " + posY + "]" + - ", popupWh = [" + popupWidth + " x " + popupHeight + "]"); - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + if (DEBUG && false) { + Log.d(TAG, "PopupVideoPlayer.onScroll = " + + ", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" + ", e1.getX,Y = [" + initialEvent.getX() + ", " + initialEvent.getY() + "]" + + ", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" + ", e2.getX,Y = [" + movingEvent.getX() + ", " + movingEvent.getY() + "]" + + ", distanceX,Y = [" + distanceX + ", " + distanceY + "]" + + ", posX,Y = [" + posX + ", " + posY + "]" + + ", popupW,H = [" + popupWidth + " x " + popupHeight + "]"); + } + windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); return true; } - private void onScrollEnd() { + private void onScrollEnd(MotionEvent event) { if (DEBUG) Log.d(TAG, "onScrollEnd() called"); if (playerImpl == null) return; if (playerImpl.isControlsVisible() && playerImpl.getCurrentState() == STATE_PLAYING) { playerImpl.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); } + + if (isInsideClosingRadius(event)) { + closePopup(); + } else { + animateView(playerImpl.getClosingOverlayView(), false, 0); + + if (!isPopupClosing) { + animateView(closeOverlayButton, false, 200); + } + } } @Override @@ -893,14 +1027,11 @@ public final class PopupVideoPlayer extends Service { final float absVelocityX = Math.abs(velocityX); final float absVelocityY = Math.abs(velocityY); - if (absVelocityX > shutdownFlingVelocity) { - onClose(); - return true; - } else if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) { - if (absVelocityX > tossFlingVelocity) windowLayoutParams.x = (int) velocityX; - if (absVelocityY > tossFlingVelocity) windowLayoutParams.y = (int) velocityY; - checkPositionBounds(); - windowManager.updateViewLayout(playerImpl.getRootView(), windowLayoutParams); + if (Math.max(absVelocityX, absVelocityY) > tossFlingVelocity) { + if (absVelocityX > tossFlingVelocity) popupLayoutParams.x = (int) velocityX; + if (absVelocityY > tossFlingVelocity) popupLayoutParams.y = (int) velocityY; + checkPopupPositionBounds(); + windowManager.updateViewLayout(playerImpl.getRootView(), popupLayoutParams); return true; } return false; @@ -908,7 +1039,7 @@ public final class PopupVideoPlayer extends Service { @Override public boolean onTouch(View v, MotionEvent event) { - gestureDetector.onTouchEvent(event); + popupGestureDetector.onTouchEvent(event); if (playerImpl == null) return false; if (event.getPointerCount() == 2 && !isResizing) { if (DEBUG) Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing."); @@ -931,7 +1062,7 @@ public final class PopupVideoPlayer extends Service { Log.d(TAG, "onTouch() ACTION_UP > v = [" + v + "], e1.getRaw = [" + event.getRawX() + ", " + event.getRawY() + "]"); if (isMoving) { isMoving = false; - onScrollEnd(); + onScrollEnd(event); } if (isResizing) { @@ -939,7 +1070,10 @@ public final class PopupVideoPlayer extends Service { animateView(playerImpl.getResizingIndicator(), false, 100, 0); playerImpl.changeState(playerImpl.getCurrentState()); } - savePositionAndSize(); + + if (!isPopupClosing) { + savePositionAndSize(); + } } v.performClick(); @@ -955,13 +1089,13 @@ public final class PopupVideoPlayer extends Service { final float diff = Math.abs(firstPointerX - secondPointerX); if (firstPointerX > secondPointerX) { // second pointer is the anchor (the leftmost pointer) - windowLayoutParams.x = (int) (event.getRawX() - diff); + popupLayoutParams.x = (int) (event.getRawX() - diff); } else { // first pointer is the anchor - windowLayoutParams.x = (int) event.getRawX(); + popupLayoutParams.x = (int) event.getRawX(); } - checkPositionBounds(); + checkPopupPositionBounds(); updateScreenSize(); final int width = (int) Math.min(screenWidth, diff); @@ -969,5 +1103,29 @@ public final class PopupVideoPlayer extends Service { return true; } + + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + + private int distanceFromCloseButton(MotionEvent popupMotionEvent) { + final int closeOverlayButtonX = closeOverlayButton.getLeft() + closeOverlayButton.getWidth() / 2; + final int closeOverlayButtonY = closeOverlayButton.getTop() + closeOverlayButton.getHeight() / 2; + + float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); + float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); + + return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + Math.pow(closeOverlayButtonY - fingerY, 2)); + } + + private float getClosingRadius() { + final int buttonRadius = closeOverlayButton.getWidth() / 2; + // 20% wider than the button itself + return buttonRadius * 1.2f; + } + + private boolean isInsideClosingRadius(MotionEvent popupMotionEvent) { + return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); + } } } \ 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 b57a710ed..94305e6c4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -16,6 +16,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.PopupMenu; @@ -562,6 +563,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity if (player != null) { progressLiveSync.setClickable(!player.isLiveEdge()); } + + // this will make shure progressCurrentTime has the same width as progressEndTime + final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams(); + final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams(); + currentTimeParams.width = progressEndTime.getWidth(); + progressCurrentTime.setLayoutParams(currentTimeParams); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 275f488e3..ae187a834 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -251,10 +251,6 @@ public class PlayerHelper { return true; } - public static int getShutdownFlingVelocity(@NonNull final Context context) { - return 6000; - } - public static int getTossFlingVelocity(@NonNull final Context context) { return 2500; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index a21560abd..c9e07c96a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -6,6 +6,7 @@ import android.util.Log; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; +import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.player.playqueue.events.AppendEvent; import org.schabi.newpipe.player.playqueue.events.ErrorEvent; import org.schabi.newpipe.player.playqueue.events.InitEvent; @@ -41,7 +42,7 @@ import io.reactivex.subjects.BehaviorSubject; public abstract class PlayQueue implements Serializable { private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode()); - public static final boolean DEBUG = true; + public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); private ArrayList backup; private ArrayList streams; diff --git a/app/src/main/java/org/schabi/newpipe/report/UserAction.java b/app/src/main/java/org/schabi/newpipe/report/UserAction.java index 93a3ce16c..00a25ed8d 100644 --- a/app/src/main/java/org/schabi/newpipe/report/UserAction.java +++ b/app/src/main/java/org/schabi/newpipe/report/UserAction.java @@ -15,7 +15,8 @@ public enum UserAction { REQUESTED_CHANNEL("requested channel"), REQUESTED_PLAYLIST("requested playlist"), REQUESTED_KIOSK("requested kiosk"), - DELETE_FROM_HISTORY("delete from history"); + DELETE_FROM_HISTORY("delete from history"), + PLAY_STREAM("Play stream"); private final String message; diff --git a/app/src/main/java/org/schabi/newpipe/settings/AddTabsDialog.java b/app/src/main/java/org/schabi/newpipe/settings/AddTabsDialog.java new file mode 100644 index 000000000..5130df3bf --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/AddTabsDialog.java @@ -0,0 +1,41 @@ +package org.schabi.newpipe.settings; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; + +public class AddTabsDialog { + private final AlertDialog dialog; + + public AddTabsDialog(@NonNull final Context context, + @NonNull final String title, + @NonNull final String[] commands, + @NonNull final DialogInterface.OnClickListener actions) { + + final View bannerView = View.inflate(context, R.layout.dialog_title, null); + bannerView.setSelected(true); + + TextView titleView = bannerView.findViewById(R.id.itemTitleView); + titleView.setText(title); + + TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails); + detailsView.setVisibility(View.GONE); + + dialog = new AlertDialog.Builder(context) + .setCustomTitle(bannerView) + .setItems(commands, actions) + .create(); + } + + public void show() { + dialog.show(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/ChoseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ChoseTabsFragment.java new file mode 100644 index 000000000..d6238c7c4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/ChoseTabsFragment.java @@ -0,0 +1,291 @@ +package org.schabi.newpipe.settings; + +import android.app.Dialog; +import android.content.SharedPreferences; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.CardView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.util.ThemeHelper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ChoseTabsFragment extends Fragment { + + public ChoseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; + + RecyclerView selectedTabsView; + + List selectedTabs = new ArrayList<>(); + private String saveString; + public String[] availableTabs = new String[7]; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ((AppCompatActivity)getContext()).getSupportActionBar().setTitle(R.string.main_page_content); + return inflater.inflate(R.layout.fragment_chose_tabs, container, false); + } + + + @Override + public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) { + super.onViewCreated(rootView, savedInstanceState); + + tabNames(); + initUsedTabs(); + initButton(rootView); + + selectedTabsView = rootView.findViewById(R.id.usedTabs); + selectedTabsView.setLayoutManager(new LinearLayoutManager(getContext())); + selectedTabsAdapter = new ChoseTabsFragment.SelectedTabsAdapter(); + + + ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + itemTouchHelper.attachToRecyclerView(selectedTabsView); + selectedTabsAdapter.setOnItemSelectedListener(itemTouchHelper); + + selectedTabsView.setAdapter(selectedTabsAdapter); + } + + private void saveChanges() { + StringBuilder save = new StringBuilder(); + if(selectedTabs.size()==0) { + save = new StringBuilder("0"); + } else { + for(String s: selectedTabs) { + save.append(s); + save.append("\n"); + } + } + saveString = save.toString(); + } + + @Override + public void onPause() { + saveChanges(); + SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(getContext()); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("saveUsedTabs", saveString); + editor.commit(); + super.onPause(); + } + + private void initUsedTabs() { + String save = android.preference.PreferenceManager.getDefaultSharedPreferences(getContext()).getString("saveUsedTabs", "1\tTrending\t0\n2\n4\n"); + String tabs[] = save.trim().split("\n"); + selectedTabs.addAll(Arrays.asList(tabs)); + } + + private void tabNames() { + availableTabs[0] = getString(R.string.blank_page_summary); + availableTabs[1] = getString(R.string.kiosk_page_summary); + availableTabs[2] = getString(R.string.subscription_page_summary); + availableTabs[3] = getString(R.string.feed_page_summary); + availableTabs[4] = getString(R.string.tab_bookmarks); + availableTabs[5] = getString(R.string.title_activity_history); + availableTabs[6] = getString(R.string.channel_page_summary); + } + + private void initButton(View rootView) { + FloatingActionButton fab = rootView.findViewById(R.id.floatingActionButton); + fab.setImageResource(ThemeHelper.getIconByAttr(R.attr.ic_add, getContext())); + fab.setOnClickListener(v -> { + Dialog.OnClickListener onClickListener = (dialog, which) -> addTab(which); + + new AddTabsDialog(getContext(), + getString(R.string.tab_chose), + availableTabs, + onClickListener) + .show(); + }); + + TypedValue typedValue = new TypedValue(); + getActivity().getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true); + int color = typedValue.data; + fab.setBackgroundTintList(ColorStateList.valueOf(color)); + } + + + private void addTab(int position) { + if(position==6) { + SelectChannelFragment selectChannelFragment = new SelectChannelFragment(); + selectChannelFragment.setOnSelectedLisener((String url, String name, int service) -> { + selectedTabs.add(position+"\t"+url+"\t"+name+"\t"+service); + selectedTabsAdapter.notifyDataSetChanged(); + saveChanges(); + }); + selectChannelFragment.show(getFragmentManager(), "select_channel"); + } else if(position==1) { + SelectKioskFragment selectKioskFragment = new SelectKioskFragment(); + selectKioskFragment.setOnSelectedLisener((String kioskId, int service_id) -> { + selectedTabs.add(position+"\t"+kioskId+"\t"+service_id); + selectedTabsAdapter.notifyDataSetChanged(); + saveChanges(); + }); + selectKioskFragment.show(getFragmentManager(), "select_kiosk"); + } else { + selectedTabs.add(String.valueOf(position)); + selectedTabsAdapter.notifyDataSetChanged(); + saveChanges(); + } + } + + public class SelectedTabsAdapter extends RecyclerView.Adapter{ + private ItemTouchHelper itemTouchHelper; + + public void setOnItemSelectedListener(ItemTouchHelper mItemTouchHelper) { + itemTouchHelper = mItemTouchHelper; + } + + public void swapItems(int fromPosition, int toPosition) { + String temp = selectedTabs.get(fromPosition); + selectedTabs.set(fromPosition, selectedTabs.get(toPosition)); + selectedTabs.set(toPosition, temp); + notifyItemMoved(fromPosition, toPosition); + saveChanges(); + } + + @Override + public ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + LayoutInflater inflater = LayoutInflater.from(getContext()); + View view = inflater.inflate(R.layout.viewholder_chose_tabs, parent, false); + return new ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) { + holder.bind(position, holder); + } + + @Override + public int getItemCount() { + return selectedTabs.size(); + } + + class TabViewHolder extends RecyclerView.ViewHolder { + + TextView text; + View view; + CardView cardView; + ImageView handle; + + public TabViewHolder(View itemView) { + super(itemView); + + text = itemView.findViewById(R.id.tabName); + cardView = itemView.findViewById(R.id.layoutCard); + handle = itemView.findViewById(R.id.handle); + view = itemView; + } + + void bind(int position, TabViewHolder holder) { + handle.setImageResource(ThemeHelper.getIconByAttr(R.attr.drag_handle, getContext())); + handle.setOnTouchListener(getOnTouchListener(holder)); + + view.setOnLongClickListener(getOnLongClickListener(holder)); + + if(selectedTabs.get(position).startsWith("6\t")) { + String channelInfo[] = selectedTabs.get(position).split("\t"); + String channelName = ""; + if (channelInfo.length == 4) channelName = channelInfo[2]; + String textToSet = availableTabs[6] + ": " + channelName; + text.setText(textToSet); + } else if(selectedTabs.get(position).startsWith("1\t")) { + String kioskInfo[] = selectedTabs.get(position).split("\t"); + String kioskName = ""; + if (kioskInfo.length == 3) kioskName = kioskInfo[1]; + String textToSet = availableTabs[1] + ": " + kioskName; + text.setText(textToSet); + } else { + text.setText(availableTabs[Integer.parseInt(selectedTabs.get(position))]); + } + } + + private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { + return (view, motionEvent) -> { + view.performClick(); + if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { + if(itemTouchHelper != null) itemTouchHelper.startDrag(item); + } + return false; + }; + } + + private View.OnLongClickListener getOnLongClickListener(TabViewHolder holder) { + return (view) -> { + if(itemTouchHelper != null) itemTouchHelper.startSwipe(holder); + return false; + }; + } + + } + } + + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.START | ItemTouchHelper.END) { + @Override + public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, + int viewSizeOutOfBounds, int totalSize, + long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int minimumAbsVelocity = Math.max(12, + Math.abs(standardSpeed)); + return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType() || + selectedTabsAdapter == null) { + return false; + } + + final int sourceIndex = source.getAdapterPosition(); + final int targetIndex = target.getAdapterPosition(); + selectedTabsAdapter.swapItems(sourceIndex, targetIndex); + return true; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { + int position = viewHolder.getAdapterPosition(); + selectedTabs.remove(position); + selectedTabsAdapter.notifyItemRemoved(position); + saveChanges(); + } + }; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 8f4e6d471..0ca78b34a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.util.Log; @@ -20,15 +21,12 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.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; @@ -42,11 +40,8 @@ import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import static android.content.Context.MODE_PRIVATE; - public class ContentSettingsFragment extends BasePreferenceFragment { private static final int REQUEST_IMPORT_PATH = 8945; @@ -98,68 +93,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment { addPreferencesFromResource(R.xml.content_settings); - final ListPreference mainPageContentPref = (ListPreference) findPreference(getString(R.string.main_page_content_key)); - 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); - - 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().getName(); - } 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) @@ -349,66 +282,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment { } } - @Override - public void onResume() { - super.onResume(); - - final String mainPageContentKey = getString(R.string.main_page_content_key); - final Preference mainPagePref = findPreference(getString(R.string.main_page_content_key)); - final String bpk = getString(R.string.blank_page_key); - if(defaultPreferences.getString(mainPageContentKey, bpk) - .equals(getString(R.string.channel_page_key))) { - mainPagePref.setSummary(defaultPreferences.getString(getString(R.string.main_page_selected_channel_name), "error")); - } else if(defaultPreferences.getString(mainPageContentKey, bpk) - .equals(getString(R.string.kiosk_page_key))) { - try { - StreamingService service = NewPipe.getService( - defaultPreferences.getInt( - getString(R.string.main_page_selected_service), 0)); - - String kioskName = KioskTranslator.getTranslatedKioskName( - defaultPreferences.getString( - getString(R.string.main_page_selectd_kiosk_id), "Trending"), - getContext()); - - String summary = - String.format(getString(R.string.service_kiosk_string), - service.getServiceInfo().getName(), - kioskName); - - mainPagePref.setSummary(summary); - } catch (Exception e) { - onError(e); - } - } - } - - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - private String getMainPagePrefSummery(final String mainPrefOldValue, final ListPreference mainPageContentPref) { - if(mainPrefOldValue.equals(getString(R.string.channel_page_key))) { - return defaultPreferences.getString(getString(R.string.main_page_selected_channel_name), "error"); - } else { - return mainPageContentPref.getSummary().toString(); - } - } - - private int getMainPageSummeryByKey(final String key) { - if(key.equals(getString(R.string.blank_page_key))) { - return R.string.blank_page_summary; - } else if(key.equals(getString(R.string.kiosk_page_key))) { - return R.string.kiosk_page_summary; - } else if(key.equals(getString(R.string.feed_page_key))) { - return R.string.feed_page_summary; - } else if(key.equals(getString(R.string.subscription_page_key))) { - return R.string.subscription_page_summary; - } else if(key.equals(getString(R.string.channel_page_key))) { - return R.string.channel_page_summary; - } - return R.string.blank_page_summary; - } - /*////////////////////////////////////////////////////////////////////////// // Error //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index 92f98a9a2..2a0e2645b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -71,7 +71,7 @@ public class NewPipeSettings { } public static File getVideoDownloadFolder(Context context) { - return getFolder(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES); + return getDir(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES); } public static String getVideoDownloadPath(Context context) { @@ -81,7 +81,7 @@ public class NewPipeSettings { } public static File getAudioDownloadFolder(Context context) { - return getFolder(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC); + return getDir(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC); } public static String getAudioDownloadPath(Context context) { @@ -90,21 +90,37 @@ public class NewPipeSettings { return prefs.getString(key, Environment.DIRECTORY_MUSIC); } - private static File getFolder(Context context, int keyID, String defaultDirectoryName) { + private static File getDir(Context context, int keyID, String defaultDirectoryName) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); final String key = context.getString(keyID); String downloadPath = prefs.getString(key, null); if ((downloadPath != null) && (!downloadPath.isEmpty())) return new File(downloadPath.trim()); - final File folder = getFolder(defaultDirectoryName); + final File dir = getDir(defaultDirectoryName); SharedPreferences.Editor spEditor = prefs.edit(); - spEditor.putString(key, new File(folder, "NewPipe").getAbsolutePath()); + spEditor.putString(key, getNewPipeChildFolderPathForDir(dir)); spEditor.apply(); - return folder; + return dir; } @NonNull - private static File getFolder(String defaultDirectoryName) { + private static File getDir(String defaultDirectoryName) { return new File(Environment.getExternalStorageDirectory(), defaultDirectoryName); } + + public static void resetDownloadFolders(Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + resetDownloadFolder(prefs, context.getString(R.string.download_path_audio_key), Environment.DIRECTORY_MUSIC); + resetDownloadFolder(prefs, context.getString(R.string.download_path_key), Environment.DIRECTORY_MOVIES); + } + + private static void resetDownloadFolder(SharedPreferences prefs, String key, String defaultDirectoryName) { + SharedPreferences.Editor spEditor = prefs.edit(); + spEditor.putString(key, getNewPipeChildFolderPathForDir(getDir(defaultDirectoryName))); + spEditor.apply(); + } + + private static String getNewPipeChildFolderPathForDir(File dir) { + return new File(dir, "NewPipe").getAbsolutePath(); + } } 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 22b5bfba9..e412c474f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -74,7 +74,7 @@ public final class ExtractorHelper { return Single.fromCallable(() -> SearchInfo.getInfo(NewPipe.getService(serviceId), NewPipe.getService(serviceId) - .getSearchQIHFactory() + .getSearchQHFactory() .fromQuery(searchString, contentFilter, sortFilter), contentCountry)); } @@ -89,7 +89,7 @@ public final class ExtractorHelper { return Single.fromCallable(() -> SearchInfo.getMoreItems(NewPipe.getService(serviceId), NewPipe.getService(serviceId) - .getSearchQIHFactory() + .getSearchQHFactory() .fromQuery(searchString, contentFilter, sortFilter), contentCountry, pageUrl)); diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java index 4740b82e0..392892cef 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java +++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java @@ -24,7 +24,7 @@ import org.schabi.newpipe.R; public class KioskTranslator { public static String getTranslatedKioskName(String kioskId, Context c) { - switch(kioskId) { + switch (kioskId) { case "Trending": return c.getString(R.string.trending); case "Top 50": @@ -35,4 +35,17 @@ public class KioskTranslator { return kioskId; } } + + public static int getKioskIcons(String kioskId, Context c) { + switch(kioskId) { + case "Trending": + return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); + case "Top 50": + return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); + case "New & hot": + return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot); + default: + return 0; + } + } } 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 85367e2a5..13767125d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -31,17 +31,17 @@ 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.extractor.linkhandler.ListLinkHandler; -import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler; import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; +import org.schabi.newpipe.local.bookmark.BookmarkFragment; import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; +import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; import org.schabi.newpipe.player.BackgroundPlayer; import org.schabi.newpipe.player.BackgroundPlayerActivity; @@ -349,6 +349,20 @@ public class NavigationHelper { .commit(); } + public static void openBookmarksFragment(FragmentManager fragmentManager) { + defaultTransaction(fragmentManager) + .replace(R.id.fragment_holder, new BookmarkFragment()) + .addToBackStack(null) + .commit(); + } + + public static void openSubscriptionFragment(FragmentManager fragmentManager) { + defaultTransaction(fragmentManager) + .replace(R.id.fragment_holder, new SubscriptionFragment()) + .addToBackStack(null) + .commit(); + } + public static void openKioskFragment(FragmentManager fragmentManager, int serviceId, String kioskId) throws ExtractionException { defaultTransaction(fragmentManager) .replace(R.id.fragment_holder, KioskFragment.getInstance(serviceId, kioskId)) diff --git a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java index ecd3ce562..3294f5164 100755 --- a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java @@ -1,10 +1,17 @@ package us.shandian.giga.get; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; +import org.schabi.newpipe.download.ExtSDDownloadFailedActivity; + import java.io.File; import java.io.FilenameFilter; +import java.io.IOException; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; @@ -23,7 +30,9 @@ public class DownloadManagerImpl implements DownloadManager { private static final String TAG = DownloadManagerImpl.class.getSimpleName(); private final DownloadDataSource mDownloadDataSource; - private final ArrayList mMissions = new ArrayList(); + private final ArrayList mMissions = new ArrayList<>(); + @NonNull + private final Context context; /** * Create a new instance @@ -33,6 +42,13 @@ public class DownloadManagerImpl implements DownloadManager { */ public DownloadManagerImpl(Collection searchLocations, DownloadDataSource downloadDataSource) { mDownloadDataSource = downloadDataSource; + this.context = null; + loadMissions(searchLocations); + } + + public DownloadManagerImpl(Collection searchLocations, DownloadDataSource downloadDataSource, Context context) { + mDownloadDataSource = downloadDataSource; + this.context = context; loadMissions(searchLocations); } @@ -277,10 +293,12 @@ public class DownloadManagerImpl implements DownloadManager { } private class Initializer extends Thread { - private DownloadMission mission; + private final DownloadMission mission; + private final Handler handler; public Initializer(DownloadMission mission) { this.mission = mission; + this.handler = new Handler(); } @Override @@ -335,6 +353,13 @@ public class DownloadManagerImpl implements DownloadManager { af.close(); mission.start(); + } catch (IOException ie) { + if(context == null) throw new RuntimeException(ie); + + if(ie.getMessage().contains("Permission denied")) { + handler.post(() -> + context.startActivity(new Intent(context, ExtSDDownloadFailedActivity.class))); + } else throw new RuntimeException(ie); } catch (Exception e) { // TODO Notify throw new RuntimeException(e); diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 50975728f..59f5e2225 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -81,7 +81,7 @@ public class DownloadManagerService extends Service { ArrayList paths = new ArrayList<>(2); paths.add(NewPipeSettings.getVideoDownloadPath(this)); paths.add(NewPipeSettings.getAudioDownloadPath(this)); - mManager = new DownloadManagerImpl(paths, mDataSource); + mManager = new DownloadManagerImpl(paths, mDataSource, this); if (DEBUG) { Log.d(TAG, "mManager == null"); Log.d(TAG, "Download directory: " + paths); diff --git a/app/src/main/res/drawable-hdpi/ic_add.png b/app/src/main/res/drawable-hdpi/ic_add.png new file mode 100644 index 000000000..1ae5b2dc4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_down_white.png b/app/src/main/res/drawable-hdpi/ic_arrow_down_white.png new file mode 100644 index 000000000..33939600d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_down_white.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_up_white.png b/app/src/main/res/drawable-hdpi/ic_arrow_up_white.png new file mode 100644 index 000000000..0972a9bca Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_arrow_up_white.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_remove.png b/app/src/main/res/drawable-hdpi/ic_remove.png new file mode 100644 index 000000000..75e65bc9c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_add.png b/app/src/main/res/drawable-mdpi/ic_add.png new file mode 100644 index 000000000..d51f0ddad Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_down_white.png b/app/src/main/res/drawable-mdpi/ic_arrow_down_white.png new file mode 100644 index 000000000..40a0f499e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_down_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_up_white.png b/app/src/main/res/drawable-mdpi/ic_arrow_up_white.png new file mode 100644 index 000000000..fe67b4673 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_arrow_up_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_remove.png b/app/src/main/res/drawable-mdpi/ic_remove.png new file mode 100644 index 000000000..a1816d4c6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_add.png b/app/src/main/res/drawable-xhdpi/ic_add.png new file mode 100644 index 000000000..9ea0eeb7e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_down_white.png b/app/src/main/res/drawable-xhdpi/ic_arrow_down_white.png new file mode 100644 index 000000000..86bc5db3b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_down_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_up_white.png b/app/src/main/res/drawable-xhdpi/ic_arrow_up_white.png new file mode 100644 index 000000000..dda36882e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_arrow_up_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_remove.png b/app/src/main/res/drawable-xhdpi/ic_remove.png new file mode 100644 index 000000000..ffbdaa6ed Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_add.png b/app/src/main/res/drawable-xxhdpi/ic_add.png new file mode 100644 index 000000000..75f192aab Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_add.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_down_white.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_down_white.png new file mode 100644 index 000000000..7e901e098 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_down_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_up_white.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_up_white.png new file mode 100644 index 000000000..bc71e23de Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_up_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_remove.png b/app/src/main/res/drawable-xxhdpi/ic_remove.png new file mode 100644 index 000000000..d35469d3c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_remove.png differ diff --git a/app/src/main/res/drawable/background_oval_black_transparent.xml b/app/src/main/res/drawable/background_oval_black_transparent.xml new file mode 100644 index 000000000..5db5969c6 --- /dev/null +++ b/app/src/main/res/drawable/background_oval_black_transparent.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml new file mode 100644 index 000000000..0258249cc --- /dev/null +++ b/app/src/main/res/drawable/ic_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_white_24dp.xml b/app/src/main/res/drawable/ic_add_white_24dp.xml new file mode 100644 index 000000000..e3979cd7f --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_brightness_high_white_72dp.xml b/app/src/main/res/drawable/ic_brightness_high_white_72dp.xml new file mode 100644 index 000000000..12d0084a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_high_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_brightness_low_white_72dp.xml b/app/src/main/res/drawable/ic_brightness_low_white_72dp.xml new file mode 100644 index 000000000..9c4f2f71e --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_low_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_brightness_medium_white_72dp.xml b/app/src/main/res/drawable/ic_brightness_medium_white_72dp.xml new file mode 100644 index 000000000..fc100086f --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_medium_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_down_white_72dp.xml b/app/src/main/res/drawable/ic_volume_down_white_72dp.xml new file mode 100644 index 000000000..a7fafb3a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_down_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_mute_white_72dp.xml b/app/src/main/res/drawable/ic_volume_mute_white_72dp.xml new file mode 100644 index 000000000..1a8ab7e86 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_mute_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_off_white_72dp.xml b/app/src/main/res/drawable/ic_volume_off_white_72dp.xml new file mode 100644 index 000000000..07f24d7aa --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_off_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_up_white_72dp.xml b/app/src/main/res/drawable/ic_volume_up_white_72dp.xml new file mode 100644 index 000000000..b2fb235a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up_white_72dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/progress_circular_white.xml b/app/src/main/res/drawable/progress_circular_white.xml new file mode 100644 index 000000000..daa6649bc --- /dev/null +++ b/app/src/main/res/drawable/progress_circular_white.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/drawer_header.xml b/app/src/main/res/layout-v21/drawer_header.xml new file mode 100644 index 000000000..918bbaf43 --- /dev/null +++ b/app/src/main/res/layout-v21/drawer_header.xml @@ -0,0 +1,69 @@ + + + +