diff --git a/app/build.gradle b/app/build.gradle index 5d718ea0e..092d2ed26 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ dependencies { exclude module: 'support-annotations' } - compile 'com.github.TeamNewPipe:NewPipeExtractor:1df3f67' + compile 'com.github.TeamNewPipe:NewPipeExtractor:7899cd1' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 67689d541..3ff55cb76 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -119,6 +119,12 @@ public class MainActivity extends AppCompatActivity implements HistoryListener { }); } + if(sharedPreferences.getBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false)) { + if (DEBUG) Log.d(TAG, "main page has changed, recreating main fragment..."); + sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, false).apply(); + NavigationHelper.openMainActivity(this); + } + } @Override diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index ce9c3802f..41e557b52 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -10,22 +10,22 @@ import org.schabi.newpipe.util.NavigationHelper; import java.util.Collection; import java.util.HashSet; -/* +/** * Copyright (C) Christian Schabesberger 2017 - * RouterActivity .java is part of NewPipe. + * RouterActivity.java is part of NewPipe. * - * OpenHitboxStreams is free software: you can redistribute it and/or modify + * NewPipe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * OpenHitboxStreams is distributed in the hope that it will be useful, + * NewPipe is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with OpenHitboxStreams. If not, see . + * along with NewPipe. If not, see . */ /** 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 5a8d8dd52..80f05585b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -50,6 +50,8 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC protected Button errorButtonRetry; protected TextView errorTextView; + protected boolean useAsFrontPage = false; + @Override public void onViewCreated(View rootView, Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); @@ -62,6 +64,10 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC wasLoading.set(isLoading.get()); } + public void useAsFrontPage(boolean value) { + useAsFrontPage = value; + } + /*////////////////////////////////////////////////////////////////////////// // Init //////////////////////////////////////////////////////////////////////////*/ 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 236f95968..317630faa 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.SharedPreferences; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.TabLayout; @@ -8,21 +9,50 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; +import android.support.v7.preference.PreferenceManager; import android.util.Log; 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.StreamingService; +import org.schabi.newpipe.extractor.kiosk.KioskList; +import org.schabi.newpipe.fragments.list.channel.ChannelFragment; +import org.schabi.newpipe.fragments.list.feed.FeedFragment; +import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.subscription.SubscriptionFragment; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.Constants; +import org.schabi.newpipe.util.KioskTranslator; import org.schabi.newpipe.util.NavigationHelper; +import java.util.concurrent.ExecutionException; + public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener { private ViewPager viewPager; + private boolean showBlankTab = false; + + private static final int FALLBACK_SERVICE_ID = 0; // Youtbe + 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"; + + public int currentServiceId = -1; + + /*////////////////////////////////////////////////////////////////////////// + // Konst + //////////////////////////////////////////////////////////////////////////*/ + + private static final int KIOSK_MENU_OFFSETT = 2000; /*////////////////////////////////////////////////////////////////////////// // Fragment's LifeCycle @@ -36,6 +66,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + currentServiceId = Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(getActivity()) + .getString(getString(R.string.current_service_key), "0")); return inflater.inflate(R.layout.fragment_main, container, false); } @@ -63,6 +95,16 @@ 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(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) { @@ -112,6 +154,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public Fragment getItem(int position) { switch (position) { + case 0: + if(PreferenceManager.getDefaultSharedPreferences(getActivity()) + .getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key)) + .equals(getString(R.string.subscription_page_key))) { + return new SubscriptionFragment(); + } else { + return getMainPageFramgent(); + } case 1: return new SubscriptionFragment(); default: @@ -126,7 +176,93 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte @Override public int getCount() { - return this.tabTitles.length; + if(PreferenceManager.getDefaultSharedPreferences(getActivity()) + .getString(getString(R.string.main_page_content_key), getString(R.string.blank_page_key)) + .equals(getString(R.string.subscription_page_key))) { + return 1; + } else { + return 2; + } + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Main page content + //////////////////////////////////////////////////////////////////////////*/ + + private Fragment getMainPageFramgent() { + 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_OFFSETT + i, Menu.NONE, + KioskTranslator.getTranslatedKioskName(ks, getContext())) + .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem menuItem) { + try { + NavigationHelper.openKioskFragment(getFragmentManager(), currentServiceId, ks); + } catch (Exception e) { + ErrorActivity.reportError(activity, e, + activity.getClass(), + null, + ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, + "none", "", R.string.app_ui_crash)); + } + return true; + } + }); + i++; } } } 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 4501ab859..48661969f 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 @@ -135,7 +135,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem @Override public void selected(StreamInfoItem selectedItem) { onItemSelected(selectedItem); - NavigationHelper.openVideoDetailFragment(getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name); + NavigationHelper.openVideoDetailFragment( + useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(), + selectedItem.service_id, selectedItem.url, selectedItem.name); } }); @@ -143,7 +145,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem @Override public void selected(ChannelInfoItem selectedItem) { onItemSelected(selectedItem); - NavigationHelper.openChannelFragment(getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name); + NavigationHelper.openChannelFragment( + useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(), + selectedItem.service_id, selectedItem.url, selectedItem.name); } }); @@ -151,7 +155,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem @Override public void selected(PlaylistInfoItem selectedItem) { onItemSelected(selectedItem); - NavigationHelper.openPlaylistFragment(getFragmentManager(), selectedItem.service_id, selectedItem.url, selectedItem.name); + NavigationHelper.openPlaylistFragment( + useAsFrontPage?getParentFragment().getFragmentManager():getFragmentManager(), + selectedItem.service_id, selectedItem.url, selectedItem.name); } }); 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 b7148fe63..daa1b62ed 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 @@ -7,6 +7,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -88,7 +89,8 @@ public class ChannelFragment extends BaseListInfoFragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_channel, container, false); + View v = inflater.inflate(R.layout.fragment_channel, container, false); + return v; } @Override @@ -109,6 +111,7 @@ public class ChannelFragment extends BaseListInfoFragment { headerTitleView = headerRootLayout.findViewById(R.id.channel_title_view); headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view); headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button); + return headerRootLayout; } @@ -118,15 +121,21 @@ public class ChannelFragment extends BaseListInfoFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.menu_channel, menu); + ActionBar supportActionBar = activity.getSupportActionBar(); + if(useAsFrontPage) { + supportActionBar.setDisplayHomeAsUpEnabled(false); + //supportActionBar.setDisplayShowTitleEnabled(false); + } else { + inflater.inflate(R.menu.menu_channel, menu); + + if (DEBUG) Log.d(TAG, "onCreateOptionsMenu() called with: menu = [" + menu + "], inflater = [" + inflater + "]"); + menuRssButton = menu.findItem(R.id.menu_item_rss); + if (currentInfo != null) { + menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.feed_url)); + } - menuRssButton = menu.findItem(R.id.menu_item_rss); - if (currentInfo != null) { - menuRssButton.setVisible(!TextUtils.isEmpty(currentInfo.feed_url)); } - } @Override diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java index 2af9a9270..379ec591a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java @@ -36,10 +36,8 @@ import io.reactivex.MaybeObserver; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; import io.reactivex.functions.Consumer; import io.reactivex.functions.Predicate; -import io.reactivex.schedulers.Schedulers; public class FeedFragment extends BaseListFragment, Void> { @@ -121,6 +119,11 @@ public class FeedFragment extends BaseListFragment, Voi if (supportActionBar != null) { supportActionBar.setTitle(R.string.fragment_whats_new); } + + if(useAsFrontPage) { + supportActionBar.setDisplayHomeAsUpEnabled(false); + //supportActionBar.setDisplayShowTitleEnabled(false); + } } @Override 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 new file mode 100644 index 000000000..b1387b1ad --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -0,0 +1,158 @@ +package org.schabi.newpipe.fragments.list.kiosk; + +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.UrlIdHandler; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.kiosk.KioskInfo; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.fragments.list.BaseListInfoFragment; +import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.util.NavigationHelper; + +import io.reactivex.Single; + +import static org.schabi.newpipe.util.AnimationUtils.animateView; + +/** + * Created by Christian Schabesberger on 23.09.17. + * + * Copyright (C) Christian Schabesberger 2017 + * KioskFragment.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class KioskFragment extends BaseListInfoFragment { + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + + private View headerRootLayout; + private TextView headerTitleView; + + public static KioskFragment getInstance(int serviceId) + throws ExtractionException { + return getInstance(serviceId, NewPipe.getService(serviceId) + .getKioskList() + .getDefaultKioskId()); + } + + public static KioskFragment getInstance(int serviceId, String kioskId) + throws ExtractionException { + KioskFragment instance = new KioskFragment(); + StreamingService service = NewPipe.getService(serviceId); + UrlIdHandler kioskTypeUrlIdHandler = service.getKioskList() + .getUrlIdHandlerByType(kioskId); + instance.setInitialData(serviceId, + kioskTypeUrlIdHandler.getUrl(kioskId), + kioskId); + return instance; + } + + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_kiosk, container, false); + } + + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + ActionBar supportActionBar = activity.getSupportActionBar(); + if (supportActionBar != null && useAsFrontPage) { + //supportActionBar.setDisplayShowTitleEnabled(false); + supportActionBar.setDisplayHomeAsUpEnabled(false); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Load and handle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public Single loadResult(boolean forceReload) { + String contentCountry = PreferenceManager + .getDefaultSharedPreferences(activity) + .getString(getString(R.string.search_language_key), + getString(R.string.default_language_value)); + return ExtractorHelper.getKioskInfo(serviceId, url, contentCountry, forceReload); + } + + @Override + public Single loadMoreItemsLogic() { + return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextItemsUrl); + } + + /*////////////////////////////////////////////////////////////////////////// + // Contract + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void showLoading() { + super.showLoading(); + animateView(itemsList, false, 100); + } + + @Override + public void handleResult(@NonNull final KioskInfo result) { + super.handleResult(result); + + String title = KioskTranslator.getTranslatedKioskName(result.id, getActivity()); + ActionBar supportActionBar = activity.getSupportActionBar(); + supportActionBar.setTitle(title); + + if (!result.errors.isEmpty()) { + showSnackBarError(result.errors, + UserAction.REQUESTED_PLAYLIST, + NewPipe.getNameOfService(result.service_id), result.url, 0); + } + } + + @Override + public void handleNextItems(ListExtractor.NextItemsResult result) { + super.handleNextItems(result); + + if (!result.errors.isEmpty()) { + showSnackBarError(result.errors, + UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(serviceId) + , "Get next page of: " + url, 0); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java index 646fe597e..afb2f078e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/subscription/SubscriptionFragment.java @@ -62,6 +62,8 @@ public class SubscriptionFragment extends BaseStateFragment. + */ + +public class SelectChannelFragment extends DialogFragment { + private SelectChannelAdapter channelAdapter; + private SubscriptionService subscriptionService; + private ImageLoader imageLoader = ImageLoader.getInstance(); + + private ProgressBar progressBar; + private TextView emptyView; + private RecyclerView recyclerView; + + private List subscriptions = new Vector<>(); + + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + + public interface OnSelectedLisener { + void onChannelSelected(String url, String name, int service); + } + OnSelectedLisener onSelectedLisener = null; + public void setOnSelectedLisener(OnSelectedLisener listener) { + onSelectedLisener = listener; + } + + public interface OnCancelListener { + void onCancel(); + } + OnCancelListener onCancelListener = null; + public void setOnCancelListener(OnCancelListener listener) { + onCancelListener = listener; + } + + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.select_channel_fragment, container, false); + recyclerView = (RecyclerView) v.findViewById(R.id.items_list); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + channelAdapter = new SelectChannelAdapter(); + recyclerView.setAdapter(channelAdapter); + + progressBar = v.findViewById(R.id.progressBar); + emptyView = v.findViewById(R.id.empty_state_view); + progressBar.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + + + subscriptionService = SubscriptionService.getInstance(); + subscriptionService.getSubscription().toObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getSubscriptionObserver()); + + return v; + } + + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCancel(final DialogInterface dialogInterface) { + super.onCancel(dialogInterface); + if(onCancelListener != null) { + onCancelListener.onCancel(); + } + } + + private void clickedItem(int position) { + if(onSelectedLisener != null) { + SubscriptionEntity entry = subscriptions.get(position); + onSelectedLisener.onChannelSelected(entry.getUrl(), entry.getName(), entry.getServiceId()); + } + dismiss(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Item handling + //////////////////////////////////////////////////////////////////////////*/ + + private void displayChannels(List subscriptions) { + this.subscriptions = subscriptions; + progressBar.setVisibility(View.GONE); + if(subscriptions.isEmpty()) { + emptyView.setVisibility(View.VISIBLE); + return; + } + recyclerView.setVisibility(View.VISIBLE); + + } + + private Observer> getSubscriptionObserver() { + return new Observer>() { + @Override + public void onSubscribe(Disposable d) { + } + + @Override + public void onNext(List subscriptions) { + displayChannels(subscriptions); + } + + @Override + public void onError(Throwable exception) { + onError(exception); + } + + @Override + public void onComplete() { + } + }; + } + + private class SelectChannelAdapter extends + RecyclerView.Adapter { + + @Override + public SelectChannelItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View item = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_channel_item, parent, false); + return new SelectChannelItemHolder(item); + } + + @Override + public void onBindViewHolder(SelectChannelItemHolder holder, final int position) { + SubscriptionEntity entry = subscriptions.get(position); + holder.titleView.setText(entry.getName()); + holder.view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + clickedItem(position); + } + }); + imageLoader.displayImage(entry.getAvatarUrl(), holder.thumbnailView, DISPLAY_IMAGE_OPTIONS); + } + + @Override + public int getItemCount() { + return subscriptions.size(); + } + + public class SelectChannelItemHolder extends RecyclerView.ViewHolder { + public SelectChannelItemHolder(View v) { + super(v); + this.view = v; + thumbnailView = v.findViewById(R.id.itemThumbnailView); + titleView = v.findViewById(R.id.itemTitleView); + } + public View view; + public CircleImageView thumbnailView; + public TextView titleView; + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + + protected boolean onError(Throwable e) { + final Activity activity = getActivity(); + ErrorActivity.reportError(activity, e, + activity.getClass(), + null, + ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, + "none", "", R.string.app_ui_crash)); + return true; + } + + + /*////////////////////////////////////////////////////////////////////////// + // ImageLoaderOptions + //////////////////////////////////////////////////////////////////////////*/ + + /** + * Base display options + */ + public static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS = + new DisplayImageOptions.Builder() + .cacheInMemory(true) + .build(); +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java new file mode 100644 index 000000000..a9f0a53ed --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -0,0 +1,192 @@ +package org.schabi.newpipe.settings; + +import android.app.Activity; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +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.database.subscription.SubscriptionEntity; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.fragments.subscription.SubscriptionService; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.KioskTranslator; +import org.schabi.newpipe.util.ServiceIconMapper; + +import java.util.List; +import java.util.Vector; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by Christian Schabesberger on 09.10.17. + * SelectKioskFragment.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class SelectKioskFragment extends DialogFragment { + + RecyclerView recyclerView = null; + SelectKioskAdapter selectKioskAdapter = null; + + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + + public interface OnSelectedLisener { + void onKioskSelected(String kioskId, int service_id); + } + + OnSelectedLisener onSelectedLisener = null; + public void setOnSelectedLisener(OnSelectedLisener listener) { + onSelectedLisener = listener; + } + + public interface OnCancelListener { + void onCancel(); + } + OnCancelListener onCancelListener = null; + public void setOnCancelListener(OnCancelListener listener) { + onCancelListener = listener; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.select_kiosk_fragment, container, false); + recyclerView = (RecyclerView) v.findViewById(R.id.items_list); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + try { + selectKioskAdapter = new SelectKioskAdapter(); + } catch (Exception e) { + onError(e); + } + recyclerView.setAdapter(selectKioskAdapter); + + return v; + } + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCancel(final DialogInterface dialogInterface) { + super.onCancel(dialogInterface); + if(onCancelListener != null) { + onCancelListener.onCancel(); + } + } + + private void clickedItem(SelectKioskAdapter.Entry entry) { + if(onSelectedLisener != null) { + onSelectedLisener.onKioskSelected(entry.kioskId, entry.serviceId); + } + dismiss(); + } + + private class SelectKioskAdapter + extends RecyclerView.Adapter { + public class Entry { + public Entry (int i, int si, String ki, String kn){ + icon = i; serviceId=si; kioskId=ki; kioskName = kn; + } + int icon; + int serviceId; + String kioskId; + String kioskName; + } + + private List kioskList = new Vector<>(); + + public SelectKioskAdapter() + throws Exception { + + for(StreamingService service : NewPipe.getServices()) { + for(String kioskId : service.getKioskList().getAvailableKiosks()) { + String name = String.format(getString(R.string.service_kosk_string), + service.getServiceInfo().name, + KioskTranslator.getTranslatedKioskName(kioskId, getContext())); + kioskList.add(new Entry( + //ServiceIconMapper.getIconResource(service.getServiceId()), + ServiceIconMapper.getIconResource(-1), + service.getServiceId(), + kioskId, + name)); + } + } + } + + public int getItemCount() { + //todo: uncommend this line on multyservice support + //return kioskList.size(); + return 1; + } + + public SelectKioskItemHolder onCreateViewHolder(ViewGroup parent, int type) { + View item = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_kiosk_item, parent, false); + return new SelectKioskItemHolder(item); + } + + public class SelectKioskItemHolder extends RecyclerView.ViewHolder { + public SelectKioskItemHolder(View v) { + super(v); + this.view = v; + thumbnailView = v.findViewById(R.id.itemThumbnailView); + titleView = v.findViewById(R.id.itemTitleView); + } + public View view; + public ImageView thumbnailView; + public TextView titleView; + } + + public void onBindViewHolder(SelectKioskItemHolder holder, final int position) { + final Entry entry = kioskList.get(position); + holder.titleView.setText(entry.kioskName); + holder.thumbnailView.setImageDrawable(ContextCompat.getDrawable(getContext(), entry.icon)); + holder.view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + clickedItem(entry); + } + }); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + + protected boolean onError(Throwable e) { + final Activity activity = getActivity(); + ErrorActivity.reportError(activity, e, + activity.getClass(), + null, + ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, + "none", "", R.string.app_ui_crash)); + return true; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/Constants.java b/app/src/main/java/org/schabi/newpipe/util/Constants.java index b31a95cca..a6aec96e2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Constants.java +++ b/app/src/main/java/org/schabi/newpipe/util/Constants.java @@ -9,6 +9,7 @@ public class Constants { public static final String KEY_QUERY = "key_query"; public static final String KEY_THEME_CHANGE = "key_theme_change"; + public static final String KEY_MAIN_PAGE_CHANGE = "key_main_page_change"; public static final int NO_SERVICE_ID = -1; } 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 0dd2c00ab..4763b6a02 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -26,6 +26,7 @@ import org.schabi.newpipe.extractor.Info; import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.kiosk.KioskInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchResult; @@ -138,6 +139,24 @@ public final class ExtractorHelper { }); } + public static Single getKioskInfo(final int serviceId, final String url, final String contentCountry, boolean forceLoad) { + return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { + @Override + public KioskInfo call() throws Exception { + return KioskInfo.getInfo(NewPipe.getService(serviceId), url, contentCountry); + } + })); + } + + public static Single getMoreKioskItems(final int serviceId, final String url, final String nextStreamsUrl) { + return Single.fromCallable(new Callable() { + @Override + public NextItemsResult call() throws Exception { + return KioskInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl); + } + }); + } + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java new file mode 100644 index 000000000..4740b82e0 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/KioskTranslator.java @@ -0,0 +1,38 @@ +package org.schabi.newpipe.util; + +import android.content.Context; + +import org.schabi.newpipe.R; + +/** + * Created by Chrsitian Schabesberger on 28.09.17. + * KioskTranslator.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class KioskTranslator { + public static String getTranslatedKioskName(String kioskId, Context c) { + switch(kioskId) { + case "Trending": + return c.getString(R.string.trending); + case "Top 50": + return c.getString(R.string.top_50); + case "New & hot": + return c.getString(R.string.new_and_hot); + default: + return kioskId; + } + } +} 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 538675685..b08251436 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -23,6 +23,7 @@ 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.fragments.list.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.history.HistoryActivity; @@ -93,7 +94,7 @@ public class NavigationHelper { if (!popped) openMainFragment(fragmentManager); } - private static void openMainFragment(FragmentManager fragmentManager) { + public static void openMainFragment(FragmentManager fragmentManager) { InfoCache.getInstance().trimCache(); fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); @@ -163,6 +164,15 @@ public class NavigationHelper { .commit(); } + public static void openKioskFragment(FragmentManager fragmentManager, int serviceId, String kioskId) + throws ExtractionException { + fragmentManager.beginTransaction() + .setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out, R.animator.custom_fade_in, R.animator.custom_fade_out) + .replace(R.id.fragment_holder, KioskFragment.getInstance(serviceId, kioskId)) + .addToBackStack(null) + .commit(); + } + /*////////////////////////////////////////////////////////////////////////// // Through Intents //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceIconMapper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceIconMapper.java new file mode 100644 index 000000000..060013dd2 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceIconMapper.java @@ -0,0 +1,35 @@ +package org.schabi.newpipe.util; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; + +/** + * Created by Chrsitian Schabesberger on 09.10.17. + * ServiceIconMapper.java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class ServiceIconMapper { + public static int getIconResource(int service_id) { + switch(service_id) { + case 0: + return R.drawable.youtube; + case 1: + return R.drawable.soud_cloud; + default: + return R.drawable.service; + } + } +} diff --git a/app/src/main/res/drawable-nodpi/service.png b/app/src/main/res/drawable-nodpi/service.png new file mode 100644 index 000000000..cfaff19e2 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/service.png differ diff --git a/app/src/main/res/drawable-nodpi/soud_cloud.png b/app/src/main/res/drawable-nodpi/soud_cloud.png new file mode 100644 index 000000000..0fa6045d5 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/soud_cloud.png differ diff --git a/app/src/main/res/drawable-nodpi/youtube.png b/app/src/main/res/drawable-nodpi/youtube.png new file mode 100644 index 000000000..82aa58ff1 Binary files /dev/null and b/app/src/main/res/drawable-nodpi/youtube.png differ diff --git a/app/src/main/res/layout/fragment_kiosk.xml b/app/src/main/res/layout/fragment_kiosk.xml new file mode 100644 index 000000000..a85c30cb1 --- /dev/null +++ b/app/src/main/res/layout/fragment_kiosk.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/select_channel_fragment.xml b/app/src/main/res/layout/select_channel_fragment.xml new file mode 100644 index 000000000..11c723b4b --- /dev/null +++ b/app/src/main/res/layout/select_channel_fragment.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml new file mode 100644 index 000000000..09602a371 --- /dev/null +++ b/app/src/main/res/layout/select_channel_item.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/select_kiosk_fragment.xml b/app/src/main/res/layout/select_kiosk_fragment.xml new file mode 100644 index 000000000..8e376742b --- /dev/null +++ b/app/src/main/res/layout/select_kiosk_fragment.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/select_kiosk_item.xml b/app/src/main/res/layout/select_kiosk_item.xml new file mode 100644 index 000000000..2efadca79 --- /dev/null +++ b/app/src/main/res/layout/select_kiosk_item.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index be3548532..02402e2e8 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -20,6 +20,6 @@ + android:title="@string/action_about"/> \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 32e300ca1..1c5265b8b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -67,4 +67,6 @@ 24dp 28dp + + 30sp diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 76cd10681..dabbfb40d 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -1,5 +1,8 @@ + + current_service + download_path download_path_audio @@ -89,6 +92,7 @@ @string/black_theme_title + show_search_suggestions show_play_with_kodi show_next_video @@ -98,6 +102,23 @@ use_tor enable_search_history enable_watch_history + main_page_content + blank_page + feed_page + subscription_page_key + kiosk_page + channel_page + + @string/blank_page_key + @string/kiosk_page_key + @string/feed_page_key + @string/subscription_page_key + @string/channel_page_key + + main_page_selected_service + main_page_selected_channel_name + main_page_selected_channel_url + main_page_selectd_kiosk_id file_rename diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e798e62e9..94b185988 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -266,4 +266,29 @@ History cleared Item deleted Do you want to delete this item from search history? + + + Content of main page + Blank Page + Kiosk Page + Subscription Page + Feed Page + Channel Page + + @string/blank_page_summary + @string/kiosk_page_summary + @string/feed_page_summary + @string/subscription_page_summary + @string/channel_page_summary + + Select a channel + No channel subscribed yet + Select a kiosk + + + Kiosk + Trending + Top 50 + New & hot + %1$s/%2$s diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 63cd3cd01..11672671e 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -21,5 +21,12 @@ android:key="@string/show_search_suggestions_key" android:summary="@string/show_search_suggestions_summary" android:title="@string/show_search_suggestions_title"/> + diff --git a/assets/service.svg b/assets/service.svg new file mode 100644 index 000000000..172cc106f --- /dev/null +++ b/assets/service.svg @@ -0,0 +1,109 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + +