Browse Source

Merge pull request #526 from twig/combine_rss_feed_views

Combine RSS local and remote feed views
pull/540/head
Eric Kok 4 years ago committed by GitHub
parent
commit
4e0c384e4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/build.gradle
  2. 8
      app/src/main/AndroidManifest.xml
  3. 16
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  4. 253
      app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssActivity.java
  5. 127
      app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java
  6. 424
      app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java
  7. 18
      app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java
  8. 4
      app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java
  9. 26
      app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java
  10. 263
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java
  11. 4
      app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java
  12. 63
      app/src/main/res/layout-w900dp/activity_rssfeeds.xml
  13. 79
      app/src/main/res/layout/activity_remoterss.xml
  14. 45
      app/src/main/res/layout/activity_rssfeeds.xml
  15. 4
      app/src/main/res/layout/activity_rssitems.xml
  16. 35
      app/src/main/res/layout/fragment_remoterss.xml
  17. 5
      app/src/main/res/menu/activity_torrents_main.xml
  18. 290
      app/src/main/res/values/strings.xml

2
app/build.gradle

@ -73,6 +73,8 @@ dependencies { @@ -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'

8
app/src/main/AndroidManifest.xml

@ -248,18 +248,14 @@ @@ -248,18 +248,14 @@
<!-- RSS -->
<activity
android:name="org.transdroid.core.gui.rss.RssfeedsActivity_"
android:name="org.transdroid.core.gui.rss.RssFeedsActivity_"
android:label="@string/rss_feeds"
android:launchMode="singleTop"
android:theme="@style/TransdroidTheme" />
<activity
android:name="org.transdroid.core.gui.rss.RssitemsActivity_"
android:name="org.transdroid.core.gui.rss.RssItemsActivity_"
android:label="@string/rss_feeds"
android:theme="@style/TransdroidTheme" />
<activity
android:name=".core.gui.remoterss.RemoteRssActivity_"
android:label="Remote RSS feeds"
android:theme="@style/TransdroidTheme" />
<receiver android:name="org.transdroid.core.service.BootReceiver_" >
<intent-filter>

16
app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java

@ -82,8 +82,7 @@ import org.transdroid.core.gui.navigation.NavigationFilter; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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/")));

253
app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssActivity.java

@ -1,253 +0,0 @@ @@ -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 <http://www.gnu.org/licenses/>.
*/
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<RemoteRssChannel> feeds;
@InstanceState
protected int selectedFilter;
@NonConfigurationInstance
protected ArrayList<RemoteRssItem> 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<RemoteRssItem>() {
@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<RemoteRssChannel> 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();
}
}

127
app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssFragment.java

@ -17,32 +17,26 @@ @@ -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 { @@ -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<RemoteRssItem> 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<RemoteRssChannel> feedLabels) {
List<String> labels = new ArrayList<>();
for (RemoteRssChannel feedLabel : feedLabels) {
labels.add(feedLabel.getName());
}
ArrayAdapter<String> 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 { @@ -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);
}
}

424
app/src/main/java/org/transdroid/core/gui/rss/RssFeedsActivity.java

@ -0,0 +1,424 @@ @@ -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 <http://www.gnu.org/licenses/>.
*/
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<RemoteRssChannel> feeds;
@InstanceState
protected int selectedFilter;
@NonConfigurationInstance
protected ArrayList<RemoteRssItem> 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<RssfeedLoader> 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<RemoteRssItem>() {
@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<RemoteRssChannel> 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);
}
}

18
app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java → app/src/main/java/org/transdroid/core/gui/rss/RssFeedsFragment.java

@ -16,7 +16,7 @@ @@ -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; @@ -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 { @@ -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);
}
/**

4
app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java → app/src/main/java/org/transdroid/core/gui/rss/RssItemsActivity.java

@ -36,7 +36,7 @@ import org.transdroid.core.gui.navigation.NavigationHelper; @@ -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 { @@ -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;

26
app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java → app/src/main/java/org/transdroid/core/gui/rss/RssItemsFragment.java

@ -17,13 +17,13 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -98,9 +98,9 @@ public class RssitemsFragment extends Fragment {
// Get checked torrents
List<Item> 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 { @@ -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 { @@ -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 { @@ -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);
}

263
app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java

@ -21,52 +21,151 @@ import android.content.Intent; @@ -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<RssfeedLoader> 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<RemoteRssChannel> feeds;
@InstanceState
protected int selectedFilter;
@NonConfigurationInstance
protected ArrayList<RemoteRssItem> 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 { @@ -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 { @@ -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<RssfeedLoader> 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 { @@ -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 { @@ -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 { @@ -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 { @@ -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<RemoteRssItem>() {
@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<RemoteRssChannel> 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);
}
}

4
app/src/main/java/org/transdroid/core/service/RssCheckerJobRunner.java

@ -33,7 +33,7 @@ import org.transdroid.core.app.settings.ApplicationSettings; @@ -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 { @@ -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)

63
app/src/main/res/layout-w900dp/activity_rssfeeds.xml

@ -19,7 +19,7 @@ @@ -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.support.v7.widget.Toolbar
android:id="@+id/rssfeeds_toolbar"
@ -32,28 +32,53 @@ @@ -32,28 +32,53 @@
style="@style/DefaultToolbarShadow"
android:layout_below="@id/rssfeeds_toolbar" />
<LinearLayout
<android.support.v4.view.ViewPager
android:id="@+id/rssfeeds_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/rssfeeds_toolbar"
android:baselineAligned="false"
android:orientation="horizontal">
<fragment
android:id="@+id/rssfeeds_fragment"
class="org.transdroid.core.gui.rss.RssfeedsFragment_"
android:layout_width="0dip"
android:layout_below="@+id/rssfeeds_toolbar">
<android.support.design.widget.TabLayout
android:id="@+id/rssfeeds_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/layout_rssfeeds_local"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="2"
tools:layout="@layout/fragment_rssfeeds" />
android:baselineAligned="false"
android:orientation="horizontal">
<fragment
android:id="@+id/rssfeeds_fragment"
class="org.transdroid.core.gui.rss.RssFeedsFragment_"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="2"
tools:layout="@layout/fragment_rssfeeds" />
<fragment
android:id="@+id/rssitems_fragment"
class="org.transdroid.core.gui.rss.RssItemsFragment_"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="3"
tools:layout="@layout/fragment_rssitems" />
</LinearLayout>
<fragment
android:id="@+id/rssitems_fragment"
class="org.transdroid.core.gui.rss.RssitemsFragment_"
android:layout_width="0dip"
<LinearLayout
android:id="@+id/layout_rss_feeds_remote"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3"
tools:layout="@layout/fragment_rssitems" />
</LinearLayout>
android:baselineAligned="false"
android:orientation="horizontal">
<fragment
android:id="@+id/remoterss_fragment"
class="org.transdroid.core.gui.remoterss.RemoteRssFragment_"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_torrents" />
</LinearLayout>
</android.support.v4.view.ViewPager>
</RelativeLayout>

79
app/src/main/res/layout/activity_remoterss.xml

@ -1,79 +0,0 @@ @@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 <http://www.gnu.org/licenses/>.
-->
<!-- This layout is for phones in portrait and shows the remote RSS items list with the filters as navigation drawer. -->
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".core.gui.remoterss.RemoteRssActivity_">
<!-- The main content view -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/torrents_toolbar"
style="@style/DefaultToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize" />
<fragment
android:id="@+id/remoterss_fragment"
class="org.transdroid.core.gui.remoterss.RemoteRssFragment_"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/torrents_toolbar"
tools:layout="@layout/fragment_torrents" />
<View
style="@style/DefaultToolbarShadow"
android:layout_below="@id/torrents_toolbar" />
<View
style="@style/SplitToolbarShadow"
/>
</RelativeLayout>
<!-- The navigation drawer -->
<LinearLayout
android:id="@+id/drawer_container"
android:layout_width="@dimen/ui_filters_list"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/drawer_background"
android:orientation="vertical">
<ListView
android:id="@+id/drawer_list"
android:layout_width="@dimen/ui_filters_list"
android:layout_height="0dp"
android:layout_weight="1"
android:choiceMode="singleChoice"
android:divider="@null"
tools:listitem="@layout/list_item_filter" />
</LinearLayout>
</android.support.v4.widget.DrawerLayout>

45
app/src/main/res/layout/activity_rssfeeds.xml

@ -19,7 +19,7 @@ @@ -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.support.v7.widget.Toolbar
android:id="@+id/rssfeeds_toolbar"
@ -32,12 +32,47 @@ @@ -32,12 +32,47 @@
style="@style/DefaultToolbarShadow"
android:layout_below="@id/rssfeeds_toolbar" />
<fragment
android:id="@+id/rssfeeds_fragment"
class="org.transdroid.core.gui.rss.RssfeedsFragment_"
<android.support.v4.view.ViewPager
android:id="@+id/rssfeeds_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/rssfeeds_toolbar"
tools:layout="@layout/fragment_rssfeeds" />
android:layout_marginTop="-2dp">
<android.support.design.widget.TabLayout
android:id="@+id/rssfeeds_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="@+id/layout_rssfeeds_local"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal">
<fragment
android:id="@+id/rssfeeds_fragment"
class="org.transdroid.core.gui.rss.RssFeedsFragment_"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="2"
tools:layout="@layout/fragment_rssfeeds" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_rss_feeds_remote"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal">
<fragment
android:id="@+id/remoterss_fragment"
class="org.transdroid.core.gui.remoterss.RemoteRssFragment_"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_torrents" />
</LinearLayout>
</android.support.v4.view.ViewPager>
</RelativeLayout>

4
app/src/main/res/layout/activity_rssitems.xml

@ -19,7 +19,7 @@ @@ -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.support.v7.widget.Toolbar
android:id="@+id/rssfeeds_toolbar"
@ -34,7 +34,7 @@ @@ -34,7 +34,7 @@
<fragment
android:id="@+id/rssitems_fragment"
class="org.transdroid.core.gui.rss.RssitemsFragment_"
class="org.transdroid.core.gui.rss.RssItemsFragment_"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/rssfeeds_toolbar"

35
app/src/main/res/layout/fragment_remoterss.xml

@ -15,10 +15,18 @@ @@ -15,10 +15,18 @@
You should have received a copy of the GNU General Public License
along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Spinner
android:id="@+id/remoterss_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spinnerMode="dialog" />
<TextView
android:layout_width="match_parent"
@ -27,16 +35,11 @@ @@ -27,16 +35,11 @@
android:gravity="center"
android:id="@+id/remoterss_status_message"/>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
<ListView
android:id="@+id/torrents_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/torrents_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/list_item_remoterssitem"
tools:visibility="visible"/>
</android.support.v4.widget.SwipeRefreshLayout>
</FrameLayout>
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/list_item_remoterssitem"
tools:visibility="visible" />
</LinearLayout>

5
app/src/main/res/menu/activity_torrents_main.xml

@ -28,11 +28,6 @@ @@ -28,11 +28,6 @@
android:icon="@drawable/ic_action_rss"
android:title="@string/action_rss"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_remoterss"
android:icon="@drawable/ic_cloud_download"
android:title="@string/action_remoterss"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_help"
android:title="@string/action_help"

290
app/src/main/res/values/strings.xml

@ -76,151 +76,153 @@ @@ -76,151 +76,153 @@
<string name="action_visitwebsite">Visit transdroid.org</string>
<string name="action_close">Close</string>
<string name="navigation_opendrawer">Select server and filter</string>
<string name="navigation_closedrawer">Close filters list</string>
<string name="navigation_nosettings">%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!</string>
<string name="navigation_emptytorrents">Connected, but no torrents are active within the current filter</string>
<string name="navigation_emptydetails">Select a torrent to view its details</string>
<string name="navigation_servers">Servers</string>
<string name="navigation_status">Status</string>
<string name="navigation_labels">Labels</string>
<string name="navigation_status_showall">All</string>
<string name="navigation_status_onlydown">Downloading</string>
<string name="navigation_status_onlyup">Seeding</string>
<string name="navigation_status_onlyactive">Active</string>
<string name="navigation_status_onlyinactive">Inactive</string>
<plurals name="navigation_torrentsselected">
<item quantity="one">%1$d torrent selected</item>
<item quantity="other">%1$d torrents selected</item>
</plurals>
<plurals name="navigation_filesselected">
<item quantity="one">%1$d files selected</item>
<item quantity="other">%1$d files selected</item>
</plurals>
<string name="navigation_selectall">Select all</string>
<string name="navigation_selectfinished">Select finished</string>
<string name="navigation_invertselection">Invert selection</string>
<string name="navigation_pickserver">Add torrent to&#8230;</string>
<string name="navigation_url_hint" translatable="false">http://…</string>
<string name="navigation_opendrawer">Select server and filter</string>
<string name="navigation_closedrawer">Close filters list</string>
<string name="navigation_nosettings">%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!</string>
<string name="navigation_emptytorrents">Connected, but no torrents are active within the current filter</string>
<string name="navigation_emptydetails">Select a torrent to view its details</string>
<string name="navigation_servers">Servers</string>
<string name="navigation_status">Status</string>
<string name="navigation_labels">Labels</string>
<string name="navigation_status_showall">All</string>
<string name="navigation_status_onlydown">Downloading</string>
<string name="navigation_status_onlyup">Seeding</string>
<string name="navigation_status_onlyactive">Active</string>
<string name="navigation_status_onlyinactive">Inactive</string>
<plurals name="navigation_torrentsselected">
<item quantity="one">%1$d torrent selected</item>
<item quantity="other">%1$d torrents selected</item>
</plurals>
<plurals name="navigation_filesselected">
<item quantity="one">%1$d files selected</item>
<item quantity="other">%1$d files selected</item>
</plurals>
<string name="navigation_selectall">Select all</string>
<string name="navigation_selectfinished">Select finished</string>
<string name="navigation_invertselection">Invert selection</string>
<string name="navigation_pickserver">Add torrent to&#8230;</string>
<string name="navigation_url_hint" translatable="false">http://…</string>
<string name="navigation_rss_tabs_local">Transdroid</string>
<string name="navigation_rss_tabs_remote">Server</string>
<string name="status_status">STATUS: %1$s</string>
<string name="status_waiting">Waiting to check&#8230;</string>
<string name="status_checking">Verifying local data&#8230;</string>
<string name="status_waitingtodl">Waiting to download %s</string>
<string name="status_error">Error&#8230;</string>
<string name="status_size1">%1$s OF %2$s (%3$s)</string>
<string name="status_size2">%1$s, UPLOADED %2$s</string>
<string name="status_sincedate">SINCE %1$s</string>
<string name="status_eta">~ %1$s</string>
<string name="status_etalong">ETA %1$s</string>
<string name="status_ofsize">OF %1$s</string>
<string name="status_unknowneta">UNKNOWN ETA</string>
<string name="status_ratio">RATIO %1$s</string>
<string name="status_seeders">%1$s OF %2$s SEEDERS</string>
<string name="status_leechers">%1$s OF %2$s LEECHERS</string>
<string name="status_speed_up" translatable="false">↑ %1$s</string>
<string name="status_speed_down" translatable="false">↓ %1$s</string>
<string name="status_speed_down_details" translatable="false">%1$s ↓</string>
<string name="status_downloading">Downloading</string>
<string name="status_seeding">Seeding</string>
<string name="status_paused">Paused</string>
<string name="status_queued">Queued</string>
<string name="status_stopped">Stopped</string>
<string name="status_unknown">Unknown status</string>
<string name="status_priority_off">Not downloaded</string>
<string name="status_priority_low">Low priority</string>
<string name="status_priority_normal">Normal priority</string>
<string name="status_priority_high">High priority</string>
<string name="status_pieces">PIECES</string>
<string name="status_trackers">TRACKERS</string>
<string name="status_errors">ERRORS</string>
<string name="status_files">FILES</string>
<string name="status_maxspeed">Maximum transfer speeds</string>
<string name="status_maxspeed_down">MAX DOWNLOAD</string>
<string name="status_maxspeed_up">MAX UPLOAD</string>
<string name="status_maxspeed_unit">KB/S</string>
<string name="status_maxspeed_reset">Reset</string>
<string name="status_maxspeed_novalue" translatable="false">-</string>
<string name="status_update">Update</string>
<plurals name="status_service_added">
<item quantity="one">New torrent added</item>
<item quantity="other">%1$s new torrents added</item>
</plurals>
<plurals name="status_service_finished">
<item quantity="one">Torrent is finished</item>
<item quantity="other">%1$s torrents are finished</item>
</plurals>
<plurals name="status_service_addedfinished">
<item quantity="one">%1$s added, %2$s finished torrent</item>
<item quantity="other">%1$s added, %2$s finished torrents</item>
</plurals>
<string name="status_service_andothers">%1$s and others</string>
<string name="labels_showall">All labels</string>
<string name="labels_unlabeled">Unlabeled</string>
<string name="labels_newlabel">New label</string>
<string name="labels_no_support">Setting a label is not supported by your client</string>
<string name="status_label_pick">PICK A LABEL</string>
<string name="status_label_new">NEW LABEL</string>
<string name="status_label_remove">Remove label</string>
<string name="status_label_hint">E.g. movies or linux</string>
<string name="result_added">%1$s added (refreshing)</string>
<string name="result_removed">%1$s removed</string>
<string name="result_removed_with_data">%1$s removed and data deleted</string>
<string name="result_resumed">%1$s resumed (refreshing)</string>
<string name="result_stopped">%1$s stopped (refreshing)</string>
<string name="result_started">%1$s started (refreshing)</string>
<string name="result_paused">%1$s paused (refreshing)</string>
<string name="result_paused_all">Torrents paused (refreshing)</string>
<string name="result_resumed_all">Torrents resumed (refreshing)</string>
<string name="result_stopped_all">Torrents stopped (refreshing)</string>
<string name="result_started_all">Torrents started (refreshing)</string>
<string name="result_trackersupdated">Trackers updated</string>
<string name="result_labelset">Label set to \'%1$s\'</string>
<string name="result_labelremoved">Label removed</string>
<string name="result_togglesequential">%1$s is downloading %2$s</string>
<string name="result_togglesequential_offstate">normally</string>
<string name="result_togglesequential_onstate">sequentially</string>
<string name="result_togglefirstlastpiece">%1$s has %2$s</string>
<string name="result_togglefirstlastpiece_onstate">first and last piece priority</string>
<string name="result_togglefirstlastpiece_offstate">normal piece priority</string>
<string name="result_recheckedstarted">Checking %1$s data</string>
<string name="result_locationset">Torrent moved to \'%1$s\'</string>
<string name="result_priotitiesset">File priorities updated</string>
<string name="result_maxspeedsset">Maximum transfer speeds set</string>
<string name="search_torrentsearch">Torrent search</string>
<string name="search_queryonsite">\'%1$s\' on %2$s</string>
<string name="search_hint">Search for torrents</string>
<string name="search_noresults">No results for your query</string>
<string name="search_seeders">S: %1$s</string>
<string name="search_leechers">L: %1$s</string>
<string name="search_installsearch">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.</string>
<string name="search_download">Download module</string>
<string name="search_openingdetails">Opening details for %1$s</string>
<string name="search_barcodescannernotfound">The Barcode Scanner could not be found. Would you like to install it from the Play Store?</string>
<string name="search_filemanagernotfound">No compatible file manager could not be found. Would you like to install IO File Manager from the Play Store?</string>
<plurals name="search_resutlsselected">
<item quantity="one">%1$d result selected</item>
<item quantity="other">%1$d results selected</item>
</plurals>
<string name="rss_feeds">RSS feeds</string>
<string name="rss_nosettings">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.</string>
<string name="rss_noselection">Select an RSS feed to view the new items</string>
<string name="rss_empty">The RSS feed is not available or it contains no items</string>
<string name="rss_notloaded">Sorry, please wait until the RSS feed is loaded</string>
<string name="rss_error">Sorry, this RSS feed could not be loaded at this time</string>
<plurals name="rss_itemsselected">
<item quantity="one">%1$d item selected</item>
<item quantity="other">%1$d items selected</item>
</plurals>
<plurals name="rss_service_new">
<item quantity="one">New RSS feed torrent available</item>
<item quantity="other">%1$s new RSS feed torrents</item>
</plurals>
<string name="rss_service_newfor">New torrents for %1$s</string>
<string name="status_status">STATUS: %1$s</string>
<string name="status_waiting">Waiting to check&#8230;</string>
<string name="status_checking">Verifying local data&#8230;</string>
<string name="status_waitingtodl">Waiting to download %s</string>
<string name="status_error">Error&#8230;</string>
<string name="status_size1">%1$s OF %2$s (%3$s)</string>
<string name="status_size2">%1$s, UPLOADED %2$s</string>
<string name="status_sincedate">SINCE %1$s</string>
<string name="status_eta">~ %1$s</string>
<string name="status_etalong">ETA %1$s</string>
<string name="status_ofsize">OF %1$s</string>
<string name="status_unknowneta">UNKNOWN ETA</string>
<string name="status_ratio">RATIO %1$s</string>
<string name="status_seeders">%1$s OF %2$s SEEDERS</string>
<string name="status_leechers">%1$s OF %2$s LEECHERS</string>
<string name="status_speed_up" translatable="false">↑ %1$s</string>
<string name="status_speed_down" translatable="false">↓ %1$s</string>
<string name="status_speed_down_details" translatable="false">%1$s ↓</string>
<string name="status_downloading">Downloading</string>
<string name="status_seeding">Seeding</string>
<string name="status_paused">Paused</string>
<string name="status_queued">Queued</string>
<string name="status_stopped">Stopped</string>
<string name="status_unknown">Unknown status</string>
<string name="status_priority_off">Not downloaded</string>
<string name="status_priority_low">Low priority</string>
<string name="status_priority_normal">Normal priority</string>
<string name="status_priority_high">High priority</string>
<string name="status_pieces">PIECES</string>
<string name="status_trackers">TRACKERS</string>
<string name="status_errors">ERRORS</string>
<string name="status_files">FILES</string>
<string name="status_maxspeed">Maximum transfer speeds</string>
<string name="status_maxspeed_down">MAX DOWNLOAD</string>
<string name="status_maxspeed_up">MAX UPLOAD</string>
<string name="status_maxspeed_unit">KB/S</string>
<string name="status_maxspeed_reset">Reset</string>
<string name="status_maxspeed_novalue" translatable="false">-</string>
<string name="status_update">Update</string>
<plurals name="status_service_added">
<item quantity="one">New torrent added</item>
<item quantity="other">%1$s new torrents added</item>
</plurals>
<plurals name="status_service_finished">
<item quantity="one">Torrent is finished</item>
<item quantity="other">%1$s torrents are finished</item>
</plurals>
<plurals name="status_service_addedfinished">
<item quantity="one">%1$s added, %2$s finished torrent</item>
<item quantity="other">%1$s added, %2$s finished torrents</item>
</plurals>
<string name="status_service_andothers">%1$s and others</string>
<string name="labels_showall">All labels</string>
<string name="labels_unlabeled">Unlabeled</string>
<string name="labels_newlabel">New label</string>
<string name="labels_no_support">Setting a label is not supported by your client</string>
<string name="status_label_pick">PICK A LABEL</string>
<string name="status_label_new">NEW LABEL</string>
<string name="status_label_remove">Remove label</string>
<string name="status_label_hint">E.g. movies or linux</string>
<string name="result_added">%1$s added (refreshing)</string>
<string name="result_removed">%1$s removed</string>
<string name="result_removed_with_data">%1$s removed and data deleted</string>
<string name="result_resumed">%1$s resumed (refreshing)</string>
<string name="result_stopped">%1$s stopped (refreshing)</string>
<string name="result_started">%1$s started (refreshing)</string>
<string name="result_paused">%1$s paused (refreshing)</string>
<string name="result_paused_all">Torrents paused (refreshing)</string>
<string name="result_resumed_all">Torrents resumed (refreshing)</string>
<string name="result_stopped_all">Torrents stopped (refreshing)</string>
<string name="result_started_all">Torrents started (refreshing)</string>
<string name="result_trackersupdated">Trackers updated</string>
<string name="result_labelset">Label set to \'%1$s\'</string>
<string name="result_labelremoved">Label removed</string>
<string name="result_togglesequential">%1$s is downloading %2$s</string>
<string name="result_togglesequential_offstate">normally</string>
<string name="result_togglesequential_onstate">sequentially</string>
<string name="result_togglefirstlastpiece">%1$s has %2$s</string>
<string name="result_togglefirstlastpiece_onstate">first and last piece priority</string>
<string name="result_togglefirstlastpiece_offstate">normal piece priority</string>
<string name="result_recheckedstarted">Checking %1$s data</string>
<string name="result_locationset">Torrent moved to \'%1$s\'</string>
<string name="result_priotitiesset">File priorities updated</string>
<string name="result_maxspeedsset">Maximum transfer speeds set</string>
<string name="search_torrentsearch">Torrent search</string>
<string name="search_queryonsite">\'%1$s\' on %2$s</string>
<string name="search_hint">Search for torrents</string>
<string name="search_noresults">No results for your query</string>
<string name="search_seeders">S: %1$s</string>
<string name="search_leechers">L: %1$s</string>
<string name="search_installsearch">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.</string>
<string name="search_download">Download module</string>
<string name="search_openingdetails">Opening details for %1$s</string>
<string name="search_barcodescannernotfound">The Barcode Scanner could not be found. Would you like to install it from the Play Store?</string>
<string name="search_filemanagernotfound">No compatible file manager could not be found. Would you like to install IO File Manager from the Play Store?</string>
<plurals name="search_resutlsselected">
<item quantity="one">%1$d result selected</item>
<item quantity="other">%1$d results selected</item>
</plurals>
<string name="rss_feeds">RSS feeds</string>
<string name="rss_nosettings">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.</string>
<string name="rss_noselection">Select an RSS feed to view the new items</string>
<string name="rss_empty">The RSS feed is not available or it contains no items</string>
<string name="rss_notloaded">Sorry, please wait until the RSS feed is loaded</string>
<string name="rss_error">Sorry, this RSS feed could not be loaded at this time</string>
<plurals name="rss_itemsselected">
<item quantity="one">%1$d item selected</item>
<item quantity="other">%1$d items selected</item>
</plurals>
<plurals name="rss_service_new">
<item quantity="one">New RSS feed torrent available</item>
<item quantity="other">%1$s new RSS feed torrents</item>
</plurals>
<string name="rss_service_newfor">New torrents for %1$s</string>
<string name="remoterss_filter_allrecent">(All recent)</string>
<string name="remoterss_no_files">No torrent files found.\n\nAre your RSS feeds configured correctly?</string>

Loading…
Cancel
Save