diff --git a/app/build.gradle b/app/build.gradle index 1bf718ab..a895eb85 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,6 +73,8 @@ dependencies { implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:support-annotations:28.0.0' + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' implementation 'com.getbase:floatingactionbutton:1.10.1' implementation 'com.nispok:snackbar:2.11.0' implementation 'com.github.aegnor:rencode-java:cb628e824e' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 31d29570..b1ec99be 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -248,18 +248,14 @@ - diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java index a8f7e6e5..054f140a 100644 --- a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java @@ -82,8 +82,7 @@ import org.transdroid.core.gui.navigation.NavigationFilter; import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.RefreshableActivity; import org.transdroid.core.gui.navigation.StatusType; -import org.transdroid.core.gui.remoterss.RemoteRssActivity_; -import org.transdroid.core.gui.rss.RssfeedsActivity_; +import org.transdroid.core.gui.rss.RssFeedsActivity_; import org.transdroid.core.gui.search.FilePickerHelper; import org.transdroid.core.gui.search.UrlEntryDialog; import org.transdroid.core.gui.settings.MainSettingsActivity_; @@ -471,7 +470,6 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE filterSearch.setVisibility(View.GONE); torrentsToolbar.getMenu().findItem(R.id.action_search).setVisible(false); torrentsToolbar.getMenu().findItem(R.id.action_rss).setVisible(false); - torrentsToolbar.getMenu().findItem(R.id.action_remoterss).setVisible(false); torrentsToolbar.getMenu().findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(true); actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(false); @@ -495,12 +493,10 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE filtersList.setVisibility(View.VISIBLE); filterSearch.setVisibility(View.VISIBLE); boolean addByFile = Daemon.supportsAddByFile(currentConnection.getType()); - boolean hasRemoteRss = Daemon.supportsRemoteRssManagement(currentConnection.getType()); addmenuFileButton.setVisibility(addByFile ? View.VISIBLE : View.GONE); // Primary toolbar menu torrentsToolbar.getMenu().findItem(R.id.action_search).setVisible(navigationHelper.enableSearchUi()); torrentsToolbar.getMenu().findItem(R.id.action_rss).setVisible(navigationHelper.enableRssUi()); - torrentsToolbar.getMenu().findItem(R.id.action_remoterss).setVisible(hasRemoteRss); torrentsToolbar.getMenu().findItem(R.id.action_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(false); // Secondary toolbar menu @@ -815,7 +811,7 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE @OptionsItem(R.id.action_rss) protected void openRss() { - RssfeedsActivity_.intent(this).start(); + RssFeedsActivity_.intent(this).start(); } @OptionsItem(R.id.action_settings) @@ -823,14 +819,6 @@ public class TorrentsActivity extends AppCompatActivity implements TorrentTasksE MainSettingsActivity_.intent(this).start(); } - @OptionsItem(R.id.action_remoterss) - @Background - protected void openRemoteRss() { - // Passing the items over as a feed can overload the Intent size limit and crash without a stack trace. - // Loading the items can take a while so we don't want to load them here and again in the RemoteRssActivity. - RemoteRssActivity_.intent(this).start(); - } - @OptionsItem(R.id.action_help) protected void openHelp() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/download/"))); diff --git a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssActivity.java b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssActivity.java deleted file mode 100644 index 7b390c15..00000000 --- a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssActivity.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright 2010-2018 Eric Kok et al. - * - * Transdroid 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. - * - * Transdroid 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 Transdroid. If not, see . - */ -package org.transdroid.core.gui.remoterss; - -import android.annotation.TargetApi; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.widget.LinearLayout; -import android.widget.ListView; - -import com.nispok.snackbar.Snackbar; -import com.nispok.snackbar.SnackbarManager; -import com.nispok.snackbar.enums.SnackbarType; - -import org.androidannotations.annotations.AfterViews; -import org.androidannotations.annotations.Background; -import org.androidannotations.annotations.Bean; -import org.androidannotations.annotations.EActivity; -import org.androidannotations.annotations.FragmentById; -import org.androidannotations.annotations.InstanceState; -import org.androidannotations.annotations.ItemClick; -import org.androidannotations.annotations.NonConfigurationInstance; -import org.androidannotations.annotations.OptionsItem; -import org.androidannotations.annotations.UiThread; -import org.androidannotations.annotations.ViewById; -import org.transdroid.R; -import org.transdroid.core.app.settings.ApplicationSettings; -import org.transdroid.core.app.settings.ServerSetting; -import org.transdroid.core.app.settings.SettingsUtils; -import org.transdroid.core.gui.lists.LocalTorrent; -import org.transdroid.core.gui.lists.SimpleListItemAdapter; -import org.transdroid.core.gui.log.Log; -import org.transdroid.core.gui.navigation.RefreshableActivity; -import org.transdroid.core.gui.remoterss.data.RemoteRssChannel; -import org.transdroid.core.gui.remoterss.data.RemoteRssItem; -import org.transdroid.core.gui.remoterss.data.RemoteRssSupplier; -import org.transdroid.core.service.ConnectivityHelper; -import org.transdroid.daemon.DaemonException; -import org.transdroid.daemon.IDaemonAdapter; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.List; - -/** - * An activity that displays a list of {@link RemoteRssItem}s via an instance of {@link RemoteRssFragment}. - * The activity manages the drawer to filter items by the feed they came through. - * - * By default it displays the latest items within the last month. - * - * @author Twig Nguyen - */ -@EActivity(R.layout.activity_remoterss) -public class RemoteRssActivity extends AppCompatActivity implements RefreshableActivity { - @NonConfigurationInstance - protected ArrayList feeds; - - @InstanceState - protected int selectedFilter; - - @NonConfigurationInstance - protected ArrayList recentItems; - - // Server connection - @Bean - protected ApplicationSettings applicationSettings; - @Bean - protected Log log; - @Bean - protected ConnectivityHelper connectivityHelper; - private IDaemonAdapter currentConnection; - - // Details view components - @ViewById - protected DrawerLayout drawerLayout; - @ViewById - protected LinearLayout drawerContainer; - - @ViewById - protected Toolbar torrentsToolbar; - - @ViewById - protected ListView drawerList; - - @FragmentById(R.id.remoterss_fragment) - protected RemoteRssFragment fragmentRemoteRss; - - @Override - public void onCreate(Bundle savedInstanceState) { - SettingsUtils.applyDayNightTheme(this); - super.onCreate(savedInstanceState); - } - - @AfterViews - protected void init() { - // Simple action bar with up, torrent name as title and refresh button - torrentsToolbar.setNavigationIcon(R.drawable.ic_action_drawer); - setSupportActionBar(torrentsToolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - // Connect to the last used server - ServerSetting lastUsed = applicationSettings.getLastUsedServer(); - currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); - - if (feeds != null) { - // Called from a configuration change. No need to load anything from server - showChannelFilters(); - fragmentRemoteRss.updateRemoteItems( - selectedFilter == 0 ? recentItems : feeds.get(selectedFilter - 1).getItems(), - false /* allow android to restore scroll position */ ); - } else { - loadFeeds(); - } - - } - - @Background - protected void loadFeeds() { - try { - fragmentRemoteRss.setRefreshing(true); - feeds = ((RemoteRssSupplier) (currentConnection)).getRemoteRssChannels(log); - fragmentRemoteRss.setRefreshing(false); - } catch (DaemonException e) { - onCommunicationError(e); - } - - recentItems = new ArrayList<>(); - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.MONTH, -1); - Date oneMonthAgo = calendar.getTime(); - - for (RemoteRssChannel feed : feeds) { - for (RemoteRssItem item : feed.getItems()) { - if (item.getTimestamp().after(oneMonthAgo)) { - recentItems.add(item); - } - } - } - // Sort by -newest - Collections.sort(recentItems, new Comparator() { - @Override - public int compare(RemoteRssItem lhs, RemoteRssItem rhs) { - return rhs.getTimestamp().compareTo(lhs.getTimestamp()); - } - }); - - afterLoadFeeds(); - } - - @UiThread - protected void afterLoadFeeds() { - // We need to wrap these calls in a @UiThread rather than make them each a @UiThread themselves - // because they need to run sequentially in a configuration change scenario in order for Android - // to maintain scroll position on the fragment adapter. - showChannelFilters(); - onFeedSelected(selectedFilter); - } - - @UiThread - protected void onCommunicationError(DaemonException daemonException) { - //noinspection ThrowableResultOfMethodCallIgnored - log.i(this, daemonException.toString()); - String error = getString(LocalTorrent.getResourceForDaemonException(daemonException)); - SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE)); - } - - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - @OptionsItem(android.R.id.home) - protected void navigateUp() { - if (drawerLayout.isDrawerOpen(drawerContainer)) { - drawerLayout.closeDrawers(); - } else { - drawerLayout.openDrawer(drawerContainer); - } - } - - @Override - public void onBackPressed() { - if (drawerLayout.isDrawerOpen(drawerContainer)) { - drawerLayout.closeDrawers(); - } else { - finish(); - } - } - - private void showChannelFilters() { - List feedLabels = new ArrayList<>(feeds.size() +1); - feedLabels.add(new RemoteRssChannel() { - @Override - public String getName() { - return getString(R.string.remoterss_filter_allrecent); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } - }); - feedLabels.addAll(feeds); - - drawerList.setAdapter(new SimpleListItemAdapter(this, feedLabels)); - } - - @ItemClick(R.id.drawer_list) - protected void onFeedSelected(int position) { - selectedFilter = position; - fragmentRemoteRss.updateRemoteItems(position == 0 ? recentItems : feeds.get(position - 1).getItems(), true); - - RemoteRssChannel channel = (RemoteRssChannel) drawerList.getAdapter().getItem(position); - getSupportActionBar().setSubtitle(channel.getName()); - - drawerLayout.closeDrawers(); - } - - public IDaemonAdapter getCurrentConnection() { - return currentConnection; - } - - public RemoteRssChannel getChannel(String name) { - for (RemoteRssChannel feed : feeds) { - if (feed.getName().equals(name)) { - return feed; - } - } - return null; - } - - @Override - public void refreshScreen() { - loadFeeds(); - } -} diff --git a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java index fe51e925..43cb2e87 100644 --- a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java @@ -17,32 +17,26 @@ package org.transdroid.core.gui.remoterss; -import android.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.widget.ActionMenuView; +import android.support.v4.app.Fragment; import android.view.View; +import android.widget.ArrayAdapter; import android.widget.ListView; +import android.widget.Spinner; import android.widget.TextView; -import com.nispok.snackbar.Snackbar; -import com.nispok.snackbar.SnackbarManager; -import com.nispok.snackbar.enums.SnackbarType; - import org.androidannotations.annotations.AfterViews; -import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.ItemClick; -import org.androidannotations.annotations.UiThread; +import org.androidannotations.annotations.ItemSelect; +import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.ViewById; import org.transdroid.R; -import org.transdroid.core.gui.lists.LocalTorrent; import org.transdroid.core.gui.log.Log; -import org.transdroid.core.gui.navigation.RefreshableActivity; +import org.transdroid.core.gui.remoterss.data.RemoteRssChannel; import org.transdroid.core.gui.remoterss.data.RemoteRssItem; -import org.transdroid.core.gui.remoterss.data.RemoteRssSupplier; -import org.transdroid.daemon.DaemonException; -import org.transdroid.daemon.task.DaemonTaskSuccessResult; +import org.transdroid.core.gui.rss.RssFeedsActivity; +import org.transdroid.core.gui.settings.MainSettingsActivity_; import java.util.ArrayList; import java.util.List; @@ -63,72 +57,74 @@ public class RemoteRssFragment extends Fragment { // Views @ViewById protected View detailsContainer; - @ViewById(R.id.contextual_menu) - protected ActionMenuView contextualMenu; - @ViewById - protected SwipeRefreshLayout swipeRefreshLayout; + @ViewById(R.id.remoterss_filter) + protected Spinner remoteRssFilter; @ViewById protected ListView torrentsList; - @ViewById - protected TextView remoterssStatusMessage; + @ViewById(R.id.remoterss_status_message) + protected TextView remoteRssStatusMessage; - protected RemoteRssItemsAdapter adapter; @AfterViews protected void init() { - // Inject menu options in the actions toolbar setHasOptionsMenu(true); -// // On large screens where this fragment is shown next to the torrents list, we show a continues grey vertical -// // line to separate the lists visually -// if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) { -// if (SystemSettings_.getInstance_(getActivity()).useDarkTheme()) { -// detailsContainer.setBackgroundResource(R.drawable.details_list_background_dark); -// } else { -// detailsContainer.setBackgroundResource(R.drawable.details_list_background_light); -// } -// } - // Set up details adapter - adapter = new RemoteRssItemsAdapter(getActivity()); + RemoteRssItemsAdapter adapter = new RemoteRssItemsAdapter(getActivity()); torrentsList.setAdapter(adapter); torrentsList.setFastScrollEnabled(true); + } - // Allow pulls on the list view to refresh the torrents - if (getActivity() != null && getActivity() instanceof RefreshableActivity) { - swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - ((RefreshableActivity) getActivity()).refreshScreen(); - } - }); - } + @Override + public void onResume() { + super.onResume(); + this.refreshScreen(); + } + + @OptionsItem(R.id.action_refresh) + protected void refreshScreen() { + RssFeedsActivity rssActivity = (RssFeedsActivity) getActivity(); + rssActivity.refreshRemoteFeeds(); + } + + @OptionsItem(R.id.action_settings) + protected void openSettings() { + MainSettingsActivity_.intent(getActivity()).start(); } /** * Updates the UI with a new list of RSS items. */ public void updateRemoteItems(List remoteItems, boolean scrollToTop) { + RemoteRssItemsAdapter adapter = (RemoteRssItemsAdapter) torrentsList.getAdapter(); + remoteRssItems = new ArrayList<>(remoteItems); adapter.updateItems(remoteRssItems); + if (scrollToTop) { torrentsList.smoothScrollToPosition(0); } // Show/hide a nice message if there are no items to show if (remoteRssItems.size() > 0) { - remoterssStatusMessage.setVisibility(View.GONE); + remoteRssStatusMessage.setVisibility(View.GONE); } else { - remoterssStatusMessage.setVisibility(View.VISIBLE); - remoterssStatusMessage.setText(R.string.remoterss_no_files); + remoteRssStatusMessage.setVisibility(View.VISIBLE); + remoteRssStatusMessage.setText(R.string.remoterss_no_files); } - swipeRefreshLayout.setRefreshing(false); } - @UiThread - public void setRefreshing(boolean refreshing) { - swipeRefreshLayout.setRefreshing(refreshing); + public void updateChannelFilters(List feedLabels) { + List labels = new ArrayList<>(); + + for (RemoteRssChannel feedLabel : feedLabels) { + labels.add(feedLabel.getName()); + } + + ArrayAdapter adapter = new ArrayAdapter<>(this.getContext(), android.R.layout.simple_spinner_dropdown_item, labels); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + remoteRssFilter.setAdapter(adapter); } /** @@ -136,37 +132,14 @@ public class RemoteRssFragment extends Fragment { */ @ItemClick(resName = "torrents_list") protected void detailsListClicked(int position) { + RemoteRssItemsAdapter adapter = (RemoteRssItemsAdapter) torrentsList.getAdapter(); RemoteRssItem item = (RemoteRssItem) adapter.getItem(position); - downloadRemoteRssItem(item); - } - - /** - * Download the item in a background thread and display success/fail accordingly. - */ - @Background - protected void downloadRemoteRssItem(RemoteRssItem item) { - final RemoteRssActivity activity = (RemoteRssActivity) getActivity(); - final RemoteRssSupplier supplier = (RemoteRssSupplier) activity.getCurrentConnection(); - - try { - supplier.downloadRemoteRssItem(log, item, activity.getChannel(item.getSourceName())); - onTaskSucceeded(null, getString(R.string.result_added, item.getTitle())); - } catch (DaemonException e) { - onTaskFailed(getString(LocalTorrent.getResourceForDaemonException(e))); - } - } - @UiThread - protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) { - SnackbarManager.show(Snackbar.with(getActivity()).text(successMessage)); + ((RssFeedsActivity) getActivity()).downloadRemoteRssItem(item); } - @UiThread - protected void onTaskFailed(String message) { - SnackbarManager.show(Snackbar.with(getActivity()) - .text(message) - .colorResource(R.color.red) - .type(SnackbarType.MULTI_LINE) - ); + @ItemSelect(R.id.remoterss_filter) + protected void onFeedSelected(boolean selected, int position) { + ((RssFeedsActivity) getActivity()).onFeedSelected(position); } } diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java new file mode 100644 index 00000000..8fc448ce --- /dev/null +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java @@ -0,0 +1,424 @@ +/* + * Copyright 2010-2018 Eric Kok et al. + * + * Transdroid 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. + * + * Transdroid 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 Transdroid. If not, see . + */ +package org.transdroid.core.gui.rss; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcel; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; + +import com.nispok.snackbar.Snackbar; +import com.nispok.snackbar.SnackbarManager; +import com.nispok.snackbar.enums.SnackbarType; + +import org.androidannotations.annotations.AfterViews; +import org.androidannotations.annotations.Background; +import org.androidannotations.annotations.Bean; +import org.androidannotations.annotations.EActivity; +import org.androidannotations.annotations.FragmentById; +import org.androidannotations.annotations.InstanceState; +import org.androidannotations.annotations.NonConfigurationInstance; +import org.androidannotations.annotations.OptionsItem; +import org.androidannotations.annotations.UiThread; +import org.androidannotations.annotations.ViewById; +import org.transdroid.R; +import org.transdroid.core.app.settings.ApplicationSettings; +import org.transdroid.core.app.settings.RssfeedSetting; +import org.transdroid.core.app.settings.ServerSetting; +import org.transdroid.core.app.settings.SettingsUtils; +import org.transdroid.core.gui.TorrentsActivity_; +import org.transdroid.core.gui.lists.LocalTorrent; +import org.transdroid.core.gui.log.Log; +import org.transdroid.core.gui.navigation.NavigationHelper; +import org.transdroid.core.gui.remoterss.RemoteRssFragment; +import org.transdroid.core.gui.remoterss.data.RemoteRssChannel; +import org.transdroid.core.gui.remoterss.data.RemoteRssItem; +import org.transdroid.core.gui.remoterss.data.RemoteRssSupplier; +import org.transdroid.core.rssparser.Channel; +import org.transdroid.core.rssparser.RssParser; +import org.transdroid.core.service.ConnectivityHelper; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.DaemonException; +import org.transdroid.daemon.IDaemonAdapter; +import org.transdroid.daemon.task.DaemonTaskSuccessResult; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +@EActivity(R.layout.activity_rssfeeds) +public class RssFeedsActivity extends AppCompatActivity { + + // Settings and local data + @Bean + protected Log log; + @Bean + protected ApplicationSettings applicationSettings; + + protected static final int RSS_FEEDS_LOCAL = 0; + protected static final int RSS_FEEDS_REMOTE = 1; + + @FragmentById(R.id.rssfeeds_fragment) + protected RssFeedsFragment fragmentLocalFeeds; + @FragmentById(R.id.rssitems_fragment) + protected RssItemsFragment fragmentItems; + @FragmentById(R.id.remoterss_fragment) + protected RemoteRssFragment fragmentRemoteFeeds; + + @ViewById(R.id.rssfeeds_toolbar) + protected Toolbar rssFeedsToolbar; + @ViewById(R.id.rssfeeds_tabs) + protected TabLayout tabLayout; + @ViewById(R.id.rssfeeds_pager) + protected ViewPager viewPager; + + // remote RSS stuff + @NonConfigurationInstance + protected ArrayList feeds; + @InstanceState + protected int selectedFilter; + @NonConfigurationInstance + protected ArrayList recentItems; + @Bean + protected ConnectivityHelper connectivityHelper; + + + protected class LayoutPagerAdapter extends PagerAdapter { + boolean hasRemoteRss; + String serverName; + + public LayoutPagerAdapter(boolean hasRemoteRss, String name) { + super(); + + this.hasRemoteRss = hasRemoteRss; + this.serverName = (name.length() > 0 ? name : getString(R.string.navigation_rss_tabs_remote)); + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + int resId = 0; + + if (position == RSS_FEEDS_LOCAL) { + resId = R.id.layout_rssfeeds_local; + } + else if (position == RSS_FEEDS_REMOTE) { + resId = R.id.layout_rss_feeds_remote; + } + + return findViewById(resId); + } + + @Override + public int getCount() { + return (this.hasRemoteRss ? 2 : 1); + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { + return (view == o); + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + container.removeView((View) object); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case RSS_FEEDS_LOCAL: + return getString(R.string.navigation_rss_tabs_local); + case RSS_FEEDS_REMOTE: + return this.serverName; + } + + return super.getPageTitle(position); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + SettingsUtils.applyDayNightTheme(this); + super.onCreate(savedInstanceState); + } + + @AfterViews + protected void init() { + setSupportActionBar(rssFeedsToolbar); + getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.rss_feeds))); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + IDaemonAdapter currentConnection = this.getCurrentConnection(); + boolean hasRemoteRss = Daemon.supportsRemoteRssManagement(currentConnection.getType()); + + PagerAdapter pagerAdapter = new LayoutPagerAdapter(hasRemoteRss, currentConnection.getSettings().getName()); + viewPager.setAdapter(pagerAdapter); + tabLayout.setupWithViewPager(viewPager); + viewPager.setCurrentItem(0); + + if (!hasRemoteRss) { + tabLayout.setVisibility(View.GONE); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @OptionsItem(android.R.id.home) + protected void navigateUp() { + TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); + } + + /** + * Reload the RSS feed settings and start loading all the feeds. To be called from contained fragments. + */ + public void refreshFeeds() { + List loaders = new ArrayList<>(); + // For each RSS feed setting the user created, start a loader that retrieved the RSS feed (via a background + // thread) and, on success, determines the new items in the feed + for (RssfeedSetting setting : applicationSettings.getRssfeedSettings()) { + RssfeedLoader loader = new RssfeedLoader(setting); + loaders.add(loader); + loadRssfeed(loader); + } + + fragmentLocalFeeds.update(loaders); + } + + /** + * Performs the loading of the RSS feed content and parsing of items, in a background thread. + * @param loader The RSS feed loader for which to retrieve the contents + */ + @Background + protected void loadRssfeed(RssfeedLoader loader) { + try { + // Load and parse the feed + RssParser parser = + new RssParser(loader.getSetting().getUrl(), loader.getSetting().getExcludeFilter(), loader.getSetting().getIncludeFilter()); + parser.parse(); + handleRssfeedResult(loader, parser.getChannel(), false); + } catch (Exception e) { + // Catch any error that may occurred and register this failure + handleRssfeedResult(loader, null, true); + log.i(this, "RSS feed " + loader.getSetting().getUrl() + " error: " + e.toString()); + } + } + + /** + * Stores the retrieved RSS feed content channel into the loader and updates the RSS feed in the feeds list fragment. + * @param loader The RSS feed loader that was executed + * @param channel The data that was retrieved, or null if it could not be parsed + * @param hasError True if a connection error occurred in the loading of the feed; false otherwise + */ + @UiThread + protected void handleRssfeedResult(RssfeedLoader loader, Channel channel, boolean hasError) { + loader.update(channel, hasError); + + fragmentLocalFeeds.notifyDataSetChanged(); + } + + /** + * Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssItemsActivity}. Optionally this also registers in + * the user preferences that the feed was now viewed, so that in the future the new items can be properly marked. + * @param loader The RSS feed loader (with settings and the loaded content channel) to show + * @param markAsViewedNow True if the user settings should be updated to reflect this feed's last viewed date; false otherwise + */ + public void openRssfeed(RssfeedLoader loader, boolean markAsViewedNow) { + + // The RSS feed content was loaded and can now be shown in the dedicated fragment or a new activity + if (fragmentItems != null && fragmentItems.isAdded()) { + + // If desired, update the lastViewedDate and lastViewedItemUrl of this feed in the user setting; this won't + // be loaded until the RSS feeds screen in opened again. + if (!loader.hasError() && loader.getChannel() != null && markAsViewedNow) { + String lastViewedItemUrl = null; + if (loader.getChannel().getItems() != null && loader.getChannel().getItems().size() > 0) { + lastViewedItemUrl = loader.getChannel().getItems().get(0).getTheLink(); + } + applicationSettings.setRssfeedLastViewer(loader.getSetting().getOrder(), new Date(), lastViewedItemUrl); + } + fragmentItems.update(loader.getChannel(), loader.hasError(), loader.getSetting().requiresExternalAuthentication()); + + } else { + + // Error message or not yet loaded? Show a toast message instead of opening the items activity + if (loader.hasError()) { + SnackbarManager.show(Snackbar.with(this).text(R.string.rss_error).colorResource(R.color.red)); + return; + } + if (loader.getChannel() == null || loader.getChannel().getItems().size() == 0) { + SnackbarManager.show(Snackbar.with(this).text(R.string.rss_notloaded).colorResource(R.color.red)); + return; + } + + // If desired, update the lastViewedDate and lastViewedItemUrl of this feed in the user setting; this won't + // be loaded until the RSS feeds screen in opened again + if (markAsViewedNow) { + String lastViewedItemUrl = null; + if (loader.getChannel().getItems() != null && loader.getChannel().getItems().size() > 0) { + lastViewedItemUrl = loader.getChannel().getItems().get(0).getTheLink(); + } + applicationSettings.setRssfeedLastViewer(loader.getSetting().getOrder(), new Date(), lastViewedItemUrl); + } + + String name = loader.getChannel().getTitle(); + if (TextUtils.isEmpty(name)) { + name = loader.getSetting().getName(); + } + if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(loader.getSetting().getUrl())) { + name = Uri.parse(loader.getSetting().getUrl()).getHost(); + } + RssItemsActivity_.intent(this).rssfeed(loader.getChannel()).rssfeedName(name) + .requiresExternalAuthentication(loader.getSetting().requiresExternalAuthentication()).start(); + + } + } + + protected IDaemonAdapter getCurrentConnection() { + ServerSetting lastUsed = applicationSettings.getLastUsedServer(); + return lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); + } + + // @Background + public void refreshRemoteFeeds() { + // Connect to the last used server + IDaemonAdapter currentConnection = this.getCurrentConnection(); + + // remote rss not supported for this connection type + if (currentConnection instanceof RemoteRssSupplier == false) { + return; + } + + try { + feeds = ((RemoteRssSupplier) (currentConnection)).getRemoteRssChannels(log); + + // By default it displays the latest items within the last month. + recentItems = new ArrayList<>(); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MONTH, -1); + Date oneMonthAgo = calendar.getTime(); + + for (RemoteRssChannel feed : feeds) { + for (RemoteRssItem item : feed.getItems()) { + if (item.getTimestamp().after(oneMonthAgo)) { + recentItems.add(item); + } + } + } + + // Sort by -newest + Collections.sort(recentItems, new Comparator() { + @Override + public int compare(RemoteRssItem lhs, RemoteRssItem rhs) { + return rhs.getTimestamp().compareTo(lhs.getTimestamp()); + } + }); + } catch (DaemonException e) { + onCommunicationError(e); + return; + } + + // @UIThread + fragmentRemoteFeeds.updateRemoteItems( + selectedFilter == 0 ? recentItems : feeds.get(selectedFilter -1).getItems(), + false /* allow android to restore scroll position */ ); + showRemoteChannelFilters(); + } + + @UiThread + protected void onCommunicationError(DaemonException daemonException) { + //noinspection ThrowableResultOfMethodCallIgnored + log.i(this, daemonException.toString()); + String error = getString(LocalTorrent.getResourceForDaemonException(daemonException)); + SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE)); + } + + + public void onFeedSelected(int position) { + selectedFilter = position; + + if (position == 0) { + fragmentRemoteFeeds.updateRemoteItems(recentItems, true); + } + else { + RemoteRssChannel channel = feeds.get(selectedFilter -1); + fragmentRemoteFeeds.updateRemoteItems(channel.getItems(), true); + } + } + + /** + * Download the item in a background thread and display success/fail accordingly. + */ + @Background + public void downloadRemoteRssItem(RemoteRssItem item) { + final RemoteRssSupplier supplier = (RemoteRssSupplier) this.getCurrentConnection(); + + try { + RemoteRssChannel channel = feeds.get(selectedFilter); + supplier.downloadRemoteRssItem(log, item, channel); + onTaskSucceeded(null, getString(R.string.result_added, item.getTitle())); + } catch (DaemonException e) { + onTaskFailed(getString(LocalTorrent.getResourceForDaemonException(e))); + } + } + + @UiThread + protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) { + SnackbarManager.show(Snackbar.with(this).text(successMessage)); + } + + @UiThread + protected void onTaskFailed(String message) { + SnackbarManager.show(Snackbar.with(this) + .text(message) + .colorResource(R.color.red) + .type(SnackbarType.MULTI_LINE) + ); + } + + private void showRemoteChannelFilters() { + List feedLabels = new ArrayList<>(feeds.size() +1); + feedLabels.add(new RemoteRssChannel() { + @Override + public String getName() { + return getString(R.string.remoterss_filter_allrecent); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + }); + feedLabels.addAll(feeds); + + fragmentRemoteFeeds.updateChannelFilters(feedLabels); + } +} diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java similarity index 89% rename from app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java rename to app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java index df227b4b..894bf4f0 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java @@ -16,7 +16,7 @@ */ package org.transdroid.core.gui.rss; -import android.app.Fragment; +import android.support.v4.app.Fragment; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -41,7 +41,7 @@ import java.util.List; */ @EFragment(R.layout.fragment_rssfeeds) @OptionsMenu(R.menu.fragment_rssfeeds) -public class RssfeedsFragment extends Fragment { +public class RssFeedsFragment extends Fragment { // Views @ViewById(R.id.rssfeeds_list) @@ -77,14 +77,24 @@ public class RssfeedsFragment extends Fragment { MainSettingsActivity_.intent(getActivity()).start(); } + protected RssFeedsActivity getRssActivity() { + return (RssFeedsActivity) getActivity(); + } + + @Override + public void onResume() { + super.onResume(); + this.refreshScreen(); + } + @OptionsItem(R.id.action_refresh) protected void refreshScreen() { - ((RssfeedsActivity) getActivity()).refreshFeeds(); + getRssActivity().refreshFeeds(); } @ItemClick(R.id.rssfeeds_list) protected void onFeedClicked(RssfeedLoader loader) { - ((RssfeedsActivity) getActivity()).openRssfeed(loader, true); + getRssActivity().openRssfeed(loader, true); } /** diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java similarity index 96% rename from app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java rename to app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java index c0440c39..46e9a7aa 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java @@ -36,7 +36,7 @@ import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.rssparser.Channel; @EActivity(R.layout.activity_rssitems) -public class RssitemsActivity extends AppCompatActivity { +public class RssItemsActivity extends AppCompatActivity { @Extra protected Channel rssfeed = null; @@ -46,7 +46,7 @@ public class RssitemsActivity extends AppCompatActivity { protected boolean requiresExternalAuthentication; @FragmentById(R.id.rssitems_fragment) - protected RssitemsFragment fragmentItems; + protected RssItemsFragment fragmentItems; @ViewById protected Toolbar rssfeedsToolbar; diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java similarity index 92% rename from app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java rename to app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java index 869baf68..e14c42cf 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java @@ -17,13 +17,13 @@ package org.transdroid.core.gui.rss; import android.app.AlertDialog; -import android.app.Fragment; import android.app.SearchManager; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.view.ActionMode; @@ -60,10 +60,10 @@ import java.util.List; * @author Eric Kok */ @EFragment(R.layout.fragment_rssitems) -public class RssitemsFragment extends Fragment { +public class RssItemsFragment extends Fragment { @InstanceState - protected Channel rssfeed = null; + protected Channel rssFeed = null; @InstanceState protected boolean hasError = false; @InstanceState @@ -74,7 +74,7 @@ public class RssitemsFragment extends Fragment { // Views @ViewById(R.id.rssitems_list) - protected ListView rssitemsList; + protected ListView rssItemsList; private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() { SelectionManagerMode selectionManagerMode; @@ -84,7 +84,7 @@ public class RssitemsFragment extends Fragment { // Show contextual action bar to add items in batch mode mode.getMenuInflater().inflate(R.menu.fragment_rssitems_cab, menu); Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext(); - selectionManagerMode = new SelectionManagerMode(themedContext, rssitemsList, R.plurals.rss_itemsselected); + selectionManagerMode = new SelectionManagerMode(themedContext, rssItemsList, R.plurals.rss_itemsselected); selectionManagerMode.onCreateActionMode(mode, menu); return true; } @@ -98,9 +98,9 @@ public class RssitemsFragment extends Fragment { // Get checked torrents List checked = new ArrayList<>(); - for (int i = 0; i < rssitemsList.getCheckedItemPositions().size(); i++) { - if (rssitemsList.getCheckedItemPositions().valueAt(i)) { - checked.add(rssitemsAdapter.getItem(rssitemsList.getCheckedItemPositions().keyAt(i))); + for (int i = 0; i < rssItemsList.getCheckedItemPositions().size(); i++) { + if (rssItemsList.getCheckedItemPositions().valueAt(i)) { + checked.add(rssitemsAdapter.getItem(rssItemsList.getCheckedItemPositions().keyAt(i))); } } @@ -189,9 +189,9 @@ public class RssitemsFragment extends Fragment { protected void init() { // Set up the list adapter, which allows multi-select - rssitemsList.setAdapter(rssitemsAdapter); - rssitemsList.setMultiChoiceModeListener(onItemsSelected); - update(rssfeed, hasError, requiresExternalAuthentication); + rssItemsList.setAdapter(rssitemsAdapter); + rssItemsList.setMultiChoiceModeListener(onItemsSelected); + update(rssFeed, hasError, requiresExternalAuthentication); } @@ -204,7 +204,7 @@ public class RssitemsFragment extends Fragment { public void update(Channel channel, boolean hasError, boolean requiresExternalAuthentication) { this.requiresExternalAuthentication = requiresExternalAuthentication; rssitemsAdapter.update(channel); - rssitemsList.setVisibility(View.GONE); + rssItemsList.setVisibility(View.GONE); emptyText.setVisibility(View.VISIBLE); if (hasError) { emptyText.setText(R.string.rss_error); @@ -218,7 +218,7 @@ public class RssitemsFragment extends Fragment { emptyText.setText(R.string.rss_empty); return; } - rssitemsList.setVisibility(View.VISIBLE); + rssItemsList.setVisibility(View.VISIBLE); emptyText.setVisibility(View.INVISIBLE); } diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java index c3b81a08..8fc448ce 100644 --- a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java +++ b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java @@ -21,52 +21,151 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Parcel; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.TabLayout; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; import com.nispok.snackbar.Snackbar; import com.nispok.snackbar.SnackbarManager; +import com.nispok.snackbar.enums.SnackbarType; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.FragmentById; +import org.androidannotations.annotations.InstanceState; +import org.androidannotations.annotations.NonConfigurationInstance; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.ViewById; import org.transdroid.R; import org.transdroid.core.app.settings.ApplicationSettings; import org.transdroid.core.app.settings.RssfeedSetting; +import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.SettingsUtils; import org.transdroid.core.gui.TorrentsActivity_; +import org.transdroid.core.gui.lists.LocalTorrent; import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.navigation.NavigationHelper; +import org.transdroid.core.gui.remoterss.RemoteRssFragment; +import org.transdroid.core.gui.remoterss.data.RemoteRssChannel; +import org.transdroid.core.gui.remoterss.data.RemoteRssItem; +import org.transdroid.core.gui.remoterss.data.RemoteRssSupplier; import org.transdroid.core.rssparser.Channel; import org.transdroid.core.rssparser.RssParser; +import org.transdroid.core.service.ConnectivityHelper; +import org.transdroid.daemon.Daemon; +import org.transdroid.daemon.DaemonException; +import org.transdroid.daemon.IDaemonAdapter; +import org.transdroid.daemon.task.DaemonTaskSuccessResult; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.List; @EActivity(R.layout.activity_rssfeeds) -public class RssfeedsActivity extends AppCompatActivity { +public class RssFeedsActivity extends AppCompatActivity { // Settings and local data @Bean protected Log log; @Bean protected ApplicationSettings applicationSettings; - protected List loaders; - // Contained feeds and items fragments + protected static final int RSS_FEEDS_LOCAL = 0; + protected static final int RSS_FEEDS_REMOTE = 1; + @FragmentById(R.id.rssfeeds_fragment) - protected RssfeedsFragment fragmentFeeds; + protected RssFeedsFragment fragmentLocalFeeds; @FragmentById(R.id.rssitems_fragment) - protected RssitemsFragment fragmentItems; - @ViewById - protected Toolbar rssfeedsToolbar; + protected RssItemsFragment fragmentItems; + @FragmentById(R.id.remoterss_fragment) + protected RemoteRssFragment fragmentRemoteFeeds; + + @ViewById(R.id.rssfeeds_toolbar) + protected Toolbar rssFeedsToolbar; + @ViewById(R.id.rssfeeds_tabs) + protected TabLayout tabLayout; + @ViewById(R.id.rssfeeds_pager) + protected ViewPager viewPager; + + // remote RSS stuff + @NonConfigurationInstance + protected ArrayList feeds; + @InstanceState + protected int selectedFilter; + @NonConfigurationInstance + protected ArrayList recentItems; + @Bean + protected ConnectivityHelper connectivityHelper; + + + protected class LayoutPagerAdapter extends PagerAdapter { + boolean hasRemoteRss; + String serverName; + + public LayoutPagerAdapter(boolean hasRemoteRss, String name) { + super(); + + this.hasRemoteRss = hasRemoteRss; + this.serverName = (name.length() > 0 ? name : getString(R.string.navigation_rss_tabs_remote)); + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + int resId = 0; + + if (position == RSS_FEEDS_LOCAL) { + resId = R.id.layout_rssfeeds_local; + } + else if (position == RSS_FEEDS_REMOTE) { + resId = R.id.layout_rss_feeds_remote; + } + + return findViewById(resId); + } + + @Override + public int getCount() { + return (this.hasRemoteRss ? 2 : 1); + } + + @Override + public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { + return (view == o); + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + container.removeView((View) object); + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case RSS_FEEDS_LOCAL: + return getString(R.string.navigation_rss_tabs_local); + case RSS_FEEDS_REMOTE: + return this.serverName; + } + + return super.getPageTitle(position); + } + } @Override public void onCreate(Bundle savedInstanceState) { @@ -76,9 +175,21 @@ public class RssfeedsActivity extends AppCompatActivity { @AfterViews protected void init() { - setSupportActionBar(rssfeedsToolbar); + setSupportActionBar(rssFeedsToolbar); getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.rss_feeds))); getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + IDaemonAdapter currentConnection = this.getCurrentConnection(); + boolean hasRemoteRss = Daemon.supportsRemoteRssManagement(currentConnection.getType()); + + PagerAdapter pagerAdapter = new LayoutPagerAdapter(hasRemoteRss, currentConnection.getSettings().getName()); + viewPager.setAdapter(pagerAdapter); + tabLayout.setupWithViewPager(viewPager); + viewPager.setCurrentItem(0); + + if (!hasRemoteRss) { + tabLayout.setVisibility(View.GONE); + } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -87,17 +198,11 @@ public class RssfeedsActivity extends AppCompatActivity { TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); } - @Override - protected void onResume() { - super.onResume(); - refreshFeeds(); - } - /** * Reload the RSS feed settings and start loading all the feeds. To be called from contained fragments. */ public void refreshFeeds() { - loaders = new ArrayList<>(); + List loaders = new ArrayList<>(); // For each RSS feed setting the user created, start a loader that retrieved the RSS feed (via a background // thread) and, on success, determines the new items in the feed for (RssfeedSetting setting : applicationSettings.getRssfeedSettings()) { @@ -105,7 +210,8 @@ public class RssfeedsActivity extends AppCompatActivity { loaders.add(loader); loadRssfeed(loader); } - fragmentFeeds.update(loaders); + + fragmentLocalFeeds.update(loaders); } /** @@ -125,7 +231,6 @@ public class RssfeedsActivity extends AppCompatActivity { handleRssfeedResult(loader, null, true); log.i(this, "RSS feed " + loader.getSetting().getUrl() + " error: " + e.toString()); } - } /** @@ -137,11 +242,12 @@ public class RssfeedsActivity extends AppCompatActivity { @UiThread protected void handleRssfeedResult(RssfeedLoader loader, Channel channel, boolean hasError) { loader.update(channel, hasError); - fragmentFeeds.notifyDataSetChanged(); + + fragmentLocalFeeds.notifyDataSetChanged(); } /** - * Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssitemsActivity}. Optionally this also registers in + * Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssItemsActivity}. Optionally this also registers in * the user preferences that the feed was now viewed, so that in the future the new items can be properly marked. * @param loader The RSS feed loader (with settings and the loaded content channel) to show * @param markAsViewedNow True if the user settings should be updated to reflect this feed's last viewed date; false otherwise @@ -191,11 +297,128 @@ public class RssfeedsActivity extends AppCompatActivity { if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(loader.getSetting().getUrl())) { name = Uri.parse(loader.getSetting().getUrl()).getHost(); } - RssitemsActivity_.intent(this).rssfeed(loader.getChannel()).rssfeedName(name) + RssItemsActivity_.intent(this).rssfeed(loader.getChannel()).rssfeedName(name) .requiresExternalAuthentication(loader.getSetting().requiresExternalAuthentication()).start(); } + } + protected IDaemonAdapter getCurrentConnection() { + ServerSetting lastUsed = applicationSettings.getLastUsedServer(); + return lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this); } + // @Background + public void refreshRemoteFeeds() { + // Connect to the last used server + IDaemonAdapter currentConnection = this.getCurrentConnection(); + + // remote rss not supported for this connection type + if (currentConnection instanceof RemoteRssSupplier == false) { + return; + } + + try { + feeds = ((RemoteRssSupplier) (currentConnection)).getRemoteRssChannels(log); + + // By default it displays the latest items within the last month. + recentItems = new ArrayList<>(); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MONTH, -1); + Date oneMonthAgo = calendar.getTime(); + + for (RemoteRssChannel feed : feeds) { + for (RemoteRssItem item : feed.getItems()) { + if (item.getTimestamp().after(oneMonthAgo)) { + recentItems.add(item); + } + } + } + + // Sort by -newest + Collections.sort(recentItems, new Comparator() { + @Override + public int compare(RemoteRssItem lhs, RemoteRssItem rhs) { + return rhs.getTimestamp().compareTo(lhs.getTimestamp()); + } + }); + } catch (DaemonException e) { + onCommunicationError(e); + return; + } + + // @UIThread + fragmentRemoteFeeds.updateRemoteItems( + selectedFilter == 0 ? recentItems : feeds.get(selectedFilter -1).getItems(), + false /* allow android to restore scroll position */ ); + showRemoteChannelFilters(); + } + + @UiThread + protected void onCommunicationError(DaemonException daemonException) { + //noinspection ThrowableResultOfMethodCallIgnored + log.i(this, daemonException.toString()); + String error = getString(LocalTorrent.getResourceForDaemonException(daemonException)); + SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE)); + } + + + public void onFeedSelected(int position) { + selectedFilter = position; + + if (position == 0) { + fragmentRemoteFeeds.updateRemoteItems(recentItems, true); + } + else { + RemoteRssChannel channel = feeds.get(selectedFilter -1); + fragmentRemoteFeeds.updateRemoteItems(channel.getItems(), true); + } + } + + /** + * Download the item in a background thread and display success/fail accordingly. + */ + @Background + public void downloadRemoteRssItem(RemoteRssItem item) { + final RemoteRssSupplier supplier = (RemoteRssSupplier) this.getCurrentConnection(); + + try { + RemoteRssChannel channel = feeds.get(selectedFilter); + supplier.downloadRemoteRssItem(log, item, channel); + onTaskSucceeded(null, getString(R.string.result_added, item.getTitle())); + } catch (DaemonException e) { + onTaskFailed(getString(LocalTorrent.getResourceForDaemonException(e))); + } + } + + @UiThread + protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) { + SnackbarManager.show(Snackbar.with(this).text(successMessage)); + } + + @UiThread + protected void onTaskFailed(String message) { + SnackbarManager.show(Snackbar.with(this) + .text(message) + .colorResource(R.color.red) + .type(SnackbarType.MULTI_LINE) + ); + } + + private void showRemoteChannelFilters() { + List feedLabels = new ArrayList<>(feeds.size() +1); + feedLabels.add(new RemoteRssChannel() { + @Override + public String getName() { + return getString(R.string.remoterss_filter_allrecent); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + }); + feedLabels.addAll(feeds); + + fragmentRemoteFeeds.updateChannelFilters(feedLabels); + } } diff --git a/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java b/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java index 1ecbea54..910c90da 100644 --- a/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java +++ b/app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java @@ -33,7 +33,7 @@ import org.transdroid.core.app.settings.ApplicationSettings; import org.transdroid.core.app.settings.NotificationSettings; import org.transdroid.core.app.settings.RssfeedSetting; import org.transdroid.core.gui.log.Log; -import org.transdroid.core.gui.rss.RssfeedsActivity_; +import org.transdroid.core.gui.rss.RssFeedsActivity_; import org.transdroid.core.rssparser.Item; import org.transdroid.core.rssparser.RssParser; import org.transdroid.daemon.util.Collections2; @@ -121,7 +121,7 @@ public class RssCheckerJobRunner { // Provide a notification, since there are new RSS items PendingIntent pi = PendingIntent - .getActivity(context, 80000, new Intent(context, RssfeedsActivity_.class), PendingIntent.FLAG_UPDATE_CURRENT); + .getActivity(context, 80000, new Intent(context, RssFeedsActivity_.class), PendingIntent.FLAG_UPDATE_CURRENT); String title = context.getResources().getQuantityString(R.plurals.rss_service_new, unread, Integer.toString(unread)); String forString = Collections2.joinString(hasUnread, ", "); final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationChannels.CHANNEL_RSS_CHECKER) diff --git a/app/src/main/res/layout-w900dp/activity_rssfeeds.xml b/app/src/main/res/layout-w900dp/activity_rssfeeds.xml index bd4390d3..997ccd2e 100644 --- a/app/src/main/res/layout-w900dp/activity_rssfeeds.xml +++ b/app/src/main/res/layout-w900dp/activity_rssfeeds.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".core.gui.rss.RssfeedsActivity_"> + tools:context=".core.gui.rss.RssFeedsActivity_"> - - - + + + + + android:baselineAligned="false" + android:orientation="horizontal"> + + + + - - + android:baselineAligned="false" + android:orientation="horizontal"> + + + diff --git a/app/src/main/res/layout/activity_remoterss.xml b/app/src/main/res/layout/activity_remoterss.xml deleted file mode 100644 index 0d28fa6b..00000000 --- a/app/src/main/res/layout/activity_remoterss.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_rssfeeds.xml b/app/src/main/res/layout/activity_rssfeeds.xml index 6fb3f0e4..436fd436 100644 --- a/app/src/main/res/layout/activity_rssfeeds.xml +++ b/app/src/main/res/layout/activity_rssfeeds.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".core.gui.rss.RssfeedsActivity_"> + tools:context=".core.gui.rss.RssFeedsActivity_"> - + android:layout_marginTop="-2dp"> + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_rssitems.xml b/app/src/main/res/layout/activity_rssitems.xml index 15e62d88..1350234e 100644 --- a/app/src/main/res/layout/activity_rssitems.xml +++ b/app/src/main/res/layout/activity_rssitems.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".core.gui.rss.RssitemsActivity_"> + tools:context=".core.gui.rss.RssItemsActivity_"> . --> - + + + - - - - + android:layout_height="match_parent" + android:clipToPadding="false" + tools:listitem="@layout/list_item_remoterssitem" + tools:visibility="visible" /> + diff --git a/app/src/main/res/menu/activity_torrents_main.xml b/app/src/main/res/menu/activity_torrents_main.xml index ba8b6e5c..01fc6115 100644 --- a/app/src/main/res/menu/activity_torrents_main.xml +++ b/app/src/main/res/menu/activity_torrents_main.xml @@ -28,11 +28,6 @@ android:icon="@drawable/ic_action_rss" android:title="@string/action_rss" app:showAsAction="ifRoom" /> - Visit transdroid.org Close - Select server and filter - Close filters list - %1$s allows you to monitor and manage the torrent client you run at home or on your seedbox. Setting things up can be a bit tricky, but we offer step-by-step guides and promise it\'ll be worth it! - Connected, but no torrents are active within the current filter - Select a torrent to view its details - Servers - Status - Labels - All - Downloading - Seeding - Active - Inactive - - %1$d torrent selected - %1$d torrents selected - - - %1$d files selected - %1$d files selected - - Select all - Select finished - Invert selection - Add torrent to… - http://… + Select server and filter + Close filters list + %1$s allows you to monitor and manage the torrent client you run at home or on your seedbox. Setting things up can be a bit tricky, but we offer step-by-step guides and promise it\'ll be worth it! + Connected, but no torrents are active within the current filter + Select a torrent to view its details + Servers + Status + Labels + All + Downloading + Seeding + Active + Inactive + + %1$d torrent selected + %1$d torrents selected + + + %1$d files selected + %1$d files selected + + Select all + Select finished + Invert selection + Add torrent to… + http://… + Transdroid + Server - STATUS: %1$s - Waiting to check… - Verifying local data… - Waiting to download %s - Error… - %1$s OF %2$s (%3$s) - %1$s, UPLOADED %2$s - SINCE %1$s - ~ %1$s - ETA %1$s - OF %1$s - UNKNOWN ETA - RATIO %1$s - %1$s OF %2$s SEEDERS - %1$s OF %2$s LEECHERS - ↑ %1$s - ↓ %1$s - %1$s ↓ - Downloading - Seeding - Paused - Queued - Stopped - Unknown status - Not downloaded - Low priority - Normal priority - High priority - PIECES - TRACKERS - ERRORS - FILES - Maximum transfer speeds - MAX DOWNLOAD - MAX UPLOAD - KB/S - Reset - - - Update - - New torrent added - %1$s new torrents added - - - Torrent is finished - %1$s torrents are finished - - - %1$s added, %2$s finished torrent - %1$s added, %2$s finished torrents - - %1$s and others - - All labels - Unlabeled - New label - Setting a label is not supported by your client - PICK A LABEL - NEW LABEL - Remove label - E.g. movies or linux - - %1$s added (refreshing) - %1$s removed - %1$s removed and data deleted - %1$s resumed (refreshing) - %1$s stopped (refreshing) - %1$s started (refreshing) - %1$s paused (refreshing) - Torrents paused (refreshing) - Torrents resumed (refreshing) - Torrents stopped (refreshing) - Torrents started (refreshing) - Trackers updated - Label set to \'%1$s\' - Label removed - %1$s is downloading %2$s - normally - sequentially - %1$s has %2$s - first and last piece priority - normal piece priority - Checking %1$s data - Torrent moved to \'%1$s\' - File priorities updated - Maximum transfer speeds set - - Torrent search - \'%1$s\' on %2$s - Search for torrents - No results for your query - S: %1$s - L: %1$s - This feature requires a one-time installation of the Torrent Search module. Click download to get the install package (apk) from transdroid.org and restart your search. - Download module - Opening details for %1$s - The Barcode Scanner could not be found. Would you like to install it from the Play Store? - No compatible file manager could not be found. Would you like to install IO File Manager from the Play Store? - - %1$d result selected - %1$d results selected - - - RSS feeds - You have not defined any RSS feeds yet to monitor. Torrent-specific RSS feeds keep you up to date with new releases and you are notified of new items. - Select an RSS feed to view the new items - The RSS feed is not available or it contains no items - Sorry, please wait until the RSS feed is loaded - Sorry, this RSS feed could not be loaded at this time - - %1$d item selected - %1$d items selected - - - New RSS feed torrent available - %1$s new RSS feed torrents - - New torrents for %1$s + STATUS: %1$s + Waiting to check… + Verifying local data… + Waiting to download %s + Error… + %1$s OF %2$s (%3$s) + %1$s, UPLOADED %2$s + SINCE %1$s + ~ %1$s + ETA %1$s + OF %1$s + UNKNOWN ETA + RATIO %1$s + %1$s OF %2$s SEEDERS + %1$s OF %2$s LEECHERS + ↑ %1$s + ↓ %1$s + %1$s ↓ + Downloading + Seeding + Paused + Queued + Stopped + Unknown status + Not downloaded + Low priority + Normal priority + High priority + PIECES + TRACKERS + ERRORS + FILES + Maximum transfer speeds + MAX DOWNLOAD + MAX UPLOAD + KB/S + Reset + - + Update + + New torrent added + %1$s new torrents added + + + Torrent is finished + %1$s torrents are finished + + + %1$s added, %2$s finished torrent + %1$s added, %2$s finished torrents + + %1$s and others + + All labels + Unlabeled + New label + Setting a label is not supported by your client + PICK A LABEL + NEW LABEL + Remove label + E.g. movies or linux + + %1$s added (refreshing) + %1$s removed + %1$s removed and data deleted + %1$s resumed (refreshing) + %1$s stopped (refreshing) + %1$s started (refreshing) + %1$s paused (refreshing) + Torrents paused (refreshing) + Torrents resumed (refreshing) + Torrents stopped (refreshing) + Torrents started (refreshing) + Trackers updated + Label set to \'%1$s\' + Label removed + %1$s is downloading %2$s + normally + sequentially + %1$s has %2$s + first and last piece priority + normal piece priority + Checking %1$s data + Torrent moved to \'%1$s\' + File priorities updated + Maximum transfer speeds set + + Torrent search + \'%1$s\' on %2$s + Search for torrents + No results for your query + S: %1$s + L: %1$s + This feature requires a one-time installation of the Torrent Search module. Click download to get the install package (apk) from transdroid.org and restart your search. + Download module + Opening details for %1$s + The Barcode Scanner could not be found. Would you like to install it from the Play Store? + No compatible file manager could not be found. Would you like to install IO File Manager from the Play Store? + + %1$d result selected + %1$d results selected + + + RSS feeds + You have not defined any RSS feeds yet to monitor. Torrent-specific RSS feeds keep you up to date with new releases and you are notified of new items. + Select an RSS feed to view the new items + The RSS feed is not available or it contains no items + Sorry, please wait until the RSS feed is loaded + Sorry, this RSS feed could not be loaded at this time + + %1$d item selected + %1$d items selected + + + New RSS feed torrent available + %1$s new RSS feed torrents + + New torrents for %1$s (All recent) No torrent files found.\n\nAre your RSS feeds configured correctly?