diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a003040c8..4a3cc8254 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -34,6 +34,8 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras * Make sure your PR is up-to-date with the rest of the code. Often, a simple click on "Update branch" will do the job, but if not, you are asked to merge the master branch manually and resolve the problems on your own. That will make the maintainers' jobs way easier. * Please show intention to maintain your features and code after you contributed it. Unmaintained code is a hassle for the core developers, and just adds work. If you do not intend to maintain features you contributed, please think again about sumission, or clearly state that in the description of your PR. * Respond yourselves if someone requests changes or otherwise raises issues about your PRs. +* Check if your contributions align with the [fdroid inclusion guidelines](https://f-droid.org/en/docs/Inclusion_Policy/). +* Check if your submission can be build with the current fdroid build server setup. ## Communication 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 + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 74bb77845..52dd1f044 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip