diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..b1b9eb9a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+# editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1fae5479..86468441 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,333 +1,330 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:tools="http://schemas.android.com/tools"
+ package="org.transdroid">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/androidx/preference/PreferenceManagerBinder.java b/app/src/main/java/androidx/preference/PreferenceManagerBinder.java
index 63457113..0b5e962f 100644
--- a/app/src/main/java/androidx/preference/PreferenceManagerBinder.java
+++ b/app/src/main/java/androidx/preference/PreferenceManagerBinder.java
@@ -5,7 +5,8 @@ package androidx.preference;
* around the protected visibility of {@link Preference#onAttachedToHierarchy(PreferenceManager)}.
*/
public class PreferenceManagerBinder {
- private PreferenceManagerBinder() {}
+ private PreferenceManagerBinder() {
+ }
public static void bind(Preference pref, PreferenceManager manager) {
pref.onAttachedToHierarchy(manager);
diff --git a/app/src/main/java/org/transdroid/core/app/search/SearchHelper.java b/app/src/main/java/org/transdroid/core/app/search/SearchHelper.java
index fea93f02..c63aa763 100644
--- a/app/src/main/java/org/transdroid/core/app/search/SearchHelper.java
+++ b/app/src/main/java/org/transdroid/core/app/search/SearchHelper.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -35,134 +35,138 @@ import android.net.Uri;
@EBean(scope = Scope.Singleton)
public class SearchHelper {
- static final int CURSOR_SEARCH_ID = 0;
- static final int CURSOR_SEARCH_NAME = 1;
- static final int CURSOR_SEARCH_TORRENTURL = 2;
- static final int CURSOR_SEARCH_DETAILSURL = 3;
- static final int CURSOR_SEARCH_SIZE = 4;
- static final int CURSOR_SEARCH_ADDED = 5;
- static final int CURSOR_SEARCH_SEEDERS = 6;
- static final int CURSOR_SEARCH_LEECHERS = 7;
-
- static final int CURSOR_SITE_ID = 0;
- static final int CURSOR_SITE_CODE = 1;
- static final int CURSOR_SITE_NAME = 2;
- static final int CURSOR_SITE_RSSURL = 3;
- static final int CURSOR_SITE_ISPRIVATE = 4;
-
- @RootContext
- protected Context context;
-
- public enum SearchSortOrder {
- Combined, BySeeders
- }
-
- /**
- * Return whether the Torrent Search package is installed and available to query against
- * @return True if the available sites can be retrieved from the content provider, false otherwise
- */
- public boolean isTorrentSearchInstalled() {
- return getAvailableSites() != null;
- }
-
- /**
- * Queries the Torrent Search package for all available in-app search sites. This method is synchronous.
- * @return A list of available search sites as POJOs, or null if the Torrent Search package is not installed
- */
- public List getAvailableSites() {
-
- // Try to access the TorrentSitesProvider of the Torrent Search app
- Uri uri = Uri.parse("content://org.transdroid.search.torrentsitesprovider/sites");
- ContentProviderClient test = context.getContentResolver().acquireContentProviderClient(uri);
- if (test == null) {
- // Torrent Search package is not yet installed
- return null;
- }
-
- // Query the available in-app torrent search sites
- Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
- if (cursor == null) {
- // The installed Torrent Search version is corrupt or incompatible
- return null;
- }
- List sites = new ArrayList<>();
- if (cursor.moveToFirst()) {
- do {
- // Read the cursor fields into the SearchSite object
- sites.add(new SearchSite(cursor.getInt(CURSOR_SITE_ID), cursor.getString(CURSOR_SITE_CODE), cursor
- .getString(CURSOR_SITE_NAME), cursor.getString(CURSOR_SITE_RSSURL),
- cursor.getColumnNames().length > 4 && cursor.getInt(CURSOR_SITE_ISPRIVATE) == 1));
- } while (cursor.moveToNext());
- }
-
- cursor.close();
- return sites;
-
- }
-
- /**
- * Queries the Torrent Search module to search for torrents on the web. This method is synchronous and should always
- * be called in a background thread.
- * @param query The search query to pass to the torrent site
- * @param site The site to search, as retrieved from the TorrentSitesProvider, or null if the Torrent Search package
- * @param sortBy The sort order to request from the torrent site, if supported
- * @return A list of torrent search results as POJOs, or null if the Torrent Search package is not installed or
- * there is no internet connection
- */
- public ArrayList search(String query, SearchSite site, SearchSortOrder sortBy) {
-
- // Try to query the TorrentSearchProvider to search for torrents on the web
- Uri uri = Uri.parse("content://org.transdroid.search.torrentsearchprovider/search/" + query);
- Cursor cursor;
- if (site == null) {
- // If no explicit site was supplied, rely on the Torrent Search package's default
- cursor = context.getContentResolver().query(uri, null, null, null, sortBy.name());
- } else {
- cursor = context.getContentResolver().query(uri, null, "SITE = ?", new String[] { site.getKey() },
- sortBy.name());
- }
- if (cursor == null) {
- // The content provider could not load any content (for example when there is no connection)
- return null;
- }
- if (cursor.moveToFirst()) {
- ArrayList results = new ArrayList<>();
- do {
- // Read the cursor fields into the SearchResult object
- results.add(new SearchResult(cursor.getInt(CURSOR_SEARCH_ID), cursor.getString(CURSOR_SEARCH_NAME),
- cursor.getString(CURSOR_SEARCH_TORRENTURL), cursor.getString(CURSOR_SEARCH_DETAILSURL), cursor
- .getString(CURSOR_SEARCH_SIZE), cursor.getLong(CURSOR_SEARCH_ADDED), cursor
- .getString(CURSOR_SEARCH_SEEDERS), cursor.getString(CURSOR_SEARCH_LEECHERS)));
- } while (cursor.moveToNext());
- cursor.close();
- return results;
- }
-
- // Torrent Search package is not yet installed
- cursor.close();
- return null;
-
- }
-
- /**
- * Asks the Torrent Search module to download a torrent file given the provided url, while using the specifics of
- * the supplied torrent search site to do so. This way the Search Module can take care of user credentials, for
- * example.
- * @param site The unique key of the search site that this url belongs to, which is used to create a connection
- * specific to this (private) site
- * @param url The full url of the torrent to download
- * @return A file input stream handler that points to the locally downloaded file
- * @throws FileNotFoundException Thrown when the requested url could not be downloaded or is not locally available
- */
- public InputStream getFile(String site, String url) throws FileNotFoundException {
- try {
- Uri uri = Uri.parse("content://org.transdroid.search.torrentsearchprovider/get/" + site + "/"
- + URLEncoder.encode(url, "UTF-8"));
- return context.getContentResolver().openInputStream(uri);
- } catch (UnsupportedEncodingException e) {
- // Ignore
- return null;
- }
- }
+ static final int CURSOR_SEARCH_ID = 0;
+ static final int CURSOR_SEARCH_NAME = 1;
+ static final int CURSOR_SEARCH_TORRENTURL = 2;
+ static final int CURSOR_SEARCH_DETAILSURL = 3;
+ static final int CURSOR_SEARCH_SIZE = 4;
+ static final int CURSOR_SEARCH_ADDED = 5;
+ static final int CURSOR_SEARCH_SEEDERS = 6;
+ static final int CURSOR_SEARCH_LEECHERS = 7;
+
+ static final int CURSOR_SITE_ID = 0;
+ static final int CURSOR_SITE_CODE = 1;
+ static final int CURSOR_SITE_NAME = 2;
+ static final int CURSOR_SITE_RSSURL = 3;
+ static final int CURSOR_SITE_ISPRIVATE = 4;
+
+ @RootContext
+ protected Context context;
+
+ public enum SearchSortOrder {
+ Combined, BySeeders
+ }
+
+ /**
+ * Return whether the Torrent Search package is installed and available to query against
+ *
+ * @return True if the available sites can be retrieved from the content provider, false otherwise
+ */
+ public boolean isTorrentSearchInstalled() {
+ return getAvailableSites() != null;
+ }
+
+ /**
+ * Queries the Torrent Search package for all available in-app search sites. This method is synchronous.
+ *
+ * @return A list of available search sites as POJOs, or null if the Torrent Search package is not installed
+ */
+ public List getAvailableSites() {
+
+ // Try to access the TorrentSitesProvider of the Torrent Search app
+ Uri uri = Uri.parse("content://org.transdroid.search.torrentsitesprovider/sites");
+ ContentProviderClient test = context.getContentResolver().acquireContentProviderClient(uri);
+ if (test == null) {
+ // Torrent Search package is not yet installed
+ return null;
+ }
+
+ // Query the available in-app torrent search sites
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ if (cursor == null) {
+ // The installed Torrent Search version is corrupt or incompatible
+ return null;
+ }
+ List sites = new ArrayList<>();
+ if (cursor.moveToFirst()) {
+ do {
+ // Read the cursor fields into the SearchSite object
+ sites.add(new SearchSite(cursor.getInt(CURSOR_SITE_ID), cursor.getString(CURSOR_SITE_CODE), cursor
+ .getString(CURSOR_SITE_NAME), cursor.getString(CURSOR_SITE_RSSURL),
+ cursor.getColumnNames().length > 4 && cursor.getInt(CURSOR_SITE_ISPRIVATE) == 1));
+ } while (cursor.moveToNext());
+ }
+
+ cursor.close();
+ return sites;
+
+ }
+
+ /**
+ * Queries the Torrent Search module to search for torrents on the web. This method is synchronous and should always
+ * be called in a background thread.
+ *
+ * @param query The search query to pass to the torrent site
+ * @param site The site to search, as retrieved from the TorrentSitesProvider, or null if the Torrent Search package
+ * @param sortBy The sort order to request from the torrent site, if supported
+ * @return A list of torrent search results as POJOs, or null if the Torrent Search package is not installed or
+ * there is no internet connection
+ */
+ public ArrayList search(String query, SearchSite site, SearchSortOrder sortBy) {
+
+ // Try to query the TorrentSearchProvider to search for torrents on the web
+ Uri uri = Uri.parse("content://org.transdroid.search.torrentsearchprovider/search/" + query);
+ Cursor cursor;
+ if (site == null) {
+ // If no explicit site was supplied, rely on the Torrent Search package's default
+ cursor = context.getContentResolver().query(uri, null, null, null, sortBy.name());
+ } else {
+ cursor = context.getContentResolver().query(uri, null, "SITE = ?", new String[]{site.getKey()},
+ sortBy.name());
+ }
+ if (cursor == null) {
+ // The content provider could not load any content (for example when there is no connection)
+ return null;
+ }
+ if (cursor.moveToFirst()) {
+ ArrayList results = new ArrayList<>();
+ do {
+ // Read the cursor fields into the SearchResult object
+ results.add(new SearchResult(cursor.getInt(CURSOR_SEARCH_ID), cursor.getString(CURSOR_SEARCH_NAME),
+ cursor.getString(CURSOR_SEARCH_TORRENTURL), cursor.getString(CURSOR_SEARCH_DETAILSURL), cursor
+ .getString(CURSOR_SEARCH_SIZE), cursor.getLong(CURSOR_SEARCH_ADDED), cursor
+ .getString(CURSOR_SEARCH_SEEDERS), cursor.getString(CURSOR_SEARCH_LEECHERS)));
+ } while (cursor.moveToNext());
+ cursor.close();
+ return results;
+ }
+
+ // Torrent Search package is not yet installed
+ cursor.close();
+ return null;
+
+ }
+
+ /**
+ * Asks the Torrent Search module to download a torrent file given the provided url, while using the specifics of
+ * the supplied torrent search site to do so. This way the Search Module can take care of user credentials, for
+ * example.
+ *
+ * @param site The unique key of the search site that this url belongs to, which is used to create a connection
+ * specific to this (private) site
+ * @param url The full url of the torrent to download
+ * @return A file input stream handler that points to the locally downloaded file
+ * @throws FileNotFoundException Thrown when the requested url could not be downloaded or is not locally available
+ */
+ public InputStream getFile(String site, String url) throws FileNotFoundException {
+ try {
+ Uri uri = Uri.parse("content://org.transdroid.search.torrentsearchprovider/get/" + site + "/"
+ + URLEncoder.encode(url, "UTF-8"));
+ return context.getContentResolver().openInputStream(uri);
+ } catch (UnsupportedEncodingException e) {
+ // Ignore
+ return null;
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/search/SearchResult.java b/app/src/main/java/org/transdroid/core/app/search/SearchResult.java
index ba54524f..353e80f1 100644
--- a/app/src/main/java/org/transdroid/core/app/search/SearchResult.java
+++ b/app/src/main/java/org/transdroid/core/app/search/SearchResult.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -23,100 +23,101 @@ import android.os.Parcelable;
/**
* Represents a search result as retrieved by querying the Torrent Search package.
+ *
* @author Eric Kok
*/
public class SearchResult implements Parcelable {
- private final int id;
- private final String name;
- private final String torrentUrl;
- private final String detailsUrl;
- private final String size;
- private final Date addedOn;
- private final String seeders;
- private final String leechers;
-
- public SearchResult(int id, String name, String torrentUrl, String detailsUrl, String size, long addedOnTime,
- String seeders, String leechers) {
- this.id = id;
- this.name = name;
- this.torrentUrl = torrentUrl;
- this.detailsUrl = detailsUrl;
- this.size = size;
- this.addedOn = (addedOnTime == -1L) ? null : new Date(addedOnTime);
- this.seeders = seeders;
- this.leechers = leechers;
- }
-
- public int getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public String getTorrentUrl() {
- return torrentUrl;
- }
-
- public String getDetailsUrl() {
- return detailsUrl;
- }
-
- public String getSize() {
- return size;
- }
-
- public Date getAddedOn() {
- return addedOn;
- }
-
- public String getSeeders() {
- return seeders;
- }
-
- public String getLeechers() {
- return leechers;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(id);
- out.writeString(name);
- out.writeString(torrentUrl);
- out.writeString(detailsUrl);
- out.writeString(size);
- out.writeLong(addedOn == null ? -1 : addedOn.getTime());
- out.writeString(seeders);
- out.writeString(leechers);
- }
-
- public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
- public SearchResult createFromParcel(Parcel in) {
- return new SearchResult(in);
- }
-
- public SearchResult[] newArray(int size) {
- return new SearchResult[size];
- }
- };
-
- public SearchResult(Parcel in) {
- id = in.readInt();
- name = in.readString();
- torrentUrl = in.readString();
- detailsUrl = in.readString();
- size = in.readString();
- long addedOnIn = in.readLong();
- addedOn = addedOnIn == -1 ? null : new Date(addedOnIn);
- seeders = in.readString();
- leechers = in.readString();
- }
+ private final int id;
+ private final String name;
+ private final String torrentUrl;
+ private final String detailsUrl;
+ private final String size;
+ private final Date addedOn;
+ private final String seeders;
+ private final String leechers;
+
+ public SearchResult(int id, String name, String torrentUrl, String detailsUrl, String size, long addedOnTime,
+ String seeders, String leechers) {
+ this.id = id;
+ this.name = name;
+ this.torrentUrl = torrentUrl;
+ this.detailsUrl = detailsUrl;
+ this.size = size;
+ this.addedOn = (addedOnTime == -1L) ? null : new Date(addedOnTime);
+ this.seeders = seeders;
+ this.leechers = leechers;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getTorrentUrl() {
+ return torrentUrl;
+ }
+
+ public String getDetailsUrl() {
+ return detailsUrl;
+ }
+
+ public String getSize() {
+ return size;
+ }
+
+ public Date getAddedOn() {
+ return addedOn;
+ }
+
+ public String getSeeders() {
+ return seeders;
+ }
+
+ public String getLeechers() {
+ return leechers;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(id);
+ out.writeString(name);
+ out.writeString(torrentUrl);
+ out.writeString(detailsUrl);
+ out.writeString(size);
+ out.writeLong(addedOn == null ? -1 : addedOn.getTime());
+ out.writeString(seeders);
+ out.writeString(leechers);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SearchResult createFromParcel(Parcel in) {
+ return new SearchResult(in);
+ }
+
+ public SearchResult[] newArray(int size) {
+ return new SearchResult[size];
+ }
+ };
+
+ public SearchResult(Parcel in) {
+ id = in.readInt();
+ name = in.readString();
+ torrentUrl = in.readString();
+ detailsUrl = in.readString();
+ size = in.readString();
+ long addedOnIn = in.readLong();
+ addedOn = addedOnIn == -1 ? null : new Date(addedOnIn);
+ seeders = in.readString();
+ leechers = in.readString();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/search/SearchSite.java b/app/src/main/java/org/transdroid/core/app/search/SearchSite.java
index bd7f02fb..cd21db43 100644
--- a/app/src/main/java/org/transdroid/core/app/search/SearchSite.java
+++ b/app/src/main/java/org/transdroid/core/app/search/SearchSite.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -21,48 +21,49 @@ import org.transdroid.core.gui.search.SearchSetting;
/**
* Represents an available torrent site that can be searched using the Torrent Search package.
+ *
* @author Eric Kok
*/
public class SearchSite implements SimpleListItem, SearchSetting {
- private final int id;
- private final String key;
- private final String name;
- private final String rssFeedUrl;
- private final boolean isPrivate;
-
- public SearchSite(int id, String key, String name, String rssFeedUrl, boolean isPrivate) {
- this.id = id;
- this.key = key;
- this.name = name;
- this.rssFeedUrl = rssFeedUrl;
- this.isPrivate = isPrivate;
- }
+ private final int id;
+ private final String key;
+ private final String name;
+ private final String rssFeedUrl;
+ private final boolean isPrivate;
- public int getId() {
- return id;
- }
+ public SearchSite(int id, String key, String name, String rssFeedUrl, boolean isPrivate) {
+ this.id = id;
+ this.key = key;
+ this.name = name;
+ this.rssFeedUrl = rssFeedUrl;
+ this.isPrivate = isPrivate;
+ }
- public String getKey() {
- return key;
- }
+ public int getId() {
+ return id;
+ }
- @Override
- public String getName() {
- return name;
- }
+ public String getKey() {
+ return key;
+ }
- public String getRssFeedUrl() {
- return rssFeedUrl;
- }
+ @Override
+ public String getName() {
+ return name;
+ }
- @Override
- public String getBaseUrl() {
- return rssFeedUrl;
- }
+ public String getRssFeedUrl() {
+ return rssFeedUrl;
+ }
+
+ @Override
+ public String getBaseUrl() {
+ return rssFeedUrl;
+ }
+
+ public boolean isPrivate() {
+ return isPrivate;
+ }
- public boolean isPrivate() {
- return isPrivate;
- }
-
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java b/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java
index 9fe56f30..bc4f5c28 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -49,724 +49,764 @@ import java.util.List;
/**
* Singleton object to access all application settings, including stored servers, web search sites and RSS feeds.
+ *
* @author Eric Kok
*/
@EBean(scope = Scope.Singleton)
public class ApplicationSettings {
- public static final int DEFAULTSERVER_LASTUSED = -2;
- public static final int DEFAULTSERVER_ASKONADD = -1;
-
- @RootContext
- protected Context context;
- private SharedPreferences prefs;
- @Bean
- protected SearchHelper searchHelper;
-
- protected ApplicationSettings(Context context) {
- prefs = PreferenceManager.getDefaultSharedPreferences(context);
- }
-
- /**
- * Returns all available user-configured normal and seed servers
- * @return A list of all stored server settings objects
- */
- public List getAllServerSettings() {
- List all = new ArrayList<>();
- all.addAll(getNormalServerSettings());
- for (SeedboxProvider provider : SeedboxProvider.values()) {
- all.addAll(provider.getSettings().getAllServerSettings(prefs, all.size()));
- }
- return all;
- }
-
- /**
- * Returns the order number/identifying key of the last server, normal or seedbox configured
- * @return The zero-based order number (index) of the last stored server settings
- */
- public int getMaxOfAllServers() {
- int max = getMaxNormalServer();
- for (SeedboxProvider provider : SeedboxProvider.values()) {
- max += provider.getSettings().getMaxSeedboxOrder(prefs) + 1;
- }
- return max;
- }
-
- /**
- * Returns the server settings for either a normal or a seedbox server as the user configured. WARNING: This method
- * does not check if the settings actually exist and may reply on empty default if called for a non-existing server.
- * @param order The order number/identifying key of the server's settings to retrieve, where the normal servers are
- * first and the seedboxes are numbers thereafter onwards
- * @return The server settings object, loaded from shared preferences
- */
- public ServerSetting getServerSetting(int order) {
- int max = getMaxNormalServer() + 1;
- if (order < max) {
- return getNormalServerSetting(order);
- }
- for (SeedboxProvider provider : SeedboxProvider.values()) {
- int offset = max;
- max += provider.getSettings().getMaxSeedboxOrder(prefs) + 1;
- if (order < max) {
- return provider.getSettings().getServerSetting(prefs, offset, order - offset);
- }
- }
- return null;
- }
-
- /**
- * Returns all available normal, user-configured servers (so no seedbox settings)
- * @return A list of all stored server settings objects
- */
- public List getNormalServerSettings() {
- List servers = new ArrayList<>();
- for (int i = 0; i <= getMaxNormalServer(); i++) {
- servers.add(getNormalServerSetting(i));
- }
- return Collections.unmodifiableList(servers);
- }
-
- /**
- * Returns the order number/identifying key of the last normal server
- * @return The zero-based order number (index) of the last stored normal server settings
- */
- public int getMaxNormalServer() {
- for (int i = 0; true; i++) {
- if (prefs.getString("server_type_" + i, null) == null || prefs.getString("server_address_" + i, null) == null)
- return i - 1;
- }
- }
-
- /**
- * Returns the user-specified server settings for a normal (non-seedbox) server. WARNING: This method does not check
- * if the settings actually exist and may rely on empty defaults if called for a non-existing server.
- * @param order The order number/identifying key of the normal server's settings to retrieve
- * @return The server settings object, loaded from shared preferences
- */
- public ServerSetting getNormalServerSetting(int order) {
- // @formatter:off
- Daemon type = Daemon.fromCode(prefs.getString("server_type_" + order, null));
- boolean ssl = prefs.getBoolean("server_sslenabled_" + order, false);
- boolean localSsl = prefs.getBoolean("server_localsslenabled_" + order, ssl);
-
- String port = prefs.getString("server_port_" + order, null);
- if (TextUtils.isEmpty(port))
- port = Integer.toString(Daemon.getDefaultPortNumber(type, ssl));
- String localPort = prefs.getString("server_localport_" + order, null);
- if (TextUtils.isEmpty(localPort))
- localPort = port; // Default to the normal (non-local) port
- try {
- parseInt(port, Daemon.getDefaultPortNumber(type, ssl));
- } catch (NumberFormatException e) {
- port = Integer.toString(Daemon.getDefaultPortNumber(type, ssl));
- }
- try {
- parseInt(localPort, parseInt(port, Daemon.getDefaultPortNumber(type, ssl)));
- } catch (NumberFormatException e) {
- localPort = port;
- }
-
- return new ServerSetting(order,
- prefs.getString("server_name_" + order, null),
- type,
- trim(prefs.getString("server_address_" + order, null)),
- trim(prefs.getString("server_localaddress_" + order, null)),
- parseInt(localPort, parseInt(port, Daemon.getDefaultPortNumber(type, ssl))),
- prefs.getString("server_localnetwork_" + order, null),
- parseInt(port, Daemon.getDefaultPortNumber(type, ssl)),
- ssl, localSsl,
- prefs.getBoolean("server_ssltrustall_" + order, false),
- prefs.getString("server_ssltrustkey_" + order, null),
- prefs.getString("server_folder_" + order, null),
- !prefs.getBoolean("server_disableauth_" + order, false),
- prefs.getString("server_user_" + order, null),
- prefs.getString("server_pass_" + order, null),
- prefs.getString("server_extrapass_" + order, null),
- OS.fromCode(prefs.getString("server_os_" + order, "type_linux")),
- prefs.getString("server_downloaddir_" + order, null),
- prefs.getString("server_ftpurl_" + order, null),
- prefs.getString("server_ftppass_" + order, null),
- parseInt(prefs.getString("server_timeout_" + order, "8"), 8),
- prefs.getBoolean("server_alarmfinished_" + order, true),
- prefs.getBoolean("server_alarmnew_" + order, false),
- prefs.getString("server_alarmexclude_" + order, null),
- prefs.getString("server_alarminclude_" + order, null),
- false);
- // @formatter:on
- }
-
- /**
- * Removes all settings related to a configured server. Since servers are ordered, the order of the remaining
- * servers will be updated accordingly.
- * @param order The identifying order number/key of the settings to remove
- */
- public void removeNormalServerSettings(int order) {
- if (prefs.getString("server_type_" + order, null) == null)
- return; // The settings that were requested to be removed do not exist
-
- // Copy all settings higher than the supplied order number to the previous spot
- Editor edit = prefs.edit();
- int max = getMaxNormalServer();
- for (int i = order; i < max; i++) {
- edit.putString("server_name_" + i, prefs.getString("server_name_" + (i + 1), null));
- edit.putString("server_type_" + i, prefs.getString("server_type_" + (i + 1), null));
- edit.putString("server_address_" + i, prefs.getString("server_address_" + (i + 1), null));
- edit.putString("server_localaddress_" + i, prefs.getString("server_localaddress_" + (i + 1), null));
- edit.putString("server_localnetwork_" + i, prefs.getString("server_localnetwork_" + (i + 1), null));
- edit.putString("server_port_" + i, prefs.getString("server_port_" + (i + 1), null));
- edit.putBoolean("server_sslenabled_" + i, prefs.getBoolean("server_sslenabled_" + (i + 1), false));
- edit.putBoolean("server_localsslenabled_" + i, prefs.getBoolean("server_localsslenabled_" + (i + 1), false));
- edit.putBoolean("server_ssltrustall_" + i, prefs.getBoolean("server_ssltrustall_" + (i + 1), false));
- edit.putString("server_ssltrustkey_" + i, prefs.getString("server_ssltrustkey_" + (i + 1), null));
- edit.putString("server_folder_" + i, prefs.getString("server_folder_" + (i + 1), null));
- edit.putBoolean("server_disableauth_" + i, prefs.getBoolean("server_disableauth_" + (i + 1), false));
- edit.putString("server_user_" + i, prefs.getString("server_user_" + (i + 1), null));
- edit.putString("server_pass_" + i, prefs.getString("server_pass_" + (i + 1), null));
- edit.putString("server_extrapass_" + i, prefs.getString("server_extrapass_" + (i + 1), null));
- edit.putString("server_os_" + i, prefs.getString("server_os_" + (i + 1), null));
- edit.putString("server_downloaddir_" + i, prefs.getString("server_downloaddir_" + (i + 1), null));
- edit.putString("server_ftpurl_" + i, prefs.getString("server_ftpurl_" + (i + 1), null));
- edit.putString("server_ftppass_" + i, prefs.getString("server_ftppass_" + (i + 1), null));
- edit.putString("server_timeout_" + i, prefs.getString("server_timeout_" + (i + 1), null));
- edit.putBoolean("server_alarmfinished_" + i, prefs.getBoolean("server_alarmfinished_" + (i + 1), true));
- edit.putBoolean("server_alarmfinished_" + i, prefs.getBoolean("server_alarmfinished_" + (i + 1), false));
- }
-
- // Remove the last settings, of which we are now sure are no longer required
- edit.remove("server_name_" + max);
- edit.remove("server_type_" + max);
- edit.remove("server_address_" + max);
- edit.remove("server_localaddress_" + max);
- edit.remove("server_localnetwork_" + max);
- edit.remove("server_port_" + max);
- edit.remove("server_sslenabled_" + max);
- edit.remove("server_localsslenabled_" + max);
- edit.remove("server_ssltrustall_" + max);
- edit.remove("server_ssltrustkey_" + max);
- edit.remove("server_folder_" + max);
- edit.remove("server_disableauth_" + max);
- edit.remove("server_user_" + max);
- edit.remove("server_pass_" + max);
- edit.remove("server_extrapass_" + max);
- edit.remove("server_os_" + max);
- edit.remove("server_downloaddir_" + max);
- edit.remove("server_ftpurl_" + max);
- edit.remove("server_ftppass_" + max);
- edit.remove("server_timeout_" + max);
- edit.remove("server_alarmfinished_" + max);
- edit.remove("server_alarmfinished_" + max);
-
- // Perhaps we should also update the default server to match the server's new id or remove the default selection
- // in case it was this server that was removed
- int defaultServer = getDefaultServerKey();
- if (defaultServer == order) {
- edit.remove("header_defaultserver");
- } else if (defaultServer > order) {
- // Move 'up' one place to account for the removed server setting
- edit.putString("header_defaultserver", String.valueOf(--order));
- }
-
- edit.apply();
-
- }
-
- /**
- * Returns the settings of the server that was explicitly selected by the user to select as default or, when no
- * specific default server was selected, the last used server settings. As opposed to getDefaultServerKey(int), this
- * method checks whether the particular server still exists (and returns the first server if not). If no servers are
- * configured, null is returned.
- * @return A server settings object of the server to use by default, or null if no server is yet configured
- */
- public ServerSetting getDefaultServer() {
-
- int defaultServer = getDefaultServerKey();
- if (defaultServer == DEFAULTSERVER_LASTUSED || defaultServer == DEFAULTSERVER_ASKONADD) {
- return getLastUsedServer();
- }
-
- // Use the explicitly selected default server
- int max = getMaxOfAllServers(); // Zero-based index, so with max == 0 there is 1 server
- if (max < 0) {
- // No servers configured
- return null;
- }
- if (defaultServer < 0 || defaultServer > max) {
- // Last server was never set or no longer exists
- return getServerSetting(0);
- }
- return getServerSetting(defaultServer);
-
- }
-
- /**
- * Returns the unique key of the server setting that the user selected as their default server, or code indicating
- * that the last used server should be selected by default; use with getDefaultServer directly. WARNING: the
- * returned string may no longer refer to a known server setting key.
- * @return An integer; if it is 0 or higher it represents the unique key of a configured server setting, -2 means
- * the last used server should be selected as default instead and -1 means the last used server should be
- * selected by default for viewing yet it should always ask when adding a new torrent
- */
- public int getDefaultServerKey() {
- String defaultServer = prefs.getString("header_defaultserver", Integer.toString(DEFAULTSERVER_LASTUSED));
- try {
- return Integer.parseInt(defaultServer);
- } catch (NumberFormatException e) {
- // This should NEVER happen but if the setting somehow is not a number, return the default
- return DEFAULTSERVER_LASTUSED;
- }
- }
-
- /**
- * Returns the settings of the server that was last used by the user. As opposed to getLastUsedServerKey(int), this
- * method checks whether a server was already registered as being last used and check whether the server still
- * exists. It returns the first server if that fails. If no servers are configured, null is returned.
- * @return A server settings object of the last used server (or, if not known, the first server), or null if no
- * servers exist
- */
- public ServerSetting getLastUsedServer() {
- int max = getMaxOfAllServers(); // Zero-based index, so with max == 0 there is 1 server
- if (max < 0) {
- // No servers configured
- return null;
- }
- int last = getLastUsedServerKey();
- if (last < 0 || last > max) {
- // Last server was never set or no longer exists
- return getServerSetting(0);
- }
- return getServerSetting(last);
- }
-
- /**
- * Returns the order number/unique key of the server that the used last used; use with getServerSettings(int) or
- * call getLastUsedServer directly. WARNING: the returned integer may no longer refer to a known server settings
- * object: check the bounds.
- * @return An integer indicating the order number/key or the last used server, or -1 if it was not set
- */
- public int getLastUsedServerKey() {
- return prefs.getInt("system_lastusedserver", -1);
- }
-
- /**
- * Registers some server as being the last used by the user
- * @param server The settings of the server that the user last used
- */
- public void setLastUsedServer(ServerSetting server) {
- setLastUsedServerKey(server.getOrder());
- }
-
- /**
- * Registers the order number/unique key of some server as being last used by the user
- * @param order The key identifying the specific server
- */
- public void setLastUsedServerKey(int order) {
- prefs.edit().putInt("system_lastusedserver", order).apply();
- }
-
- /**
- * Returns the unique code that (should) uniquely identify a navigation filter, such as a label, in the list of all
- * available filters
- * @return A code that the last used navigation filter reported as uniquely identifying itself, or null if no last
- * used filter is known
- */
- public String getLastUsedNavigationFilter() {
- return prefs.getString("system_lastusedfilter", null);
- }
-
- /**
- * Registers some navigation filter as being the last used by the user
- * @param filter The navigation filter that the user last used in the interface
- */
- public void setLastUsedNavigationFilter(NavigationFilter filter) {
- prefs.edit().putString("system_lastusedfilter", filter.getCode()).apply();
- }
-
- /**
- * Returns all available user-configured web-based (as opped to in-app) search sites
- * @return A list of all stored web search site settings objects
- */
- public List getWebsearchSettings() {
- List websearches = new ArrayList<>();
- for (int i = 0; i <= getMaxWebsearch(); i++) {
- websearches.add(getWebsearchSetting(i));
- }
- return Collections.unmodifiableList(websearches);
- }
-
- /**
- * Returns the order number/identifying key of the last web search site
- * @return The zero-based order number (index) of the last stored web search site
- */
- public int getMaxWebsearch() {
- for (int i = 0; true; i++) {
- if (prefs.getString("websearch_baseurl_" + i, null) == null)
- return i - 1;
- }
- }
-
- /**
- * Returns the user-specified web-based search site setting for a specific site
- * @param order The order number/identifying key of the settings to retrieve
- * @return The web search site settings object, loaded from shared preferences
- */
- public WebsearchSetting getWebsearchSetting(int order) {
- // @formatter:off
- return new WebsearchSetting(order,
- prefs.getString("websearch_name_" + order, null),
- prefs.getString("websearch_baseurl_" + order, null),
- prefs.getString("websearch_cookies_" + order, null));
- // @formatter:on
- }
-
- /**
- * Removes all settings related to a configured web-based search site. Since sites are ordered, the order of the
- * remaining sites will be updated accordingly.
- * @param order The identifying order number/key of the settings to remove
- */
- public void removeWebsearchSettings(int order) {
- if (prefs.getString("websearch_baseurl_" + order, null) == null)
- return; // The settings that were requested to be removed do not exist
-
- // Copy all settings higher than the supplied order number to the previous spot
- Editor edit = prefs.edit();
- int max = getMaxWebsearch();
- for (int i = order; i < max; i++) {
- edit.putString("websearch_name_" + i, prefs.getString("websearch_name_" + (i + 1), null));
- edit.putString("websearch_baseurl_" + i, prefs.getString("websearch_baseurl_" + (i + 1), null));
- edit.putString("websearch_cookies_" + i, prefs.getString("websearch_cookies_" + (i + 1), null));
- }
-
- // Remove the last settings, of which we are now sure are no longer required
- edit.remove("websearch_name_" + max);
- edit.remove("websearch_baseurl_" + max);
- edit.remove("websearch_cookies_" + max);
- edit.apply();
-
- }
-
- /**
- * Returns all available user-configured RSS feeds
- * @return A list of all stored RSS feed settings objects
- */
- public List getRssfeedSettings() {
- List rssfeeds = new ArrayList<>();
- for (int i = 0; i <= getMaxRssfeed(); i++) {
- rssfeeds.add(getRssfeedSetting(i));
- }
- return Collections.unmodifiableList(rssfeeds);
- }
-
- /**
- * Returns the order number/identifying key of the last stored RSS feed
- * @return The zero-based order number (index) of the last stored RSS feed
- */
- public int getMaxRssfeed() {
- for (int i = 0; true; i++) {
- if (prefs.getString("rssfeed_url_" + i, null) == null)
- return i - 1;
- }
- }
-
- /**
- * Returns the user-specified RSS feed setting for a specific feed
- * @param order The order number/identifying key of the settings to retrieve
- * @return The RSS feed settings object, loaded from shared preferences
- */
- public RssfeedSetting getRssfeedSetting(int order) {
- // @formatter:off
- long lastViewed = prefs.getLong("rssfeed_lastviewed_" + order, -1);
- return new RssfeedSetting(order,
- prefs.getString("rssfeed_name_" + order, null),
- prefs.getString("rssfeed_url_" + order, null),
- prefs.getBoolean("rssfeed_reqauth_" + order, false),
- prefs.getBoolean("rssfeed_alarmnew_" + order, true),
- prefs.getString("rssfeed_exclude_" + order, null),
- prefs.getString("rssfeed_include_" + order, null),
- lastViewed == -1L ? null : new Date(lastViewed),
- prefs.getString("rssfeed_lastvieweditemurl_" + order, null));
- // @formatter:on
- }
-
- /**
- * Removes all settings related to a configured RSS feed. Since feeds are ordered, the order of the remaining feeds
- * will be updated accordingly.
- * @param order The identifying order number/key of the settings to remove
- */
- public void removeRssfeedSettings(int order) {
- if (prefs.getString("rssfeed_url_" + order, null) == null)
- return; // The settings that were requested to be removed do not exist
-
- // Copy all settings higher than the supplied order number to the previous spot
- Editor edit = prefs.edit();
- int max = getMaxRssfeed();
- for (int i = order; i < max; i++) {
- edit.putString("rssfeed_name_" + i, prefs.getString("rssfeed_name_" + (i + 1), null));
- edit.putString("rssfeed_url_" + i, prefs.getString("rssfeed_url_" + (i + 1), null));
- edit.putBoolean("rssfeed_reqauth_" + i, prefs.getBoolean("rssfeed_reqauth_" + (i + 1), false));
- edit.putBoolean("rssfeed_alarmnew_" + i, prefs.getBoolean("rssfeed_alarmnew_" + (i + 1), true));
- edit.putString("rssfeed_exclude_" + i, prefs.getString("rssfeed_exclude_" + (i + 1), null));
- edit.putString("rssfeed_include_" + i, prefs.getString("rssfeed_include_" + (i + 1), null));
- edit.putLong("rssfeed_lastviewed_" + i, prefs.getLong("rssfeed_lastviewed_" + (i + 1), -1));
- edit.putString("rssfeed_lastvieweditemurl_" + i, prefs.getString("rssfeed_lastvieweditemurl_" + (i + 1), null));
- }
-
- // Remove the last settings, of which we are now sure are no longer required
- edit.remove("rssfeed_name_" + max);
- edit.remove("rssfeed_url_" + max);
- edit.remove("rssfeed_reqauth_" + max);
- edit.remove("rssfeed_alarmnew_" + max);
- edit.remove("rssfeed_exclude_" + max);
- edit.remove("rssfeed_include_" + max);
- edit.remove("rssfeed_lastviewed_" + max);
- edit.remove("rssfeed_lastvieweditemurl_" + max);
- edit.apply();
-
- }
-
- /**
- * Registers for some RSS feed (as identified by its order numbe/key) the last date and time that it was viewed by
- * the user. This is used to determine which items in an RSS feed are 'new'. Warning: any previously retrieved
- * {@link RssfeedSetting} object is now no longer in sync, as this will not automatically be updated in the object.
- * Use {@link #getRssfeedSetting(int)} to get fresh data.
- * @param order The identifying order number/key of the settings of te RSS feed that was viewed
- * @param lastViewed The date and time that the feed was last viewed; typically now
- * @param lastViewedItemUrl The url of the last item the last time that the feed was viewed
- */
- public void setRssfeedLastViewer(int order, Date lastViewed, String lastViewedItemUrl) {
- if (prefs.getString("rssfeed_url_" + order, null) == null)
- return; // The settings that were requested to be removed do not exist
- Editor edit = prefs.edit();
- edit.putLong("rssfeed_lastviewed_" + order, lastViewed.getTime());
- edit.putString("rssfeed_lastvieweditemurl_" + order, lastViewedItemUrl);
- edit.apply();
- }
-
- /**
- * Registers the torrents list sort order as being last used by the user
- * @param currentSortOrder The sort order property the user selected last
- * @param currentSortAscending The sort order direction that was last used
- */
- public void setLastUsedSortOrder(TorrentsSortBy currentSortOrder, boolean currentSortAscending) {
- Editor edit = prefs.edit();
- edit.putInt("system_lastusedsortorder", currentSortOrder.getCode());
- edit.putBoolean("system_lastusedsortdirection", currentSortAscending);
- edit.apply();
- }
-
- /**
- * Returns the sort order property that the user last used. Use together with {@link #getLastUsedSortDescending()}
- * to get the full last used sort settings.
- * @return The last used sort order enumeration value
- */
- public TorrentsSortBy getLastUsedSortOrder() {
- return TorrentsSortBy
- .getStatus(prefs.getInt("system_lastusedsortorder", TorrentsSortBy.Alphanumeric.getCode()));
- }
-
- /**
- * Registers the search list sort order as being last used by the user
- * @param currentSortOrder The sort order property the user selected last
- */
- public void setLastUsedSearchSortOrder(SearchSortOrder currentSortOrder) {
- Editor edit = prefs.edit();
- edit.putInt("system_lastusedsearchsortorder", currentSortOrder.ordinal());
- edit.apply();
- }
-
- /**
- * Returns the search sort order property that the user last used.
- * @return The last used sort order enumeration value
- */
- public SearchSortOrder getLastUsedSearchSortOrder() {
- return SearchSortOrder.values()[(prefs.getInt("system_lastusedsearchsortorder", SearchSortOrder.BySeeders.ordinal()))];
- }
-
- /**
- * Returns the sort order direction that the user last used. Use together with {@link #getLastUsedSortOrder()} to
- * get the full last used sort settings.
- * @return True if the last used sort direction was descending, false otherwise (i.e. the default ascending
- * direction)
- */
- public boolean getLastUsedSortDescending() {
- return prefs.getBoolean("system_lastusedsortdirection", false);
- }
-
- /**
- * Returns the list of all available in-app search sites as well as all web searches that the user configured.
- * @return A list of search settings, all of which are either a {@link SearchSite} or {@link WebsearchSetting}
- */
- public List getSearchSettings() {
- List all = new ArrayList<>();
- all.addAll(searchHelper.getAvailableSites());
- all.addAll(getWebsearchSettings());
- return Collections.unmodifiableList(all);
- }
-
- /**
- * Returns the settings of the search site that was last used by the user or was selected by the user as default
- * site in the main settings. As opposed to getLastUsedSearchSiteKey(int), this method checks whether a site was
- * already registered as being last used (or set as default) and checks whether the site still exists. It returns
- * the first in-app search site if that fails.
- * @return A site settings object of the last used server (or, if not known, the first server), or null if no
- * servers exist
- */
- public SearchSetting getLastUsedSearchSite() {
- String lastKey = getLastUsedSearchSiteKey();
- List allsites = searchHelper.getAvailableSites();
-
- if (lastKey == null) {
- // No site yet set specified; return the first in-app one, if available
- if (allsites != null) {
- return allsites.get(0);
- }
- return null;
- }
-
- int lastWebsearch = -1;
- if (lastKey.startsWith(WebsearchSetting.KEY_PREFIX)) {
- try {
- lastWebsearch = Integer.parseInt(lastKey.substring(WebsearchSetting.KEY_PREFIX.length()));
- } catch (Exception e) {
- // Not an in-app search site, but probably an in-app search
- }
- }
- if (lastWebsearch >= 0) {
- // The last used site should be a user-configured web search site
- int max = getMaxWebsearch(); // Zero-based index, so with max == 0 there is 1 server
- if (max < 0 || lastWebsearch > max) {
- // No web search sites configured
- return null;
- }
- return getWebsearchSetting(lastWebsearch);
- }
-
- // Should be an in-app search key
- if (allsites != null && !allsites.isEmpty()) {
- for (SearchSite searchSite : allsites) {
- if (searchSite.getKey().equals(lastKey)) {
- return searchSite;
- }
- }
- // Not found at all; probably a no longer existing web search; return the first in-app one
- return allsites.get(0);
- }
-
- return null;
- }
-
- /**
- * Returns the unique key of the site that the used last used or selected as default form the main settings; use
- * with getLastUsedSearchSite directly. WARNING: the returned string may no longer refer to a known web search site
- * or in-app search settings object.
- * @return A string indicating the key of the last used search site, or null if no site was yet used or set as
- * default
- */
- private String getLastUsedSearchSiteKey() {
- return prefs.getString("header_setsearchsite", null);
- }
-
- /**
- * Registers the unique key of some web search or in-app search site as being last used by the user
- * @param site The site settings to register as being last used
- */
- public void setLastUsedSearchSite(SearchSetting site) {
- prefs.edit().putString("header_setsearchsite", site.getKey()).apply();
- }
-
- /**
- * Returns the statistics of this server as it was last seen by the background server checker service.
- * @param server The server for which to retrieved the statistics from the stored preferences
- * @return A JSON array of JSON objects, each which represent a since torrent
- */
- public JSONArray getServerLastStats(ServerSetting server) {
- String lastStats = prefs.getString(server.getUniqueIdentifier(), null);
- if (lastStats == null)
- return null;
- try {
- return new JSONArray(lastStats);
- } catch (JSONException e) {
- return null;
- }
- }
-
- /**
- * Stores the now-last seen statistics of the supplied server by the background server checker service to the
- * internal stored preferences.
- * @param server The server to which the statistics apply to
- * @param lastStats A JSON array of JSON objects that each represent a single seen torrent
- */
- public void setServerLastStats(ServerSetting server, JSONArray lastStats) {
- prefs.edit().putString(server.getUniqueIdentifier(), lastStats.toString()).apply();
- }
-
- /**
- * Returns the user configuration for some specific app widget, if the widget is known at all.
- * @param appWidgetId The unique ID of the app widget to retrieve settings for, as supplied by the AppWidgetManager
- * @return A widget configuration object, or null if no settings were stored for the widget ID
- */
- public ListWidgetConfig getWidgetConfig(int appWidgetId) {
- if (!prefs.contains("widget_server_" + appWidgetId))
- return null;
- // @formatter:off
- return new ListWidgetConfig(
- prefs.getInt("widget_server_" + appWidgetId, -1),
- StatusType.valueOf(prefs.getString("widget_status_" + appWidgetId, StatusType.ShowAll.name())),
- TorrentsSortBy.valueOf(prefs.getString("widget_sortby_" + appWidgetId, TorrentsSortBy.Alphanumeric.name())),
- prefs.getBoolean("widget_reverse_" + appWidgetId, false),
- prefs.getBoolean("widget_showstatus_" + appWidgetId, false));
- // @formatter:on
- }
-
- /**
- * Stores the user settings for some specific app widget. Existing settings for the supplied app widget ID will be
- * overridden.
- * @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager
- * @param settings A widget configuration object, which may not be null
- */
- public void setWidgetConfig(int appWidgetId, ListWidgetConfig settings) {
- if (settings == null)
- throw new InvalidParameterException(
- "The widget setting may not be null. Use removeWidgetConfig instead to remove existing settings for some app widget.");
- Editor edit = prefs.edit();
- edit.putInt("widget_server_" + appWidgetId, settings.getServerId());
- edit.putString("widget_status_" + appWidgetId, settings.getStatusType().name());
- edit.putString("widget_sortby_" + appWidgetId, settings.getSortBy().name());
- edit.putBoolean("widget_reverse_" + appWidgetId, settings.shouldReserveSort());
- edit.putBoolean("widget_showstatus_" + appWidgetId, settings.shouldShowStatusView());
- edit.apply();
- }
-
- /**
- * Remove the setting for some specific app widget.
- * @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager
- */
- public void removeWidgetConfig(int appWidgetId) {
- Editor edit = prefs.edit();
- edit.remove("widget_server_" + appWidgetId);
- edit.remove("widget_status_" + appWidgetId);
- edit.remove("widget_sortby_" + appWidgetId);
- edit.remove("widget_reverse_" + appWidgetId);
- edit.remove("widget_showstatus_" + appWidgetId);
- edit.remove("widget_darktheme_" + appWidgetId);
- edit.apply();
- }
-
- /**
- * Trims away whitespace around a string, or returns null if str is null
- * @param str The string to trim, or null
- * @return The trimmed string, or null if str is null
- */
- private String trim(String str) {
- if (str == null) return null;
- return str.trim();
- }
-
- private int parseInt(String string, int defaultValue) {
- try {
- return Integer.parseInt(string);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
+ public static final int DEFAULTSERVER_LASTUSED = -2;
+ public static final int DEFAULTSERVER_ASKONADD = -1;
+
+ @RootContext
+ protected Context context;
+ private SharedPreferences prefs;
+ @Bean
+ protected SearchHelper searchHelper;
+
+ protected ApplicationSettings(Context context) {
+ prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ /**
+ * Returns all available user-configured normal and seed servers
+ *
+ * @return A list of all stored server settings objects
+ */
+ public List getAllServerSettings() {
+ List all = new ArrayList<>();
+ all.addAll(getNormalServerSettings());
+ for (SeedboxProvider provider : SeedboxProvider.values()) {
+ all.addAll(provider.getSettings().getAllServerSettings(prefs, all.size()));
+ }
+ return all;
+ }
+
+ /**
+ * Returns the order number/identifying key of the last server, normal or seedbox configured
+ *
+ * @return The zero-based order number (index) of the last stored server settings
+ */
+ public int getMaxOfAllServers() {
+ int max = getMaxNormalServer();
+ for (SeedboxProvider provider : SeedboxProvider.values()) {
+ max += provider.getSettings().getMaxSeedboxOrder(prefs) + 1;
+ }
+ return max;
+ }
+
+ /**
+ * Returns the server settings for either a normal or a seedbox server as the user configured. WARNING: This method
+ * does not check if the settings actually exist and may reply on empty default if called for a non-existing server.
+ *
+ * @param order The order number/identifying key of the server's settings to retrieve, where the normal servers are
+ * first and the seedboxes are numbers thereafter onwards
+ * @return The server settings object, loaded from shared preferences
+ */
+ public ServerSetting getServerSetting(int order) {
+ int max = getMaxNormalServer() + 1;
+ if (order < max) {
+ return getNormalServerSetting(order);
+ }
+ for (SeedboxProvider provider : SeedboxProvider.values()) {
+ int offset = max;
+ max += provider.getSettings().getMaxSeedboxOrder(prefs) + 1;
+ if (order < max) {
+ return provider.getSettings().getServerSetting(prefs, offset, order - offset);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns all available normal, user-configured servers (so no seedbox settings)
+ *
+ * @return A list of all stored server settings objects
+ */
+ public List getNormalServerSettings() {
+ List servers = new ArrayList<>();
+ for (int i = 0; i <= getMaxNormalServer(); i++) {
+ servers.add(getNormalServerSetting(i));
+ }
+ return Collections.unmodifiableList(servers);
+ }
+
+ /**
+ * Returns the order number/identifying key of the last normal server
+ *
+ * @return The zero-based order number (index) of the last stored normal server settings
+ */
+ public int getMaxNormalServer() {
+ for (int i = 0; true; i++) {
+ if (prefs.getString("server_type_" + i, null) == null || prefs.getString("server_address_" + i, null) == null)
+ return i - 1;
+ }
+ }
+
+ /**
+ * Returns the user-specified server settings for a normal (non-seedbox) server. WARNING: This method does not check
+ * if the settings actually exist and may rely on empty defaults if called for a non-existing server.
+ *
+ * @param order The order number/identifying key of the normal server's settings to retrieve
+ * @return The server settings object, loaded from shared preferences
+ */
+ public ServerSetting getNormalServerSetting(int order) {
+ // @formatter:off
+ Daemon type = Daemon.fromCode(prefs.getString("server_type_" + order, null));
+ boolean ssl = prefs.getBoolean("server_sslenabled_" + order, false);
+ boolean localSsl = prefs.getBoolean("server_localsslenabled_" + order, ssl);
+
+ String port = prefs.getString("server_port_" + order, null);
+ if (TextUtils.isEmpty(port))
+ port = Integer.toString(Daemon.getDefaultPortNumber(type, ssl));
+ String localPort = prefs.getString("server_localport_" + order, null);
+ if (TextUtils.isEmpty(localPort))
+ localPort = port; // Default to the normal (non-local) port
+ try {
+ parseInt(port, Daemon.getDefaultPortNumber(type, ssl));
+ } catch (NumberFormatException e) {
+ port = Integer.toString(Daemon.getDefaultPortNumber(type, ssl));
+ }
+ try {
+ parseInt(localPort, parseInt(port, Daemon.getDefaultPortNumber(type, ssl)));
+ } catch (NumberFormatException e) {
+ localPort = port;
+ }
+
+ return new ServerSetting(order,
+ prefs.getString("server_name_" + order, null),
+ type,
+ trim(prefs.getString("server_address_" + order, null)),
+ trim(prefs.getString("server_localaddress_" + order, null)),
+ parseInt(localPort, parseInt(port, Daemon.getDefaultPortNumber(type, ssl))),
+ prefs.getString("server_localnetwork_" + order, null),
+ parseInt(port, Daemon.getDefaultPortNumber(type, ssl)),
+ ssl, localSsl,
+ prefs.getBoolean("server_ssltrustall_" + order, false),
+ prefs.getString("server_ssltrustkey_" + order, null),
+ prefs.getString("server_folder_" + order, null),
+ !prefs.getBoolean("server_disableauth_" + order, false),
+ prefs.getString("server_user_" + order, null),
+ prefs.getString("server_pass_" + order, null),
+ prefs.getString("server_extrapass_" + order, null),
+ OS.fromCode(prefs.getString("server_os_" + order, "type_linux")),
+ prefs.getString("server_downloaddir_" + order, null),
+ prefs.getString("server_ftpurl_" + order, null),
+ prefs.getString("server_ftppass_" + order, null),
+ parseInt(prefs.getString("server_timeout_" + order, "8"), 8),
+ prefs.getBoolean("server_alarmfinished_" + order, true),
+ prefs.getBoolean("server_alarmnew_" + order, false),
+ prefs.getString("server_alarmexclude_" + order, null),
+ prefs.getString("server_alarminclude_" + order, null),
+ false);
+ // @formatter:on
+ }
+
+ /**
+ * Removes all settings related to a configured server. Since servers are ordered, the order of the remaining
+ * servers will be updated accordingly.
+ *
+ * @param order The identifying order number/key of the settings to remove
+ */
+ public void removeNormalServerSettings(int order) {
+ if (prefs.getString("server_type_" + order, null) == null)
+ return; // The settings that were requested to be removed do not exist
+
+ // Copy all settings higher than the supplied order number to the previous spot
+ Editor edit = prefs.edit();
+ int max = getMaxNormalServer();
+ for (int i = order; i < max; i++) {
+ edit.putString("server_name_" + i, prefs.getString("server_name_" + (i + 1), null));
+ edit.putString("server_type_" + i, prefs.getString("server_type_" + (i + 1), null));
+ edit.putString("server_address_" + i, prefs.getString("server_address_" + (i + 1), null));
+ edit.putString("server_localaddress_" + i, prefs.getString("server_localaddress_" + (i + 1), null));
+ edit.putString("server_localnetwork_" + i, prefs.getString("server_localnetwork_" + (i + 1), null));
+ edit.putString("server_port_" + i, prefs.getString("server_port_" + (i + 1), null));
+ edit.putBoolean("server_sslenabled_" + i, prefs.getBoolean("server_sslenabled_" + (i + 1), false));
+ edit.putBoolean("server_localsslenabled_" + i, prefs.getBoolean("server_localsslenabled_" + (i + 1), false));
+ edit.putBoolean("server_ssltrustall_" + i, prefs.getBoolean("server_ssltrustall_" + (i + 1), false));
+ edit.putString("server_ssltrustkey_" + i, prefs.getString("server_ssltrustkey_" + (i + 1), null));
+ edit.putString("server_folder_" + i, prefs.getString("server_folder_" + (i + 1), null));
+ edit.putBoolean("server_disableauth_" + i, prefs.getBoolean("server_disableauth_" + (i + 1), false));
+ edit.putString("server_user_" + i, prefs.getString("server_user_" + (i + 1), null));
+ edit.putString("server_pass_" + i, prefs.getString("server_pass_" + (i + 1), null));
+ edit.putString("server_extrapass_" + i, prefs.getString("server_extrapass_" + (i + 1), null));
+ edit.putString("server_os_" + i, prefs.getString("server_os_" + (i + 1), null));
+ edit.putString("server_downloaddir_" + i, prefs.getString("server_downloaddir_" + (i + 1), null));
+ edit.putString("server_ftpurl_" + i, prefs.getString("server_ftpurl_" + (i + 1), null));
+ edit.putString("server_ftppass_" + i, prefs.getString("server_ftppass_" + (i + 1), null));
+ edit.putString("server_timeout_" + i, prefs.getString("server_timeout_" + (i + 1), null));
+ edit.putBoolean("server_alarmfinished_" + i, prefs.getBoolean("server_alarmfinished_" + (i + 1), true));
+ edit.putBoolean("server_alarmfinished_" + i, prefs.getBoolean("server_alarmfinished_" + (i + 1), false));
+ }
+
+ // Remove the last settings, of which we are now sure are no longer required
+ edit.remove("server_name_" + max);
+ edit.remove("server_type_" + max);
+ edit.remove("server_address_" + max);
+ edit.remove("server_localaddress_" + max);
+ edit.remove("server_localnetwork_" + max);
+ edit.remove("server_port_" + max);
+ edit.remove("server_sslenabled_" + max);
+ edit.remove("server_localsslenabled_" + max);
+ edit.remove("server_ssltrustall_" + max);
+ edit.remove("server_ssltrustkey_" + max);
+ edit.remove("server_folder_" + max);
+ edit.remove("server_disableauth_" + max);
+ edit.remove("server_user_" + max);
+ edit.remove("server_pass_" + max);
+ edit.remove("server_extrapass_" + max);
+ edit.remove("server_os_" + max);
+ edit.remove("server_downloaddir_" + max);
+ edit.remove("server_ftpurl_" + max);
+ edit.remove("server_ftppass_" + max);
+ edit.remove("server_timeout_" + max);
+ edit.remove("server_alarmfinished_" + max);
+ edit.remove("server_alarmfinished_" + max);
+
+ // Perhaps we should also update the default server to match the server's new id or remove the default selection
+ // in case it was this server that was removed
+ int defaultServer = getDefaultServerKey();
+ if (defaultServer == order) {
+ edit.remove("header_defaultserver");
+ } else if (defaultServer > order) {
+ // Move 'up' one place to account for the removed server setting
+ edit.putString("header_defaultserver", String.valueOf(--order));
+ }
+
+ edit.apply();
+
+ }
+
+ /**
+ * Returns the settings of the server that was explicitly selected by the user to select as default or, when no
+ * specific default server was selected, the last used server settings. As opposed to getDefaultServerKey(int), this
+ * method checks whether the particular server still exists (and returns the first server if not). If no servers are
+ * configured, null is returned.
+ *
+ * @return A server settings object of the server to use by default, or null if no server is yet configured
+ */
+ public ServerSetting getDefaultServer() {
+
+ int defaultServer = getDefaultServerKey();
+ if (defaultServer == DEFAULTSERVER_LASTUSED || defaultServer == DEFAULTSERVER_ASKONADD) {
+ return getLastUsedServer();
+ }
+
+ // Use the explicitly selected default server
+ int max = getMaxOfAllServers(); // Zero-based index, so with max == 0 there is 1 server
+ if (max < 0) {
+ // No servers configured
+ return null;
+ }
+ if (defaultServer < 0 || defaultServer > max) {
+ // Last server was never set or no longer exists
+ return getServerSetting(0);
+ }
+ return getServerSetting(defaultServer);
+
+ }
+
+ /**
+ * Returns the unique key of the server setting that the user selected as their default server, or code indicating
+ * that the last used server should be selected by default; use with getDefaultServer directly. WARNING: the
+ * returned string may no longer refer to a known server setting key.
+ *
+ * @return An integer; if it is 0 or higher it represents the unique key of a configured server setting, -2 means
+ * the last used server should be selected as default instead and -1 means the last used server should be
+ * selected by default for viewing yet it should always ask when adding a new torrent
+ */
+ public int getDefaultServerKey() {
+ String defaultServer = prefs.getString("header_defaultserver", Integer.toString(DEFAULTSERVER_LASTUSED));
+ try {
+ return Integer.parseInt(defaultServer);
+ } catch (NumberFormatException e) {
+ // This should NEVER happen but if the setting somehow is not a number, return the default
+ return DEFAULTSERVER_LASTUSED;
+ }
+ }
+
+ /**
+ * Returns the settings of the server that was last used by the user. As opposed to getLastUsedServerKey(int), this
+ * method checks whether a server was already registered as being last used and check whether the server still
+ * exists. It returns the first server if that fails. If no servers are configured, null is returned.
+ *
+ * @return A server settings object of the last used server (or, if not known, the first server), or null if no
+ * servers exist
+ */
+ public ServerSetting getLastUsedServer() {
+ int max = getMaxOfAllServers(); // Zero-based index, so with max == 0 there is 1 server
+ if (max < 0) {
+ // No servers configured
+ return null;
+ }
+ int last = getLastUsedServerKey();
+ if (last < 0 || last > max) {
+ // Last server was never set or no longer exists
+ return getServerSetting(0);
+ }
+ return getServerSetting(last);
+ }
+
+ /**
+ * Returns the order number/unique key of the server that the used last used; use with getServerSettings(int) or
+ * call getLastUsedServer directly. WARNING: the returned integer may no longer refer to a known server settings
+ * object: check the bounds.
+ *
+ * @return An integer indicating the order number/key or the last used server, or -1 if it was not set
+ */
+ public int getLastUsedServerKey() {
+ return prefs.getInt("system_lastusedserver", -1);
+ }
+
+ /**
+ * Registers some server as being the last used by the user
+ *
+ * @param server The settings of the server that the user last used
+ */
+ public void setLastUsedServer(ServerSetting server) {
+ setLastUsedServerKey(server.getOrder());
+ }
+
+ /**
+ * Registers the order number/unique key of some server as being last used by the user
+ *
+ * @param order The key identifying the specific server
+ */
+ public void setLastUsedServerKey(int order) {
+ prefs.edit().putInt("system_lastusedserver", order).apply();
+ }
+
+ /**
+ * Returns the unique code that (should) uniquely identify a navigation filter, such as a label, in the list of all
+ * available filters
+ *
+ * @return A code that the last used navigation filter reported as uniquely identifying itself, or null if no last
+ * used filter is known
+ */
+ public String getLastUsedNavigationFilter() {
+ return prefs.getString("system_lastusedfilter", null);
+ }
+
+ /**
+ * Registers some navigation filter as being the last used by the user
+ *
+ * @param filter The navigation filter that the user last used in the interface
+ */
+ public void setLastUsedNavigationFilter(NavigationFilter filter) {
+ prefs.edit().putString("system_lastusedfilter", filter.getCode()).apply();
+ }
+
+ /**
+ * Returns all available user-configured web-based (as opped to in-app) search sites
+ *
+ * @return A list of all stored web search site settings objects
+ */
+ public List getWebsearchSettings() {
+ List websearches = new ArrayList<>();
+ for (int i = 0; i <= getMaxWebsearch(); i++) {
+ websearches.add(getWebsearchSetting(i));
+ }
+ return Collections.unmodifiableList(websearches);
+ }
+
+ /**
+ * Returns the order number/identifying key of the last web search site
+ *
+ * @return The zero-based order number (index) of the last stored web search site
+ */
+ public int getMaxWebsearch() {
+ for (int i = 0; true; i++) {
+ if (prefs.getString("websearch_baseurl_" + i, null) == null)
+ return i - 1;
+ }
+ }
+
+ /**
+ * Returns the user-specified web-based search site setting for a specific site
+ *
+ * @param order The order number/identifying key of the settings to retrieve
+ * @return The web search site settings object, loaded from shared preferences
+ */
+ public WebsearchSetting getWebsearchSetting(int order) {
+ // @formatter:off
+ return new WebsearchSetting(order,
+ prefs.getString("websearch_name_" + order, null),
+ prefs.getString("websearch_baseurl_" + order, null),
+ prefs.getString("websearch_cookies_" + order, null));
+ // @formatter:on
+ }
+
+ /**
+ * Removes all settings related to a configured web-based search site. Since sites are ordered, the order of the
+ * remaining sites will be updated accordingly.
+ *
+ * @param order The identifying order number/key of the settings to remove
+ */
+ public void removeWebsearchSettings(int order) {
+ if (prefs.getString("websearch_baseurl_" + order, null) == null)
+ return; // The settings that were requested to be removed do not exist
+
+ // Copy all settings higher than the supplied order number to the previous spot
+ Editor edit = prefs.edit();
+ int max = getMaxWebsearch();
+ for (int i = order; i < max; i++) {
+ edit.putString("websearch_name_" + i, prefs.getString("websearch_name_" + (i + 1), null));
+ edit.putString("websearch_baseurl_" + i, prefs.getString("websearch_baseurl_" + (i + 1), null));
+ edit.putString("websearch_cookies_" + i, prefs.getString("websearch_cookies_" + (i + 1), null));
+ }
+
+ // Remove the last settings, of which we are now sure are no longer required
+ edit.remove("websearch_name_" + max);
+ edit.remove("websearch_baseurl_" + max);
+ edit.remove("websearch_cookies_" + max);
+ edit.apply();
+
+ }
+
+ /**
+ * Returns all available user-configured RSS feeds
+ *
+ * @return A list of all stored RSS feed settings objects
+ */
+ public List getRssfeedSettings() {
+ List rssfeeds = new ArrayList<>();
+ for (int i = 0; i <= getMaxRssfeed(); i++) {
+ rssfeeds.add(getRssfeedSetting(i));
+ }
+ return Collections.unmodifiableList(rssfeeds);
+ }
+
+ /**
+ * Returns the order number/identifying key of the last stored RSS feed
+ *
+ * @return The zero-based order number (index) of the last stored RSS feed
+ */
+ public int getMaxRssfeed() {
+ for (int i = 0; true; i++) {
+ if (prefs.getString("rssfeed_url_" + i, null) == null)
+ return i - 1;
+ }
+ }
+
+ /**
+ * Returns the user-specified RSS feed setting for a specific feed
+ *
+ * @param order The order number/identifying key of the settings to retrieve
+ * @return The RSS feed settings object, loaded from shared preferences
+ */
+ public RssfeedSetting getRssfeedSetting(int order) {
+ // @formatter:off
+ long lastViewed = prefs.getLong("rssfeed_lastviewed_" + order, -1);
+ return new RssfeedSetting(order,
+ prefs.getString("rssfeed_name_" + order, null),
+ prefs.getString("rssfeed_url_" + order, null),
+ prefs.getBoolean("rssfeed_reqauth_" + order, false),
+ prefs.getBoolean("rssfeed_alarmnew_" + order, true),
+ prefs.getString("rssfeed_exclude_" + order, null),
+ prefs.getString("rssfeed_include_" + order, null),
+ lastViewed == -1L ? null : new Date(lastViewed),
+ prefs.getString("rssfeed_lastvieweditemurl_" + order, null));
+ // @formatter:on
+ }
+
+ /**
+ * Removes all settings related to a configured RSS feed. Since feeds are ordered, the order of the remaining feeds
+ * will be updated accordingly.
+ *
+ * @param order The identifying order number/key of the settings to remove
+ */
+ public void removeRssfeedSettings(int order) {
+ if (prefs.getString("rssfeed_url_" + order, null) == null)
+ return; // The settings that were requested to be removed do not exist
+
+ // Copy all settings higher than the supplied order number to the previous spot
+ Editor edit = prefs.edit();
+ int max = getMaxRssfeed();
+ for (int i = order; i < max; i++) {
+ edit.putString("rssfeed_name_" + i, prefs.getString("rssfeed_name_" + (i + 1), null));
+ edit.putString("rssfeed_url_" + i, prefs.getString("rssfeed_url_" + (i + 1), null));
+ edit.putBoolean("rssfeed_reqauth_" + i, prefs.getBoolean("rssfeed_reqauth_" + (i + 1), false));
+ edit.putBoolean("rssfeed_alarmnew_" + i, prefs.getBoolean("rssfeed_alarmnew_" + (i + 1), true));
+ edit.putString("rssfeed_exclude_" + i, prefs.getString("rssfeed_exclude_" + (i + 1), null));
+ edit.putString("rssfeed_include_" + i, prefs.getString("rssfeed_include_" + (i + 1), null));
+ edit.putLong("rssfeed_lastviewed_" + i, prefs.getLong("rssfeed_lastviewed_" + (i + 1), -1));
+ edit.putString("rssfeed_lastvieweditemurl_" + i, prefs.getString("rssfeed_lastvieweditemurl_" + (i + 1), null));
+ }
+
+ // Remove the last settings, of which we are now sure are no longer required
+ edit.remove("rssfeed_name_" + max);
+ edit.remove("rssfeed_url_" + max);
+ edit.remove("rssfeed_reqauth_" + max);
+ edit.remove("rssfeed_alarmnew_" + max);
+ edit.remove("rssfeed_exclude_" + max);
+ edit.remove("rssfeed_include_" + max);
+ edit.remove("rssfeed_lastviewed_" + max);
+ edit.remove("rssfeed_lastvieweditemurl_" + max);
+ edit.apply();
+
+ }
+
+ /**
+ * Registers for some RSS feed (as identified by its order numbe/key) the last date and time that it was viewed by
+ * the user. This is used to determine which items in an RSS feed are 'new'. Warning: any previously retrieved
+ * {@link RssfeedSetting} object is now no longer in sync, as this will not automatically be updated in the object.
+ * Use {@link #getRssfeedSetting(int)} to get fresh data.
+ *
+ * @param order The identifying order number/key of the settings of te RSS feed that was viewed
+ * @param lastViewed The date and time that the feed was last viewed; typically now
+ * @param lastViewedItemUrl The url of the last item the last time that the feed was viewed
+ */
+ public void setRssfeedLastViewer(int order, Date lastViewed, String lastViewedItemUrl) {
+ if (prefs.getString("rssfeed_url_" + order, null) == null)
+ return; // The settings that were requested to be removed do not exist
+ Editor edit = prefs.edit();
+ edit.putLong("rssfeed_lastviewed_" + order, lastViewed.getTime());
+ edit.putString("rssfeed_lastvieweditemurl_" + order, lastViewedItemUrl);
+ edit.apply();
+ }
+
+ /**
+ * Registers the torrents list sort order as being last used by the user
+ *
+ * @param currentSortOrder The sort order property the user selected last
+ * @param currentSortAscending The sort order direction that was last used
+ */
+ public void setLastUsedSortOrder(TorrentsSortBy currentSortOrder, boolean currentSortAscending) {
+ Editor edit = prefs.edit();
+ edit.putInt("system_lastusedsortorder", currentSortOrder.getCode());
+ edit.putBoolean("system_lastusedsortdirection", currentSortAscending);
+ edit.apply();
+ }
+
+ /**
+ * Returns the sort order property that the user last used. Use together with {@link #getLastUsedSortDescending()}
+ * to get the full last used sort settings.
+ *
+ * @return The last used sort order enumeration value
+ */
+ public TorrentsSortBy getLastUsedSortOrder() {
+ return TorrentsSortBy
+ .getStatus(prefs.getInt("system_lastusedsortorder", TorrentsSortBy.Alphanumeric.getCode()));
+ }
+
+ /**
+ * Registers the search list sort order as being last used by the user
+ *
+ * @param currentSortOrder The sort order property the user selected last
+ */
+ public void setLastUsedSearchSortOrder(SearchSortOrder currentSortOrder) {
+ Editor edit = prefs.edit();
+ edit.putInt("system_lastusedsearchsortorder", currentSortOrder.ordinal());
+ edit.apply();
+ }
+
+ /**
+ * Returns the search sort order property that the user last used.
+ *
+ * @return The last used sort order enumeration value
+ */
+ public SearchSortOrder getLastUsedSearchSortOrder() {
+ return SearchSortOrder.values()[(prefs.getInt("system_lastusedsearchsortorder", SearchSortOrder.BySeeders.ordinal()))];
+ }
+
+ /**
+ * Returns the sort order direction that the user last used. Use together with {@link #getLastUsedSortOrder()} to
+ * get the full last used sort settings.
+ *
+ * @return True if the last used sort direction was descending, false otherwise (i.e. the default ascending
+ * direction)
+ */
+ public boolean getLastUsedSortDescending() {
+ return prefs.getBoolean("system_lastusedsortdirection", false);
+ }
+
+ /**
+ * Returns the list of all available in-app search sites as well as all web searches that the user configured.
+ *
+ * @return A list of search settings, all of which are either a {@link SearchSite} or {@link WebsearchSetting}
+ */
+ public List getSearchSettings() {
+ List all = new ArrayList<>();
+ all.addAll(searchHelper.getAvailableSites());
+ all.addAll(getWebsearchSettings());
+ return Collections.unmodifiableList(all);
+ }
+
+ /**
+ * Returns the settings of the search site that was last used by the user or was selected by the user as default
+ * site in the main settings. As opposed to getLastUsedSearchSiteKey(int), this method checks whether a site was
+ * already registered as being last used (or set as default) and checks whether the site still exists. It returns
+ * the first in-app search site if that fails.
+ *
+ * @return A site settings object of the last used server (or, if not known, the first server), or null if no
+ * servers exist
+ */
+ public SearchSetting getLastUsedSearchSite() {
+ String lastKey = getLastUsedSearchSiteKey();
+ List allsites = searchHelper.getAvailableSites();
+
+ if (lastKey == null) {
+ // No site yet set specified; return the first in-app one, if available
+ if (allsites != null) {
+ return allsites.get(0);
+ }
+ return null;
+ }
+
+ int lastWebsearch = -1;
+ if (lastKey.startsWith(WebsearchSetting.KEY_PREFIX)) {
+ try {
+ lastWebsearch = Integer.parseInt(lastKey.substring(WebsearchSetting.KEY_PREFIX.length()));
+ } catch (Exception e) {
+ // Not an in-app search site, but probably an in-app search
+ }
+ }
+ if (lastWebsearch >= 0) {
+ // The last used site should be a user-configured web search site
+ int max = getMaxWebsearch(); // Zero-based index, so with max == 0 there is 1 server
+ if (max < 0 || lastWebsearch > max) {
+ // No web search sites configured
+ return null;
+ }
+ return getWebsearchSetting(lastWebsearch);
+ }
+
+ // Should be an in-app search key
+ if (allsites != null && !allsites.isEmpty()) {
+ for (SearchSite searchSite : allsites) {
+ if (searchSite.getKey().equals(lastKey)) {
+ return searchSite;
+ }
+ }
+ // Not found at all; probably a no longer existing web search; return the first in-app one
+ return allsites.get(0);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the unique key of the site that the used last used or selected as default form the main settings; use
+ * with getLastUsedSearchSite directly. WARNING: the returned string may no longer refer to a known web search site
+ * or in-app search settings object.
+ *
+ * @return A string indicating the key of the last used search site, or null if no site was yet used or set as
+ * default
+ */
+ private String getLastUsedSearchSiteKey() {
+ return prefs.getString("header_setsearchsite", null);
+ }
+
+ /**
+ * Registers the unique key of some web search or in-app search site as being last used by the user
+ *
+ * @param site The site settings to register as being last used
+ */
+ public void setLastUsedSearchSite(SearchSetting site) {
+ prefs.edit().putString("header_setsearchsite", site.getKey()).apply();
+ }
+
+ /**
+ * Returns the statistics of this server as it was last seen by the background server checker service.
+ *
+ * @param server The server for which to retrieved the statistics from the stored preferences
+ * @return A JSON array of JSON objects, each which represent a since torrent
+ */
+ public JSONArray getServerLastStats(ServerSetting server) {
+ String lastStats = prefs.getString(server.getUniqueIdentifier(), null);
+ if (lastStats == null)
+ return null;
+ try {
+ return new JSONArray(lastStats);
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Stores the now-last seen statistics of the supplied server by the background server checker service to the
+ * internal stored preferences.
+ *
+ * @param server The server to which the statistics apply to
+ * @param lastStats A JSON array of JSON objects that each represent a single seen torrent
+ */
+ public void setServerLastStats(ServerSetting server, JSONArray lastStats) {
+ prefs.edit().putString(server.getUniqueIdentifier(), lastStats.toString()).apply();
+ }
+
+ /**
+ * Returns the user configuration for some specific app widget, if the widget is known at all.
+ *
+ * @param appWidgetId The unique ID of the app widget to retrieve settings for, as supplied by the AppWidgetManager
+ * @return A widget configuration object, or null if no settings were stored for the widget ID
+ */
+ public ListWidgetConfig getWidgetConfig(int appWidgetId) {
+ if (!prefs.contains("widget_server_" + appWidgetId))
+ return null;
+ // @formatter:off
+ return new ListWidgetConfig(
+ prefs.getInt("widget_server_" + appWidgetId, -1),
+ StatusType.valueOf(prefs.getString("widget_status_" + appWidgetId, StatusType.ShowAll.name())),
+ TorrentsSortBy.valueOf(prefs.getString("widget_sortby_" + appWidgetId, TorrentsSortBy.Alphanumeric.name())),
+ prefs.getBoolean("widget_reverse_" + appWidgetId, false),
+ prefs.getBoolean("widget_showstatus_" + appWidgetId, false));
+ // @formatter:on
+ }
+
+ /**
+ * Stores the user settings for some specific app widget. Existing settings for the supplied app widget ID will be
+ * overridden.
+ *
+ * @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager
+ * @param settings A widget configuration object, which may not be null
+ */
+ public void setWidgetConfig(int appWidgetId, ListWidgetConfig settings) {
+ if (settings == null)
+ throw new InvalidParameterException(
+ "The widget setting may not be null. Use removeWidgetConfig instead to remove existing settings for some app widget.");
+ Editor edit = prefs.edit();
+ edit.putInt("widget_server_" + appWidgetId, settings.getServerId());
+ edit.putString("widget_status_" + appWidgetId, settings.getStatusType().name());
+ edit.putString("widget_sortby_" + appWidgetId, settings.getSortBy().name());
+ edit.putBoolean("widget_reverse_" + appWidgetId, settings.shouldReserveSort());
+ edit.putBoolean("widget_showstatus_" + appWidgetId, settings.shouldShowStatusView());
+ edit.apply();
+ }
+
+ /**
+ * Remove the setting for some specific app widget.
+ *
+ * @param appWidgetId The unique ID of the app widget to store settings for, as supplied by the AppWidgetManager
+ */
+ public void removeWidgetConfig(int appWidgetId) {
+ Editor edit = prefs.edit();
+ edit.remove("widget_server_" + appWidgetId);
+ edit.remove("widget_status_" + appWidgetId);
+ edit.remove("widget_sortby_" + appWidgetId);
+ edit.remove("widget_reverse_" + appWidgetId);
+ edit.remove("widget_showstatus_" + appWidgetId);
+ edit.remove("widget_darktheme_" + appWidgetId);
+ edit.apply();
+ }
+
+ /**
+ * Trims away whitespace around a string, or returns null if str is null
+ *
+ * @param str The string to trim, or null
+ * @return The trimmed string, or null if str is null
+ */
+ private String trim(String str) {
+ if (str == null) return null;
+ return str.trim();
+ }
+
+ private int parseInt(String string, int defaultValue) {
+ try {
+ return Integer.parseInt(string);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/NotificationSettings.java b/app/src/main/java/org/transdroid/core/app/settings/NotificationSettings.java
index 6691d0d0..63a442e2 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/NotificationSettings.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/NotificationSettings.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -30,103 +30,111 @@ import org.transdroid.R;
/**
* Allows instantiation of the settings specified in R.xml.pref_notifications.
+ *
* @author Eric Kok
*/
@EBean(scope = Scope.Singleton)
public class NotificationSettings {
- private static final long MINIMUM_BACKGROUND_INTERVAL = 900_000; // 15 minutes
-
- @RootContext
- protected Context context;
- private SharedPreferences prefs;
-
- protected NotificationSettings(Context context) {
- prefs = PreferenceManager.getDefaultSharedPreferences(context);
- }
-
- /**
- * Whether the background service is enabled and the user wants to receive RSS-related notifications
- * @return True if the server should be checked for RSS feed updates
- */
- public boolean isEnabledForRss() {
- return prefs.getBoolean("notifications_enabledrss", true);
- }
-
- /**
- * Whether the background service is enabled and the user wants to receive torrent-related notifications
- * @return True if the server should be checked for torrent status updates
- */
- public boolean isEnabledForTorrents() {
- return prefs.getBoolean("notifications_enabled", true);
- }
-
- private String getRawInverval() {
- return prefs.getString("notifications_interval", "10800");
- }
-
- /**
- * Returns the interval between two server checks
- * @return The interval, in milliseconds
- */
- public Long getInvervalInMilliseconds() {
- return Math.max(Long.parseLong(getRawInverval()) * 1000L, MINIMUM_BACKGROUND_INTERVAL);
- }
-
- private String getRawSound() {
- return prefs.getString("notifications_sound", null);
- }
-
- /**
- * Returns the sound (ring tone) to play on a new notification, or null if it should not play any
- * @return Either the user-specified sound, null if the user specified 'Silent' or the system default notification sound
- */
- public Uri getSound() {
- String raw = getRawSound();
- if (raw == null)
- return null;
- if (raw.equals(""))
- return Settings.System.DEFAULT_NOTIFICATION_URI;
- return Uri.parse(raw);
- }
-
- /**
- * Whether the device should vibrate on a new notification
- */
- public boolean shouldVibrate() {
- return prefs.getBoolean("notifications_vibrate", false);
- }
-
- /**
- * Returns the default vibrate pattern to use if the user enabled notification vibrations; check
- * {@link #shouldVibrate()},
- * @return A unique pattern for vibrations in Transdroid
- */
- public long[] getDefaultVibratePattern() {
- return new long[]{100, 100, 200, 300, 400, 700}; // Unique pattern?
- }
-
- private int getRawLedColour() {
- return prefs.getInt("notifications_ledcolour", -1);
- }
-
- /**
- * Returns the LED colour to use on a new notification
- * @return The integer value of the user-specified or default colour
- */
- public int getDesiredLedColour() {
- int raw = getRawLedColour();
- if (raw <= 0)
- return context.getResources().getColor(R.color.ledgreen);
- return raw;
- }
-
- /**
- * Whether the background service should report to ADW Launcher
- * @return True if the user want Transdroid to report to ADW Launcher
- */
- public boolean shouldReportToAdwLauncher() {
- return prefs.getBoolean("notifications_adwnotify", false);
- }
+ private static final long MINIMUM_BACKGROUND_INTERVAL = 900_000; // 15 minutes
+
+ @RootContext
+ protected Context context;
+ private SharedPreferences prefs;
+
+ protected NotificationSettings(Context context) {
+ prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ /**
+ * Whether the background service is enabled and the user wants to receive RSS-related notifications
+ *
+ * @return True if the server should be checked for RSS feed updates
+ */
+ public boolean isEnabledForRss() {
+ return prefs.getBoolean("notifications_enabledrss", true);
+ }
+
+ /**
+ * Whether the background service is enabled and the user wants to receive torrent-related notifications
+ *
+ * @return True if the server should be checked for torrent status updates
+ */
+ public boolean isEnabledForTorrents() {
+ return prefs.getBoolean("notifications_enabled", true);
+ }
+
+ private String getRawInverval() {
+ return prefs.getString("notifications_interval", "10800");
+ }
+
+ /**
+ * Returns the interval between two server checks
+ *
+ * @return The interval, in milliseconds
+ */
+ public Long getInvervalInMilliseconds() {
+ return Math.max(Long.parseLong(getRawInverval()) * 1000L, MINIMUM_BACKGROUND_INTERVAL);
+ }
+
+ private String getRawSound() {
+ return prefs.getString("notifications_sound", null);
+ }
+
+ /**
+ * Returns the sound (ring tone) to play on a new notification, or null if it should not play any
+ *
+ * @return Either the user-specified sound, null if the user specified 'Silent' or the system default notification sound
+ */
+ public Uri getSound() {
+ String raw = getRawSound();
+ if (raw == null)
+ return null;
+ if (raw.equals(""))
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
+ return Uri.parse(raw);
+ }
+
+ /**
+ * Whether the device should vibrate on a new notification
+ */
+ public boolean shouldVibrate() {
+ return prefs.getBoolean("notifications_vibrate", false);
+ }
+
+ /**
+ * Returns the default vibrate pattern to use if the user enabled notification vibrations; check
+ * {@link #shouldVibrate()},
+ *
+ * @return A unique pattern for vibrations in Transdroid
+ */
+ public long[] getDefaultVibratePattern() {
+ return new long[]{100, 100, 200, 300, 400, 700}; // Unique pattern?
+ }
+
+ private int getRawLedColour() {
+ return prefs.getInt("notifications_ledcolour", -1);
+ }
+
+ /**
+ * Returns the LED colour to use on a new notification
+ *
+ * @return The integer value of the user-specified or default colour
+ */
+ public int getDesiredLedColour() {
+ int raw = getRawLedColour();
+ if (raw <= 0)
+ return context.getResources().getColor(R.color.ledgreen);
+ return raw;
+ }
+
+ /**
+ * Whether the background service should report to ADW Launcher
+ *
+ * @return True if the user want Transdroid to report to ADW Launcher
+ */
+ public boolean shouldReportToAdwLauncher() {
+ return prefs.getBoolean("notifications_adwnotify", false);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/RssfeedSetting.java b/app/src/main/java/org/transdroid/core/app/settings/RssfeedSetting.java
index 018dcb4d..79c83dce 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/RssfeedSetting.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/RssfeedSetting.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -25,98 +25,102 @@ import android.text.TextUtils;
/**
* Represents a user-specified RSS feed.
+ *
* @author Eric Kok
*/
public class RssfeedSetting implements SimpleListItem {
- private static final String DEFAULT_NAME = "Default";
-
- private final int order;
- private final String name;
- private final String url;
- private final boolean requiresAuth;
- private final boolean alarm;
- private final String excludeFilter;
- private final String includeFilter;
- private Date lastViewed;
- private final String lastViewedItemUrl;
-
- public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth, boolean alarm, String excludeFilter, String includeFilter, Date lastViewed,
- String lastViewedItemUrl) {
- this.order = order;
- this.name = name;
- this.url = baseUrl;
- this.requiresAuth = needsAuth;
- this.alarm = alarm;
- this.excludeFilter = excludeFilter;
- this.includeFilter = includeFilter;
- this.lastViewed = lastViewed;
- this.lastViewedItemUrl = lastViewedItemUrl;
- }
-
- public int getOrder() {
- return order;
- }
-
- @Override
- public String getName() {
- if (!TextUtils.isEmpty(name))
- return name;
- if (!TextUtils.isEmpty(url)) {
- String host = Uri.parse(url).getHost();
- return host == null ? DEFAULT_NAME : host;
- }
- return DEFAULT_NAME;
- }
-
- public String getUrl() {
- return url;
- }
-
- public boolean requiresExternalAuthentication() {
- return requiresAuth;
- }
-
- public boolean shouldAlarmOnNewItems() {
- return alarm;
- }
-
- public String getExcludeFilter() {
- return excludeFilter;
- }
-
- public String getIncludeFilter() {
- return includeFilter;
- }
-
- /**
- * Returns the date on which we last checked this feed. Note that this is NOT updated automatically after the
- * settings were loaded from {@link ApplicationSettings}; instead the settings have to be manually loaded again
- * using {@link ApplicationSettings#getRssfeedSetting(int)}.
- * @return The last new item's URL as URL-encoded string
- */
- public Date getLastViewed() {
- return this.lastViewed;
- }
-
- /**
- * Returns the URL of the item that was the newest last time we checked this feed. Note that this is NOT updated
- * automatically after the settings were loaded from {@link ApplicationSettings}; instead the settings have to be
- * manually loaded again using {@link ApplicationSettings#getRssfeedSetting(int)}.
- * @return The last new item's URL as URL-encoded string
- */
- public String getLastViewedItemUrl() {
- return this.lastViewedItemUrl;
- }
-
- /**
- * Returns a nicely formatted identifier containing (a portion of) the feed URL
- * @return A string to identify this feed's URL
- */
- public String getHumanReadableIdentifier() {
- String host = Uri.parse(url).getHost();
- String path = Uri.parse(url).getPath();
- return (host == null ? null : host + (path == null ? "" : path));
- }
+ private static final String DEFAULT_NAME = "Default";
+
+ private final int order;
+ private final String name;
+ private final String url;
+ private final boolean requiresAuth;
+ private final boolean alarm;
+ private final String excludeFilter;
+ private final String includeFilter;
+ private Date lastViewed;
+ private final String lastViewedItemUrl;
+
+ public RssfeedSetting(int order, String name, String baseUrl, boolean needsAuth, boolean alarm, String excludeFilter, String includeFilter, Date lastViewed,
+ String lastViewedItemUrl) {
+ this.order = order;
+ this.name = name;
+ this.url = baseUrl;
+ this.requiresAuth = needsAuth;
+ this.alarm = alarm;
+ this.excludeFilter = excludeFilter;
+ this.includeFilter = includeFilter;
+ this.lastViewed = lastViewed;
+ this.lastViewedItemUrl = lastViewedItemUrl;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ @Override
+ public String getName() {
+ if (!TextUtils.isEmpty(name))
+ return name;
+ if (!TextUtils.isEmpty(url)) {
+ String host = Uri.parse(url).getHost();
+ return host == null ? DEFAULT_NAME : host;
+ }
+ return DEFAULT_NAME;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public boolean requiresExternalAuthentication() {
+ return requiresAuth;
+ }
+
+ public boolean shouldAlarmOnNewItems() {
+ return alarm;
+ }
+
+ public String getExcludeFilter() {
+ return excludeFilter;
+ }
+
+ public String getIncludeFilter() {
+ return includeFilter;
+ }
+
+ /**
+ * Returns the date on which we last checked this feed. Note that this is NOT updated automatically after the
+ * settings were loaded from {@link ApplicationSettings}; instead the settings have to be manually loaded again
+ * using {@link ApplicationSettings#getRssfeedSetting(int)}.
+ *
+ * @return The last new item's URL as URL-encoded string
+ */
+ public Date getLastViewed() {
+ return this.lastViewed;
+ }
+
+ /**
+ * Returns the URL of the item that was the newest last time we checked this feed. Note that this is NOT updated
+ * automatically after the settings were loaded from {@link ApplicationSettings}; instead the settings have to be
+ * manually loaded again using {@link ApplicationSettings#getRssfeedSetting(int)}.
+ *
+ * @return The last new item's URL as URL-encoded string
+ */
+ public String getLastViewedItemUrl() {
+ return this.lastViewedItemUrl;
+ }
+
+ /**
+ * Returns a nicely formatted identifier containing (a portion of) the feed URL
+ *
+ * @return A string to identify this feed's URL
+ */
+ public String getHumanReadableIdentifier() {
+ String host = Uri.parse(url).getHost();
+ String path = Uri.parse(url).getPath();
+ return (host == null ? null : host + (path == null ? "" : path));
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java b/app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java
index 5191b077..c3e061af 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,303 +29,309 @@ import org.transdroid.daemon.OS;
/**
* Represents a user-configured remote server.
+ *
* @author Eric Kok
*/
public class ServerSetting implements SimpleListItem {
- private static final String DEFAULT_NAME = "Default";
-
- private final int key;
- private final String name;
- private final Daemon type;
- private final String address;
- private final String localAddress;
- private final int localPort;
- private final String localNetwork;
- private final int port;
- private final String folder;
- private final boolean useAuthentication;
- private final String username;
- private final String password;
- private final String extraPass;
- private final OS os;
- private final String downloadDir;
- private final String ftpUrl;
- private final String ftpPassword;
- private final int timeout;
- private final boolean alarmOnFinishedDownload;
- private final boolean alarmOnNewTorrent;
- private final boolean ssl;
- private final boolean localSsl;
- private final boolean sslTrustAll;
- private final String sslTrustKey;
- private final String excludeFilter;
- private final String includeFilter;
- private final boolean isAutoGenerated;
-
- /**
- * Creates a daemon settings instance, providing full connection details
- * @param name A name used to identify this server to the user
- * @param type The server daemon type
- * @param address The server domain name or IP address
- * @param localAddress The server domain or IP address when connected to the server's local network
- * @param localPort The port on which the server is running in the server's local network
- * @param localNetwork The server's local network SSID
- * @param port The port on which the server daemon is running
- * @param sslTrustKey The specific key that will be accepted.
- * @param folder The server folder (like a virtual sub-folder or an SCGI mount point)
- * @param useAuthentication Whether to use basic authentication
- * @param username The user name to provide during authentication
- * @param password The password to provide during authentication
- * @param extraPass The Deluge web interface password
- * @param downloadDir The default download directory (which may also be used as base directory for file paths)
- * @param ftpUrl The partial URL to connect to when requesting FTP-style transfers
- * @param timeout The number of seconds to wait before timing out a connection attempt
- * @param isAutoGenerated Whether this setting was generated rather than manually inputed by the user
- */
- public ServerSetting(int key, String name, Daemon type, String address, String localAddress, int localPort, String localNetwork, int port,
- boolean ssl, boolean localSsl, boolean sslTrustAll, String sslTrustKey, String folder, boolean useAuthentication, String username,
- String password, String extraPass, OS os, String downloadDir, String ftpUrl, String ftpPassword, int timeout,
- boolean alarmOnFinishedDownload, boolean alarmOnNewTorrent, String excludeFilter, String includeFilter,
- boolean isAutoGenerated) {
- this.key = key;
- this.name = name;
- this.type = type;
- this.address = address;
- this.localAddress = localAddress;
- this.localPort = localPort;
- this.localNetwork = localNetwork;
- this.port = port;
- this.ssl = ssl;
- this.localSsl = localSsl;
- this.sslTrustAll = sslTrustAll;
- this.sslTrustKey = sslTrustKey;
- this.folder = folder;
- this.useAuthentication = useAuthentication;
- this.username = username;
- this.password = password;
- this.extraPass = extraPass;
- this.os = os;
- this.downloadDir = downloadDir;
- this.ftpUrl = ftpUrl;
- this.ftpPassword = ftpPassword;
- this.timeout = timeout;
- this.alarmOnFinishedDownload = alarmOnFinishedDownload;
- this.alarmOnNewTorrent = alarmOnNewTorrent;
- this.excludeFilter = excludeFilter;
- this.includeFilter = includeFilter;
- this.isAutoGenerated = isAutoGenerated;
- }
-
- @Override
- public String getName() {
- if (!TextUtils.isEmpty(name)) {
- return name;
- }
- if (!TextUtils.isEmpty(address)) {
- String host = Uri.parse(address).getHost();
- return host == null ? DEFAULT_NAME : host;
- }
- return DEFAULT_NAME;
- }
-
- public Daemon getType() {
- return type;
- }
-
- public String getAddress() {
- return address;
- }
-
- public String getLocalAddress() {
- return localAddress;
- }
-
- public int getLocalPort() {
- return localPort;
- }
-
- public String getLocalNetwork() {
- return localNetwork;
- }
-
- public int getPort() {
- return port;
- }
-
- public boolean getSsl() {
- return ssl;
- }
-
- public boolean getLocalSsl() {
- return localSsl;
- }
-
- public boolean getSslTrustAll() {
- return sslTrustAll;
- }
-
- public String getSslTrustKey() {
- return sslTrustKey;
- }
-
- public String getFolder() {
- return folder;
- }
-
- public boolean shouldUseAuthentication() {
- return useAuthentication;
- }
-
- public String getUsername() {
- return username;
- }
-
- public String getPassword() {
- return password;
- }
-
- public String getExtraPassword() {
- return extraPass;
- }
-
- public OS getOS() {
- return os;
- }
-
- public String getDownloadDir() {
- return downloadDir;
- }
-
- public String getFtpUrl() {
- return ftpUrl;
- }
-
- public String getFtpPassword() {
- return ftpPassword;
- }
-
- public int getTimeoutInMilliseconds() {
- return timeout * 1000;
- }
-
- public boolean shouldAlarmOnFinishedDownload() {
- return alarmOnFinishedDownload;
- }
-
- public boolean shouldAlarmOnNewTorrent() {
- return alarmOnNewTorrent;
- }
-
- public String getExcludeFilter() {
- return excludeFilter;
- }
-
- public String getIncludeFilter() {
- return includeFilter;
- }
-
- public boolean isAutoGenerated() {
- return isAutoGenerated;
- }
-
- public int getOrder() {
- return this.key;
- }
-
- /**
- * Returns a string that the user can use to identify the server by internal settings (rather than the name).
- * @return A human-readable identifier in the form [https://]username@address:port/folder
- */
- public String getHumanReadableIdentifier() {
- if (isAutoGenerated) {
- // Hide the 'implementation details'; just give the username and server
- return (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ?
- this.getUsername() + "@" : "") + getAddress();
- }
- return (this.ssl ? "https://" : "http://") +
- (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" :
- "") + getAddress() + ":" + getPort() +
- (Daemon.supportsCustomFolder(getType()) && getFolder() != null ? getFolder() : "");
- }
-
- /**
- * Returns a string that acts as a unique identifier for this server, non-depending on the internal storage
- * order/index. THis may be used to store additional details about this server elsewhere. It may change if the user
- * changes server settings, but not with name or notification settings.
- * @return A unique identifying string, based primarily on the configured address, port number, SSL settings and
- * user name; returns null if the server is not yet fully identifiable (during configuration, for example)
- */
- public String getUniqueIdentifier() {
- if (getType() == null || getAddress() == null || getAddress().equals("")) {
- return null;
- }
- return getType().toString() + "|" + getHumanReadableIdentifier();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof ServerSetting) {
- // Directly compare order numbers/unique keys
- return ((ServerSetting) o).getOrder() == this.key;
- } else if (o instanceof DaemonSettings) {
- // Old-style DaemonSettings objects can be equal if they were constructed from a ServerSettings object:
- // idString should reflect the local key/order
- return ((DaemonSettings) o).getIdString().equals(Integer.toString(this.key));
- }
- // Other objects are never equal to this
- return false;
- }
-
- @Override
- public String toString() {
- return getUniqueIdentifier();
- }
-
- /**
- * Returns the appropriate daemon adapter to which tasks can be executed, in accordance with this server's settings
- * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
- * be determined
- * @param context A context to access the logger
- * @return An IDaemonAdapter instance of the specific torrent client daemon type
- */
- public IDaemonAdapter createServerAdapter(String connectedToNetwork, Context context) {
- return type.createAdapter(convertToDaemonSettings(connectedToNetwork, context));
- }
-
- /**
- * Converts local server settings into an old-style {@link DaemonSettings} object.
- * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
- * be determined
- * @param caller A context to access the logger
- * @return A {@link DaemonSettings} object to execute server commands against
- */
- private DaemonSettings convertToDaemonSettings(String connectedToNetwork, Context caller) {
- // The local integer key is converted to the idString string.
- // The host name address used is dependent on the network that we are currently connected to (to allow a
- // distinct connection IP or host name when connected to a local network).
- if (!TextUtils.isEmpty(localNetwork)) {
- Log_.getInstance_(caller)
- .d("ServerSetting", "Creating adapter for " + name + " of type " + type.name() + ": connected to " +
- connectedToNetwork + " and configured local network is " + localNetwork);
- }
- String addressToUse = address;
- int portToUse = port;
- boolean sslEnable = ssl;
- if (!TextUtils.isEmpty(localNetwork) && !TextUtils.isEmpty(localAddress) &&
- !TextUtils.isEmpty(connectedToNetwork)) {
- String[] localNetworks = localNetwork.split("\\|");
- for (String network : localNetworks) {
- if (connectedToNetwork.equals(network)) {
- addressToUse = localAddress;
- portToUse = localPort;
- sslEnable = localSsl;
- break;
- }
- }
- }
- return new DaemonSettings(name, type, addressToUse, portToUse, sslEnable, sslTrustAll, sslTrustKey, folder,
- useAuthentication, username, password, extraPass, os, downloadDir, ftpUrl, ftpPassword, timeout,
- alarmOnFinishedDownload, alarmOnNewTorrent, Integer.toString(key), isAutoGenerated);
- }
+ private static final String DEFAULT_NAME = "Default";
+
+ private final int key;
+ private final String name;
+ private final Daemon type;
+ private final String address;
+ private final String localAddress;
+ private final int localPort;
+ private final String localNetwork;
+ private final int port;
+ private final String folder;
+ private final boolean useAuthentication;
+ private final String username;
+ private final String password;
+ private final String extraPass;
+ private final OS os;
+ private final String downloadDir;
+ private final String ftpUrl;
+ private final String ftpPassword;
+ private final int timeout;
+ private final boolean alarmOnFinishedDownload;
+ private final boolean alarmOnNewTorrent;
+ private final boolean ssl;
+ private final boolean localSsl;
+ private final boolean sslTrustAll;
+ private final String sslTrustKey;
+ private final String excludeFilter;
+ private final String includeFilter;
+ private final boolean isAutoGenerated;
+
+ /**
+ * Creates a daemon settings instance, providing full connection details
+ *
+ * @param name A name used to identify this server to the user
+ * @param type The server daemon type
+ * @param address The server domain name or IP address
+ * @param localAddress The server domain or IP address when connected to the server's local network
+ * @param localPort The port on which the server is running in the server's local network
+ * @param localNetwork The server's local network SSID
+ * @param port The port on which the server daemon is running
+ * @param sslTrustKey The specific key that will be accepted.
+ * @param folder The server folder (like a virtual sub-folder or an SCGI mount point)
+ * @param useAuthentication Whether to use basic authentication
+ * @param username The user name to provide during authentication
+ * @param password The password to provide during authentication
+ * @param extraPass The Deluge web interface password
+ * @param downloadDir The default download directory (which may also be used as base directory for file paths)
+ * @param ftpUrl The partial URL to connect to when requesting FTP-style transfers
+ * @param timeout The number of seconds to wait before timing out a connection attempt
+ * @param isAutoGenerated Whether this setting was generated rather than manually inputed by the user
+ */
+ public ServerSetting(int key, String name, Daemon type, String address, String localAddress, int localPort, String localNetwork, int port,
+ boolean ssl, boolean localSsl, boolean sslTrustAll, String sslTrustKey, String folder, boolean useAuthentication, String username,
+ String password, String extraPass, OS os, String downloadDir, String ftpUrl, String ftpPassword, int timeout,
+ boolean alarmOnFinishedDownload, boolean alarmOnNewTorrent, String excludeFilter, String includeFilter,
+ boolean isAutoGenerated) {
+ this.key = key;
+ this.name = name;
+ this.type = type;
+ this.address = address;
+ this.localAddress = localAddress;
+ this.localPort = localPort;
+ this.localNetwork = localNetwork;
+ this.port = port;
+ this.ssl = ssl;
+ this.localSsl = localSsl;
+ this.sslTrustAll = sslTrustAll;
+ this.sslTrustKey = sslTrustKey;
+ this.folder = folder;
+ this.useAuthentication = useAuthentication;
+ this.username = username;
+ this.password = password;
+ this.extraPass = extraPass;
+ this.os = os;
+ this.downloadDir = downloadDir;
+ this.ftpUrl = ftpUrl;
+ this.ftpPassword = ftpPassword;
+ this.timeout = timeout;
+ this.alarmOnFinishedDownload = alarmOnFinishedDownload;
+ this.alarmOnNewTorrent = alarmOnNewTorrent;
+ this.excludeFilter = excludeFilter;
+ this.includeFilter = includeFilter;
+ this.isAutoGenerated = isAutoGenerated;
+ }
+
+ @Override
+ public String getName() {
+ if (!TextUtils.isEmpty(name)) {
+ return name;
+ }
+ if (!TextUtils.isEmpty(address)) {
+ String host = Uri.parse(address).getHost();
+ return host == null ? DEFAULT_NAME : host;
+ }
+ return DEFAULT_NAME;
+ }
+
+ public Daemon getType() {
+ return type;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public String getLocalAddress() {
+ return localAddress;
+ }
+
+ public int getLocalPort() {
+ return localPort;
+ }
+
+ public String getLocalNetwork() {
+ return localNetwork;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public boolean getSsl() {
+ return ssl;
+ }
+
+ public boolean getLocalSsl() {
+ return localSsl;
+ }
+
+ public boolean getSslTrustAll() {
+ return sslTrustAll;
+ }
+
+ public String getSslTrustKey() {
+ return sslTrustKey;
+ }
+
+ public String getFolder() {
+ return folder;
+ }
+
+ public boolean shouldUseAuthentication() {
+ return useAuthentication;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getExtraPassword() {
+ return extraPass;
+ }
+
+ public OS getOS() {
+ return os;
+ }
+
+ public String getDownloadDir() {
+ return downloadDir;
+ }
+
+ public String getFtpUrl() {
+ return ftpUrl;
+ }
+
+ public String getFtpPassword() {
+ return ftpPassword;
+ }
+
+ public int getTimeoutInMilliseconds() {
+ return timeout * 1000;
+ }
+
+ public boolean shouldAlarmOnFinishedDownload() {
+ return alarmOnFinishedDownload;
+ }
+
+ public boolean shouldAlarmOnNewTorrent() {
+ return alarmOnNewTorrent;
+ }
+
+ public String getExcludeFilter() {
+ return excludeFilter;
+ }
+
+ public String getIncludeFilter() {
+ return includeFilter;
+ }
+
+ public boolean isAutoGenerated() {
+ return isAutoGenerated;
+ }
+
+ public int getOrder() {
+ return this.key;
+ }
+
+ /**
+ * Returns a string that the user can use to identify the server by internal settings (rather than the name).
+ *
+ * @return A human-readable identifier in the form [https://]username@address:port/folder
+ */
+ public String getHumanReadableIdentifier() {
+ if (isAutoGenerated) {
+ // Hide the 'implementation details'; just give the username and server
+ return (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ?
+ this.getUsername() + "@" : "") + getAddress();
+ }
+ return (this.ssl ? "https://" : "http://") +
+ (this.shouldUseAuthentication() && !TextUtils.isEmpty(this.getUsername()) ? this.getUsername() + "@" :
+ "") + getAddress() + ":" + getPort() +
+ (Daemon.supportsCustomFolder(getType()) && getFolder() != null ? getFolder() : "");
+ }
+
+ /**
+ * Returns a string that acts as a unique identifier for this server, non-depending on the internal storage
+ * order/index. THis may be used to store additional details about this server elsewhere. It may change if the user
+ * changes server settings, but not with name or notification settings.
+ *
+ * @return A unique identifying string, based primarily on the configured address, port number, SSL settings and
+ * user name; returns null if the server is not yet fully identifiable (during configuration, for example)
+ */
+ public String getUniqueIdentifier() {
+ if (getType() == null || getAddress() == null || getAddress().equals("")) {
+ return null;
+ }
+ return getType().toString() + "|" + getHumanReadableIdentifier();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ServerSetting) {
+ // Directly compare order numbers/unique keys
+ return ((ServerSetting) o).getOrder() == this.key;
+ } else if (o instanceof DaemonSettings) {
+ // Old-style DaemonSettings objects can be equal if they were constructed from a ServerSettings object:
+ // idString should reflect the local key/order
+ return ((DaemonSettings) o).getIdString().equals(Integer.toString(this.key));
+ }
+ // Other objects are never equal to this
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getUniqueIdentifier();
+ }
+
+ /**
+ * Returns the appropriate daemon adapter to which tasks can be executed, in accordance with this server's settings
+ *
+ * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
+ * be determined
+ * @param context A context to access the logger
+ * @return An IDaemonAdapter instance of the specific torrent client daemon type
+ */
+ public IDaemonAdapter createServerAdapter(String connectedToNetwork, Context context) {
+ return type.createAdapter(convertToDaemonSettings(connectedToNetwork, context));
+ }
+
+ /**
+ * Converts local server settings into an old-style {@link DaemonSettings} object.
+ *
+ * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not
+ * be determined
+ * @param caller A context to access the logger
+ * @return A {@link DaemonSettings} object to execute server commands against
+ */
+ private DaemonSettings convertToDaemonSettings(String connectedToNetwork, Context caller) {
+ // The local integer key is converted to the idString string.
+ // The host name address used is dependent on the network that we are currently connected to (to allow a
+ // distinct connection IP or host name when connected to a local network).
+ if (!TextUtils.isEmpty(localNetwork)) {
+ Log_.getInstance_(caller)
+ .d("ServerSetting", "Creating adapter for " + name + " of type " + type.name() + ": connected to " +
+ connectedToNetwork + " and configured local network is " + localNetwork);
+ }
+ String addressToUse = address;
+ int portToUse = port;
+ boolean sslEnable = ssl;
+ if (!TextUtils.isEmpty(localNetwork) && !TextUtils.isEmpty(localAddress) &&
+ !TextUtils.isEmpty(connectedToNetwork)) {
+ String[] localNetworks = localNetwork.split("\\|");
+ for (String network : localNetworks) {
+ if (connectedToNetwork.equals(network)) {
+ addressToUse = localAddress;
+ portToUse = localPort;
+ sslEnable = localSsl;
+ break;
+ }
+ }
+ }
+ return new DaemonSettings(name, type, addressToUse, portToUse, sslEnable, sslTrustAll, sslTrustKey, folder,
+ useAuthentication, username, password, extraPass, os, downloadDir, ftpUrl, ftpPassword, timeout,
+ alarmOnFinishedDownload, alarmOnNewTorrent, Integer.toString(key), isAutoGenerated);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java b/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
index 60781efa..3ca52134 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/SettingsPersistence.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -38,48 +38,51 @@ import java.io.OutputStream;
/**
* Singleton class that can persist user settings (servers, RSS feeds, etc.) to and from a plain text JSON file.
- *
+ *
* @author Eric Kok
*/
@EBean(scope = Scope.Singleton)
public class SettingsPersistence {
- @Bean
- protected ApplicationSettings applicationSettings;
- @Bean
- protected SystemSettings systemSettings;
-
- public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString()
- + "/Transdroid/";
- public static final String DEFAULT_SETTINGS_FILENAME = "settings.json";
- public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME);
-
- /**
- * Reads the server, web searches, RSS feed, background service and system settings from a JSON-encoded String, such as when read via a QR code.
- * @param prefs The application-global preferences object to write settings to
- * @param contents The JSON-encoded settings as raw String
- * @throws JSONException Thrown when the file did not contain valid JSON content
- */
- public void importSettingsAsString(SharedPreferences prefs, String contents) throws JSONException {
- importSettings(prefs, new JSONObject(contents));
- }
-
- /**
- * Synchronously reads the server, web searches, RSS feed, background service and system settings from a file in
- * JSON format.
- * @param prefs The application-global preferences object to write settings to
- * @param settingsFile The local file to read the settings from
- * @throws FileNotFoundException Thrown when the settings file doesn't exist or couldn't be read
- * @throws JSONException Thrown when the file did not contain valid JSON content
- */
- public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, JSONException {
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @Bean
+ protected SystemSettings systemSettings;
+
+ public static final String DEFAULT_SETTINGS_DIR = Environment.getExternalStorageDirectory().toString()
+ + "/Transdroid/";
+ public static final String DEFAULT_SETTINGS_FILENAME = "settings.json";
+ public static final File DEFAULT_SETTINGS_FILE = new File(DEFAULT_SETTINGS_DIR + DEFAULT_SETTINGS_FILENAME);
+
+ /**
+ * Reads the server, web searches, RSS feed, background service and system settings from a JSON-encoded String, such as when read via a QR code.
+ *
+ * @param prefs The application-global preferences object to write settings to
+ * @param contents The JSON-encoded settings as raw String
+ * @throws JSONException Thrown when the file did not contain valid JSON content
+ */
+ public void importSettingsAsString(SharedPreferences prefs, String contents) throws JSONException {
+ importSettings(prefs, new JSONObject(contents));
+ }
+
+ /**
+ * Synchronously reads the server, web searches, RSS feed, background service and system settings from a file in
+ * JSON format.
+ *
+ * @param prefs The application-global preferences object to write settings to
+ * @param settingsFile The local file to read the settings from
+ * @throws FileNotFoundException Thrown when the settings file doesn't exist or couldn't be read
+ * @throws JSONException Thrown when the file did not contain valid JSON content
+ */
+ public void importSettingsFromFile(SharedPreferences prefs, File settingsFile) throws FileNotFoundException, JSONException {
importSettingsFromStream(prefs, new FileInputStream(settingsFile));
- }
+ }
/**
* Synchronously reads the server, web searches, RSS feed, background service and system settings from a stream (file) in
* JSON format.
- * @param prefs The application-global preferences object to write settings to
+ *
+ * @param prefs The application-global preferences object to write settings to
* @param settingsStream The stream to read the settings from
* @throws JSONException Thrown when the file did not contain valid JSON content
*/
@@ -88,287 +91,289 @@ public class SettingsPersistence {
importSettings(prefs, new JSONObject(raw));
}
- public void importSettings(SharedPreferences prefs, JSONObject json) throws JSONException {
-
- Editor editor = prefs.edit();
-
- // Import servers
- if (json.has("servers")) {
- JSONArray servers = json.getJSONArray("servers");
- for (int i = 0; i < servers.length(); i++) {
- JSONObject server = servers.getJSONObject(i);
- String postfix = Integer.toString(applicationSettings.getMaxOfAllServers() + 1 + i);
-
- if (server.has("name"))
- editor.putString("server_name_" + postfix, server.getString("name"));
- if (server.has("type"))
- editor.putString("server_type_" + postfix, server.getString("type"));
- if (server.has("host"))
- editor.putString("server_address_" + postfix, server.getString("host"));
- if (server.has("local_network"))
- editor.putString("server_localnetwork_" + postfix, server.getString("local_network"));
- if (server.has("local_host"))
- editor.putString("server_localaddress_" + postfix, server.getString("local_host"));
- if (server.has("local_port"))
- editor.putString("server_localport_" + postfix, server.getString("local_port"));
- if (server.has("port"))
- editor.putString("server_port_" + postfix, server.getString("port"));
- if (server.has("ssl"))
- editor.putBoolean("server_sslenabled_" + postfix, server.getBoolean("ssl"));
- if (server.has("local_ssl"))
- editor.putBoolean("server_localsslenabled_" + postfix, server.getBoolean("local_ssl"));
- if (server.has("ssl_accept_all"))
- editor.putBoolean("server_ssltrustall_" + postfix, server.getBoolean("ssl_accept_all"));
- if (server.has("ssl_trust_key"))
- editor.putString("server_ssltrustkey_" + postfix, server.getString("ssl_trust_key"));
- if (server.has("folder"))
- editor.putString("server_folder_" + postfix, server.getString("folder"));
- if (server.has("use_auth"))
- editor.putBoolean("server_disableauth_" + postfix, !server.getBoolean("use_auth"));
- if (server.has("username"))
- editor.putString("server_user_" + postfix, server.getString("username"));
- if (server.has("password"))
- editor.putString("server_pass_" + postfix, server.getString("password"));
- if (server.has("extra_password"))
- editor.putString("server_extrapass_" + postfix, server.getString("extra_password"));
- if (server.has("os_type"))
- editor.putString("server_os_" + postfix, server.getString("os_type"));
- if (server.has("downloads_dir"))
- editor.putString("server_downloaddir_" + postfix, server.getString("downloads_dir"));
- if (server.has("base_ftp_url"))
- editor.putString("server_ftpurl_" + postfix, server.getString("base_ftp_url"));
- if (server.has("ftp_password"))
- editor.putString("server_ftppass_" + postfix, server.getString("ftp_password"));
- if (server.has("server_timeout"))
- editor.putString("server_timeout_" + postfix, server.getString("server_timeout"));
- if (server.has("download_alarm"))
- editor.putBoolean("server_alarmfinished_" + postfix, server.getBoolean("download_alarm"));
- if (server.has("new_torrent_alarm"))
- editor.putBoolean("server_alarmnew_" + postfix, server.getBoolean("new_torrent_alarm"));
- if (server.has("alarm_filter_exclude"))
- editor.putString("server_alarmexclude_" + postfix, server.getString("alarm_filter_exclude"));
- if (server.has("alarm_filter_include"))
- editor.putString("server_alarminclude_" + postfix, server.getString("alarm_filter_include"));
-
- }
- }
-
- // Import web search sites
- if (json.has("websites")) {
- JSONArray sites = json.getJSONArray("websites");
- for (int i = 0; i < sites.length(); i++) {
- JSONObject site = sites.getJSONObject(i);
- String postfix = Integer.toString(applicationSettings.getMaxWebsearch() + 1 + i);
-
- if (site.has("name"))
- editor.putString("websearch_name_" + postfix, site.getString("name"));
- if (site.has("url"))
- editor.putString("websearch_baseurl_" + postfix, site.getString("url"));
- if (site.has("cookies"))
- editor.putString("websearch_cookies_" + postfix, site.getString("cookies"));
-
- }
- }
-
- // Import RSS feeds
- if (json.has("rssfeeds")) {
- JSONArray feeds = json.getJSONArray("rssfeeds");
- for (int i = 0; i < feeds.length(); i++) {
- JSONObject feed = feeds.getJSONObject(i);
- String postfix = Integer.toString(applicationSettings.getMaxRssfeed() + 1 + i);
-
- if (feed.has("name"))
- editor.putString("rssfeed_name_" + postfix, feed.getString("name"));
- if (feed.has("url"))
- editor.putString("rssfeed_url_" + postfix, feed.getString("url"));
- if (feed.has("needs_auth"))
- editor.putBoolean("rssfeed_reqauth_" + postfix, feed.getBoolean("needs_auth"));
- if (feed.has("new_item_alarm"))
- editor.putBoolean("rssfeed_alarmnew_" + postfix, feed.getBoolean("new_item_alarm"));
- if (feed.has("alarm_filter_include"))
- editor.putString("rssfeed_include_" + postfix, feed.getString("alarm_filter_include"));
- if (feed.has("alarm_filter_exclude"))
- editor.putString("rssfeed_exclude_" + postfix, feed.getString("alarm_filter_exclude"));
- if (feed.has("last_seen_time"))
- editor.putLong("rssfeed_lastviewed_" + postfix, feed.getLong("last_seen_time"));
- if (feed.has("last_seen_item"))
- editor.putString("rssfeed_lastvieweditemurl_" + postfix, feed.getString("last_seen_item"));
-
- }
- }
-
- // Import background service and system settings
- if (json.has("alarm_enabled_rss"))
- editor.putBoolean("notifications_enabledrss", json.getBoolean("alarm_enabled_rss"));
- if (json.has("alarm_enabled_torrents"))
- editor.putBoolean("notifications_enabled", json.getBoolean("alarm_enabled_torrents"));
- else if (json.has("alarm_enabled")) // Compat
- editor.putBoolean("notifications_enabled", json.getBoolean("alarm_enabled"));
- if (json.has("alarm_interval"))
- editor.putString("notifications_interval", json.getString("alarm_interval"));
- if (json.has("alarm_sound_uri"))
- editor.putString("notifications_sound", json.getString("alarm_sound_uri"));
- if (json.has("alarm_vibrate"))
- editor.putBoolean("notifications_vibrate", json.getBoolean("alarm_vibrate"));
- if (json.has("alarm_ledcolour"))
- editor.putInt("notifications_ledcolour", json.getInt("alarm_ledcolour"));
- if (json.has("alarm_adwnotifications"))
- editor.putBoolean("notifications_adwnotify", json.getBoolean("alarm_adwnotifications"));
- if (json.has("system_dormantasinactive"))
- editor.putBoolean("system_dormantasinactive", json.getBoolean("system_dormantasinactive"));
- if (json.has("system_autorefresh"))
- editor.putString("system_autorefresh", json.getString("system_autorefresh"));
- if (json.has("system_checkupdates"))
- editor.putBoolean("system_checkupdates", json.getBoolean("system_checkupdates"));
- if (json.has("system_usedarktheme"))
- editor.putBoolean("system_usedarktheme", json.getBoolean("system_usedarktheme"));
-
- editor.apply();
-
- }
-
- /**
- * Returns encoded server, web searches, RSS feed, background service and system settings as a JSON data object structure, serialized to a String.
- * @param prefs The application-global preferences object to read settings from
- * @throws JSONException Thrown when the JSON content could not be constructed properly
- */
- public String exportSettingsAsString(SharedPreferences prefs) throws JSONException {
- return exportSettings(prefs).toString();
- }
-
- /**
- * Synchronously writes the server, web searches, RSS feed, background service and system settings to a file in JSON
- * format.
- * @param prefs The application-global preferences object to read settings from
- * @param settingsFile The local file to read the settings from
- * @throws JSONException Thrown when the JSON content could not be constructed properly
- * @throws IOException Thrown when the settings file could not be created or written to
- */
- public void exportSettingsToFile(SharedPreferences prefs, File settingsFile) throws JSONException, IOException {
- if (settingsFile.exists()) {
- settingsFile.delete();
- }
- settingsFile.getParentFile().mkdirs();
- settingsFile.createNewFile();
- exportSettingsToStream(prefs, new FileOutputStream(settingsFile));
- }
-
- /**
- * Synchronously writes the server, web searches, RSS feed, background service and system settings to a stream (file) in JSON format. The stream
- * will be closed regardless of success.
- *
- * @param prefs The application-global preferences object to read settings from
- * @param settingsStream The stream to read the settings to
- * @throws JSONException Thrown when the JSON content could not be constructed properly
- * @throws IOException Thrown when the settings file could not be created or written to
- */
- public void exportSettingsToStream(SharedPreferences prefs, OutputStream settingsStream) throws JSONException, IOException {
- try {
- JSONObject json = exportSettings(prefs);
- settingsStream.write(json.toString(2).getBytes());
- } finally {
- settingsStream.close();
- }
- }
-
- private JSONObject exportSettings(SharedPreferences prefs) throws JSONException {
-
- // Create a single JSON object that will contain all settings
- JSONObject json = new JSONObject();
-
- // Convert server settings into JSON
- JSONArray servers = new JSONArray();
- int i = 0;
- String postfixi = "0";
- while (prefs.contains("server_type_" + postfixi)) {
-
- JSONObject server = new JSONObject();
- server.put("name", prefs.getString("server_name_" + postfixi, null));
- server.put("type", prefs.getString("server_type_" + postfixi, null));
- server.put("host", prefs.getString("server_address_" + postfixi, null));
- server.put("local_network", prefs.getString("server_localnetwork_" + postfixi, null));
- server.put("local_host", prefs.getString("server_localaddress_" + postfixi, null));
- server.put("local_port", prefs.getString("server_localport_" + postfixi, null));
- server.put("port", prefs.getString("server_port_" + postfixi, null));
- server.put("ssl", prefs.getBoolean("server_sslenabled_" + postfixi, false));
- server.put("local_ssl", prefs.getBoolean("server_localsslenabled_" + postfixi, false));
- server.put("ssl_accept_all", prefs.getBoolean("server_ssltrustall_" + postfixi, false));
- server.put("ssl_trust_key", prefs.getString("server_ssltrustkey_" + postfixi, null));
- server.put("folder", prefs.getString("server_folder_" + postfixi, null));
- server.put("use_auth", !prefs.getBoolean("server_disableauth_" + postfixi, false));
- server.put("username", prefs.getString("server_user_" + postfixi, null));
- server.put("password", prefs.getString("server_pass_" + postfixi, null));
- server.put("extra_password", prefs.getString("server_extrapass_" + postfixi, null));
- server.put("os_type", prefs.getString("server_os_" + postfixi, null));
- server.put("downloads_dir", prefs.getString("server_downloaddir_" + postfixi, null));
- server.put("base_ftp_url", prefs.getString("server_ftpurl_" + postfixi, null));
- server.put("ftp_password", prefs.getString("server_ftppass_" + postfixi, null));
- server.put("server_timeout", prefs.getString("server_timeout_" + postfixi, null));
- server.put("download_alarm", prefs.getBoolean("server_alarmfinished_" + postfixi, false));
- server.put("new_torrent_alarm", prefs.getBoolean("server_alarmnew_" + postfixi, false));
- server.put("alarm_filter_exclude", prefs.getString("server_alarmexclude_" + postfixi, null));
- server.put("alarm_filter_include", prefs.getString("server_alarminclude_" + postfixi, null));
-
- servers.put(server);
- i++;
- postfixi = Integer.toString(i);
- }
- json.put("servers", servers);
-
- // Convert web search settings into JSON
- JSONArray sites = new JSONArray();
- int j = 0;
- String postfixj = "0";
- while (prefs.contains("websearch_baseurl_" + postfixj)) {
-
- JSONObject site = new JSONObject();
- site.put("name", prefs.getString("websearch_name_" + postfixj, null));
- site.put("url", prefs.getString("websearch_baseurl_" + postfixj, null));
- site.put("cookies", prefs.getString("websearch_cookies_" + postfixj, null));
-
- sites.put(site);
- j++;
- postfixj = Integer.toString(j);
- }
- json.put("websites", sites);
-
- // Convert RSS feed settings into JSON
- JSONArray feeds = new JSONArray();
- int k = 0;
- String postfixk = "0";
- while (prefs.contains("rssfeed_url_" + postfixk)) {
-
- JSONObject feed = new JSONObject();
- feed.put("name", prefs.getString("rssfeed_name_" + postfixk, null));
- feed.put("url", prefs.getString("rssfeed_url_" + postfixk, null));
- feed.put("needs_auth", prefs.getBoolean("rssfeed_reqauth_" + postfixk, false));
- feed.put("new_item_alarm", prefs.getBoolean("rssfeed_alarmnew_" + postfixk, false));
- feed.put("alarm_filter_exclude", prefs.getString("rssfeed_exclude_" + postfixk, null));
- feed.put("alarm_filter_include", prefs.getString("rssfeed_include_" + postfixk, null));
- feed.put("last_seen_time", prefs.getLong("rssfeed_lastviewed_" + postfixk, -1));
- feed.put("last_seen_item", prefs.getString("rssfeed_lastvieweditemurl_" + postfixk, null));
-
- feeds.put(feed);
- k++;
- postfixk = Integer.toString(k);
- }
- json.put("rssfeeds", feeds);
-
- // Convert background service and system settings into JSON
- json.put("alarm_enabled_rss", prefs.getBoolean("notifications_enabledrss", true));
- json.put("alarm_enabled_torrents", prefs.getBoolean("notifications_enabled", true));
- json.put("alarm_interval", prefs.getString("notifications_interval", null));
- json.put("alarm_sound_uri", prefs.getString("notifications_sound", null));
- json.put("alarm_vibrate", prefs.getBoolean("notifications_vibrate", false));
- json.put("alarm_ledcolour", prefs.getInt("notifications_ledcolour", -1));
- json.put("alarm_adwnotifications", prefs.getBoolean("notifications_adwnotify", false));
- json.put("system_dormantasinactive", prefs.getBoolean("system_dormantasinactive", false));
- json.put("system_autorefresh", prefs.getString("system_autorefresh", "0"));
- json.put("system_usedarktheme", prefs.getBoolean("system_usedarktheme", false));
- json.put("system_checkupdates", prefs.getBoolean("system_checkupdates", true));
-
- return json;
-
- }
-
+ public void importSettings(SharedPreferences prefs, JSONObject json) throws JSONException {
+
+ Editor editor = prefs.edit();
+
+ // Import servers
+ if (json.has("servers")) {
+ JSONArray servers = json.getJSONArray("servers");
+ for (int i = 0; i < servers.length(); i++) {
+ JSONObject server = servers.getJSONObject(i);
+ String postfix = Integer.toString(applicationSettings.getMaxOfAllServers() + 1 + i);
+
+ if (server.has("name"))
+ editor.putString("server_name_" + postfix, server.getString("name"));
+ if (server.has("type"))
+ editor.putString("server_type_" + postfix, server.getString("type"));
+ if (server.has("host"))
+ editor.putString("server_address_" + postfix, server.getString("host"));
+ if (server.has("local_network"))
+ editor.putString("server_localnetwork_" + postfix, server.getString("local_network"));
+ if (server.has("local_host"))
+ editor.putString("server_localaddress_" + postfix, server.getString("local_host"));
+ if (server.has("local_port"))
+ editor.putString("server_localport_" + postfix, server.getString("local_port"));
+ if (server.has("port"))
+ editor.putString("server_port_" + postfix, server.getString("port"));
+ if (server.has("ssl"))
+ editor.putBoolean("server_sslenabled_" + postfix, server.getBoolean("ssl"));
+ if (server.has("local_ssl"))
+ editor.putBoolean("server_localsslenabled_" + postfix, server.getBoolean("local_ssl"));
+ if (server.has("ssl_accept_all"))
+ editor.putBoolean("server_ssltrustall_" + postfix, server.getBoolean("ssl_accept_all"));
+ if (server.has("ssl_trust_key"))
+ editor.putString("server_ssltrustkey_" + postfix, server.getString("ssl_trust_key"));
+ if (server.has("folder"))
+ editor.putString("server_folder_" + postfix, server.getString("folder"));
+ if (server.has("use_auth"))
+ editor.putBoolean("server_disableauth_" + postfix, !server.getBoolean("use_auth"));
+ if (server.has("username"))
+ editor.putString("server_user_" + postfix, server.getString("username"));
+ if (server.has("password"))
+ editor.putString("server_pass_" + postfix, server.getString("password"));
+ if (server.has("extra_password"))
+ editor.putString("server_extrapass_" + postfix, server.getString("extra_password"));
+ if (server.has("os_type"))
+ editor.putString("server_os_" + postfix, server.getString("os_type"));
+ if (server.has("downloads_dir"))
+ editor.putString("server_downloaddir_" + postfix, server.getString("downloads_dir"));
+ if (server.has("base_ftp_url"))
+ editor.putString("server_ftpurl_" + postfix, server.getString("base_ftp_url"));
+ if (server.has("ftp_password"))
+ editor.putString("server_ftppass_" + postfix, server.getString("ftp_password"));
+ if (server.has("server_timeout"))
+ editor.putString("server_timeout_" + postfix, server.getString("server_timeout"));
+ if (server.has("download_alarm"))
+ editor.putBoolean("server_alarmfinished_" + postfix, server.getBoolean("download_alarm"));
+ if (server.has("new_torrent_alarm"))
+ editor.putBoolean("server_alarmnew_" + postfix, server.getBoolean("new_torrent_alarm"));
+ if (server.has("alarm_filter_exclude"))
+ editor.putString("server_alarmexclude_" + postfix, server.getString("alarm_filter_exclude"));
+ if (server.has("alarm_filter_include"))
+ editor.putString("server_alarminclude_" + postfix, server.getString("alarm_filter_include"));
+
+ }
+ }
+
+ // Import web search sites
+ if (json.has("websites")) {
+ JSONArray sites = json.getJSONArray("websites");
+ for (int i = 0; i < sites.length(); i++) {
+ JSONObject site = sites.getJSONObject(i);
+ String postfix = Integer.toString(applicationSettings.getMaxWebsearch() + 1 + i);
+
+ if (site.has("name"))
+ editor.putString("websearch_name_" + postfix, site.getString("name"));
+ if (site.has("url"))
+ editor.putString("websearch_baseurl_" + postfix, site.getString("url"));
+ if (site.has("cookies"))
+ editor.putString("websearch_cookies_" + postfix, site.getString("cookies"));
+
+ }
+ }
+
+ // Import RSS feeds
+ if (json.has("rssfeeds")) {
+ JSONArray feeds = json.getJSONArray("rssfeeds");
+ for (int i = 0; i < feeds.length(); i++) {
+ JSONObject feed = feeds.getJSONObject(i);
+ String postfix = Integer.toString(applicationSettings.getMaxRssfeed() + 1 + i);
+
+ if (feed.has("name"))
+ editor.putString("rssfeed_name_" + postfix, feed.getString("name"));
+ if (feed.has("url"))
+ editor.putString("rssfeed_url_" + postfix, feed.getString("url"));
+ if (feed.has("needs_auth"))
+ editor.putBoolean("rssfeed_reqauth_" + postfix, feed.getBoolean("needs_auth"));
+ if (feed.has("new_item_alarm"))
+ editor.putBoolean("rssfeed_alarmnew_" + postfix, feed.getBoolean("new_item_alarm"));
+ if (feed.has("alarm_filter_include"))
+ editor.putString("rssfeed_include_" + postfix, feed.getString("alarm_filter_include"));
+ if (feed.has("alarm_filter_exclude"))
+ editor.putString("rssfeed_exclude_" + postfix, feed.getString("alarm_filter_exclude"));
+ if (feed.has("last_seen_time"))
+ editor.putLong("rssfeed_lastviewed_" + postfix, feed.getLong("last_seen_time"));
+ if (feed.has("last_seen_item"))
+ editor.putString("rssfeed_lastvieweditemurl_" + postfix, feed.getString("last_seen_item"));
+
+ }
+ }
+
+ // Import background service and system settings
+ if (json.has("alarm_enabled_rss"))
+ editor.putBoolean("notifications_enabledrss", json.getBoolean("alarm_enabled_rss"));
+ if (json.has("alarm_enabled_torrents"))
+ editor.putBoolean("notifications_enabled", json.getBoolean("alarm_enabled_torrents"));
+ else if (json.has("alarm_enabled")) // Compat
+ editor.putBoolean("notifications_enabled", json.getBoolean("alarm_enabled"));
+ if (json.has("alarm_interval"))
+ editor.putString("notifications_interval", json.getString("alarm_interval"));
+ if (json.has("alarm_sound_uri"))
+ editor.putString("notifications_sound", json.getString("alarm_sound_uri"));
+ if (json.has("alarm_vibrate"))
+ editor.putBoolean("notifications_vibrate", json.getBoolean("alarm_vibrate"));
+ if (json.has("alarm_ledcolour"))
+ editor.putInt("notifications_ledcolour", json.getInt("alarm_ledcolour"));
+ if (json.has("alarm_adwnotifications"))
+ editor.putBoolean("notifications_adwnotify", json.getBoolean("alarm_adwnotifications"));
+ if (json.has("system_dormantasinactive"))
+ editor.putBoolean("system_dormantasinactive", json.getBoolean("system_dormantasinactive"));
+ if (json.has("system_autorefresh"))
+ editor.putString("system_autorefresh", json.getString("system_autorefresh"));
+ if (json.has("system_checkupdates"))
+ editor.putBoolean("system_checkupdates", json.getBoolean("system_checkupdates"));
+ if (json.has("system_usedarktheme"))
+ editor.putBoolean("system_usedarktheme", json.getBoolean("system_usedarktheme"));
+
+ editor.apply();
+
+ }
+
+ /**
+ * Returns encoded server, web searches, RSS feed, background service and system settings as a JSON data object structure, serialized to a String.
+ *
+ * @param prefs The application-global preferences object to read settings from
+ * @throws JSONException Thrown when the JSON content could not be constructed properly
+ */
+ public String exportSettingsAsString(SharedPreferences prefs) throws JSONException {
+ return exportSettings(prefs).toString();
+ }
+
+ /**
+ * Synchronously writes the server, web searches, RSS feed, background service and system settings to a file in JSON
+ * format.
+ *
+ * @param prefs The application-global preferences object to read settings from
+ * @param settingsFile The local file to read the settings from
+ * @throws JSONException Thrown when the JSON content could not be constructed properly
+ * @throws IOException Thrown when the settings file could not be created or written to
+ */
+ public void exportSettingsToFile(SharedPreferences prefs, File settingsFile) throws JSONException, IOException {
+ if (settingsFile.exists()) {
+ settingsFile.delete();
+ }
+ settingsFile.getParentFile().mkdirs();
+ settingsFile.createNewFile();
+ exportSettingsToStream(prefs, new FileOutputStream(settingsFile));
+ }
+
+ /**
+ * Synchronously writes the server, web searches, RSS feed, background service and system settings to a stream (file) in JSON format. The stream
+ * will be closed regardless of success.
+ *
+ * @param prefs The application-global preferences object to read settings from
+ * @param settingsStream The stream to read the settings to
+ * @throws JSONException Thrown when the JSON content could not be constructed properly
+ * @throws IOException Thrown when the settings file could not be created or written to
+ */
+ public void exportSettingsToStream(SharedPreferences prefs, OutputStream settingsStream) throws JSONException, IOException {
+ try {
+ JSONObject json = exportSettings(prefs);
+ settingsStream.write(json.toString(2).getBytes());
+ } finally {
+ settingsStream.close();
+ }
+ }
+
+ private JSONObject exportSettings(SharedPreferences prefs) throws JSONException {
+
+ // Create a single JSON object that will contain all settings
+ JSONObject json = new JSONObject();
+
+ // Convert server settings into JSON
+ JSONArray servers = new JSONArray();
+ int i = 0;
+ String postfixi = "0";
+ while (prefs.contains("server_type_" + postfixi)) {
+
+ JSONObject server = new JSONObject();
+ server.put("name", prefs.getString("server_name_" + postfixi, null));
+ server.put("type", prefs.getString("server_type_" + postfixi, null));
+ server.put("host", prefs.getString("server_address_" + postfixi, null));
+ server.put("local_network", prefs.getString("server_localnetwork_" + postfixi, null));
+ server.put("local_host", prefs.getString("server_localaddress_" + postfixi, null));
+ server.put("local_port", prefs.getString("server_localport_" + postfixi, null));
+ server.put("port", prefs.getString("server_port_" + postfixi, null));
+ server.put("ssl", prefs.getBoolean("server_sslenabled_" + postfixi, false));
+ server.put("local_ssl", prefs.getBoolean("server_localsslenabled_" + postfixi, false));
+ server.put("ssl_accept_all", prefs.getBoolean("server_ssltrustall_" + postfixi, false));
+ server.put("ssl_trust_key", prefs.getString("server_ssltrustkey_" + postfixi, null));
+ server.put("folder", prefs.getString("server_folder_" + postfixi, null));
+ server.put("use_auth", !prefs.getBoolean("server_disableauth_" + postfixi, false));
+ server.put("username", prefs.getString("server_user_" + postfixi, null));
+ server.put("password", prefs.getString("server_pass_" + postfixi, null));
+ server.put("extra_password", prefs.getString("server_extrapass_" + postfixi, null));
+ server.put("os_type", prefs.getString("server_os_" + postfixi, null));
+ server.put("downloads_dir", prefs.getString("server_downloaddir_" + postfixi, null));
+ server.put("base_ftp_url", prefs.getString("server_ftpurl_" + postfixi, null));
+ server.put("ftp_password", prefs.getString("server_ftppass_" + postfixi, null));
+ server.put("server_timeout", prefs.getString("server_timeout_" + postfixi, null));
+ server.put("download_alarm", prefs.getBoolean("server_alarmfinished_" + postfixi, false));
+ server.put("new_torrent_alarm", prefs.getBoolean("server_alarmnew_" + postfixi, false));
+ server.put("alarm_filter_exclude", prefs.getString("server_alarmexclude_" + postfixi, null));
+ server.put("alarm_filter_include", prefs.getString("server_alarminclude_" + postfixi, null));
+
+ servers.put(server);
+ i++;
+ postfixi = Integer.toString(i);
+ }
+ json.put("servers", servers);
+
+ // Convert web search settings into JSON
+ JSONArray sites = new JSONArray();
+ int j = 0;
+ String postfixj = "0";
+ while (prefs.contains("websearch_baseurl_" + postfixj)) {
+
+ JSONObject site = new JSONObject();
+ site.put("name", prefs.getString("websearch_name_" + postfixj, null));
+ site.put("url", prefs.getString("websearch_baseurl_" + postfixj, null));
+ site.put("cookies", prefs.getString("websearch_cookies_" + postfixj, null));
+
+ sites.put(site);
+ j++;
+ postfixj = Integer.toString(j);
+ }
+ json.put("websites", sites);
+
+ // Convert RSS feed settings into JSON
+ JSONArray feeds = new JSONArray();
+ int k = 0;
+ String postfixk = "0";
+ while (prefs.contains("rssfeed_url_" + postfixk)) {
+
+ JSONObject feed = new JSONObject();
+ feed.put("name", prefs.getString("rssfeed_name_" + postfixk, null));
+ feed.put("url", prefs.getString("rssfeed_url_" + postfixk, null));
+ feed.put("needs_auth", prefs.getBoolean("rssfeed_reqauth_" + postfixk, false));
+ feed.put("new_item_alarm", prefs.getBoolean("rssfeed_alarmnew_" + postfixk, false));
+ feed.put("alarm_filter_exclude", prefs.getString("rssfeed_exclude_" + postfixk, null));
+ feed.put("alarm_filter_include", prefs.getString("rssfeed_include_" + postfixk, null));
+ feed.put("last_seen_time", prefs.getLong("rssfeed_lastviewed_" + postfixk, -1));
+ feed.put("last_seen_item", prefs.getString("rssfeed_lastvieweditemurl_" + postfixk, null));
+
+ feeds.put(feed);
+ k++;
+ postfixk = Integer.toString(k);
+ }
+ json.put("rssfeeds", feeds);
+
+ // Convert background service and system settings into JSON
+ json.put("alarm_enabled_rss", prefs.getBoolean("notifications_enabledrss", true));
+ json.put("alarm_enabled_torrents", prefs.getBoolean("notifications_enabled", true));
+ json.put("alarm_interval", prefs.getString("notifications_interval", null));
+ json.put("alarm_sound_uri", prefs.getString("notifications_sound", null));
+ json.put("alarm_vibrate", prefs.getBoolean("notifications_vibrate", false));
+ json.put("alarm_ledcolour", prefs.getInt("notifications_ledcolour", -1));
+ json.put("alarm_adwnotifications", prefs.getBoolean("notifications_adwnotify", false));
+ json.put("system_dormantasinactive", prefs.getBoolean("system_dormantasinactive", false));
+ json.put("system_autorefresh", prefs.getString("system_autorefresh", "0"));
+ json.put("system_usedarktheme", prefs.getBoolean("system_usedarktheme", false));
+ json.put("system_checkupdates", prefs.getBoolean("system_checkupdates", true));
+
+ return json;
+
+ }
+
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java b/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java
index 1c5a2834..3d8d7b16 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/SettingsUtils.java
@@ -2,6 +2,7 @@ package org.transdroid.core.app.settings;
import android.content.Context;
+
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
@@ -33,6 +34,6 @@ public class SettingsUtils {
return builder;
}
- return builder.theme(settings.useDarkTheme() ? Theme.DARK: Theme.LIGHT);
+ return builder.theme(settings.useDarkTheme() ? Theme.DARK : Theme.LIGHT);
}
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java b/app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java
index 23bc3af9..7ec5a9ae 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,58 +29,62 @@ import java.util.Date;
/**
* Allows instantiation of the settings specified in R.xml.pref_system.
+ *
* @author Eric Kok
*/
@EBean(scope = Scope.Singleton)
public class SystemSettings {
- @RootContext
- protected Context context;
- private SharedPreferences prefs;
+ @RootContext
+ protected Context context;
+ private SharedPreferences prefs;
- protected SystemSettings(Context context) {
- prefs = PreferenceManager.getDefaultSharedPreferences(context);
- }
+ protected SystemSettings(Context context) {
+ prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ }
- public boolean treatDormantAsInactive() {
- return prefs.getBoolean("system_dormantasinactive", false);
- }
+ public boolean treatDormantAsInactive() {
+ return prefs.getBoolean("system_dormantasinactive", false);
+ }
- /**
- * Returns the interval in which automatic screen refreshes should be scheduled.
- * @return The selected refresh interval in milliseconds or 0 if automatic refreshes should be disabled
- */
- public long getRefreshIntervalMilliseconds() {
- return Integer.parseInt(prefs.getString("system_autorefresh", "0")) * 1000;
- }
+ /**
+ * Returns the interval in which automatic screen refreshes should be scheduled.
+ *
+ * @return The selected refresh interval in milliseconds or 0 if automatic refreshes should be disabled
+ */
+ public long getRefreshIntervalMilliseconds() {
+ return Integer.parseInt(prefs.getString("system_autorefresh", "0")) * 1000;
+ }
- public boolean checkForUpdates() {
- return prefs.getBoolean("system_checkupdates", true);
- }
+ public boolean checkForUpdates() {
+ return prefs.getBoolean("system_checkupdates", true);
+ }
- public boolean autoDarkTheme() {
- return prefs.getBoolean("system_autodarktheme", true);
- }
+ public boolean autoDarkTheme() {
+ return prefs.getBoolean("system_autodarktheme", true);
+ }
- public boolean useDarkTheme() {
- return prefs.getBoolean("system_usedarktheme", false);
- }
+ public boolean useDarkTheme() {
+ return prefs.getBoolean("system_usedarktheme", false);
+ }
- /**
- * Returns the date when we last checked transdroid.org for the latest app version.
- * @return The date/time when the {@link org.transdroid.core.service.AppUpdateJob} checked on the server for updates
- */
- public Date getLastCheckedForAppUpdates() {
- long lastChecked = prefs.getLong("system_lastappupdatecheck", -1L);
- return lastChecked == -1 ? null : new Date(lastChecked);
- }
+ /**
+ * Returns the date when we last checked transdroid.org for the latest app version.
+ *
+ * @return The date/time when the {@link org.transdroid.core.service.AppUpdateJob} checked on the server for updates
+ */
+ public Date getLastCheckedForAppUpdates() {
+ long lastChecked = prefs.getLong("system_lastappupdatecheck", -1L);
+ return lastChecked == -1 ? null : new Date(lastChecked);
+ }
- /**
- * Stores the date at which was last successfully, fully checked for new updates to the app.
- * @param lastChecked The date/time at which the {@link org.transdroid.core.service.AppUpdateJob} last checked the server for updates
- */
- public void setLastCheckedForAppUpdates(Date lastChecked) {
- prefs.edit().putLong("system_lastappupdatecheck", lastChecked == null ? -1L : lastChecked.getTime()).apply();
- }
+ /**
+ * Stores the date at which was last successfully, fully checked for new updates to the app.
+ *
+ * @param lastChecked The date/time at which the {@link org.transdroid.core.service.AppUpdateJob} last checked the server for updates
+ */
+ public void setLastCheckedForAppUpdates(Date lastChecked) {
+ prefs.edit().putLong("system_lastappupdatecheck", lastChecked == null ? -1L : lastChecked.getTime()).apply();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/app/settings/WebsearchSetting.java b/app/src/main/java/org/transdroid/core/app/settings/WebsearchSetting.java
index 3e251c09..d17d31a5 100644
--- a/app/src/main/java/org/transdroid/core/app/settings/WebsearchSetting.java
+++ b/app/src/main/java/org/transdroid/core/app/settings/WebsearchSetting.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -24,58 +24,60 @@ import android.text.TextUtils;
/**
* Represents a user-specified website that can be searched (by starting the browser, rather than in-app)
+ *
* @author Eric Kok
*/
public class WebsearchSetting implements SimpleListItem, SearchSetting {
- private static final String DEFAULT_NAME = "Default";
- public static final String KEY_PREFIX = "websearch_";
-
- private final int order;
- private final String name;
- private final String baseUrl;
- private final String cookies;
+ private static final String DEFAULT_NAME = "Default";
+ public static final String KEY_PREFIX = "websearch_";
- public WebsearchSetting(int order, String name, String baseUrl, String cookies) {
- this.order = order;
- this.name = name;
- this.baseUrl = baseUrl;
- this.cookies = cookies;
- }
+ private final int order;
+ private final String name;
+ private final String baseUrl;
+ private final String cookies;
- public int getOrder() {
- return order;
- }
+ public WebsearchSetting(int order, String name, String baseUrl, String cookies) {
+ this.order = order;
+ this.name = name;
+ this.baseUrl = baseUrl;
+ this.cookies = cookies;
+ }
- @Override
- public String getName() {
- if (!TextUtils.isEmpty(name))
- return name;
- if (!TextUtils.isEmpty(baseUrl)) {
- String host = Uri.parse(baseUrl).getHost();
- return host == null? DEFAULT_NAME: host;
- }
- return DEFAULT_NAME;
- }
-
- public String getBaseUrl() {
- return baseUrl;
- }
+ public int getOrder() {
+ return order;
+ }
- public String getCookies() {
- return cookies;
- }
-
- public String getKey() {
- return KEY_PREFIX + getOrder();
- }
+ @Override
+ public String getName() {
+ if (!TextUtils.isEmpty(name))
+ return name;
+ if (!TextUtils.isEmpty(baseUrl)) {
+ String host = Uri.parse(baseUrl).getHost();
+ return host == null ? DEFAULT_NAME : host;
+ }
+ return DEFAULT_NAME;
+ }
+
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
+ public String getCookies() {
+ return cookies;
+ }
+
+ public String getKey() {
+ return KEY_PREFIX + getOrder();
+ }
+
+ /**
+ * Returns a nicely formatted identifier containing (a portion of) the search base URL
+ *
+ * @return A string to identify this site's search URL
+ */
+ public String getHumanReadableIdentifier() {
+ return Uri.parse(baseUrl).getHost();
+ }
- /**
- * Returns a nicely formatted identifier containing (a portion of) the search base URL
- * @return A string to identify this site's search URL
- */
- public String getHumanReadableIdentifier() {
- return Uri.parse(baseUrl).getHost();
- }
-
}
diff --git a/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java b/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
index 2dc28601..87a9386f 100644
--- a/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
+++ b/app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
@@ -82,318 +83,319 @@ import java.util.List;
* An activity that holds a single torrents details fragment. It is used on devices (i.e. phones) where there is no room to show details in the {@link
* TorrentsActivity} directly. Task execution, such as loading of more details and updating file priorities, is performed in this activity via
* background methods.
+ *
* @author Eric Kok
*/
@EActivity(R.layout.activity_details)
@OptionsMenu(R.menu.activity_details)
public class DetailsActivity extends AppCompatActivity implements TorrentTasksExecutor, RefreshableActivity {
- @Extra
- @InstanceState
- protected Torrent torrent;
- @Extra
- @InstanceState
- protected ArrayList currentLabels;
-
- // Settings
- @Bean
- protected Log log;
- @Bean
- protected NavigationHelper navigationHelper;
- @Bean
- protected ConnectivityHelper connectivityHelper;
- @Bean
- protected ApplicationSettings applicationSettings;
- private IDaemonAdapter currentConnection = null;
-
- // Details view components
- @ViewById
- protected Toolbar selectionToolbar;
- @FragmentById(R.id.torrentdetails_fragment)
- protected DetailsFragment fragmentDetails;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- SettingsUtils.applyDayNightTheme(this);
- super.onCreate(savedInstanceState);
- }
-
- @AfterViews
- protected void init() {
-
- // We require a torrent to be specified; otherwise close the activity
- if (torrent == null) {
- finish();
- return;
- }
-
- // Simple action bar with up, torrent name as title and refresh button
- setSupportActionBar(selectionToolbar);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(torrent.getName()));
-
- // Connect to the last used server
- ServerSetting lastUsed = applicationSettings.getLastUsedServer();
- fragmentDetails.setCurrentServerSettings(lastUsed);
- currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
-
- // Show details and load fine stats and torrent files
- fragmentDetails.updateTorrent(torrent);
- fragmentDetails.updateLabels(currentLabels);
-
- }
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- @OptionsItem(android.R.id.home)
- protected void navigateUp() {
- TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
- }
-
- @OptionsItem(R.id.action_refresh)
- public void refreshScreen() {
- fragmentDetails.updateIsLoading(true, null);
- refreshTorrent();
- refreshTorrentDetails(torrent);
- refreshTorrentFiles(torrent);
- }
-
- @Background
- protected void refreshTorrent() {
- DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
- if (result instanceof RetrieveTaskSuccessResult) {
- onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, true);
- }
- }
-
- @Background
- public void refreshTorrentDetails(Torrent torrent) {
- if (currentConnection == null) return;
- if (!Daemon.supportsFineDetails(torrent.getDaemon())) {
- return;
- }
- DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
- if (result instanceof GetTorrentDetailsTaskSuccessResult) {
- onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- public void refreshTorrentFiles(Torrent torrent) {
- if (currentConnection == null) return;
- if (!Daemon.supportsFileListing(torrent.getDaemon())) {
- return;
- }
- DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
- if (result instanceof GetFileListTaskSuccessResult) {
- onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void resumeTorrent(Torrent torrent) {
- if (currentConnection == null) return;
- torrent.mimicResume();
- DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void pauseTorrent(Torrent torrent) {
- torrent.mimicPause();
- DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void startTorrent(Torrent torrent, boolean forced) {
- torrent.mimicStart();
- DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void stopTorrent(Torrent torrent) {
- torrent.mimicStop();
- DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void removeTorrent(Torrent torrent, boolean withData) {
- DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- // Close the details activity (as the torrent is now removed)
- closeActivity(getString(withData ? R.string.result_removed_with_data : R.string.result_removed, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @UiThread
- protected void closeActivity(String closeText) {
- setResult(RESULT_OK, new Intent().putExtra("torrent_removed", true).putExtra("affected_torrent", torrent));
- finish();
- if (closeText != null) {
- SnackbarManager.show(Snackbar.with(this).text(closeText));
- }
- }
-
- @Background
- @Override
- public void updateLabel(Torrent torrent, String newLabel) {
- torrent.mimicNewLabel(newLabel);
- DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_labelset, newLabel));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) {
- torrent.mimicSequentialDownload(sequentialState);
- String onState = getString(R.string.result_togglesequential_onstate);
- String offState = getString(R.string.result_togglesequential_offstate);
- String stateString = sequentialState ? onState : offState;
- DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential, torrent.getName(), stateString));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) {
- torrent.mimicFirstLastPieceDownload(firstLastPieceState);
- String onState = getString(R.string.result_togglefirstlastpiece_onstate);
- String offState = getString(R.string.result_togglefirstlastpiece_offstate);
- String stateString = firstLastPieceState ? onState : offState;
- DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglefirstlastpiece, torrent.getName(), stateString));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void forceRecheckTorrent(Torrent torrent) {
- torrent.mimicCheckingStatus();
- DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_recheckedstarted, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updateTrackers(Torrent torrent, List newTrackers) {
- DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updateLocation(Torrent torrent, String newLocation) {
- DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updatePriority(Torrent torrent, List files, Priority priority) {
- DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<>(files)).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @UiThread
- protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) {
- // Set the activity result so the calling activity knows it needs to update its view
- setResult(RESULT_OK, new Intent().putExtra("torrent_updated", true).putExtra("affected_torrent", torrent));
- // Refresh the screen as well
- refreshTorrent();
- refreshTorrentDetails(torrent);
- SnackbarManager.show(Snackbar.with(this).text(successMessage).duration(Snackbar.SnackbarDuration.LENGTH_SHORT));
- }
-
- @UiThread
- protected void onTorrentDetailsRetrieved(Torrent torrent, TorrentDetails torrentDetails) {
- // Update the details fragment with the new fine details for the shown torrent
- if (fragmentDetails.isResumed())
- fragmentDetails.updateTorrentDetails(torrent, torrentDetails);
- }
-
- @UiThread
- protected void onTorrentFilesRetrieved(Torrent torrent, List torrentFiles) {
- // Update the details fragment with the newly retrieved list of files
- if (fragmentDetails.isResumed())
- fragmentDetails.updateTorrentFiles(torrent, new ArrayList<>(torrentFiles));
- }
-
- @UiThread
- protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
- log.i(this, result.getException().toString());
- String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
- if (fragmentDetails.isResumed())
- fragmentDetails.updateIsLoading(false, isCritical ? error : null);
- SnackbarManager.show(Snackbar.with(this).text(getString(LocalTorrent.getResourceForDaemonException(result.getException())))
- .colorResource(R.color.red));
- }
-
- @UiThread
- protected void onTorrentsRetrieved(List torrents, List labels) {
- // Update the details fragment accordingly
- if (fragmentDetails.isResumed()) {
- fragmentDetails.updateIsLoading(false, null);
- fragmentDetails.perhapsUpdateTorrent(torrents);
- fragmentDetails.updateLabels(Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled)));
- }
- }
+ @Extra
+ @InstanceState
+ protected Torrent torrent;
+ @Extra
+ @InstanceState
+ protected ArrayList currentLabels;
+
+ // Settings
+ @Bean
+ protected Log log;
+ @Bean
+ protected NavigationHelper navigationHelper;
+ @Bean
+ protected ConnectivityHelper connectivityHelper;
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ private IDaemonAdapter currentConnection = null;
+
+ // Details view components
+ @ViewById
+ protected Toolbar selectionToolbar;
+ @FragmentById(R.id.torrentdetails_fragment)
+ protected DetailsFragment fragmentDetails;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ SettingsUtils.applyDayNightTheme(this);
+ super.onCreate(savedInstanceState);
+ }
+
+ @AfterViews
+ protected void init() {
+
+ // We require a torrent to be specified; otherwise close the activity
+ if (torrent == null) {
+ finish();
+ return;
+ }
+
+ // Simple action bar with up, torrent name as title and refresh button
+ setSupportActionBar(selectionToolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(torrent.getName()));
+
+ // Connect to the last used server
+ ServerSetting lastUsed = applicationSettings.getLastUsedServer();
+ fragmentDetails.setCurrentServerSettings(lastUsed);
+ currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
+
+ // Show details and load fine stats and torrent files
+ fragmentDetails.updateTorrent(torrent);
+ fragmentDetails.updateLabels(currentLabels);
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @OptionsItem(android.R.id.home)
+ protected void navigateUp() {
+ TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
+ }
+
+ @OptionsItem(R.id.action_refresh)
+ public void refreshScreen() {
+ fragmentDetails.updateIsLoading(true, null);
+ refreshTorrent();
+ refreshTorrentDetails(torrent);
+ refreshTorrentFiles(torrent);
+ }
+
+ @Background
+ protected void refreshTorrent() {
+ DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
+ if (result instanceof RetrieveTaskSuccessResult) {
+ onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, true);
+ }
+ }
+
+ @Background
+ public void refreshTorrentDetails(Torrent torrent) {
+ if (currentConnection == null) return;
+ if (!Daemon.supportsFineDetails(torrent.getDaemon())) {
+ return;
+ }
+ DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof GetTorrentDetailsTaskSuccessResult) {
+ onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ public void refreshTorrentFiles(Torrent torrent) {
+ if (currentConnection == null) return;
+ if (!Daemon.supportsFileListing(torrent.getDaemon())) {
+ return;
+ }
+ DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof GetFileListTaskSuccessResult) {
+ onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void resumeTorrent(Torrent torrent) {
+ if (currentConnection == null) return;
+ torrent.mimicResume();
+ DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void pauseTorrent(Torrent torrent) {
+ torrent.mimicPause();
+ DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void startTorrent(Torrent torrent, boolean forced) {
+ torrent.mimicStart();
+ DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void stopTorrent(Torrent torrent) {
+ torrent.mimicStop();
+ DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void removeTorrent(Torrent torrent, boolean withData) {
+ DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ // Close the details activity (as the torrent is now removed)
+ closeActivity(getString(withData ? R.string.result_removed_with_data : R.string.result_removed, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @UiThread
+ protected void closeActivity(String closeText) {
+ setResult(RESULT_OK, new Intent().putExtra("torrent_removed", true).putExtra("affected_torrent", torrent));
+ finish();
+ if (closeText != null) {
+ SnackbarManager.show(Snackbar.with(this).text(closeText));
+ }
+ }
+
+ @Background
+ @Override
+ public void updateLabel(Torrent torrent, String newLabel) {
+ torrent.mimicNewLabel(newLabel);
+ DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_labelset, newLabel));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) {
+ torrent.mimicSequentialDownload(sequentialState);
+ String onState = getString(R.string.result_togglesequential_onstate);
+ String offState = getString(R.string.result_togglesequential_offstate);
+ String stateString = sequentialState ? onState : offState;
+ DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential, torrent.getName(), stateString));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) {
+ torrent.mimicFirstLastPieceDownload(firstLastPieceState);
+ String onState = getString(R.string.result_togglefirstlastpiece_onstate);
+ String offState = getString(R.string.result_togglefirstlastpiece_offstate);
+ String stateString = firstLastPieceState ? onState : offState;
+ DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglefirstlastpiece, torrent.getName(), stateString));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void forceRecheckTorrent(Torrent torrent) {
+ torrent.mimicCheckingStatus();
+ DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_recheckedstarted, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updateTrackers(Torrent torrent, List newTrackers) {
+ DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updateLocation(Torrent torrent, String newLocation) {
+ DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updatePriority(Torrent torrent, List files, Priority priority) {
+ DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<>(files)).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @UiThread
+ protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) {
+ // Set the activity result so the calling activity knows it needs to update its view
+ setResult(RESULT_OK, new Intent().putExtra("torrent_updated", true).putExtra("affected_torrent", torrent));
+ // Refresh the screen as well
+ refreshTorrent();
+ refreshTorrentDetails(torrent);
+ SnackbarManager.show(Snackbar.with(this).text(successMessage).duration(Snackbar.SnackbarDuration.LENGTH_SHORT));
+ }
+
+ @UiThread
+ protected void onTorrentDetailsRetrieved(Torrent torrent, TorrentDetails torrentDetails) {
+ // Update the details fragment with the new fine details for the shown torrent
+ if (fragmentDetails.isResumed())
+ fragmentDetails.updateTorrentDetails(torrent, torrentDetails);
+ }
+
+ @UiThread
+ protected void onTorrentFilesRetrieved(Torrent torrent, List torrentFiles) {
+ // Update the details fragment with the newly retrieved list of files
+ if (fragmentDetails.isResumed())
+ fragmentDetails.updateTorrentFiles(torrent, new ArrayList<>(torrentFiles));
+ }
+
+ @UiThread
+ protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
+ log.i(this, result.getException().toString());
+ String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
+ if (fragmentDetails.isResumed())
+ fragmentDetails.updateIsLoading(false, isCritical ? error : null);
+ SnackbarManager.show(Snackbar.with(this).text(getString(LocalTorrent.getResourceForDaemonException(result.getException())))
+ .colorResource(R.color.red));
+ }
+
+ @UiThread
+ protected void onTorrentsRetrieved(List torrents, List labels) {
+ // Update the details fragment accordingly
+ if (fragmentDetails.isResumed()) {
+ fragmentDetails.updateIsLoading(false, null);
+ fragmentDetails.perhapsUpdateTorrent(torrents);
+ fragmentDetails.updateLabels(Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled)));
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
index c4b8baf2..6d2ed0b3 100644
--- a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
+++ b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -23,9 +23,11 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
+
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
@@ -75,616 +77,624 @@ import java.util.List;
* Fragment that shows detailed statistics about some torrent. These come from some already fetched {@link Torrent} object, but it also retrieves
* further detailed statistics. The actual execution of tasks is performed by the activity that contains this fragment, as per the {@link
* TorrentTasksExecutor} interface.
+ *
* @author Eric Kok
*/
@EFragment(R.layout.fragment_details)
public class DetailsFragment extends Fragment implements OnTrackersUpdatedListener, OnLabelPickedListener, OnStorageLocationUpdatedListener {
- // Local data
- @InstanceState
- protected Torrent torrent = null;
- @InstanceState
- protected String torrentId = null;
- @InstanceState
- protected TorrentDetails torrentDetails = null;
- @InstanceState
- protected ArrayList torrentFiles = null;
- @InstanceState
- protected ArrayList currentLabels = null;
- @InstanceState
- protected boolean isLoadingTorrent = false;
- @InstanceState
- protected boolean hasCriticalError = false;
- private ServerSetting currentServerSettings = null;
-
- // Views
- @ViewById
- protected View detailsContainer;
- @ViewById(R.id.details_menu)
- protected ActionMenuView detailsMenu;
- @ViewById(R.id.contextual_menu)
- protected ActionMenuView contextualMenu;
- @ViewById
- protected SwipeRefreshLayout swipeRefreshLayout;
- @ViewById
- protected ListView detailsList;
- @ViewById
- protected TextView emptyText, errorText;
- @ViewById
- protected ProgressBar loadingProgress;
-
- @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()) {
- detailsContainer.setBackgroundResource(R.drawable.details_list_background);
- }
-
- createMenuOptions();
-
- // Set up details adapter (itself containing the actual lists to show), which allows multi-select and fast
- // scrolling
- detailsList.setAdapter(new DetailsAdapter(getActivity()));
- detailsList.setMultiChoiceModeListener(onDetailsSelected);
- detailsList.setFastScrollEnabled(true);
- if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
- swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
- @Override
- public void onRefresh() {
- ((RefreshableActivity) getActivity()).refreshScreen();
- swipeRefreshLayout.setRefreshing(false); // Use our custom indicator
- }
- });
- }
-
- // Restore the fragment state (on orientation changes et al.)
- if (torrent != null) {
- updateTorrent(torrent);
- }
- if (torrent != null && torrentDetails != null) {
- updateTorrentDetails(torrent, torrentDetails);
- }
- if (torrent != null && torrentFiles != null) {
- updateTorrentFiles(torrent, torrentFiles);
- }
-
- }
-
- public void setCurrentServerSettings(ServerSetting serverSettings) {
- currentServerSettings = serverSettings;
- }
-
- /**
- * Updates the details adapter header to show the new torrent data.
- * @param newTorrent The new, non-null torrent object
- */
- public void updateTorrent(Torrent newTorrent) {
- this.torrent = newTorrent;
- this.torrentId = newTorrent.getUniqueID();
- this.hasCriticalError = false;
- ((DetailsAdapter) detailsList.getAdapter()).updateTorrent(newTorrent);
- // Make the list (with details header) visible
- detailsList.setVisibility(View.VISIBLE);
- emptyText.setVisibility(View.GONE);
- errorText.setVisibility(View.GONE);
- loadingProgress.setVisibility(View.GONE);
- // Also update the available actions in the action bar
- updateMenuOptions();
- // Refresh the detailed statistics (errors) and list of files
- torrentDetails = null;
- torrentFiles = null;
- if (getTasksExecutor() != null) {
- getTasksExecutor().refreshTorrentDetails(torrent);
- getTasksExecutor().refreshTorrentFiles(torrent);
- }
- }
-
- /**
- * Updates the details adapter to show the list of trackers and tracker errors.
- * @param checkTorrent The torrent for which the details were retrieved
- * @param newTorrentDetails The new fine details object of some torrent
- */
- public void updateTorrentDetails(Torrent checkTorrent, TorrentDetails newTorrentDetails) {
- // Check if these are actually the details of the torrent we are now showing
- if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) {
- return;
- }
- this.torrentDetails = newTorrentDetails;
- ((DetailsAdapter) detailsList.getAdapter())
- .updateTrackers(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getTrackers()));
- ((DetailsAdapter) detailsList.getAdapter())
- .updateErrors(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getErrors()));
- ((DetailsAdapter) detailsList.getAdapter())
- .updatePieces(newTorrentDetails.getPieces());
- }
-
- /**
- * Updates the list adapter to show a new list of torrent files, replacing the old files list.
- * @param checkTorrent The torrent for which the details were retrieved
- * @param newTorrentFiles The new, updated list of torrent file objects
- */
- public void updateTorrentFiles(Torrent checkTorrent, ArrayList newTorrentFiles) {
- // Check if these are actually the details of the torrent we are now showing
- if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) {
- return;
- }
- Collections.sort(newTorrentFiles);
- this.torrentFiles = newTorrentFiles;
- ((DetailsAdapter) detailsList.getAdapter()).updateTorrentFiles(newTorrentFiles);
- }
-
- /**
- * Can be called if some outside activity returned new torrents, so we can perhaps piggyback on this by update our data as well.
- * @param torrents The last of retrieved torrents
- */
- public void perhapsUpdateTorrent(List torrents) {
- // Only try to update if we actually were showing a torrent
- if (this.torrentId == null || torrents == null) {
- return;
- }
- for (Torrent newTorrent : torrents) {
- if (newTorrent.getUniqueID().equals(torrentId)) {
- // Found, so we can update our data as well
- updateTorrent(newTorrent);
- break;
- }
- }
- }
-
- /**
- * Updates the locally maintained list of labels that are active on the server. Used in the label picking dialog and should be updated every time
- * after the list of torrents was retrieved to keep it updated.
- * @param currentLabels The list of known server labels
- */
- public void updateLabels(ArrayList currentLabels) {
- this.currentLabels = currentLabels == null ? null : new ArrayList<>(currentLabels);
- }
-
- /**
- * Clear the screen by fully clearing the internal merge list (with header and other lists)
- */
- public void clear() {
- detailsList.setAdapter(new DetailsAdapter(getActivity()));
- detailsList.setVisibility(View.GONE);
- emptyText.setVisibility(!isLoadingTorrent && !hasCriticalError ? View.VISIBLE : View.GONE);
- errorText.setVisibility(!isLoadingTorrent && hasCriticalError ? View.VISIBLE : View.GONE);
- loadingProgress.setVisibility(isLoadingTorrent ? View.VISIBLE : View.GONE);
- torrent = null;
- torrentDetails = null;
- torrentFiles = null;
- }
-
- /**
- * Updates the shown screen depending on whether the torrent is loading
- * @param isLoading True if the torrent is (re)loading, false otherwise
- * @param connectionErrorMessage The error message text to show to the user, or null if there was no error
- */
- public void updateIsLoading(boolean isLoading, String connectionErrorMessage) {
- this.isLoadingTorrent = isLoading;
- this.hasCriticalError = connectionErrorMessage != null;
- errorText.setText(connectionErrorMessage);
- if (isLoading || hasCriticalError) {
- clear();
- }
- }
-
- @ItemClick(resName = "details_list")
- protected void detailsListClicked(int position) {
- detailsList.setItemChecked(position, false);
- }
-
- public void createMenuOptions() {
- getActivity().getMenuInflater().inflate(R.menu.fragment_details, detailsMenu.getMenu());
- detailsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- switch (menuItem.getItemId()) {
- case R.id.action_pause:
- pauseTorrent();
- return true;
- case R.id.action_updatetrackers:
- updateTrackers();
- return true;
- case R.id.action_start_forced:
- startTorrentForced();
- return true;
- case R.id.action_remove_withdata:
- removeTorrentWithData();
- return true;
- case R.id.action_stop:
- stopTorrent();
- return true;
- case R.id.action_toggle_sequential:
- toggleSequentialDownload(menuItem);
- return true;
- case R.id.action_toggle_firstlastpiece:
- toggleFirstLastPieceDownload(menuItem);
- return true;
- case R.id.action_forcerecheck:
- setForceRecheck();
- return true;
- case R.id.action_changelocation:
- changeStorageLocation();
- return true;
- case R.id.action_start_default:
- startTorrentDefault();
- return true;
- case R.id.action_remove_default:
- removeTorrentDefault();
- return true;
- case R.id.action_start_direct:
- startTorrentDirect();
- return true;
- case R.id.action_setlabel:
- setLabel();
- return true;
- case R.id.action_resume:
- resumeTorrent();
- return true;
- }
- return false;
- }
- });
- }
-
- private void updateMenuOptions() {
-
- if (torrent == null) {
- detailsMenu.getMenu().findItem(R.id.action_resume).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_pause).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_start).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_start_direct).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_stop).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_remove).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_remove_withdata).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(false);
- detailsMenu.getMenu().findItem(R.id.action_changelocation).setVisible(false);
- return;
- }
- // Update action availability
- boolean startStop = Daemon.supportsStoppingStarting(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_resume).setVisible(torrent.canResume());
- detailsMenu.getMenu().findItem(R.id.action_pause).setVisible(torrent.canPause());
- boolean forcedStart = Daemon.supportsForcedStarting(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_start).setVisible(startStop && forcedStart && torrent.canStart());
- detailsMenu.getMenu().findItem(R.id.action_start_direct).setVisible(startStop && !forcedStart && torrent.canStart());
- detailsMenu.getMenu().findItem(R.id.action_stop).setVisible(startStop && torrent.canStop());
- detailsMenu.getMenu().findItem(R.id.action_remove).setVisible(true);
- boolean removeWithData = Daemon.supportsRemoveWithData(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_remove_withdata).setVisible(removeWithData);
- boolean setLabel = Daemon.supportsSetLabel(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(setLabel);
- boolean forceRecheck = Daemon.supportsForceRecheck(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(forceRecheck);
- boolean sequentialdl = Daemon.supportsSequentialDownload(torrent.getDaemon());
- MenuItem seqMenuItem = detailsMenu.getMenu().findItem(R.id.action_toggle_sequential);
- seqMenuItem.setVisible(sequentialdl);
- seqMenuItem.setChecked(torrent.isSequentiallyDownloading());
- boolean firstlastpiecedl = Daemon.supportsFirstLastPiece(torrent.getDaemon());
- MenuItem flpMenuItem = detailsMenu.getMenu().findItem(R.id.action_toggle_firstlastpiece);
- flpMenuItem.setVisible(firstlastpiecedl);
- flpMenuItem.setChecked(torrent.isDownloadingFirstLastPieceFirst());
- detailsMenu.getMenu().findItem(R.id.action_download_mode).setVisible(!torrent.isFinished() && (firstlastpiecedl || sequentialdl));
- boolean setTrackers = Daemon.supportsSetTrackers(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(setTrackers);
- boolean setLocation = Daemon.supportsSetDownloadLocation(torrent.getDaemon());
- detailsMenu.getMenu().findItem(R.id.action_changelocation).setVisible(setLocation);
-
- }
-
- @OptionsItem(R.id.action_resume)
- protected void resumeTorrent() {
- if (getTasksExecutor() != null)
- getTasksExecutor().resumeTorrent(torrent);
- }
-
- @OptionsItem(R.id.action_pause)
- protected void pauseTorrent() {
- if (getTasksExecutor() != null)
- getTasksExecutor().pauseTorrent(torrent);
- }
-
- @OptionsItem(R.id.action_start_direct)
- protected void startTorrentDirect() {
- if (getTasksExecutor() != null)
- getTasksExecutor().startTorrent(torrent, false);
- }
-
- @OptionsItem(R.id.action_start_default)
- protected void startTorrentDefault() {
- if (getTasksExecutor() != null)
- getTasksExecutor().startTorrent(torrent, false);
- }
-
- @OptionsItem(R.id.action_start_forced)
- protected void startTorrentForced() {
- if (getTasksExecutor() != null)
- getTasksExecutor().startTorrent(torrent, true);
- }
-
- @OptionsItem(R.id.action_stop)
- protected void stopTorrent() {
- if (getTasksExecutor() != null)
- getTasksExecutor().stopTorrent(torrent);
- }
-
- @OptionsItem(R.id.action_remove_default)
- protected void removeTorrentDefault() {
- if (getTasksExecutor() != null)
- getTasksExecutor().removeTorrent(torrent, false);
- }
-
- @OptionsItem(R.id.action_remove_withdata)
- protected void removeTorrentWithData() {
- if (getTasksExecutor() != null)
- getTasksExecutor().removeTorrent(torrent, true);
- }
-
- @OptionsItem(R.id.action_setlabel)
- protected void setLabel() {
- if (currentLabels != null) {
- SetLabelDialog.show(getActivity(), this, currentLabels);
- }
- }
-
- @OptionsItem(R.id.action_toggle_sequential)
- protected void toggleSequentialDownload(MenuItem menuItem) {
- if (getTasksExecutor() != null)
- getTasksExecutor().toggleSequentialDownload(torrent, !menuItem.isChecked());
- }
-
- @OptionsItem(R.id.action_toggle_firstlastpiece)
- protected void toggleFirstLastPieceDownload(MenuItem menuItem) {
- if (getTasksExecutor() != null)
- getTasksExecutor().toggleFirstLastPieceDownload(torrent, !menuItem.isChecked());
- }
-
- @OptionsItem(R.id.action_forcerecheck)
- protected void setForceRecheck() {
- if (getTasksExecutor() != null)
- getTasksExecutor().forceRecheckTorrent(torrent);
- }
-
- @OptionsItem(R.id.action_updatetrackers)
- protected void updateTrackers() {
- if (torrentDetails == null) {
- SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_stillloadingdetails));
- return;
- }
- SetTrackersDialog.show(getActivity(), this, torrentDetails.getTrackersText());
- }
-
- @OptionsItem(R.id.action_changelocation)
- protected void changeStorageLocation() {
- SetStorageLocationDialog.show(getActivity(), this, torrent.getLocationDir());
- }
-
- @Override
- public void onLabelPicked(String newLabel) {
- if (torrent == null) {
- return;
- }
- if (getTasksExecutor() != null)
- getTasksExecutor().updateLabel(torrent, newLabel);
- }
-
- @Override
- public void onTrackersUpdated(List updatedTrackers) {
- if (torrent == null) {
- return;
- }
- if (getTasksExecutor() != null)
- getTasksExecutor().updateTrackers(torrent, updatedTrackers);
- }
-
- @Override
- public void onStorageLocationUpdated(String newLocation) {
- if (torrent == null) {
- return;
- }
- if (getTasksExecutor() != null)
- getTasksExecutor().updateLocation(torrent, newLocation);
- }
-
- @Click
- protected void emptyTextClicked() {
- // Refresh the activity (that contains this fragment) when the empty view gear is clicked
- if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
- ((RefreshableActivity) getActivity()).refreshScreen();
- }
- }
-
- @Click
- protected void errorTextClicked() {
- // Refresh the activity (that contains this fragment) when the error view gear is clicked
- if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
- ((RefreshableActivity) getActivity()).refreshScreen();
- }
- }
-
- private MultiChoiceModeListener onDetailsSelected = new MultiChoiceModeListener() {
-
- SelectionManagerMode selectionManagerMode;
-
- @Override
- public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
- // Show contextual action bar to start/stop/remove/etc. torrents in batch mode
- detailsMenu.setEnabled(false);
- contextualMenu.setVisibility(View.VISIBLE);
- contextualMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- return onActionItemClicked(mode, menuItem);
- }
- });
- contextualMenu.getMenu().clear();
- getActivity().getMenuInflater().inflate(R.menu.fragment_details_cab_main, contextualMenu.getMenu());
- Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
- mode.getMenuInflater().inflate(R.menu.fragment_details_cab_secondary, menu);
- selectionManagerMode = new SelectionManagerMode(themedContext, detailsList, R.plurals.navigation_filesselected);
- selectionManagerMode.setOnlyCheckClass(TorrentFile.class);
- selectionManagerMode.onCreateActionMode(mode, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- selectionManagerMode.onPrepareActionMode(mode, menu);
- // Pause autorefresh
- if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
- ((TorrentsActivity) getActivity()).stopRefresh = true;
- ((TorrentsActivity) getActivity()).stopAutoRefresh();
- }
- boolean filePaths = currentServerSettings != null && Daemon.supportsFilePaths(currentServerSettings.getType());
- contextualMenu.getMenu().findItem(R.id.action_download).setVisible(filePaths);
- boolean filePriorities = currentServerSettings != null && Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
- contextualMenu.getMenu().findItem(R.id.action_priority_off).setVisible(filePriorities);
- contextualMenu.getMenu().findItem(R.id.action_priority_low).setVisible(filePriorities);
- contextualMenu.getMenu().findItem(R.id.action_priority_normal).setVisible(filePriorities);
- contextualMenu.getMenu().findItem(R.id.action_priority_high).setVisible(filePriorities);
- return true;
- }
-
- @SuppressLint("SdCardPath")
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-
- // Get checked torrents
- List checked = new ArrayList<>();
- for (int i = 0; i < detailsList.getCheckedItemPositions().size(); i++) {
- if (detailsList.getCheckedItemPositions().valueAt(i) && i < detailsList.getAdapter().getCount() &&
- detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)) instanceof TorrentFile) {
- checked.add((TorrentFile) detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)));
- }
- }
-
- int itemId = item.getItemId();
- if (itemId == R.id.action_download) {
-
- if (checked.size() < 1 || currentServerSettings == null) {
- return true;
- }
- String urlBase = currentServerSettings.getFtpUrl();
- if (urlBase == null || urlBase.equals("")) {
- urlBase = "ftp://" + currentServerSettings.getAddress() + "/";
- }
-
- // Try using AndFTP intents
- Intent andftpStart = new Intent(Intent.ACTION_PICK);
- andftpStart.setDataAndType(Uri.parse(urlBase), "vnd.android.cursor.dir/lysesoft.andftp.uri");
- andftpStart.putExtra("command_type", "download");
- andftpStart.putExtra("ftp_pasv", "true");
- if (Uri.parse(urlBase).getUserInfo() != null) {
- andftpStart.putExtra("ftp_username", Uri.parse(urlBase).getUserInfo());
- } else {
- andftpStart.putExtra("ftp_username", currentServerSettings.getUsername());
- }
- if (currentServerSettings.getFtpPassword() != null && !currentServerSettings.getFtpPassword().equals("")) {
- andftpStart.putExtra("ftp_password", currentServerSettings.getFtpPassword());
- } else {
- andftpStart.putExtra("ftp_password", currentServerSettings.getPassword());
- }
- // Note: AndFTP doesn't understand the directory that Environment.getExternalStoragePublicDirectory()
- // uses :(
- andftpStart.putExtra("local_folder", "/sdcard/Download");
- for (int f = 0; f < checked.size(); f++) {
- String file = checked.get(f).getRelativePath();
- if (file != null) {
- // If the file is directly in the root, AndFTP fails if we supply the proper path (like
- // /file.pdf)
- // Work around this bug by removing the leading / if no further directories are used in the path
- if (file.startsWith("/") && file.indexOf("/", 1) < 0) {
- file = file.substring(1);
- }
- andftpStart.putExtra("remote_file" + (f + 1), file);
- }
- }
- if (andftpStart.resolveActivity(getActivity().getPackageManager()) != null) {
- startActivity(andftpStart);
- mode.finish();
- return true;
- }
-
- // Try using a VIEW intent given an ftp:// scheme URI
- String url = urlBase + checked.get(0).getRelativePath();
- Intent simpleStart = new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (simpleStart.resolveActivity(getActivity().getPackageManager()) != null) {
- startActivity(simpleStart);
- mode.finish();
- return true;
- }
-
- // No app is available that can handle FTP downloads
- SnackbarManager.show(Snackbar.with(getActivity()).text(getString(R.string.error_noftpapp, url)).type(SnackbarType.MULTI_LINE)
- .colorResource(R.color.red));
- mode.finish();
- return true;
-
- } else if (itemId == R.id.action_copytoclipboard) {
-
- StringBuilder names = new StringBuilder();
- for (int f = 0; f < checked.size(); f++) {
- if (f != 0) {
- names.append("\n");
- }
- names.append(checked.get(f).getName());
- }
- ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
- clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
- mode.finish();
- return true;
-
- } else {
- Priority priority = Priority.Off;
- if (itemId == R.id.action_priority_low) {
- priority = Priority.Low;
- }
- if (itemId == R.id.action_priority_normal) {
- priority = Priority.Normal;
- }
- if (itemId == R.id.action_priority_high) {
- priority = Priority.High;
- }
- if (getTasksExecutor() != null)
- getTasksExecutor().updatePriority(torrent, checked, priority);
- mode.finish();
- return true;
- }
- }
-
- @Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
- selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- // Resume autorefresh
- if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
- ((TorrentsActivity) getActivity()).stopRefresh = false;
- ((TorrentsActivity) getActivity()).startAutoRefresh();
- }
- selectionManagerMode.onDestroyActionMode(mode);
- contextualMenu.setVisibility(View.GONE);
- detailsMenu.setEnabled(true);
- }
-
- };
-
- /**
- * Returns the object responsible for executing torrent tasks against a connected server
- * @return The executor for tasks on some torrent
- */
- private TorrentTasksExecutor getTasksExecutor() {
- // NOTE: Assumes the activity implements all the required torrent tasks
- return (TorrentTasksExecutor) getActivity();
- }
+ // Local data
+ @InstanceState
+ protected Torrent torrent = null;
+ @InstanceState
+ protected String torrentId = null;
+ @InstanceState
+ protected TorrentDetails torrentDetails = null;
+ @InstanceState
+ protected ArrayList torrentFiles = null;
+ @InstanceState
+ protected ArrayList currentLabels = null;
+ @InstanceState
+ protected boolean isLoadingTorrent = false;
+ @InstanceState
+ protected boolean hasCriticalError = false;
+ private ServerSetting currentServerSettings = null;
+
+ // Views
+ @ViewById
+ protected View detailsContainer;
+ @ViewById(R.id.details_menu)
+ protected ActionMenuView detailsMenu;
+ @ViewById(R.id.contextual_menu)
+ protected ActionMenuView contextualMenu;
+ @ViewById
+ protected SwipeRefreshLayout swipeRefreshLayout;
+ @ViewById
+ protected ListView detailsList;
+ @ViewById
+ protected TextView emptyText, errorText;
+ @ViewById
+ protected ProgressBar loadingProgress;
+
+ @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()) {
+ detailsContainer.setBackgroundResource(R.drawable.details_list_background);
+ }
+
+ createMenuOptions();
+
+ // Set up details adapter (itself containing the actual lists to show), which allows multi-select and fast
+ // scrolling
+ detailsList.setAdapter(new DetailsAdapter(getActivity()));
+ detailsList.setMultiChoiceModeListener(onDetailsSelected);
+ detailsList.setFastScrollEnabled(true);
+ if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
+ swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ ((RefreshableActivity) getActivity()).refreshScreen();
+ swipeRefreshLayout.setRefreshing(false); // Use our custom indicator
+ }
+ });
+ }
+
+ // Restore the fragment state (on orientation changes et al.)
+ if (torrent != null) {
+ updateTorrent(torrent);
+ }
+ if (torrent != null && torrentDetails != null) {
+ updateTorrentDetails(torrent, torrentDetails);
+ }
+ if (torrent != null && torrentFiles != null) {
+ updateTorrentFiles(torrent, torrentFiles);
+ }
+
+ }
+
+ public void setCurrentServerSettings(ServerSetting serverSettings) {
+ currentServerSettings = serverSettings;
+ }
+
+ /**
+ * Updates the details adapter header to show the new torrent data.
+ *
+ * @param newTorrent The new, non-null torrent object
+ */
+ public void updateTorrent(Torrent newTorrent) {
+ this.torrent = newTorrent;
+ this.torrentId = newTorrent.getUniqueID();
+ this.hasCriticalError = false;
+ ((DetailsAdapter) detailsList.getAdapter()).updateTorrent(newTorrent);
+ // Make the list (with details header) visible
+ detailsList.setVisibility(View.VISIBLE);
+ emptyText.setVisibility(View.GONE);
+ errorText.setVisibility(View.GONE);
+ loadingProgress.setVisibility(View.GONE);
+ // Also update the available actions in the action bar
+ updateMenuOptions();
+ // Refresh the detailed statistics (errors) and list of files
+ torrentDetails = null;
+ torrentFiles = null;
+ if (getTasksExecutor() != null) {
+ getTasksExecutor().refreshTorrentDetails(torrent);
+ getTasksExecutor().refreshTorrentFiles(torrent);
+ }
+ }
+
+ /**
+ * Updates the details adapter to show the list of trackers and tracker errors.
+ *
+ * @param checkTorrent The torrent for which the details were retrieved
+ * @param newTorrentDetails The new fine details object of some torrent
+ */
+ public void updateTorrentDetails(Torrent checkTorrent, TorrentDetails newTorrentDetails) {
+ // Check if these are actually the details of the torrent we are now showing
+ if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) {
+ return;
+ }
+ this.torrentDetails = newTorrentDetails;
+ ((DetailsAdapter) detailsList.getAdapter())
+ .updateTrackers(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getTrackers()));
+ ((DetailsAdapter) detailsList.getAdapter())
+ .updateErrors(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getErrors()));
+ ((DetailsAdapter) detailsList.getAdapter())
+ .updatePieces(newTorrentDetails.getPieces());
+ }
+
+ /**
+ * Updates the list adapter to show a new list of torrent files, replacing the old files list.
+ *
+ * @param checkTorrent The torrent for which the details were retrieved
+ * @param newTorrentFiles The new, updated list of torrent file objects
+ */
+ public void updateTorrentFiles(Torrent checkTorrent, ArrayList newTorrentFiles) {
+ // Check if these are actually the details of the torrent we are now showing
+ if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) {
+ return;
+ }
+ Collections.sort(newTorrentFiles);
+ this.torrentFiles = newTorrentFiles;
+ ((DetailsAdapter) detailsList.getAdapter()).updateTorrentFiles(newTorrentFiles);
+ }
+
+ /**
+ * Can be called if some outside activity returned new torrents, so we can perhaps piggyback on this by update our data as well.
+ *
+ * @param torrents The last of retrieved torrents
+ */
+ public void perhapsUpdateTorrent(List torrents) {
+ // Only try to update if we actually were showing a torrent
+ if (this.torrentId == null || torrents == null) {
+ return;
+ }
+ for (Torrent newTorrent : torrents) {
+ if (newTorrent.getUniqueID().equals(torrentId)) {
+ // Found, so we can update our data as well
+ updateTorrent(newTorrent);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Updates the locally maintained list of labels that are active on the server. Used in the label picking dialog and should be updated every time
+ * after the list of torrents was retrieved to keep it updated.
+ *
+ * @param currentLabels The list of known server labels
+ */
+ public void updateLabels(ArrayList currentLabels) {
+ this.currentLabels = currentLabels == null ? null : new ArrayList<>(currentLabels);
+ }
+
+ /**
+ * Clear the screen by fully clearing the internal merge list (with header and other lists)
+ */
+ public void clear() {
+ detailsList.setAdapter(new DetailsAdapter(getActivity()));
+ detailsList.setVisibility(View.GONE);
+ emptyText.setVisibility(!isLoadingTorrent && !hasCriticalError ? View.VISIBLE : View.GONE);
+ errorText.setVisibility(!isLoadingTorrent && hasCriticalError ? View.VISIBLE : View.GONE);
+ loadingProgress.setVisibility(isLoadingTorrent ? View.VISIBLE : View.GONE);
+ torrent = null;
+ torrentDetails = null;
+ torrentFiles = null;
+ }
+
+ /**
+ * Updates the shown screen depending on whether the torrent is loading
+ *
+ * @param isLoading True if the torrent is (re)loading, false otherwise
+ * @param connectionErrorMessage The error message text to show to the user, or null if there was no error
+ */
+ public void updateIsLoading(boolean isLoading, String connectionErrorMessage) {
+ this.isLoadingTorrent = isLoading;
+ this.hasCriticalError = connectionErrorMessage != null;
+ errorText.setText(connectionErrorMessage);
+ if (isLoading || hasCriticalError) {
+ clear();
+ }
+ }
+
+ @ItemClick(resName = "details_list")
+ protected void detailsListClicked(int position) {
+ detailsList.setItemChecked(position, false);
+ }
+
+ public void createMenuOptions() {
+ getActivity().getMenuInflater().inflate(R.menu.fragment_details, detailsMenu.getMenu());
+ detailsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ switch (menuItem.getItemId()) {
+ case R.id.action_pause:
+ pauseTorrent();
+ return true;
+ case R.id.action_updatetrackers:
+ updateTrackers();
+ return true;
+ case R.id.action_start_forced:
+ startTorrentForced();
+ return true;
+ case R.id.action_remove_withdata:
+ removeTorrentWithData();
+ return true;
+ case R.id.action_stop:
+ stopTorrent();
+ return true;
+ case R.id.action_toggle_sequential:
+ toggleSequentialDownload(menuItem);
+ return true;
+ case R.id.action_toggle_firstlastpiece:
+ toggleFirstLastPieceDownload(menuItem);
+ return true;
+ case R.id.action_forcerecheck:
+ setForceRecheck();
+ return true;
+ case R.id.action_changelocation:
+ changeStorageLocation();
+ return true;
+ case R.id.action_start_default:
+ startTorrentDefault();
+ return true;
+ case R.id.action_remove_default:
+ removeTorrentDefault();
+ return true;
+ case R.id.action_start_direct:
+ startTorrentDirect();
+ return true;
+ case R.id.action_setlabel:
+ setLabel();
+ return true;
+ case R.id.action_resume:
+ resumeTorrent();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void updateMenuOptions() {
+
+ if (torrent == null) {
+ detailsMenu.getMenu().findItem(R.id.action_resume).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_pause).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_start).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_start_direct).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_stop).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_remove).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_remove_withdata).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(false);
+ detailsMenu.getMenu().findItem(R.id.action_changelocation).setVisible(false);
+ return;
+ }
+ // Update action availability
+ boolean startStop = Daemon.supportsStoppingStarting(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_resume).setVisible(torrent.canResume());
+ detailsMenu.getMenu().findItem(R.id.action_pause).setVisible(torrent.canPause());
+ boolean forcedStart = Daemon.supportsForcedStarting(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_start).setVisible(startStop && forcedStart && torrent.canStart());
+ detailsMenu.getMenu().findItem(R.id.action_start_direct).setVisible(startStop && !forcedStart && torrent.canStart());
+ detailsMenu.getMenu().findItem(R.id.action_stop).setVisible(startStop && torrent.canStop());
+ detailsMenu.getMenu().findItem(R.id.action_remove).setVisible(true);
+ boolean removeWithData = Daemon.supportsRemoveWithData(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_remove_withdata).setVisible(removeWithData);
+ boolean setLabel = Daemon.supportsSetLabel(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(setLabel);
+ boolean forceRecheck = Daemon.supportsForceRecheck(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(forceRecheck);
+ boolean sequentialdl = Daemon.supportsSequentialDownload(torrent.getDaemon());
+ MenuItem seqMenuItem = detailsMenu.getMenu().findItem(R.id.action_toggle_sequential);
+ seqMenuItem.setVisible(sequentialdl);
+ seqMenuItem.setChecked(torrent.isSequentiallyDownloading());
+ boolean firstlastpiecedl = Daemon.supportsFirstLastPiece(torrent.getDaemon());
+ MenuItem flpMenuItem = detailsMenu.getMenu().findItem(R.id.action_toggle_firstlastpiece);
+ flpMenuItem.setVisible(firstlastpiecedl);
+ flpMenuItem.setChecked(torrent.isDownloadingFirstLastPieceFirst());
+ detailsMenu.getMenu().findItem(R.id.action_download_mode).setVisible(!torrent.isFinished() && (firstlastpiecedl || sequentialdl));
+ boolean setTrackers = Daemon.supportsSetTrackers(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(setTrackers);
+ boolean setLocation = Daemon.supportsSetDownloadLocation(torrent.getDaemon());
+ detailsMenu.getMenu().findItem(R.id.action_changelocation).setVisible(setLocation);
+
+ }
+
+ @OptionsItem(R.id.action_resume)
+ protected void resumeTorrent() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().resumeTorrent(torrent);
+ }
+
+ @OptionsItem(R.id.action_pause)
+ protected void pauseTorrent() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().pauseTorrent(torrent);
+ }
+
+ @OptionsItem(R.id.action_start_direct)
+ protected void startTorrentDirect() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().startTorrent(torrent, false);
+ }
+
+ @OptionsItem(R.id.action_start_default)
+ protected void startTorrentDefault() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().startTorrent(torrent, false);
+ }
+
+ @OptionsItem(R.id.action_start_forced)
+ protected void startTorrentForced() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().startTorrent(torrent, true);
+ }
+
+ @OptionsItem(R.id.action_stop)
+ protected void stopTorrent() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().stopTorrent(torrent);
+ }
+
+ @OptionsItem(R.id.action_remove_default)
+ protected void removeTorrentDefault() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().removeTorrent(torrent, false);
+ }
+
+ @OptionsItem(R.id.action_remove_withdata)
+ protected void removeTorrentWithData() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().removeTorrent(torrent, true);
+ }
+
+ @OptionsItem(R.id.action_setlabel)
+ protected void setLabel() {
+ if (currentLabels != null) {
+ SetLabelDialog.show(getActivity(), this, currentLabels);
+ }
+ }
+
+ @OptionsItem(R.id.action_toggle_sequential)
+ protected void toggleSequentialDownload(MenuItem menuItem) {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().toggleSequentialDownload(torrent, !menuItem.isChecked());
+ }
+
+ @OptionsItem(R.id.action_toggle_firstlastpiece)
+ protected void toggleFirstLastPieceDownload(MenuItem menuItem) {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().toggleFirstLastPieceDownload(torrent, !menuItem.isChecked());
+ }
+
+ @OptionsItem(R.id.action_forcerecheck)
+ protected void setForceRecheck() {
+ if (getTasksExecutor() != null)
+ getTasksExecutor().forceRecheckTorrent(torrent);
+ }
+
+ @OptionsItem(R.id.action_updatetrackers)
+ protected void updateTrackers() {
+ if (torrentDetails == null) {
+ SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_stillloadingdetails));
+ return;
+ }
+ SetTrackersDialog.show(getActivity(), this, torrentDetails.getTrackersText());
+ }
+
+ @OptionsItem(R.id.action_changelocation)
+ protected void changeStorageLocation() {
+ SetStorageLocationDialog.show(getActivity(), this, torrent.getLocationDir());
+ }
+
+ @Override
+ public void onLabelPicked(String newLabel) {
+ if (torrent == null) {
+ return;
+ }
+ if (getTasksExecutor() != null)
+ getTasksExecutor().updateLabel(torrent, newLabel);
+ }
+
+ @Override
+ public void onTrackersUpdated(List updatedTrackers) {
+ if (torrent == null) {
+ return;
+ }
+ if (getTasksExecutor() != null)
+ getTasksExecutor().updateTrackers(torrent, updatedTrackers);
+ }
+
+ @Override
+ public void onStorageLocationUpdated(String newLocation) {
+ if (torrent == null) {
+ return;
+ }
+ if (getTasksExecutor() != null)
+ getTasksExecutor().updateLocation(torrent, newLocation);
+ }
+
+ @Click
+ protected void emptyTextClicked() {
+ // Refresh the activity (that contains this fragment) when the empty view gear is clicked
+ if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
+ ((RefreshableActivity) getActivity()).refreshScreen();
+ }
+ }
+
+ @Click
+ protected void errorTextClicked() {
+ // Refresh the activity (that contains this fragment) when the error view gear is clicked
+ if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
+ ((RefreshableActivity) getActivity()).refreshScreen();
+ }
+ }
+
+ private MultiChoiceModeListener onDetailsSelected = new MultiChoiceModeListener() {
+
+ SelectionManagerMode selectionManagerMode;
+
+ @Override
+ public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
+ // Show contextual action bar to start/stop/remove/etc. torrents in batch mode
+ detailsMenu.setEnabled(false);
+ contextualMenu.setVisibility(View.VISIBLE);
+ contextualMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ return onActionItemClicked(mode, menuItem);
+ }
+ });
+ contextualMenu.getMenu().clear();
+ getActivity().getMenuInflater().inflate(R.menu.fragment_details_cab_main, contextualMenu.getMenu());
+ Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
+ mode.getMenuInflater().inflate(R.menu.fragment_details_cab_secondary, menu);
+ selectionManagerMode = new SelectionManagerMode(themedContext, detailsList, R.plurals.navigation_filesselected);
+ selectionManagerMode.setOnlyCheckClass(TorrentFile.class);
+ selectionManagerMode.onCreateActionMode(mode, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ selectionManagerMode.onPrepareActionMode(mode, menu);
+ // Pause autorefresh
+ if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
+ ((TorrentsActivity) getActivity()).stopRefresh = true;
+ ((TorrentsActivity) getActivity()).stopAutoRefresh();
+ }
+ boolean filePaths = currentServerSettings != null && Daemon.supportsFilePaths(currentServerSettings.getType());
+ contextualMenu.getMenu().findItem(R.id.action_download).setVisible(filePaths);
+ boolean filePriorities = currentServerSettings != null && Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
+ contextualMenu.getMenu().findItem(R.id.action_priority_off).setVisible(filePriorities);
+ contextualMenu.getMenu().findItem(R.id.action_priority_low).setVisible(filePriorities);
+ contextualMenu.getMenu().findItem(R.id.action_priority_normal).setVisible(filePriorities);
+ contextualMenu.getMenu().findItem(R.id.action_priority_high).setVisible(filePriorities);
+ return true;
+ }
+
+ @SuppressLint("SdCardPath")
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+
+ // Get checked torrents
+ List checked = new ArrayList<>();
+ for (int i = 0; i < detailsList.getCheckedItemPositions().size(); i++) {
+ if (detailsList.getCheckedItemPositions().valueAt(i) && i < detailsList.getAdapter().getCount() &&
+ detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)) instanceof TorrentFile) {
+ checked.add((TorrentFile) detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)));
+ }
+ }
+
+ int itemId = item.getItemId();
+ if (itemId == R.id.action_download) {
+
+ if (checked.size() < 1 || currentServerSettings == null) {
+ return true;
+ }
+ String urlBase = currentServerSettings.getFtpUrl();
+ if (urlBase == null || urlBase.equals("")) {
+ urlBase = "ftp://" + currentServerSettings.getAddress() + "/";
+ }
+
+ // Try using AndFTP intents
+ Intent andftpStart = new Intent(Intent.ACTION_PICK);
+ andftpStart.setDataAndType(Uri.parse(urlBase), "vnd.android.cursor.dir/lysesoft.andftp.uri");
+ andftpStart.putExtra("command_type", "download");
+ andftpStart.putExtra("ftp_pasv", "true");
+ if (Uri.parse(urlBase).getUserInfo() != null) {
+ andftpStart.putExtra("ftp_username", Uri.parse(urlBase).getUserInfo());
+ } else {
+ andftpStart.putExtra("ftp_username", currentServerSettings.getUsername());
+ }
+ if (currentServerSettings.getFtpPassword() != null && !currentServerSettings.getFtpPassword().equals("")) {
+ andftpStart.putExtra("ftp_password", currentServerSettings.getFtpPassword());
+ } else {
+ andftpStart.putExtra("ftp_password", currentServerSettings.getPassword());
+ }
+ // Note: AndFTP doesn't understand the directory that Environment.getExternalStoragePublicDirectory()
+ // uses :(
+ andftpStart.putExtra("local_folder", "/sdcard/Download");
+ for (int f = 0; f < checked.size(); f++) {
+ String file = checked.get(f).getRelativePath();
+ if (file != null) {
+ // If the file is directly in the root, AndFTP fails if we supply the proper path (like
+ // /file.pdf)
+ // Work around this bug by removing the leading / if no further directories are used in the path
+ if (file.startsWith("/") && file.indexOf("/", 1) < 0) {
+ file = file.substring(1);
+ }
+ andftpStart.putExtra("remote_file" + (f + 1), file);
+ }
+ }
+ if (andftpStart.resolveActivity(getActivity().getPackageManager()) != null) {
+ startActivity(andftpStart);
+ mode.finish();
+ return true;
+ }
+
+ // Try using a VIEW intent given an ftp:// scheme URI
+ String url = urlBase + checked.get(0).getRelativePath();
+ Intent simpleStart = new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (simpleStart.resolveActivity(getActivity().getPackageManager()) != null) {
+ startActivity(simpleStart);
+ mode.finish();
+ return true;
+ }
+
+ // No app is available that can handle FTP downloads
+ SnackbarManager.show(Snackbar.with(getActivity()).text(getString(R.string.error_noftpapp, url)).type(SnackbarType.MULTI_LINE)
+ .colorResource(R.color.red));
+ mode.finish();
+ return true;
+
+ } else if (itemId == R.id.action_copytoclipboard) {
+
+ StringBuilder names = new StringBuilder();
+ for (int f = 0; f < checked.size(); f++) {
+ if (f != 0) {
+ names.append("\n");
+ }
+ names.append(checked.get(f).getName());
+ }
+ ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
+ mode.finish();
+ return true;
+
+ } else {
+ Priority priority = Priority.Off;
+ if (itemId == R.id.action_priority_low) {
+ priority = Priority.Low;
+ }
+ if (itemId == R.id.action_priority_normal) {
+ priority = Priority.Normal;
+ }
+ if (itemId == R.id.action_priority_high) {
+ priority = Priority.High;
+ }
+ if (getTasksExecutor() != null)
+ getTasksExecutor().updatePriority(torrent, checked, priority);
+ mode.finish();
+ return true;
+ }
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ // Resume autorefresh
+ if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
+ ((TorrentsActivity) getActivity()).stopRefresh = false;
+ ((TorrentsActivity) getActivity()).startAutoRefresh();
+ }
+ selectionManagerMode.onDestroyActionMode(mode);
+ contextualMenu.setVisibility(View.GONE);
+ detailsMenu.setEnabled(true);
+ }
+
+ };
+
+ /**
+ * Returns the object responsible for executing torrent tasks against a connected server
+ *
+ * @return The executor for tasks on some torrent
+ */
+ private TorrentTasksExecutor getTasksExecutor() {
+ // NOTE: Assumes the activity implements all the required torrent tasks
+ return (TorrentTasksExecutor) getActivity();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java b/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java
index fa159c1a..425018e9 100644
--- a/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java
+++ b/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java
@@ -21,6 +21,7 @@ import java.util.List;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;
+
import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting;
@@ -30,36 +31,37 @@ import android.content.DialogInterface.OnClickListener;
public class ServerPickerDialog extends DialogFragment {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- String[] serverNames = getArguments().getStringArray("serverNames");
- return new AlertDialog.Builder(getActivity()).setTitle(R.string.navigation_pickserver)
- .setItems(serverNames, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (getActivity() != null && getActivity() instanceof TorrentsActivity)
- ((TorrentsActivity) getActivity()).switchServerAndAddFromIntent(which);
- }
- }).create();
- }
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ String[] serverNames = getArguments().getStringArray("serverNames");
+ return new AlertDialog.Builder(getActivity()).setTitle(R.string.navigation_pickserver)
+ .setItems(serverNames, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (getActivity() != null && getActivity() instanceof TorrentsActivity)
+ ((TorrentsActivity) getActivity()).switchServerAndAddFromIntent(which);
+ }
+ }).create();
+ }
- /**
- * Opens a dialog that allows the selection of a configured server (manual or seedbox). The calling activity will
- * receive a callback on its switchServerAndAddFromIntent(int) method.
- * @param activity The torrents activity from which the picker is started (and which received the callback)
- * @param serverSettings The list of all available servers, of which their names will be offered to the user to pick
- * from (and its position in the list is returned to the activity)
- */
- public static void startServerPicker(final TorrentsActivity activity, List serverSettings) {
- final String[] serverNames = new String[serverSettings.size()];
- for (int i = 0; i < serverSettings.size(); i++) {
- serverNames[i] = serverSettings.get(i).getName();
- }
- ServerPickerDialog dialog = new ServerPickerDialog();
- Bundle arguments = new Bundle();
- arguments.putStringArray("serverNames", serverNames);
- dialog.setArguments(arguments);
- dialog.show(activity.getFragmentManager(), "serverpicker");
- }
+ /**
+ * Opens a dialog that allows the selection of a configured server (manual or seedbox). The calling activity will
+ * receive a callback on its switchServerAndAddFromIntent(int) method.
+ *
+ * @param activity The torrents activity from which the picker is started (and which received the callback)
+ * @param serverSettings The list of all available servers, of which their names will be offered to the user to pick
+ * from (and its position in the list is returned to the activity)
+ */
+ public static void startServerPicker(final TorrentsActivity activity, List serverSettings) {
+ final String[] serverNames = new String[serverSettings.size()];
+ for (int i = 0; i < serverSettings.size(); i++) {
+ serverNames[i] = serverSettings.get(i).getName();
+ }
+ ServerPickerDialog dialog = new ServerPickerDialog();
+ Bundle arguments = new Bundle();
+ arguments.putStringArray("serverNames", serverNames);
+ dialog.setArguments(arguments);
+ dialog.show(activity.getFragmentManager(), "serverpicker");
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java b/app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java
index 7abeba18..88195e47 100644
--- a/app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java
+++ b/app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,31 +29,33 @@ import org.transdroid.daemon.IDaemonAdapter;
@EViewGroup(R.layout.actionbar_serverselection)
public class ServerSelectionView extends RelativeLayout {
- @ViewById
- protected TextView filterText, serverText;
-
- public ServerSelectionView(Context context) {
- super(context);
- }
-
- public ServerSelectionView(TorrentsActivity activity) {
- super(activity.torrentsToolbar.getContext());
- }
-
- /**
- * Updates the name of the current connected server.
- * @param currentServer The server currently connected to
- */
- public void updateCurrentServer(IDaemonAdapter currentServer) {
- serverText.setText(currentServer.getSettings().getName());
- }
-
- /**
- * Updates the name of the selected filter.
- * @param currentFilter The filter that is currently selected
- */
- public void updateCurrentFilter(NavigationFilter currentFilter) {
- filterText.setText(currentFilter.getName());
- }
+ @ViewById
+ protected TextView filterText, serverText;
+
+ public ServerSelectionView(Context context) {
+ super(context);
+ }
+
+ public ServerSelectionView(TorrentsActivity activity) {
+ super(activity.torrentsToolbar.getContext());
+ }
+
+ /**
+ * Updates the name of the current connected server.
+ *
+ * @param currentServer The server currently connected to
+ */
+ public void updateCurrentServer(IDaemonAdapter currentServer) {
+ serverText.setText(currentServer.getSettings().getName());
+ }
+
+ /**
+ * Updates the name of the selected filter.
+ *
+ * @param currentFilter The filter that is currently selected
+ */
+ public void updateCurrentFilter(NavigationFilter currentFilter) {
+ filterText.setText(currentFilter.getName());
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/ServerStatusView.java b/app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
index 22f38282..cfcbaa77 100644
--- a/app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
+++ b/app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -37,87 +37,88 @@ import java.util.List;
@EViewGroup(R.layout.actionbar_serverstatus)
public class ServerStatusView extends RelativeLayout implements OnRatesPickedListener {
- @ViewById
- protected TextView downcountText, upcountText, downcountSign, upcountSign, downspeedText, upspeedText;
- @ViewById
- protected View speedswrapperLayout;
- private TorrentsActivity activity;
-
- public ServerStatusView(Context context) {
- super(context);
- }
-
- public ServerStatusView(TorrentsActivity activity) {
- super(activity);
- this.activity = activity;
- }
-
- /**
- * Updates the statistics as shown in the action bar through this server status view.
- * @param torrents The most recently received list of torrents
- * @param dormantAsInactive Whether to treat dormant (0KB/s) torrent as inactive state torrents
- * @param supportsSetTransferRates Whether the connected torrent client supports setting of max transfer speeds
- */
- public void updateStatus(List torrents, boolean dormantAsInactive, boolean supportsSetTransferRates) {
-
- if (torrents == null) {
- downcountText.setText(null);
- upcountText.setText(null);
- downspeedText.setText(null);
- upspeedText.setText(null);
- downcountSign.setVisibility(View.INVISIBLE);
- upcountSign.setVisibility(View.INVISIBLE);
- speedswrapperLayout.setOnClickListener(null);
- return;
- }
-
- int downcount = 0, upcount = 0, downspeed = 0, upspeed = 0;
- for (Torrent torrent : torrents) {
-
- // Downloading torrents count towards downloads and uploads, seeding torrents towards uploads
- if (torrent.isDownloading(dormantAsInactive)) {
- downcount++;
- upcount++;
- } else if (torrent.isSeeding(dormantAsInactive)) {
- upcount++;
- }
- downspeed += torrent.getRateDownload();
- upspeed += torrent.getRateUpload();
-
- }
-
- downcountText.setText(Integer.toString(downcount));
- upcountText.setText(Integer.toString(upcount));
- downspeedText.setText(FileSizeConverter.getSize(downspeed) + "/s");
- upspeedText.setText(FileSizeConverter.getSize(upspeed) + "/s");
- downcountSign.setVisibility(View.VISIBLE);
- upcountSign.setVisibility(View.VISIBLE);
- if (supportsSetTransferRates)
- speedswrapperLayout.setOnClickListener(onStartDownPickerClicked);
- else
- speedswrapperLayout.setBackgroundDrawable(null);
-
- }
-
- private OnClickListener onStartDownPickerClicked = new OnClickListener() {
- public void onClick(View v) {
- SetTransferRatesDialog.show(getContext(), ServerStatusView.this);
- }
- };
-
- @Override
- public void onRatesPicked(int maxDownloadSpeed, int maxUploadSpeed) {
- activity.updateMaxSpeeds(maxDownloadSpeed, maxUploadSpeed);
- }
-
- @Override
- public void resetRates() {
- activity.updateMaxSpeeds(null, null);
- }
-
- @Override
- public void onInvalidNumber() {
- SnackbarManager.show(Snackbar.with(activity).text(R.string.error_notanumber).colorResource(R.color.red));
- }
+ @ViewById
+ protected TextView downcountText, upcountText, downcountSign, upcountSign, downspeedText, upspeedText;
+ @ViewById
+ protected View speedswrapperLayout;
+ private TorrentsActivity activity;
+
+ public ServerStatusView(Context context) {
+ super(context);
+ }
+
+ public ServerStatusView(TorrentsActivity activity) {
+ super(activity);
+ this.activity = activity;
+ }
+
+ /**
+ * Updates the statistics as shown in the action bar through this server status view.
+ *
+ * @param torrents The most recently received list of torrents
+ * @param dormantAsInactive Whether to treat dormant (0KB/s) torrent as inactive state torrents
+ * @param supportsSetTransferRates Whether the connected torrent client supports setting of max transfer speeds
+ */
+ public void updateStatus(List torrents, boolean dormantAsInactive, boolean supportsSetTransferRates) {
+
+ if (torrents == null) {
+ downcountText.setText(null);
+ upcountText.setText(null);
+ downspeedText.setText(null);
+ upspeedText.setText(null);
+ downcountSign.setVisibility(View.INVISIBLE);
+ upcountSign.setVisibility(View.INVISIBLE);
+ speedswrapperLayout.setOnClickListener(null);
+ return;
+ }
+
+ int downcount = 0, upcount = 0, downspeed = 0, upspeed = 0;
+ for (Torrent torrent : torrents) {
+
+ // Downloading torrents count towards downloads and uploads, seeding torrents towards uploads
+ if (torrent.isDownloading(dormantAsInactive)) {
+ downcount++;
+ upcount++;
+ } else if (torrent.isSeeding(dormantAsInactive)) {
+ upcount++;
+ }
+ downspeed += torrent.getRateDownload();
+ upspeed += torrent.getRateUpload();
+
+ }
+
+ downcountText.setText(Integer.toString(downcount));
+ upcountText.setText(Integer.toString(upcount));
+ downspeedText.setText(FileSizeConverter.getSize(downspeed) + "/s");
+ upspeedText.setText(FileSizeConverter.getSize(upspeed) + "/s");
+ downcountSign.setVisibility(View.VISIBLE);
+ upcountSign.setVisibility(View.VISIBLE);
+ if (supportsSetTransferRates)
+ speedswrapperLayout.setOnClickListener(onStartDownPickerClicked);
+ else
+ speedswrapperLayout.setBackgroundDrawable(null);
+
+ }
+
+ private OnClickListener onStartDownPickerClicked = new OnClickListener() {
+ public void onClick(View v) {
+ SetTransferRatesDialog.show(getContext(), ServerStatusView.this);
+ }
+ };
+
+ @Override
+ public void onRatesPicked(int maxDownloadSpeed, int maxUploadSpeed) {
+ activity.updateMaxSpeeds(maxDownloadSpeed, maxUploadSpeed);
+ }
+
+ @Override
+ public void resetRates() {
+ activity.updateMaxSpeeds(null, null);
+ }
+
+ @Override
+ public void onInvalidNumber() {
+ SnackbarManager.show(Snackbar.with(activity).text(R.string.error_notanumber).colorResource(R.color.red));
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
index 297dec6c..55244e52 100644
--- a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
+++ b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -27,34 +27,35 @@ import java.util.List;
/**
* Interface to be implemented by any activity that wants containing fragments to be able to load data and execute commands against a torrent server.
+ *
* @author Eric Kok
*/
public interface TorrentTasksExecutor {
- void resumeTorrent(Torrent torrent);
+ void resumeTorrent(Torrent torrent);
- void pauseTorrent(Torrent torrent);
+ void pauseTorrent(Torrent torrent);
- void startTorrent(Torrent torrent, boolean forced);
+ void startTorrent(Torrent torrent, boolean forced);
- void stopTorrent(Torrent torrent);
+ void stopTorrent(Torrent torrent);
- void removeTorrent(Torrent torrent, boolean withData);
+ void removeTorrent(Torrent torrent, boolean withData);
- void toggleSequentialDownload(Torrent torrent, boolean sequentialState);
+ void toggleSequentialDownload(Torrent torrent, boolean sequentialState);
- void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState);
+ void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState);
- void forceRecheckTorrent(Torrent torrent);
+ void forceRecheckTorrent(Torrent torrent);
- void updateLabel(Torrent torrent, String newLabel);
+ void updateLabel(Torrent torrent, String newLabel);
- void updateTrackers(Torrent torrent, List newTrackers);
+ void updateTrackers(Torrent torrent, List newTrackers);
- void updateLocation(Torrent torrent, String newLocation);
+ void updateLocation(Torrent torrent, String newLocation);
- void refreshTorrentDetails(Torrent torrent);
+ void refreshTorrentDetails(Torrent torrent);
- void refreshTorrentFiles(Torrent torrent);
+ void refreshTorrentFiles(Torrent torrent);
- void updatePriority(Torrent torrent, List files, Priority priority);
+ void updatePriority(Torrent torrent, List files, Priority priority);
}
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 0d67a33f..8f3d3de6 100644
--- a/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
+++ b/app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+
import androidx.annotation.NonNull;
import androidx.core.view.MenuItemCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -32,6 +33,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
+
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -142,1244 +144,1249 @@ import java.util.Map.Entry;
* Main activity that holds the fragment that shows the torrents list, presents a way to filter the list (via an action bar spinner or list side list)
* and potentially shows a torrent details fragment too, if there is room. Task execution such as loading of and adding torrents is performs in this
* activity, using background methods. Finally, the activity offers navigation elements such as access to settings and showing connection issues.
+ *
* @author Eric Kok
*/
@EActivity(R.layout.activity_torrents)
public class TorrentsActivity extends AppCompatActivity implements TorrentTasksExecutor, RefreshableActivity {
- private static final int RESULT_DETAILS = 0;
-
- // Fragment uses this to pause the refresh across restarts
- public boolean stopRefresh = false;
-
- // Navigation components
- @SystemService
- protected SearchManager searchManager;
- @Bean
- protected Log log;
- @Bean
- protected NavigationHelper navigationHelper;
- @Bean
- protected ConnectivityHelper connectivityHelper;
- @ViewById
- protected Toolbar selectionToolbar;
- @ViewById
- protected Toolbar torrentsToolbar;
- @ViewById
- protected Toolbar actionsToolbar;
- @ViewById(R.id.contextual_menu)
- protected ActionMenuView contextualMenu;
- @ViewById
- protected FloatingActionsMenu addmenuButton;
- @ViewById
- protected FloatingActionButton addmenuFileButton;
- @ViewById
- protected DrawerLayout drawerLayout;
- @ViewById(R.id.drawer_container)
- protected ViewGroup drawerContainer;
- @ViewById
- protected ListView drawerList;
- @ViewById
- protected ListView filtersList;
- @ViewById
- protected SearchView filterSearch;
- private ListView navigationList;
- private FilterListAdapter navigationListAdapter;
- private ServerSelectionView serverSelectionView;
- private ServerStatusView serverStatusView;
- private ActionBarDrawerToggle drawerToggle;
-
- // Settings
- @Bean
- protected ApplicationSettings applicationSettings;
- @Bean
- protected SystemSettings systemSettings;
- @InstanceState
- protected NavigationFilter currentFilter = null;
- @InstanceState
- protected String preselectNavigationFilter = null;
- @InstanceState
- protected boolean turtleModeEnabled = false;
- @InstanceState
- protected ArrayList lastNavigationLabels;
- // Contained torrent and details fragments
- @FragmentById(R.id.torrents_fragment)
- protected TorrentsFragment fragmentTorrents;
- @FragmentById(R.id.torrentdetails_fragment)
- protected DetailsFragment fragmentDetails;
- @InstanceState
- boolean firstStart = true;
- private MenuItem searchMenu = null;
- private IDaemonAdapter currentConnection = null;
-
- // Auto refresh task
- private AsyncTask autoRefreshTask;
-
- private String awaitingAddLocalFile;
- private String awaitingAddTitle;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- SettingsUtils.applyDayNightTheme(this);
-
- // Catch any uncaught exception to log it
- Thread.setDefaultUncaughtExceptionHandler(new LogUncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler()));
- super.onCreate(savedInstanceState);
- }
-
- @AfterViews
- protected void init() {
-
- // Use custom views as action bar content, showing filter selection and current torrent counts/speeds
- serverSelectionView = ServerSelectionView_.build(this);
- serverStatusView = ServerStatusView_.build(this);
- if (selectionToolbar != null) {
- selectionToolbar.addView(serverSelectionView);
- } else {
- torrentsToolbar.addView(serverSelectionView);
- }
- actionsToolbar.addView(serverStatusView);
- actionsToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- // Redirect to the classic activity implementation so we can use @OptionsItem methods
- return onOptionsItemSelected(menuItem);
- }
- });
- setSupportActionBar(torrentsToolbar); // For direct menu item inflation by the contained fragments
- getSupportActionBar().setDisplayShowTitleEnabled(false);
-
- // Construct the filters list, i.e. the list of servers, status types and labels
- navigationListAdapter = FilterListAdapter_.getInstance_(this);
- navigationListAdapter.updateServers(applicationSettings.getAllServerSettings());
- navigationListAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this));
- // Add an empty labels list (which will be updated later, but the adapter needs to be created now)
- navigationListAdapter.updateLabels(new ArrayList());
-
- // Apply the filters list to the navigation drawer (on phones) or the dedicated side bar (i.e. on tablets)
- if (filtersList != null) {
- navigationList = filtersList;
- } else {
- navigationList = drawerList;
- drawerToggle =
- new ActionBarDrawerToggle(this, drawerLayout, torrentsToolbar, R.string.navigation_opendrawer, R.string.navigation_closedrawer);
- drawerToggle.setDrawerIndicatorEnabled(true);
- drawerLayout.setDrawerListener(drawerToggle);
- }
- navigationList.setAdapter(navigationListAdapter);
- navigationList.setOnItemClickListener(onFilterListItemClicked);
- // Now that all items (or at least their adapters) have been added, ensure a filter is selected
- // NOTE When this is a fresh start, we might override the filter later (based on the last user selection)
- if (currentFilter == null) {
- currentFilter = StatusType.getShowAllType(this);
- }
- filterSearch.setOnQueryTextListener(filterQueryTextChanged);
-
- // Load the default server or a server that was explicitly supplied in the starting intent
- ServerSetting defaultServer = applicationSettings.getDefaultServer();
- if (defaultServer == null) {
- // No server settings yet
- return;
- }
- Torrent openTorrent = null;
- if (getIntent().getAction() != null && getIntent().getAction().equals(ListWidgetProvider.INTENT_STARTSERVER) &&
- getIntent().getExtras() == null && getIntent().hasExtra(ListWidgetProvider.EXTRA_SERVER)) {
- // A server settings order ID was provided in this org.transdroid.START_SERVER action intent
- int serverId = getIntent().getExtras().getInt(ListWidgetProvider.EXTRA_SERVER);
- if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) {
- log.e(this, "Tried to start with " + ListWidgetProvider.EXTRA_SERVER + " intent but " + serverId +
- " is not an existing server order id");
- } else {
- defaultServer = applicationSettings.getServerSetting(serverId);
- if (getIntent().hasExtra(ListWidgetProvider.EXTRA_TORRENT)) {
- openTorrent = getIntent().getParcelableExtra(ListWidgetProvider.EXTRA_TORRENT);
- }
- }
- }
-
- // Connect to the last used server or a server that was explicitly supplied in the starting intent
- if (firstStart) {
- // Force first torrents refresh
- filterSelected(defaultServer, true);
- // Perhaps we can select the last used navigation filter, but only after a first refresh was completed
- preselectNavigationFilter = applicationSettings.getLastUsedNavigationFilter();
- // Handle any start up intents
- if (openTorrent != null) {
- openDetails(openTorrent);
- } else if (getIntent() != null) {
- handleStartIntent();
- }
- } else {
- // Resume after instead of fully loading the torrents list; create connection and set action bar title
- ServerSetting lastUsed = applicationSettings.getLastUsedServer();
- currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
- serverSelectionView.updateCurrentServer(currentConnection);
- serverSelectionView.updateCurrentFilter(currentFilter);
- }
- firstStart = false;
-
- // Start the jobs for the background services, if needed
- ServerCheckerJob.schedule(getApplicationContext());
- RssCheckerJob.schedule(getApplicationContext());
- AppUpdateJob.schedule(getApplicationContext());
-
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- // Sync the toggle state after onRestoreInstanceState has occurred
- if (drawerToggle != null) {
- drawerToggle.syncState();
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- // update navigation labels
- navigationListAdapter.updateLabels(lastNavigationLabels);
-
- // Refresh server settings
- navigationListAdapter.updateServers(applicationSettings.getAllServerSettings());
- ServerSetting lastUsed = applicationSettings.getLastUsedServer();
-
- if (lastUsed == null) {
- // Still no settings
- updateFragmentVisibility(false);
- return;
- }
-
- // If we had no connection before, establish it now; otherwise just reload the settings
- if (currentConnection == null) {
- filterSelected(lastUsed, true);
- } else {
- currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
- }
-
- // Start auto refresh
- startAutoRefresh();
-
- }
-
- @OnActivityResult(RESULT_DETAILS)
- protected void onDetailsScreenResult(Intent result) {
- // If the details activity returns whether the torrent was removed or updated, update the torrents list as well
- // (the details fragment is the source, so no need to update that)
- if (result != null && result.hasExtra("affected_torrent")) {
- Torrent affected = result.getParcelableExtra("affected_torrent");
- fragmentTorrents.quickUpdateTorrent(affected, result.getBooleanExtra("torrent_removed", false));
- }
- }
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- public void startAutoRefresh() {
- // Check if already running
- if (autoRefreshTask != null || stopRefresh || systemSettings.getRefreshIntervalMilliseconds() == 0) {
- return;
- }
-
- autoRefreshTask = new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- while (!isCancelled()) {
- try {
- Thread.sleep(systemSettings.getRefreshIntervalMilliseconds());
- } catch (InterruptedException e) {
- // Ignore
- }
- // Just in case it was cancelled during sleep
- if (isCancelled()) {
- return null;
- }
-
- refreshTorrents();
- if (Daemon.supportsStats(currentConnection.getType())) {
- getAdditionalStats();
- }
- }
- return null;
- }
-
- };
- autoRefreshTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- public void stopAutoRefresh() {
- if (autoRefreshTask != null) {
- autoRefreshTask.cancel(true);
- }
- autoRefreshTask = null;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- // Manually insert the actions into the main torrent and secondary actions toolbars
- torrentsToolbar.inflateMenu(R.menu.activity_torrents_main);
- if (actionsToolbar.getMenu().size() == 0) {
- actionsToolbar.inflateMenu(R.menu.activity_torrents_secondary);
- }
- if (navigationHelper.enableSearchUi()) {
- // Add an expandable SearchView to the action bar
- MenuItem item = menu.findItem(R.id.action_search);
- SearchView searchView = new SearchView(torrentsToolbar.getContext());
- searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
- searchView.setQueryRefinementEnabled(true);
- searchView.setOnSearchClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // Pause autorefresh
- stopRefresh = true;
- stopAutoRefresh();
- }
- });
- MenuItemCompat.setOnActionExpandListener(item, new MenuItemCompat.OnActionExpandListener() {
- @Override
- public boolean onMenuItemActionExpand(MenuItem item) {
- return true;
- }
-
- @Override
- public boolean onMenuItemActionCollapse(MenuItem item) {
- stopRefresh = false;
- startAutoRefresh();
- return true;
- }
- });
- MenuItemCompat.setActionView(item, searchView);
- searchMenu = item;
- }
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- // No connection yet; hide all menu options except settings
- if (currentConnection == null) {
- torrentsToolbar.setNavigationIcon(null);
- if (selectionToolbar != null)
- selectionToolbar.setVisibility(View.GONE);
- addmenuButton.setVisibility(View.GONE);
- actionsToolbar.setVisibility(View.GONE);
- if (filtersList != null)
- filtersList.setVisibility(View.GONE);
- 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_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);
- actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(false);
- actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(false);
- actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(false);
- if (fragmentTorrents != null) {
- fragmentTorrents.updateConnectionStatus(false, null);
- }
- return true;
- }
-
- // There is a connection (read: settings to some server known)
- if (drawerToggle != null)
- torrentsToolbar.setNavigationIcon(R.drawable.ic_action_drawer);
- if (selectionToolbar != null)
- selectionToolbar.setVisibility(View.VISIBLE);
- addmenuButton.setVisibility(View.VISIBLE);
- actionsToolbar.setVisibility(View.VISIBLE);
- if (filtersList != null)
- filtersList.setVisibility(View.VISIBLE);
- filterSearch.setVisibility(View.VISIBLE);
- boolean addByFile = Daemon.supportsAddByFile(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_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(false);
- // Secondary toolbar menu
- boolean hasAltMode = Daemon.supportsSetAlternativeMode(currentConnection.getType());
- actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(hasAltMode && !turtleModeEnabled);
- actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(hasAltMode && turtleModeEnabled);
- actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(true);
- actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(true);
- actionsToolbar.getMenu().findItem(R.id.action_sort_added).setVisible(Daemon.supportsDateAdded(currentConnection.getType()));
- if (fragmentTorrents != null) {
- fragmentTorrents.updateConnectionStatus(true, currentConnection.getType());
- }
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle only if this is the drawer toggle; otherwise the AndroidAnnotations will be used
- return drawerToggle != null && drawerToggle.onOptionsItemSelected(item);
- }
-
- /**
- * Handles item selections on the dedicated list of filter items
- */
- private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- navigationList.setItemChecked(position, true);
- Object item = navigationList.getAdapter().getItem(position);
- if (item instanceof SimpleListItem) {
- filterSelected((SimpleListItem) item, false);
- }
- if (drawerLayout != null)
- drawerLayout.closeDrawer(drawerContainer);
- }
- };
-
- /**
- * A new filter was selected; update the view over the current data
- * @param item The touched filter item
- * @param forceNewConnection Whether a new connection should be initialised regardless of the old server selection
- */
- protected void filterSelected(SimpleListItem item, boolean forceNewConnection) {
-
- // No longer apply the last used filter (on a fresh application start), if we still needed to
- preselectNavigationFilter = null;
-
- // Server selection
- if (item instanceof ServerSetting) {
- ServerSetting server = (ServerSetting) item;
-
- if (!forceNewConnection && currentConnection != null && server.equals(currentConnection.getSettings())) {
- // Already connected to this server; just ask for a refresh instead
- fragmentTorrents.updateIsLoading(true);
- refreshTorrents();
- return;
- }
-
- // Update connection to the newly selected server and refresh
- currentConnection = server.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
- applicationSettings.setLastUsedServer(server);
- serverSelectionView.updateCurrentServer(currentConnection);
- if (forceNewConnection) {
- serverSelectionView.updateCurrentFilter(currentFilter);
- }
-
- // Clear the currently shown list of torrents and perhaps the details
- fragmentTorrents.clear(true, true);
- if (fragmentDetails != null && fragmentDetails.isResumed() && fragmentDetails.getActivity() != null) {
- fragmentDetails.updateIsLoading(false, null);
- fragmentDetails.clear();
- fragmentDetails.setCurrentServerSettings(server);
- }
- updateFragmentVisibility(true);
- refreshScreen();
- return;
-
- }
-
- // Status type or label selection - both of which are navigation filters
- if (item instanceof NavigationFilter) {
- // Set new filter
- currentFilter = (NavigationFilter) item;
- fragmentTorrents.applyNavigationFilter(currentFilter);
- serverSelectionView.updateCurrentFilter(currentFilter);
- // Remember that the user last selected this
- applicationSettings.setLastUsedNavigationFilter(currentFilter);
- // Clear the details view
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.updateIsLoading(false, null);
- fragmentDetails.clear();
- }
- }
-
- }
-
- /**
- * Hides the filter list and details fragment's full view if there is no configured connection
- * @param hasServerSettings Whether there are server settings available, so we can continue to connect
- */
- private void updateFragmentVisibility(boolean hasServerSettings) {
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- if (hasServerSettings) {
- getFragmentManager().beginTransaction().show(fragmentDetails).commit();
- } else {
- getFragmentManager().beginTransaction().hide(fragmentDetails).commit();
- }
- }
- invalidateOptionsMenu();
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- setIntent(intent);
- handleStartIntent();
- }
-
- protected void handleStartIntent() {
- // For intents that come from out of the application, perhaps we can not directly add them
- if (applicationSettings.getDefaultServerKey() == ApplicationSettings.DEFAULTSERVER_ASKONADD && getIntent().getData() != null) {
- // First ask which server to use before adding any intent from the extras
- ServerPickerDialog.startServerPicker(this, applicationSettings.getAllServerSettings());
- return;
- }
- addFromIntent();
- }
-
- public void switchServerAndAddFromIntent(int position) {
- // Callback from the ServerPickerDialog; force a connection before selecting it (in the navigation)
- // Note: we can just use the list position as we have stable server setting ids
- ServerSetting selectedServer = applicationSettings.getAllServerSettings().get(position);
- filterSelected(selectedServer, false);
- addFromIntent();
- }
-
- /**
- * If required, add torrents from the supplied intent extras.
- */
- protected void addFromIntent() {
- Intent intent = getIntent();
- Uri dataUri = intent.getData();
- String data = intent.getDataString();
- String action = intent.getAction();
-
- // Adding multiple torrents at the same time (as found in the Intent extras Bundle)
- if (action != null && action.equals("org.transdroid.ADD_MULTIPLE")) {
- // Intent should have some extras pointing to possibly multiple torrents
- String[] urls = intent.getStringArrayExtra("TORRENT_URLS");
- String[] titles = intent.getStringArrayExtra("TORRENT_TITLES");
- if (urls != null) {
- for (int i = 0; i < urls.length; i++) {
- String title = (titles != null && titles.length >= i ? titles[i] : NavigationHelper.extractNameFromUri(Uri.parse(urls[i])));
- if (intent.hasExtra("PRIVATE_SOURCE")) {
- // This is marked by the Search Module as being a private source site; get the url locally first
- addTorrentFromPrivateSource(urls[i], title, intent.getStringExtra("PRIVATE_SOURCE"));
- } else {
- addTorrentByUrl(urls[i], title);
- }
- }
- }
- return;
- }
-
- // Add a torrent from a local or remote data URI?
- if (dataUri == null) {
- return;
- }
- if (dataUri.getScheme() == null) {
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_invalid_url_form).colorResource(R.color.red));
- return;
- }
-
- // Get torrent title
- String title = NavigationHelper.extractNameFromUri(dataUri);
- if (intent.hasExtra("TORRENT_TITLE")) {
- title = intent.getStringExtra("TORRENT_TITLE");
- }
-
- // Adding a torrent from the Android downloads manager
- if (dataUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
- addTorrentFromDownloads(dataUri, title);
- return;
- }
-
- // Adding a torrent from http or https URL
- if (dataUri.getScheme().equals("http") || dataUri.getScheme().equals("https")) {
-
- String privateSource = getIntent().getStringExtra("PRIVATE_SOURCE");
-
- WebsearchSetting match = null;
- if (privateSource == null) {
- // Check if the target URL is also defined as a web search in the user's settings
- List websearches = applicationSettings.getWebsearchSettings();
- for (WebsearchSetting setting : websearches) {
- Uri uri = Uri.parse(setting.getBaseUrl());
- if (uri.getHost() != null && uri.getHost().equals(dataUri.getHost())) {
- match = setting;
- break;
- }
- }
- }
-
- // If the URL is also a web search and it defines cookies, use the cookies by downloading the targeted
- // torrent file (while supplies the cookies to the HTTP request) instead of sending the URL directly to the
- // torrent client. If instead it is marked (by the Torrent Search module) as being form a private site, use
- // the Search Module instead to download the url locally first.
- if (match != null && match.getCookies() != null) {
- addTorrentFromWeb(data, match, title);
- } else if (privateSource != null) {
- addTorrentFromPrivateSource(data, title, privateSource);
- } else {
- // Normally send the URL to the torrent client
- addTorrentByUrl(data, title);
- }
- return;
- }
-
- // Adding a torrent from magnet URL
- if (dataUri.getScheme().equals("magnet")) {
- addTorrentByMagnetUrl(data, title);
- return;
- }
-
- // Adding a local .torrent file; the title we show is just the file name
- if (dataUri.getScheme().equals("file")) {
- addTorrentByFile(data, title);
- }
-
- }
-
- @Override
- protected void onPause() {
- if (searchMenu != null) {
- searchMenu.collapseActionView();
- }
- stopAutoRefresh();
- super.onPause();
- }
-
- @Override
- public boolean onSearchRequested() {
- if (searchMenu != null) {
- searchMenu.expandActionView();
- }
- return true;
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- if (awaitingAddLocalFile != null && awaitingAddTitle != null &&
- Boolean.TRUE.equals(navigationHelper.handleTorrentReadPermissionResult(requestCode, grantResults))) {
- addTorrentByFile(awaitingAddLocalFile, awaitingAddTitle);
- }
- }
-
- @Click(R.id.addmenu_link_button)
- protected void startUrlEntryDialog() {
- addmenuButton.collapse();
- UrlEntryDialog.show(this);
- }
-
- @Click(R.id.addmenu_file_button)
- protected void startFilePicker() {
- addmenuButton.collapse();
- FilePickerHelper.startFilePicker(this);
- }
-
- @Background
- @OnActivityResult(FilePickerHelper.ACTIVITY_FILEPICKER)
- public void onFilePicked(int resultCode, Intent data) {
- // We should have received an Intent with a local torrent's Uri as data from the file picker
- if (data != null && data.getData() != null && !data.getData().toString().equals("")) {
- Uri dataUri = data.getData();
-
- // Get torrent title
- String title = NavigationHelper.extractNameFromUri(dataUri);
-
- // Adding a torrent from the via a content:// scheme (access through content provider stream)
- if (dataUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
- addTorrentFromDownloads(dataUri, title);
- return;
- }
-
- // Adding a .torrent file directly via the file:// scheme (we can access it directly)
- if (dataUri.getScheme().equals("file")) {
- addTorrentByFile(data.getDataString(), title);
- }
-
- }
- }
-
- @OptionsItem(R.id.action_refresh)
- public void refreshScreen() {
- if (fragmentTorrents.isAdded())
- fragmentTorrents.updateIsLoading(true);
- refreshTorrents();
- if (Daemon.supportsStats(currentConnection.getType())) {
- getAdditionalStats();
- }
- }
-
- @OptionsItem(R.id.action_enableturtle)
- protected void enableTurtleMode() {
- updateTurtleMode(true);
- }
-
- @OptionsItem(R.id.action_disableturtle)
- protected void disableTurtleMode() {
- updateTurtleMode(false);
- }
-
- @OptionsItem(R.id.action_rss)
- protected void openRss() {
- RssFeedsActivity_.intent(this).start();
- }
-
- @OptionsItem(R.id.action_settings)
- protected void openSettings() {
- MainSettingsActivity_.intent(this).start();
- }
-
- @OptionsItem(R.id.action_help)
- protected void openHelp() {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/download/")));
- }
-
- @OptionsItem(R.id.action_sort_byname)
- protected void sortByName() {
- fragmentTorrents.sortBy(TorrentsSortBy.Alphanumeric);
- }
-
- @OptionsItem(R.id.action_sort_status)
- protected void sortByStatus() {
- fragmentTorrents.sortBy(TorrentsSortBy.Status);
- }
-
- @OptionsItem(R.id.action_sort_done)
- protected void sortByDateDone() {
- fragmentTorrents.sortBy(TorrentsSortBy.DateDone);
- }
-
- @OptionsItem(R.id.action_sort_added)
- protected void sortByDateAdded() {
- fragmentTorrents.sortBy(TorrentsSortBy.DateAdded);
- }
-
- @OptionsItem(R.id.action_sort_percent)
- protected void sortByPercent() {
- fragmentTorrents.sortBy(TorrentsSortBy.Percent);
- }
-
- @OptionsItem(R.id.action_sort_downspeed)
- protected void sortByDownspeed() {
- fragmentTorrents.sortBy(TorrentsSortBy.DownloadSpeed);
- }
-
- @OptionsItem(R.id.action_sort_upspeed)
- protected void sortByUpspeed() {
- fragmentTorrents.sortBy(TorrentsSortBy.UploadSpeed);
- }
-
- @OptionsItem(R.id.action_sort_ratio)
- protected void sortByRatio() {
- fragmentTorrents.sortBy(TorrentsSortBy.Ratio);
- }
-
- @OptionsItem(R.id.action_sort_size)
- protected void sortBySize() {
- fragmentTorrents.sortBy(TorrentsSortBy.Size);
- }
-
- private SearchView.OnQueryTextListener filterQueryTextChanged = new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextSubmit(String query) {
- return false;
- }
-
- @Override
- public boolean onQueryTextChange(String newText) {
- // Redirect to filter method which will directly apply it
- filterTorrents(newText);
- return true;
- }
- };
-
- /**
- * Redirect the newly entered list filter to the torrents fragment.
- * @param newFilterText The newly entered filter (or empty to clear the current filter).
- */
- public void filterTorrents(String newFilterText) {
- fragmentTorrents.applyTextFilter(newFilterText);
- }
-
- /**
- * Shows the a details fragment for the given torrent, either in the dedicated details fragment pane, in the same pane as the torrent list was
- * displayed or by starting a details activity.
- * @param torrent The torrent to show detailed statistics for
- */
- public void openDetails(Torrent torrent) {
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.updateTorrent(torrent);
- } else {
- DetailsActivity_.intent(this).torrent(torrent).currentLabels(lastNavigationLabels).startForResult(RESULT_DETAILS);
- }
- }
-
- @Background
- protected void refreshTorrents() {
- String startConnectionId = currentConnection.getSettings().getIdString();
- DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
- if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
- // During the command execution the user changed the server, so we are no longer interested in the result
- return;
- }
- if (result instanceof RetrieveTaskSuccessResult) {
- onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, true);
- }
- }
-
- @Background
- public void refreshTorrentDetails(Torrent torrent) {
- if (!Daemon.supportsFineDetails(currentConnection.getType())) {
- return;
- }
- String startConnectionId = currentConnection.getSettings().getIdString();
- DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
- if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
- // During the command execution the user changed the server, so we are no longer interested in the result
- return;
- }
- if (result instanceof GetTorrentDetailsTaskSuccessResult) {
- onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- public void refreshTorrentFiles(Torrent torrent) {
- if (!Daemon.supportsFileListing(currentConnection.getType())) {
- return;
- }
- String startConnectionId = currentConnection.getSettings().getIdString();
- DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
- if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
- // During the command execution the user changed the server, so we are no longer interested in the result
- return;
- }
- if (result instanceof GetFileListTaskSuccessResult) {
- onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- protected void getAdditionalStats() {
- String startConnectionId = currentConnection.getSettings().getIdString();
- DaemonTaskResult result = GetStatsTask.create(currentConnection).execute(log);
- if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
- // During the command execution the user changed the server, so we are no longer interested in the result
- return;
- }
- if (result instanceof GetStatsTaskSuccessResult) {
- onTurtleModeRetrieved(((GetStatsTaskSuccessResult) result).isAlternativeModeEnabled());
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- protected void updateTurtleMode(boolean enable) {
- String startConnectionId = currentConnection.getSettings().getIdString();
- DaemonTaskResult result = SetAlternativeModeTask.create(currentConnection, enable).execute(log);
- if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
- // During the command execution the user changed the server, so we are no longer interested in the result
- return;
- }
- if (result instanceof DaemonTaskSuccessResult) {
- // Success; no need to retrieve it again - just update the visual indicator
- onTurtleModeRetrieved(enable);
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- public void addTorrentByUrl(String url, String title) {
- DaemonTaskResult result = AddByUrlTask.create(currentConnection, url, title).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
- refreshTorrents();
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- public void addTorrentByMagnetUrl(String url, String title) {
-
- // Since v39 Chrome sends application/x-www-form-urlencoded magnet links and most torrent clients do not understand those, so decode first
- try {
- url = URLDecoder.decode(url, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Ignore: UTF-8 is always available on Android devices
- } catch (IllegalArgumentException e) {
- // Illegal character or escape sequence; fail task to show error
- onCommunicationError(new DaemonTaskFailureResult(AddByMagnetUrlTask.create(currentConnection, url),
- new DaemonException(DaemonException.ExceptionType.FileAccessError, "Invalid characters in magnet link")), false);
- return;
- }
-
- AddByMagnetUrlTask addByMagnetUrlTask = AddByMagnetUrlTask.create(currentConnection, url);
- if (!Daemon.supportsAddByMagnetUrl(currentConnection.getType())) {
- // No support for magnet links: forcefully let the task fail to report the error
- onCommunicationError(new DaemonTaskFailureResult(addByMagnetUrlTask, new DaemonException(DaemonException.ExceptionType.MethodUnsupported,
- currentConnection.getType().name() + " does not support magnet links")), false);
- return;
- }
-
- DaemonTaskResult result = addByMagnetUrlTask.execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
- refreshTorrents();
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
-
- }
-
- @Background
- protected void addTorrentByFile(String localFile, String title) {
- if (!navigationHelper.checkTorrentReadPermission(this)) {
- // No read permission yet (which we get the result of in onRequestPermissionsResult)
- awaitingAddLocalFile = localFile;
- awaitingAddTitle = title;
- return;
- }
- DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
- refreshTorrents();
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- private void addTorrentFromDownloads(Uri contentUri, String title) {
-
- try {
- // Open the content uri as input stream and this via a local temporary file
- addTorrentFromStream(getContentResolver().openInputStream(contentUri), title);
- } catch (SecurityException e) {
- // No longer access to this file
- log.e(this, "No access given to " + contentUri.toString() + ": " + e.toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
- } catch (FileNotFoundException e) {
- log.e(this, contentUri.toString() + " does not exist: " + e.toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
- }
- }
-
- @Background
- protected void addTorrentFromPrivateSource(String url, String title, String source) {
-
- try {
- InputStream input = SearchHelper_.getInstance_(this).getFile(source, url);
- addTorrentFromStream(input, title);
- } catch (Exception e) {
- log.e(this, "Can't download private site torrent " + url + " from " + source + ": " + e.toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
- }
-
- }
-
- @Background
- protected void addTorrentFromWeb(String url, WebsearchSetting websearchSetting, String title) {
-
- try {
- // Cookies are taken from the websearchSetting that we already matched against this target URL
- DefaultHttpClient httpclient = HttpHelper.createStandardHttpClient(false, null, null, true, null, 10000, null, -1);
- Map cookies = HttpHelper.parseCookiePairs(websearchSetting.getCookies());
- String domain = Uri.parse(url).getHost();
- for (Entry pair : cookies.entrySet()) {
- BasicClientCookie cookie = new BasicClientCookie(pair.getKey(), pair.getValue());
- cookie.setPath("/");
- cookie.setDomain(domain);
- httpclient.getCookieStore().addCookie(cookie);
- }
-
- // Download the torrent at the specified URL (which will first be written to a temporary file)
- // If we get an HTTP 401, 403 or 404 response, show an error to the user
- HttpResponse response = httpclient.execute(new HttpGet(url));
- if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED ||
- response.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN ||
- response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
- log.e(this, "Can't retrieve web torrent " + url + ": Unexpected HTTP response status code " +
- response.getStatusLine().toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_401).colorResource(R.color.red));
- return;
- }
- InputStream input = response.getEntity().getContent();
- addTorrentFromStream(input, title);
- } catch (Exception e) {
- log.e(this, "Can't retrieve web torrent " + url + ": " + e.toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
- }
- }
-
- @Background
- protected void addTorrentFromStream(InputStream input, String title) {
-
- File tempFile = new File("/not/yet/set");
- try {
- // Write a temporary file with the torrent contents
- tempFile = File.createTempFile("transdroid_", ".torrent", getCacheDir());
- FileOutputStream output = new FileOutputStream(tempFile);
- try {
- final byte[] buffer = new byte[1024];
- int read;
- while ((read = input.read(buffer)) != -1) {
- output.write(buffer, 0, read);
- }
- output.flush();
- String fileName = Uri.fromFile(tempFile).toString();
- addTorrentByFile(fileName, title);
- } finally {
- output.close();
- }
- } catch (IOException e) {
- log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
- } finally {
- try {
- if (input != null) {
- input.close();
- }
- } catch (IOException e) {
- log.e(this, "Error closing the input stream " + tempFile.toString() + ": " + e.toString());
- SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
- }
- }
- }
-
- @Background
- @Override
- public void resumeTorrent(Torrent torrent) {
- torrent.mimicResume();
- DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void pauseTorrent(Torrent torrent) {
- torrent.mimicPause();
- DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void startTorrent(Torrent torrent, boolean forced) {
- torrent.mimicStart();
- DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void stopTorrent(Torrent torrent) {
- torrent.mimicStop();
- DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void removeTorrent(Torrent torrent, boolean withData) {
- DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result,
- getString(withData ? R.string.result_removed_with_data : R.string.result_removed, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updateLabel(Torrent torrent, String newLabel) {
- torrent.mimicNewLabel(newLabel);
- DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result,
- newLabel == null ? getString(R.string.result_labelremoved) : getString(R.string.result_labelset, newLabel));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) {
- torrent.mimicSequentialDownload(sequentialState);
- DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) {
- torrent.mimicFirstLastPieceDownload(firstLastPieceState);
- DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.action_toggle_firstlastpiece));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void forceRecheckTorrent(Torrent torrent) {
- torrent.mimicCheckingStatus();
- DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_recheckedstarted, torrent.getName()));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updateTrackers(Torrent torrent, List newTrackers) {
- DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updateLocation(Torrent torrent, String newLocation) {
- DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- @Override
- public void updatePriority(Torrent torrent, List files, Priority priority) {
- DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<>(files)).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @Background
- public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) {
- DaemonTaskResult result = SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed).execute(log);
- if (result instanceof DaemonTaskSuccessResult) {
- onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset));
- } else {
- onCommunicationError((DaemonTaskFailureResult) result, false);
- }
- }
-
- @UiThread
- protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) {
- // Refresh the screen as well
- refreshScreen();
- SnackbarManager.show(Snackbar.with(this).text(successMessage));
- }
-
- @UiThread
- protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
- //noinspection ThrowableResultOfMethodCallIgnored
- log.i(this, result.getException().toString());
- String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
- SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE));
- fragmentTorrents.updateIsLoading(false);
- if (isCritical) {
- fragmentTorrents.updateError(error);
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.updateIsLoading(false, error);
- }
- }
- }
-
- @UiThread
- protected void onTorrentsRetrieved(List torrents, List labels) {
-
- lastNavigationLabels = Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled));
-
- // Report the newly retrieved list of torrents to the torrents fragment
- fragmentTorrents.updateIsLoading(false);
- fragmentTorrents.updateTorrents(new ArrayList<>(torrents), lastNavigationLabels);
-
- // Update the details fragment if the currently shown torrent is in the newly retrieved list
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.perhapsUpdateTorrent(torrents);
- }
-
- // Update local list of labels in the navigation
- navigationListAdapter.updateLabels(lastNavigationLabels);
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.updateLabels(lastNavigationLabels);
- }
-
- // Perhaps we were still waiting to preselect the last used filter (on a fresh application start)
- if (preselectNavigationFilter != null && navigationListAdapter != null) {
- for (int i = 0; i < navigationListAdapter.getCount(); i++) {
- // Look up the navigation filter item, which is represented as simple list item (and might not exist any
- // more, such as with a label that is deleted on the server)
- Object item = navigationListAdapter.getItem(i);
- if (item instanceof SimpleListItem && item instanceof NavigationFilter &&
- ((NavigationFilter) item).getCode().equals(preselectNavigationFilter)) {
- filterSelected((SimpleListItem) item, false);
- break;
- }
- }
- // Only preselect after the first update we receive (even if the filter wasn't found any more)
- preselectNavigationFilter = null;
- }
-
- // Update the server status (counts and speeds) in the action bar
- serverStatusView
- .updateStatus(torrents, systemSettings.treatDormantAsInactive(), Daemon.supportsSetTransferRates(currentConnection.getType()));
-
- }
-
- @UiThread
- protected void onTorrentDetailsRetrieved(Torrent torrent, TorrentDetails torrentDetails) {
- // Update the details fragment with the new fine details for the shown torrent
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.updateTorrentDetails(torrent, torrentDetails);
- }
- }
-
- @UiThread
- protected void onTorrentFilesRetrieved(Torrent torrent, List torrentFiles) {
- // Update the details fragment with the newly retrieved list of files
- if (fragmentDetails != null && fragmentDetails.isResumed()) {
- fragmentDetails.updateTorrentFiles(torrent, new ArrayList<>(torrentFiles));
- }
- }
-
- @UiThread
- protected void onTurtleModeRetrieved(boolean turtleModeEnabled) {
- this.turtleModeEnabled = turtleModeEnabled;
- invalidateOptionsMenu();
- }
+ private static final int RESULT_DETAILS = 0;
+
+ // Fragment uses this to pause the refresh across restarts
+ public boolean stopRefresh = false;
+
+ // Navigation components
+ @SystemService
+ protected SearchManager searchManager;
+ @Bean
+ protected Log log;
+ @Bean
+ protected NavigationHelper navigationHelper;
+ @Bean
+ protected ConnectivityHelper connectivityHelper;
+ @ViewById
+ protected Toolbar selectionToolbar;
+ @ViewById
+ protected Toolbar torrentsToolbar;
+ @ViewById
+ protected Toolbar actionsToolbar;
+ @ViewById(R.id.contextual_menu)
+ protected ActionMenuView contextualMenu;
+ @ViewById
+ protected FloatingActionsMenu addmenuButton;
+ @ViewById
+ protected FloatingActionButton addmenuFileButton;
+ @ViewById
+ protected DrawerLayout drawerLayout;
+ @ViewById(R.id.drawer_container)
+ protected ViewGroup drawerContainer;
+ @ViewById
+ protected ListView drawerList;
+ @ViewById
+ protected ListView filtersList;
+ @ViewById
+ protected SearchView filterSearch;
+ private ListView navigationList;
+ private FilterListAdapter navigationListAdapter;
+ private ServerSelectionView serverSelectionView;
+ private ServerStatusView serverStatusView;
+ private ActionBarDrawerToggle drawerToggle;
+
+ // Settings
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @Bean
+ protected SystemSettings systemSettings;
+ @InstanceState
+ protected NavigationFilter currentFilter = null;
+ @InstanceState
+ protected String preselectNavigationFilter = null;
+ @InstanceState
+ protected boolean turtleModeEnabled = false;
+ @InstanceState
+ protected ArrayList lastNavigationLabels;
+ // Contained torrent and details fragments
+ @FragmentById(R.id.torrents_fragment)
+ protected TorrentsFragment fragmentTorrents;
+ @FragmentById(R.id.torrentdetails_fragment)
+ protected DetailsFragment fragmentDetails;
+ @InstanceState
+ boolean firstStart = true;
+ private MenuItem searchMenu = null;
+ private IDaemonAdapter currentConnection = null;
+
+ // Auto refresh task
+ private AsyncTask autoRefreshTask;
+
+ private String awaitingAddLocalFile;
+ private String awaitingAddTitle;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ SettingsUtils.applyDayNightTheme(this);
+
+ // Catch any uncaught exception to log it
+ Thread.setDefaultUncaughtExceptionHandler(new LogUncaughtExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler()));
+ super.onCreate(savedInstanceState);
+ }
+
+ @AfterViews
+ protected void init() {
+
+ // Use custom views as action bar content, showing filter selection and current torrent counts/speeds
+ serverSelectionView = ServerSelectionView_.build(this);
+ serverStatusView = ServerStatusView_.build(this);
+ if (selectionToolbar != null) {
+ selectionToolbar.addView(serverSelectionView);
+ } else {
+ torrentsToolbar.addView(serverSelectionView);
+ }
+ actionsToolbar.addView(serverStatusView);
+ actionsToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ // Redirect to the classic activity implementation so we can use @OptionsItem methods
+ return onOptionsItemSelected(menuItem);
+ }
+ });
+ setSupportActionBar(torrentsToolbar); // For direct menu item inflation by the contained fragments
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+
+ // Construct the filters list, i.e. the list of servers, status types and labels
+ navigationListAdapter = FilterListAdapter_.getInstance_(this);
+ navigationListAdapter.updateServers(applicationSettings.getAllServerSettings());
+ navigationListAdapter.updateStatusTypes(StatusType.getAllStatusTypes(this));
+ // Add an empty labels list (which will be updated later, but the adapter needs to be created now)
+ navigationListAdapter.updateLabels(new ArrayList());
+
+ // Apply the filters list to the navigation drawer (on phones) or the dedicated side bar (i.e. on tablets)
+ if (filtersList != null) {
+ navigationList = filtersList;
+ } else {
+ navigationList = drawerList;
+ drawerToggle =
+ new ActionBarDrawerToggle(this, drawerLayout, torrentsToolbar, R.string.navigation_opendrawer, R.string.navigation_closedrawer);
+ drawerToggle.setDrawerIndicatorEnabled(true);
+ drawerLayout.setDrawerListener(drawerToggle);
+ }
+ navigationList.setAdapter(navigationListAdapter);
+ navigationList.setOnItemClickListener(onFilterListItemClicked);
+ // Now that all items (or at least their adapters) have been added, ensure a filter is selected
+ // NOTE When this is a fresh start, we might override the filter later (based on the last user selection)
+ if (currentFilter == null) {
+ currentFilter = StatusType.getShowAllType(this);
+ }
+ filterSearch.setOnQueryTextListener(filterQueryTextChanged);
+
+ // Load the default server or a server that was explicitly supplied in the starting intent
+ ServerSetting defaultServer = applicationSettings.getDefaultServer();
+ if (defaultServer == null) {
+ // No server settings yet
+ return;
+ }
+ Torrent openTorrent = null;
+ if (getIntent().getAction() != null && getIntent().getAction().equals(ListWidgetProvider.INTENT_STARTSERVER) &&
+ getIntent().getExtras() == null && getIntent().hasExtra(ListWidgetProvider.EXTRA_SERVER)) {
+ // A server settings order ID was provided in this org.transdroid.START_SERVER action intent
+ int serverId = getIntent().getExtras().getInt(ListWidgetProvider.EXTRA_SERVER);
+ if (serverId < 0 || serverId > applicationSettings.getMaxOfAllServers()) {
+ log.e(this, "Tried to start with " + ListWidgetProvider.EXTRA_SERVER + " intent but " + serverId +
+ " is not an existing server order id");
+ } else {
+ defaultServer = applicationSettings.getServerSetting(serverId);
+ if (getIntent().hasExtra(ListWidgetProvider.EXTRA_TORRENT)) {
+ openTorrent = getIntent().getParcelableExtra(ListWidgetProvider.EXTRA_TORRENT);
+ }
+ }
+ }
+
+ // Connect to the last used server or a server that was explicitly supplied in the starting intent
+ if (firstStart) {
+ // Force first torrents refresh
+ filterSelected(defaultServer, true);
+ // Perhaps we can select the last used navigation filter, but only after a first refresh was completed
+ preselectNavigationFilter = applicationSettings.getLastUsedNavigationFilter();
+ // Handle any start up intents
+ if (openTorrent != null) {
+ openDetails(openTorrent);
+ } else if (getIntent() != null) {
+ handleStartIntent();
+ }
+ } else {
+ // Resume after instead of fully loading the torrents list; create connection and set action bar title
+ ServerSetting lastUsed = applicationSettings.getLastUsedServer();
+ currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
+ serverSelectionView.updateCurrentServer(currentConnection);
+ serverSelectionView.updateCurrentFilter(currentFilter);
+ }
+ firstStart = false;
+
+ // Start the jobs for the background services, if needed
+ ServerCheckerJob.schedule(getApplicationContext());
+ RssCheckerJob.schedule(getApplicationContext());
+ AppUpdateJob.schedule(getApplicationContext());
+
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred
+ if (drawerToggle != null) {
+ drawerToggle.syncState();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ // update navigation labels
+ navigationListAdapter.updateLabels(lastNavigationLabels);
+
+ // Refresh server settings
+ navigationListAdapter.updateServers(applicationSettings.getAllServerSettings());
+ ServerSetting lastUsed = applicationSettings.getLastUsedServer();
+
+ if (lastUsed == null) {
+ // Still no settings
+ updateFragmentVisibility(false);
+ return;
+ }
+
+ // If we had no connection before, establish it now; otherwise just reload the settings
+ if (currentConnection == null) {
+ filterSelected(lastUsed, true);
+ } else {
+ currentConnection = lastUsed.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
+ }
+
+ // Start auto refresh
+ startAutoRefresh();
+
+ }
+
+ @OnActivityResult(RESULT_DETAILS)
+ protected void onDetailsScreenResult(Intent result) {
+ // If the details activity returns whether the torrent was removed or updated, update the torrents list as well
+ // (the details fragment is the source, so no need to update that)
+ if (result != null && result.hasExtra("affected_torrent")) {
+ Torrent affected = result.getParcelableExtra("affected_torrent");
+ fragmentTorrents.quickUpdateTorrent(affected, result.getBooleanExtra("torrent_removed", false));
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void startAutoRefresh() {
+ // Check if already running
+ if (autoRefreshTask != null || stopRefresh || systemSettings.getRefreshIntervalMilliseconds() == 0) {
+ return;
+ }
+
+ autoRefreshTask = new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ while (!isCancelled()) {
+ try {
+ Thread.sleep(systemSettings.getRefreshIntervalMilliseconds());
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ // Just in case it was cancelled during sleep
+ if (isCancelled()) {
+ return null;
+ }
+
+ refreshTorrents();
+ if (Daemon.supportsStats(currentConnection.getType())) {
+ getAdditionalStats();
+ }
+ }
+ return null;
+ }
+
+ };
+ autoRefreshTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ public void stopAutoRefresh() {
+ if (autoRefreshTask != null) {
+ autoRefreshTask.cancel(true);
+ }
+ autoRefreshTask = null;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ // Manually insert the actions into the main torrent and secondary actions toolbars
+ torrentsToolbar.inflateMenu(R.menu.activity_torrents_main);
+ if (actionsToolbar.getMenu().size() == 0) {
+ actionsToolbar.inflateMenu(R.menu.activity_torrents_secondary);
+ }
+ if (navigationHelper.enableSearchUi()) {
+ // Add an expandable SearchView to the action bar
+ MenuItem item = menu.findItem(R.id.action_search);
+ SearchView searchView = new SearchView(torrentsToolbar.getContext());
+ searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
+ searchView.setQueryRefinementEnabled(true);
+ searchView.setOnSearchClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Pause autorefresh
+ stopRefresh = true;
+ stopAutoRefresh();
+ }
+ });
+ MenuItemCompat.setOnActionExpandListener(item, new MenuItemCompat.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ stopRefresh = false;
+ startAutoRefresh();
+ return true;
+ }
+ });
+ MenuItemCompat.setActionView(item, searchView);
+ searchMenu = item;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ // No connection yet; hide all menu options except settings
+ if (currentConnection == null) {
+ torrentsToolbar.setNavigationIcon(null);
+ if (selectionToolbar != null)
+ selectionToolbar.setVisibility(View.GONE);
+ addmenuButton.setVisibility(View.GONE);
+ actionsToolbar.setVisibility(View.GONE);
+ if (filtersList != null)
+ filtersList.setVisibility(View.GONE);
+ 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_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);
+ actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(false);
+ actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(false);
+ actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(false);
+ if (fragmentTorrents != null) {
+ fragmentTorrents.updateConnectionStatus(false, null);
+ }
+ return true;
+ }
+
+ // There is a connection (read: settings to some server known)
+ if (drawerToggle != null)
+ torrentsToolbar.setNavigationIcon(R.drawable.ic_action_drawer);
+ if (selectionToolbar != null)
+ selectionToolbar.setVisibility(View.VISIBLE);
+ addmenuButton.setVisibility(View.VISIBLE);
+ actionsToolbar.setVisibility(View.VISIBLE);
+ if (filtersList != null)
+ filtersList.setVisibility(View.VISIBLE);
+ filterSearch.setVisibility(View.VISIBLE);
+ boolean addByFile = Daemon.supportsAddByFile(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_settings).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ torrentsToolbar.getMenu().findItem(R.id.action_help).setVisible(false);
+ // Secondary toolbar menu
+ boolean hasAltMode = Daemon.supportsSetAlternativeMode(currentConnection.getType());
+ actionsToolbar.getMenu().findItem(R.id.action_enableturtle).setVisible(hasAltMode && !turtleModeEnabled);
+ actionsToolbar.getMenu().findItem(R.id.action_disableturtle).setVisible(hasAltMode && turtleModeEnabled);
+ actionsToolbar.getMenu().findItem(R.id.action_refresh).setVisible(true);
+ actionsToolbar.getMenu().findItem(R.id.action_sort).setVisible(true);
+ actionsToolbar.getMenu().findItem(R.id.action_sort_added).setVisible(Daemon.supportsDateAdded(currentConnection.getType()));
+ if (fragmentTorrents != null) {
+ fragmentTorrents.updateConnectionStatus(true, currentConnection.getType());
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle only if this is the drawer toggle; otherwise the AndroidAnnotations will be used
+ return drawerToggle != null && drawerToggle.onOptionsItemSelected(item);
+ }
+
+ /**
+ * Handles item selections on the dedicated list of filter items
+ */
+ private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ navigationList.setItemChecked(position, true);
+ Object item = navigationList.getAdapter().getItem(position);
+ if (item instanceof SimpleListItem) {
+ filterSelected((SimpleListItem) item, false);
+ }
+ if (drawerLayout != null)
+ drawerLayout.closeDrawer(drawerContainer);
+ }
+ };
+
+ /**
+ * A new filter was selected; update the view over the current data
+ *
+ * @param item The touched filter item
+ * @param forceNewConnection Whether a new connection should be initialised regardless of the old server selection
+ */
+ protected void filterSelected(SimpleListItem item, boolean forceNewConnection) {
+
+ // No longer apply the last used filter (on a fresh application start), if we still needed to
+ preselectNavigationFilter = null;
+
+ // Server selection
+ if (item instanceof ServerSetting) {
+ ServerSetting server = (ServerSetting) item;
+
+ if (!forceNewConnection && currentConnection != null && server.equals(currentConnection.getSettings())) {
+ // Already connected to this server; just ask for a refresh instead
+ fragmentTorrents.updateIsLoading(true);
+ refreshTorrents();
+ return;
+ }
+
+ // Update connection to the newly selected server and refresh
+ currentConnection = server.createServerAdapter(connectivityHelper.getConnectedNetworkName(), this);
+ applicationSettings.setLastUsedServer(server);
+ serverSelectionView.updateCurrentServer(currentConnection);
+ if (forceNewConnection) {
+ serverSelectionView.updateCurrentFilter(currentFilter);
+ }
+
+ // Clear the currently shown list of torrents and perhaps the details
+ fragmentTorrents.clear(true, true);
+ if (fragmentDetails != null && fragmentDetails.isResumed() && fragmentDetails.getActivity() != null) {
+ fragmentDetails.updateIsLoading(false, null);
+ fragmentDetails.clear();
+ fragmentDetails.setCurrentServerSettings(server);
+ }
+ updateFragmentVisibility(true);
+ refreshScreen();
+ return;
+
+ }
+
+ // Status type or label selection - both of which are navigation filters
+ if (item instanceof NavigationFilter) {
+ // Set new filter
+ currentFilter = (NavigationFilter) item;
+ fragmentTorrents.applyNavigationFilter(currentFilter);
+ serverSelectionView.updateCurrentFilter(currentFilter);
+ // Remember that the user last selected this
+ applicationSettings.setLastUsedNavigationFilter(currentFilter);
+ // Clear the details view
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.updateIsLoading(false, null);
+ fragmentDetails.clear();
+ }
+ }
+
+ }
+
+ /**
+ * Hides the filter list and details fragment's full view if there is no configured connection
+ *
+ * @param hasServerSettings Whether there are server settings available, so we can continue to connect
+ */
+ private void updateFragmentVisibility(boolean hasServerSettings) {
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ if (hasServerSettings) {
+ getFragmentManager().beginTransaction().show(fragmentDetails).commit();
+ } else {
+ getFragmentManager().beginTransaction().hide(fragmentDetails).commit();
+ }
+ }
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ handleStartIntent();
+ }
+
+ protected void handleStartIntent() {
+ // For intents that come from out of the application, perhaps we can not directly add them
+ if (applicationSettings.getDefaultServerKey() == ApplicationSettings.DEFAULTSERVER_ASKONADD && getIntent().getData() != null) {
+ // First ask which server to use before adding any intent from the extras
+ ServerPickerDialog.startServerPicker(this, applicationSettings.getAllServerSettings());
+ return;
+ }
+ addFromIntent();
+ }
+
+ public void switchServerAndAddFromIntent(int position) {
+ // Callback from the ServerPickerDialog; force a connection before selecting it (in the navigation)
+ // Note: we can just use the list position as we have stable server setting ids
+ ServerSetting selectedServer = applicationSettings.getAllServerSettings().get(position);
+ filterSelected(selectedServer, false);
+ addFromIntent();
+ }
+
+ /**
+ * If required, add torrents from the supplied intent extras.
+ */
+ protected void addFromIntent() {
+ Intent intent = getIntent();
+ Uri dataUri = intent.getData();
+ String data = intent.getDataString();
+ String action = intent.getAction();
+
+ // Adding multiple torrents at the same time (as found in the Intent extras Bundle)
+ if (action != null && action.equals("org.transdroid.ADD_MULTIPLE")) {
+ // Intent should have some extras pointing to possibly multiple torrents
+ String[] urls = intent.getStringArrayExtra("TORRENT_URLS");
+ String[] titles = intent.getStringArrayExtra("TORRENT_TITLES");
+ if (urls != null) {
+ for (int i = 0; i < urls.length; i++) {
+ String title = (titles != null && titles.length >= i ? titles[i] : NavigationHelper.extractNameFromUri(Uri.parse(urls[i])));
+ if (intent.hasExtra("PRIVATE_SOURCE")) {
+ // This is marked by the Search Module as being a private source site; get the url locally first
+ addTorrentFromPrivateSource(urls[i], title, intent.getStringExtra("PRIVATE_SOURCE"));
+ } else {
+ addTorrentByUrl(urls[i], title);
+ }
+ }
+ }
+ return;
+ }
+
+ // Add a torrent from a local or remote data URI?
+ if (dataUri == null) {
+ return;
+ }
+ if (dataUri.getScheme() == null) {
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_invalid_url_form).colorResource(R.color.red));
+ return;
+ }
+
+ // Get torrent title
+ String title = NavigationHelper.extractNameFromUri(dataUri);
+ if (intent.hasExtra("TORRENT_TITLE")) {
+ title = intent.getStringExtra("TORRENT_TITLE");
+ }
+
+ // Adding a torrent from the Android downloads manager
+ if (dataUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ addTorrentFromDownloads(dataUri, title);
+ return;
+ }
+
+ // Adding a torrent from http or https URL
+ if (dataUri.getScheme().equals("http") || dataUri.getScheme().equals("https")) {
+
+ String privateSource = getIntent().getStringExtra("PRIVATE_SOURCE");
+
+ WebsearchSetting match = null;
+ if (privateSource == null) {
+ // Check if the target URL is also defined as a web search in the user's settings
+ List websearches = applicationSettings.getWebsearchSettings();
+ for (WebsearchSetting setting : websearches) {
+ Uri uri = Uri.parse(setting.getBaseUrl());
+ if (uri.getHost() != null && uri.getHost().equals(dataUri.getHost())) {
+ match = setting;
+ break;
+ }
+ }
+ }
+
+ // If the URL is also a web search and it defines cookies, use the cookies by downloading the targeted
+ // torrent file (while supplies the cookies to the HTTP request) instead of sending the URL directly to the
+ // torrent client. If instead it is marked (by the Torrent Search module) as being form a private site, use
+ // the Search Module instead to download the url locally first.
+ if (match != null && match.getCookies() != null) {
+ addTorrentFromWeb(data, match, title);
+ } else if (privateSource != null) {
+ addTorrentFromPrivateSource(data, title, privateSource);
+ } else {
+ // Normally send the URL to the torrent client
+ addTorrentByUrl(data, title);
+ }
+ return;
+ }
+
+ // Adding a torrent from magnet URL
+ if (dataUri.getScheme().equals("magnet")) {
+ addTorrentByMagnetUrl(data, title);
+ return;
+ }
+
+ // Adding a local .torrent file; the title we show is just the file name
+ if (dataUri.getScheme().equals("file")) {
+ addTorrentByFile(data, title);
+ }
+
+ }
+
+ @Override
+ protected void onPause() {
+ if (searchMenu != null) {
+ searchMenu.collapseActionView();
+ }
+ stopAutoRefresh();
+ super.onPause();
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ if (searchMenu != null) {
+ searchMenu.expandActionView();
+ }
+ return true;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (awaitingAddLocalFile != null && awaitingAddTitle != null &&
+ Boolean.TRUE.equals(navigationHelper.handleTorrentReadPermissionResult(requestCode, grantResults))) {
+ addTorrentByFile(awaitingAddLocalFile, awaitingAddTitle);
+ }
+ }
+
+ @Click(R.id.addmenu_link_button)
+ protected void startUrlEntryDialog() {
+ addmenuButton.collapse();
+ UrlEntryDialog.show(this);
+ }
+
+ @Click(R.id.addmenu_file_button)
+ protected void startFilePicker() {
+ addmenuButton.collapse();
+ FilePickerHelper.startFilePicker(this);
+ }
+
+ @Background
+ @OnActivityResult(FilePickerHelper.ACTIVITY_FILEPICKER)
+ public void onFilePicked(int resultCode, Intent data) {
+ // We should have received an Intent with a local torrent's Uri as data from the file picker
+ if (data != null && data.getData() != null && !data.getData().toString().equals("")) {
+ Uri dataUri = data.getData();
+
+ // Get torrent title
+ String title = NavigationHelper.extractNameFromUri(dataUri);
+
+ // Adding a torrent from the via a content:// scheme (access through content provider stream)
+ if (dataUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ addTorrentFromDownloads(dataUri, title);
+ return;
+ }
+
+ // Adding a .torrent file directly via the file:// scheme (we can access it directly)
+ if (dataUri.getScheme().equals("file")) {
+ addTorrentByFile(data.getDataString(), title);
+ }
+
+ }
+ }
+
+ @OptionsItem(R.id.action_refresh)
+ public void refreshScreen() {
+ if (fragmentTorrents.isAdded())
+ fragmentTorrents.updateIsLoading(true);
+ refreshTorrents();
+ if (Daemon.supportsStats(currentConnection.getType())) {
+ getAdditionalStats();
+ }
+ }
+
+ @OptionsItem(R.id.action_enableturtle)
+ protected void enableTurtleMode() {
+ updateTurtleMode(true);
+ }
+
+ @OptionsItem(R.id.action_disableturtle)
+ protected void disableTurtleMode() {
+ updateTurtleMode(false);
+ }
+
+ @OptionsItem(R.id.action_rss)
+ protected void openRss() {
+ RssFeedsActivity_.intent(this).start();
+ }
+
+ @OptionsItem(R.id.action_settings)
+ protected void openSettings() {
+ MainSettingsActivity_.intent(this).start();
+ }
+
+ @OptionsItem(R.id.action_help)
+ protected void openHelp() {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/download/")));
+ }
+
+ @OptionsItem(R.id.action_sort_byname)
+ protected void sortByName() {
+ fragmentTorrents.sortBy(TorrentsSortBy.Alphanumeric);
+ }
+
+ @OptionsItem(R.id.action_sort_status)
+ protected void sortByStatus() {
+ fragmentTorrents.sortBy(TorrentsSortBy.Status);
+ }
+
+ @OptionsItem(R.id.action_sort_done)
+ protected void sortByDateDone() {
+ fragmentTorrents.sortBy(TorrentsSortBy.DateDone);
+ }
+
+ @OptionsItem(R.id.action_sort_added)
+ protected void sortByDateAdded() {
+ fragmentTorrents.sortBy(TorrentsSortBy.DateAdded);
+ }
+
+ @OptionsItem(R.id.action_sort_percent)
+ protected void sortByPercent() {
+ fragmentTorrents.sortBy(TorrentsSortBy.Percent);
+ }
+
+ @OptionsItem(R.id.action_sort_downspeed)
+ protected void sortByDownspeed() {
+ fragmentTorrents.sortBy(TorrentsSortBy.DownloadSpeed);
+ }
+
+ @OptionsItem(R.id.action_sort_upspeed)
+ protected void sortByUpspeed() {
+ fragmentTorrents.sortBy(TorrentsSortBy.UploadSpeed);
+ }
+
+ @OptionsItem(R.id.action_sort_ratio)
+ protected void sortByRatio() {
+ fragmentTorrents.sortBy(TorrentsSortBy.Ratio);
+ }
+
+ @OptionsItem(R.id.action_sort_size)
+ protected void sortBySize() {
+ fragmentTorrents.sortBy(TorrentsSortBy.Size);
+ }
+
+ private SearchView.OnQueryTextListener filterQueryTextChanged = new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ // Redirect to filter method which will directly apply it
+ filterTorrents(newText);
+ return true;
+ }
+ };
+
+ /**
+ * Redirect the newly entered list filter to the torrents fragment.
+ *
+ * @param newFilterText The newly entered filter (or empty to clear the current filter).
+ */
+ public void filterTorrents(String newFilterText) {
+ fragmentTorrents.applyTextFilter(newFilterText);
+ }
+
+ /**
+ * Shows the a details fragment for the given torrent, either in the dedicated details fragment pane, in the same pane as the torrent list was
+ * displayed or by starting a details activity.
+ *
+ * @param torrent The torrent to show detailed statistics for
+ */
+ public void openDetails(Torrent torrent) {
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.updateTorrent(torrent);
+ } else {
+ DetailsActivity_.intent(this).torrent(torrent).currentLabels(lastNavigationLabels).startForResult(RESULT_DETAILS);
+ }
+ }
+
+ @Background
+ protected void refreshTorrents() {
+ String startConnectionId = currentConnection.getSettings().getIdString();
+ DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
+ if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
+ // During the command execution the user changed the server, so we are no longer interested in the result
+ return;
+ }
+ if (result instanceof RetrieveTaskSuccessResult) {
+ onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, true);
+ }
+ }
+
+ @Background
+ public void refreshTorrentDetails(Torrent torrent) {
+ if (!Daemon.supportsFineDetails(currentConnection.getType())) {
+ return;
+ }
+ String startConnectionId = currentConnection.getSettings().getIdString();
+ DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
+ if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
+ // During the command execution the user changed the server, so we are no longer interested in the result
+ return;
+ }
+ if (result instanceof GetTorrentDetailsTaskSuccessResult) {
+ onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ public void refreshTorrentFiles(Torrent torrent) {
+ if (!Daemon.supportsFileListing(currentConnection.getType())) {
+ return;
+ }
+ String startConnectionId = currentConnection.getSettings().getIdString();
+ DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
+ if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
+ // During the command execution the user changed the server, so we are no longer interested in the result
+ return;
+ }
+ if (result instanceof GetFileListTaskSuccessResult) {
+ onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ protected void getAdditionalStats() {
+ String startConnectionId = currentConnection.getSettings().getIdString();
+ DaemonTaskResult result = GetStatsTask.create(currentConnection).execute(log);
+ if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
+ // During the command execution the user changed the server, so we are no longer interested in the result
+ return;
+ }
+ if (result instanceof GetStatsTaskSuccessResult) {
+ onTurtleModeRetrieved(((GetStatsTaskSuccessResult) result).isAlternativeModeEnabled());
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ protected void updateTurtleMode(boolean enable) {
+ String startConnectionId = currentConnection.getSettings().getIdString();
+ DaemonTaskResult result = SetAlternativeModeTask.create(currentConnection, enable).execute(log);
+ if (!startConnectionId.equals(currentConnection.getSettings().getIdString())) {
+ // During the command execution the user changed the server, so we are no longer interested in the result
+ return;
+ }
+ if (result instanceof DaemonTaskSuccessResult) {
+ // Success; no need to retrieve it again - just update the visual indicator
+ onTurtleModeRetrieved(enable);
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ public void addTorrentByUrl(String url, String title) {
+ DaemonTaskResult result = AddByUrlTask.create(currentConnection, url, title).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
+ refreshTorrents();
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ public void addTorrentByMagnetUrl(String url, String title) {
+
+ // Since v39 Chrome sends application/x-www-form-urlencoded magnet links and most torrent clients do not understand those, so decode first
+ try {
+ url = URLDecoder.decode(url, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // Ignore: UTF-8 is always available on Android devices
+ } catch (IllegalArgumentException e) {
+ // Illegal character or escape sequence; fail task to show error
+ onCommunicationError(new DaemonTaskFailureResult(AddByMagnetUrlTask.create(currentConnection, url),
+ new DaemonException(DaemonException.ExceptionType.FileAccessError, "Invalid characters in magnet link")), false);
+ return;
+ }
+
+ AddByMagnetUrlTask addByMagnetUrlTask = AddByMagnetUrlTask.create(currentConnection, url);
+ if (!Daemon.supportsAddByMagnetUrl(currentConnection.getType())) {
+ // No support for magnet links: forcefully let the task fail to report the error
+ onCommunicationError(new DaemonTaskFailureResult(addByMagnetUrlTask, new DaemonException(DaemonException.ExceptionType.MethodUnsupported,
+ currentConnection.getType().name() + " does not support magnet links")), false);
+ return;
+ }
+
+ DaemonTaskResult result = addByMagnetUrlTask.execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
+ refreshTorrents();
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+
+ }
+
+ @Background
+ protected void addTorrentByFile(String localFile, String title) {
+ if (!navigationHelper.checkTorrentReadPermission(this)) {
+ // No read permission yet (which we get the result of in onRequestPermissionsResult)
+ awaitingAddLocalFile = localFile;
+ awaitingAddTitle = title;
+ return;
+ }
+ DaemonTaskResult result = AddByFileTask.create(currentConnection, localFile).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_added, title));
+ refreshTorrents();
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ private void addTorrentFromDownloads(Uri contentUri, String title) {
+
+ try {
+ // Open the content uri as input stream and this via a local temporary file
+ addTorrentFromStream(getContentResolver().openInputStream(contentUri), title);
+ } catch (SecurityException e) {
+ // No longer access to this file
+ log.e(this, "No access given to " + contentUri.toString() + ": " + e.toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
+ } catch (FileNotFoundException e) {
+ log.e(this, contentUri.toString() + " does not exist: " + e.toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
+ }
+ }
+
+ @Background
+ protected void addTorrentFromPrivateSource(String url, String title, String source) {
+
+ try {
+ InputStream input = SearchHelper_.getInstance_(this).getFile(source, url);
+ addTorrentFromStream(input, title);
+ } catch (Exception e) {
+ log.e(this, "Can't download private site torrent " + url + " from " + source + ": " + e.toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
+ }
+
+ }
+
+ @Background
+ protected void addTorrentFromWeb(String url, WebsearchSetting websearchSetting, String title) {
+
+ try {
+ // Cookies are taken from the websearchSetting that we already matched against this target URL
+ DefaultHttpClient httpclient = HttpHelper.createStandardHttpClient(false, null, null, true, null, 10000, null, -1);
+ Map cookies = HttpHelper.parseCookiePairs(websearchSetting.getCookies());
+ String domain = Uri.parse(url).getHost();
+ for (Entry pair : cookies.entrySet()) {
+ BasicClientCookie cookie = new BasicClientCookie(pair.getKey(), pair.getValue());
+ cookie.setPath("/");
+ cookie.setDomain(domain);
+ httpclient.getCookieStore().addCookie(cookie);
+ }
+
+ // Download the torrent at the specified URL (which will first be written to a temporary file)
+ // If we get an HTTP 401, 403 or 404 response, show an error to the user
+ HttpResponse response = httpclient.execute(new HttpGet(url));
+ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED ||
+ response.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN ||
+ response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
+ log.e(this, "Can't retrieve web torrent " + url + ": Unexpected HTTP response status code " +
+ response.getStatusLine().toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_401).colorResource(R.color.red));
+ return;
+ }
+ InputStream input = response.getEntity().getContent();
+ addTorrentFromStream(input, title);
+ } catch (Exception e) {
+ log.e(this, "Can't retrieve web torrent " + url + ": " + e.toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
+ }
+ }
+
+ @Background
+ protected void addTorrentFromStream(InputStream input, String title) {
+
+ File tempFile = new File("/not/yet/set");
+ try {
+ // Write a temporary file with the torrent contents
+ tempFile = File.createTempFile("transdroid_", ".torrent", getCacheDir());
+ FileOutputStream output = new FileOutputStream(tempFile);
+ try {
+ final byte[] buffer = new byte[1024];
+ int read;
+ while ((read = input.read(buffer)) != -1) {
+ output.write(buffer, 0, read);
+ }
+ output.flush();
+ String fileName = Uri.fromFile(tempFile).toString();
+ addTorrentByFile(fileName, title);
+ } finally {
+ output.close();
+ }
+ } catch (IOException e) {
+ log.e(this, "Can't write input stream to " + tempFile.toString() + ": " + e.toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
+ } finally {
+ try {
+ if (input != null) {
+ input.close();
+ }
+ } catch (IOException e) {
+ log.e(this, "Error closing the input stream " + tempFile.toString() + ": " + e.toString());
+ SnackbarManager.show(Snackbar.with(this).text(R.string.error_torrentfile).colorResource(R.color.red));
+ }
+ }
+ }
+
+ @Background
+ @Override
+ public void resumeTorrent(Torrent torrent) {
+ torrent.mimicResume();
+ DaemonTaskResult result = ResumeTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_resumed, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void pauseTorrent(Torrent torrent) {
+ torrent.mimicPause();
+ DaemonTaskResult result = PauseTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_paused, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void startTorrent(Torrent torrent, boolean forced) {
+ torrent.mimicStart();
+ DaemonTaskResult result = StartTask.create(currentConnection, torrent, forced).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_started, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void stopTorrent(Torrent torrent) {
+ torrent.mimicStop();
+ DaemonTaskResult result = StopTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_stopped, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void removeTorrent(Torrent torrent, boolean withData) {
+ DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result,
+ getString(withData ? R.string.result_removed_with_data : R.string.result_removed, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updateLabel(Torrent torrent, String newLabel) {
+ torrent.mimicNewLabel(newLabel);
+ DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result,
+ newLabel == null ? getString(R.string.result_labelremoved) : getString(R.string.result_labelset, newLabel));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void toggleSequentialDownload(Torrent torrent, boolean sequentialState) {
+ torrent.mimicSequentialDownload(sequentialState);
+ DaemonTaskResult result = ToggleSequentialDownloadTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_togglesequential));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void toggleFirstLastPieceDownload(Torrent torrent, boolean firstLastPieceState) {
+ torrent.mimicFirstLastPieceDownload(firstLastPieceState);
+ DaemonTaskResult result = ToggleFirstLastPieceDownloadTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.action_toggle_firstlastpiece));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void forceRecheckTorrent(Torrent torrent) {
+ torrent.mimicCheckingStatus();
+ DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_recheckedstarted, torrent.getName()));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updateTrackers(Torrent torrent, List newTrackers) {
+ DaemonTaskResult result = SetTrackersTask.create(currentConnection, torrent, newTrackers).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_trackersupdated));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updateLocation(Torrent torrent, String newLocation) {
+ DaemonTaskResult result = SetDownloadLocationTask.create(currentConnection, torrent, newLocation).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_locationset, newLocation));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ @Override
+ public void updatePriority(Torrent torrent, List files, Priority priority) {
+ DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<>(files)).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @Background
+ public void updateMaxSpeeds(Integer maxDownloadSpeed, Integer maxUploadSpeed) {
+ DaemonTaskResult result = SetTransferRatesTask.create(currentConnection, maxUploadSpeed, maxDownloadSpeed).execute(log);
+ if (result instanceof DaemonTaskSuccessResult) {
+ onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_maxspeedsset));
+ } else {
+ onCommunicationError((DaemonTaskFailureResult) result, false);
+ }
+ }
+
+ @UiThread
+ protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) {
+ // Refresh the screen as well
+ refreshScreen();
+ SnackbarManager.show(Snackbar.with(this).text(successMessage));
+ }
+
+ @UiThread
+ protected void onCommunicationError(DaemonTaskFailureResult result, boolean isCritical) {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ log.i(this, result.getException().toString());
+ String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
+ SnackbarManager.show(Snackbar.with(this).text(error).colorResource(R.color.red).type(SnackbarType.MULTI_LINE));
+ fragmentTorrents.updateIsLoading(false);
+ if (isCritical) {
+ fragmentTorrents.updateError(error);
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.updateIsLoading(false, error);
+ }
+ }
+ }
+
+ @UiThread
+ protected void onTorrentsRetrieved(List torrents, List labels) {
+
+ lastNavigationLabels = Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled));
+
+ // Report the newly retrieved list of torrents to the torrents fragment
+ fragmentTorrents.updateIsLoading(false);
+ fragmentTorrents.updateTorrents(new ArrayList<>(torrents), lastNavigationLabels);
+
+ // Update the details fragment if the currently shown torrent is in the newly retrieved list
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.perhapsUpdateTorrent(torrents);
+ }
+
+ // Update local list of labels in the navigation
+ navigationListAdapter.updateLabels(lastNavigationLabels);
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.updateLabels(lastNavigationLabels);
+ }
+
+ // Perhaps we were still waiting to preselect the last used filter (on a fresh application start)
+ if (preselectNavigationFilter != null && navigationListAdapter != null) {
+ for (int i = 0; i < navigationListAdapter.getCount(); i++) {
+ // Look up the navigation filter item, which is represented as simple list item (and might not exist any
+ // more, such as with a label that is deleted on the server)
+ Object item = navigationListAdapter.getItem(i);
+ if (item instanceof SimpleListItem && item instanceof NavigationFilter &&
+ ((NavigationFilter) item).getCode().equals(preselectNavigationFilter)) {
+ filterSelected((SimpleListItem) item, false);
+ break;
+ }
+ }
+ // Only preselect after the first update we receive (even if the filter wasn't found any more)
+ preselectNavigationFilter = null;
+ }
+
+ // Update the server status (counts and speeds) in the action bar
+ serverStatusView
+ .updateStatus(torrents, systemSettings.treatDormantAsInactive(), Daemon.supportsSetTransferRates(currentConnection.getType()));
+
+ }
+
+ @UiThread
+ protected void onTorrentDetailsRetrieved(Torrent torrent, TorrentDetails torrentDetails) {
+ // Update the details fragment with the new fine details for the shown torrent
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.updateTorrentDetails(torrent, torrentDetails);
+ }
+ }
+
+ @UiThread
+ protected void onTorrentFilesRetrieved(Torrent torrent, List torrentFiles) {
+ // Update the details fragment with the newly retrieved list of files
+ if (fragmentDetails != null && fragmentDetails.isResumed()) {
+ fragmentDetails.updateTorrentFiles(torrent, new ArrayList<>(torrentFiles));
+ }
+ }
+
+ @UiThread
+ protected void onTurtleModeRetrieved(boolean turtleModeEnabled) {
+ this.turtleModeEnabled = turtleModeEnabled;
+ invalidateOptionsMenu();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java b/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
index 9e2dccd0..5b901f9a 100644
--- a/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
+++ b/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -18,10 +18,12 @@ package org.transdroid.core.gui;
import android.app.Fragment;
import android.content.Context;
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.ActionMenuView;
import androidx.appcompat.widget.Toolbar;
+
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
@@ -64,436 +66,446 @@ import java.util.Locale;
/**
* Fragment that shows a list of torrents that are active on the server. It supports sorting and filtering and can show connection progress and
* issues. However, actual task starting and execution and overall navigation elements are part of the containing activity, not this fragment.
+ *
* @author Eric Kok
*/
@EFragment(R.layout.fragment_torrents)
public class TorrentsFragment extends Fragment implements OnLabelPickedListener {
- // Local data
- @Bean
- protected ApplicationSettings applicationSettings;
- @Bean
- protected SystemSettings systemSettings;
- // HACK Working around #391 while hopefully we rework the UI in the future to persist the list in db or something
- protected static ArrayList torrents = null;
- @InstanceState
- protected ArrayList lastMultiSelectedTorrents;
- @InstanceState
- protected ArrayList currentLabels;
- @InstanceState
- protected NavigationFilter currentNavigationFilter = null;
- @InstanceState
- protected TorrentsSortBy currentSortOrder = TorrentsSortBy.Alphanumeric;
- @InstanceState
- protected boolean currentSortDescending = false;
- @InstanceState
- protected String currentTextFilter = null;
- @InstanceState
- protected boolean hasAConnection = false;
- @InstanceState
- protected boolean isLoading = true;
- @InstanceState
- protected String connectionErrorMessage = null;
- @InstanceState
- protected Daemon daemonType;
-
- // Views
- @ViewById
- protected SwipeRefreshLayout swipeRefreshLayout;
- @ViewById
- protected ListView torrentsList;
- @ViewById
- protected TextView emptyText;
- @ViewById
- protected TextView nosettingsText;
- @ViewById
- protected TextView errorText;
- @ViewById
- protected ProgressBar loadingProgress;
-
- @AfterViews
- protected void init() {
-
- // Load the requested sort order from the user settings
- this.currentSortOrder = applicationSettings.getLastUsedSortOrder();
- this.currentSortDescending = applicationSettings.getLastUsedSortDescending();
-
- // Set up the list adapter, which allows multi-select and fast scrolling
- torrentsList.setAdapter(TorrentsAdapter_.getInstance_(getActivity()));
- torrentsList.setMultiChoiceModeListener(onTorrentsSelected);
- torrentsList.setFastScrollEnabled(true);
- if (torrents != null) {
- updateTorrents(torrents, currentLabels);
- }
- // 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();
- swipeRefreshLayout.setRefreshing(false); // Use our custom indicator
- }
- });
- }
- nosettingsText.setText(getString(R.string.navigation_nosettings, getString(R.string.app_name)));
-
- }
-
- /**
- * Updates the list adapter to show a new list of torrent objects, replacing the old torrents completely
- * @param newTorrents The new, updated list of torrents
- */
- public void updateTorrents(ArrayList newTorrents, ArrayList currentLabels) {
- if (this.isDetached()) {
- return;
- }
-
- torrents = newTorrents;
- this.currentLabels = currentLabels;
- applyAllFilters();
- }
-
- /**
- * Just look for a specific torrent in the currently shown list (by its unique id) and update only this
- * @param affected The affected torrent to update
- * @param wasRemoved Whether the affected torrent was indeed removed; otherwise it was updated somehow
- */
- public void quickUpdateTorrent(Torrent affected, boolean wasRemoved) {
- if (this.isDetached()) {
- return;
- }
-
- // Remove the old torrent object first
- Iterator iter = torrents.iterator();
- while (iter.hasNext()) {
- Torrent torrent = iter.next();
- if (torrent.getUniqueID().equals(affected.getUniqueID())) {
- iter.remove();
- break;
- }
- }
- // In case it was an update, add the updated torrent object
- if (!wasRemoved) {
- torrents.add(affected);
- }
- // Now refresh the screen
- applyAllFilters();
- }
-
- /**
- * Clears the currently visible list of torrents.
- * @param clearError Also clear any error message
- * @param clearFilter Also clear any selected filter
- */
- public void clear(boolean clearError, boolean clearFilter) {
- torrents = null;
- if (clearError) {
- this.connectionErrorMessage = null;
- }
- if (clearFilter) {
- this.currentTextFilter = null;
- this.currentNavigationFilter = null;
- }
- applyAllFilters();
- }
-
- /**
- * Stores the new sort order (for future refreshes) and sorts the current visible list. If the given new sort property equals the existing
- * property, the list sort order is reversed instead.
- * @param newSortOrder The sort order that the user selected.
- */
- public void sortBy(TorrentsSortBy newSortOrder) {
- // Update the sort order property and direction and store this last used setting
- if (this.currentSortOrder == newSortOrder) {
- this.currentSortDescending = !this.currentSortDescending;
- } else {
- this.currentSortOrder = newSortOrder;
- this.currentSortDescending = false;
- }
- applicationSettings.setLastUsedSortOrder(this.currentSortOrder, this.currentSortDescending);
- applyAllFilters();
- }
-
- public void applyTextFilter(String newTextFilter) {
- this.currentTextFilter = newTextFilter;
- // Show the new filtered list
- applyAllFilters();
- }
-
- /**
- * Apply a filter on the current list of all torrents, showing the appropriate sublist of torrents only
- * @param newFilter The new filter to apply to the local list of torrents
- */
- public void applyNavigationFilter(NavigationFilter newFilter) {
- this.currentNavigationFilter = newFilter;
- applyAllFilters();
- }
-
- private void applyAllFilters() {
-
- // No torrents? Directly update views accordingly
- if (torrents == null) {
- updateViewVisibility();
- return;
- }
-
- // Filter the list of torrents to show according to navigation and text filters
- ArrayList filteredTorrents = new ArrayList<>(torrents);
- if (currentNavigationFilter != null) {
- // Remove torrents that do not match the selected navigation filter
- for (Iterator torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) {
- if (!currentNavigationFilter.matches(torrentIter.next(), systemSettings.treatDormantAsInactive())) {
- torrentIter.remove();
- }
- }
- }
- if (currentTextFilter != null) {
- // Remove torrents that do not contain the text filter string
- for (Iterator torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) {
- if (!torrentIter.next().getName().toLowerCase(Locale.getDefault()).contains(currentTextFilter.toLowerCase(Locale.getDefault()))) {
- torrentIter.remove();
- }
- }
- }
-
- // Sort the list of filtered torrents
- Collections.sort(filteredTorrents, new TorrentsComparator(daemonType, this.currentSortOrder, this.currentSortDescending));
-
- if (torrentsList.getAdapter() != null) {
- ((TorrentsAdapter) torrentsList.getAdapter()).update(filteredTorrents);
- }
- updateViewVisibility();
- }
-
- private MultiChoiceModeListener onTorrentsSelected = new MultiChoiceModeListener() {
-
- private SelectionManagerMode selectionManagerMode;
- private ActionMenuView actionsMenu;
- private Toolbar actionsToolbar;
- private FloatingActionsMenu addmenuButton;
-
- @Override
- public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
- // Show contextual action bars to start/stop/remove/etc. torrents in batch mode
- if (actionsMenu == null) {
- actionsMenu = ((TorrentsActivity) getActivity()).contextualMenu;
- actionsToolbar = ((TorrentsActivity) getActivity()).actionsToolbar;
- addmenuButton = ((TorrentsActivity) getActivity()).addmenuButton;
- }
- actionsToolbar.setEnabled(false);
- actionsMenu.setVisibility(View.VISIBLE);
- addmenuButton.setVisibility(View.GONE);
- actionsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem menuItem) {
- return onActionItemClicked(mode, menuItem);
- }
- });
- actionsMenu.getMenu().clear();
- getActivity().getMenuInflater().inflate(R.menu.fragment_torrents_cab, actionsMenu.getMenu());
- Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
- selectionManagerMode = new SelectionManagerMode(themedContext, torrentsList, R.plurals.navigation_torrentsselected);
- selectionManagerMode.onCreateActionMode(mode, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- selectionManagerMode.onPrepareActionMode(mode, menu);
- // Hide/show options depending on the type of server we are connected to
- if (daemonType != null) {
- actionsMenu.getMenu().findItem(R.id.action_start).setVisible(Daemon.supportsStoppingStarting(daemonType));
- actionsMenu.getMenu().findItem(R.id.action_stop).setVisible(Daemon.supportsStoppingStarting(daemonType));
- actionsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(Daemon.supportsSetLabel(daemonType));
- }
- // Pause autorefresh
- if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
- ((TorrentsActivity) getActivity()).stopRefresh = true;
- ((TorrentsActivity) getActivity()).stopAutoRefresh();
- }
- return true;
- }
-
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-
- // Get checked torrents
- ArrayList checked = new ArrayList<>();
- for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) {
- if (torrentsList.getCheckedItemPositions().valueAt(i) && i < torrentsList.getAdapter().getCount()) {
- checked.add((Torrent) torrentsList.getAdapter().getItem(torrentsList.getCheckedItemPositions().keyAt(i)));
- }
- }
-
- int itemId = item.getItemId();
- if (itemId == R.id.action_resume) {
- for (Torrent torrent : checked) {
- getTasksExecutor().resumeTorrent(torrent);
- }
- mode.finish();
- return true;
- } else if (itemId == R.id.action_pause) {
- for (Torrent torrent : checked) {
- getTasksExecutor().pauseTorrent(torrent);
- }
- mode.finish();
- return true;
- } else if (itemId == R.id.action_start) {
- for (Torrent torrent : checked) {
- getTasksExecutor().startTorrent(torrent, false);
- }
- mode.finish();
- return true;
- } else if (itemId == R.id.action_stop) {
- for (Torrent torrent : checked) {
- getTasksExecutor().stopTorrent(torrent);
- }
- mode.finish();
- return true;
- } else if (itemId == R.id.action_remove_default) {
- for (Torrent torrent : checked) {
- getTasksExecutor().removeTorrent(torrent, false);
- }
- mode.finish();
- return true;
- } else if (itemId == R.id.action_remove_withdata) {
- for (Torrent torrent : checked) {
- getTasksExecutor().removeTorrent(torrent, true);
- }
- mode.finish();
- return true;
- } else if (itemId == R.id.action_setlabel) {
- lastMultiSelectedTorrents = checked;
- if (currentLabels != null) {
- SetLabelDialog.show(getActivity(), TorrentsFragment.this, currentLabels);
- }
- mode.finish();
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
- selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- // Resume autorefresh
- if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
- ((TorrentsActivity) getActivity()).stopRefresh = false;
- ((TorrentsActivity) getActivity()).startAutoRefresh();
- }
- selectionManagerMode.onDestroyActionMode(mode);
- actionsMenu.setVisibility(View.GONE);
- actionsToolbar.setEnabled(true);
- addmenuButton.setVisibility(View.VISIBLE);
- }
-
- };
-
- @Click
- protected void emptyTextClicked() {
- // Refresh the activity (that contains this fragment) when the empty view gear is clicked
- if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
- ((RefreshableActivity) getActivity()).refreshScreen();
- }
- }
-
- @Click
- protected void errorTextClicked() {
- // Refresh the activity (that contains this fragment) when the error view gear is clicked
- if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
- ((RefreshableActivity) getActivity()).refreshScreen();
- }
- }
-
- @ItemClick(R.id.torrents_list)
- protected void torrentsListClicked(Torrent torrent) {
- // Show the torrent details fragment
- ((TorrentsActivity) getActivity()).openDetails(torrent);
- }
-
- @Override
- public void onLabelPicked(String newLabel) {
- for (Torrent torrent : lastMultiSelectedTorrents) {
- getTasksExecutor().updateLabel(torrent, newLabel);
- }
- }
-
- /**
- * Updates the shown screen depending on whether we have a connection (so torrents can be shown) or not (in case we need to show a message
- * suggesting help). This should only ever be called on the UI thread.
- * @param hasAConnection True if the user has servers configured and therefore has a connection that can be used
- */
- public void updateConnectionStatus(boolean hasAConnection, Daemon daemonType) {
- if (!isResumed()) return;
- this.hasAConnection = hasAConnection;
- this.daemonType = daemonType;
- if (!hasAConnection) {
- torrentsList.setVisibility(View.GONE);
- emptyText.setVisibility(View.GONE);
- loadingProgress.setVisibility(View.GONE);
- errorText.setVisibility(View.GONE);
- nosettingsText.setVisibility(View.VISIBLE);
- swipeRefreshLayout.setEnabled(false);
- clear(true, true); // Indirectly also calls updateViewVisibility()
- } else {
- updateViewVisibility();
- }
- }
-
- /**
- * Updates the shown screen depending on whether the torrents are loading. This should only ever be called on the UI thread.
- * @param isLoading True if the list of torrents is (re)loading, false otherwise
- */
- public void updateIsLoading(boolean isLoading) {
- if (!isResumed()) return;
- this.isLoading = isLoading;
- if (isLoading) {
- clear(true, false); // Indirectly also calls updateViewVisibility()
- } else {
- updateViewVisibility();
- }
- }
-
- /**
- * Updates the shown screen depending on whether a connection error occurred. This should only ever be called on the UI thread.
- * @param connectionErrorMessage The error message from the last failed connection attempt, or null to clear the visible error text
- */
- public void updateError(String connectionErrorMessage) {
- if (!isResumed()) return;
- this.connectionErrorMessage = connectionErrorMessage;
- errorText.setText(connectionErrorMessage);
- if (connectionErrorMessage != null) {
- clear(false, false); // Indirectly also calls updateViewVisibility()
- } else {
- updateViewVisibility();
- }
- }
-
- private void updateViewVisibility() {
- if (!hasAConnection) {
- return;
- }
- boolean isEmpty = torrents == null || torrentsList.getAdapter() != null && torrentsList.getAdapter().isEmpty();
- boolean hasError = connectionErrorMessage != null;
- nosettingsText.setVisibility(View.GONE);
- errorText.setVisibility(hasError ? View.VISIBLE : View.GONE);
- torrentsList.setVisibility(!hasError && !isLoading && !isEmpty ? View.VISIBLE : View.GONE);
- loadingProgress.setVisibility(!hasError && isLoading ? View.VISIBLE : View.GONE);
- emptyText.setVisibility(!hasError && !isLoading && isEmpty ? View.VISIBLE : View.GONE);
- swipeRefreshLayout.setEnabled(true);
- }
-
- /**
- * Returns the object responsible for executing torrent tasks against a connected server
- * @return The executor for tasks on some torrent
- */
- private TorrentTasksExecutor getTasksExecutor() {
- // NOTE: Assumes the activity implements all the required torrent tasks
- return (TorrentTasksExecutor) getActivity();
- }
+ // Local data
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @Bean
+ protected SystemSettings systemSettings;
+ // HACK Working around #391 while hopefully we rework the UI in the future to persist the list in db or something
+ protected static ArrayList torrents = null;
+ @InstanceState
+ protected ArrayList lastMultiSelectedTorrents;
+ @InstanceState
+ protected ArrayList currentLabels;
+ @InstanceState
+ protected NavigationFilter currentNavigationFilter = null;
+ @InstanceState
+ protected TorrentsSortBy currentSortOrder = TorrentsSortBy.Alphanumeric;
+ @InstanceState
+ protected boolean currentSortDescending = false;
+ @InstanceState
+ protected String currentTextFilter = null;
+ @InstanceState
+ protected boolean hasAConnection = false;
+ @InstanceState
+ protected boolean isLoading = true;
+ @InstanceState
+ protected String connectionErrorMessage = null;
+ @InstanceState
+ protected Daemon daemonType;
+
+ // Views
+ @ViewById
+ protected SwipeRefreshLayout swipeRefreshLayout;
+ @ViewById
+ protected ListView torrentsList;
+ @ViewById
+ protected TextView emptyText;
+ @ViewById
+ protected TextView nosettingsText;
+ @ViewById
+ protected TextView errorText;
+ @ViewById
+ protected ProgressBar loadingProgress;
+
+ @AfterViews
+ protected void init() {
+
+ // Load the requested sort order from the user settings
+ this.currentSortOrder = applicationSettings.getLastUsedSortOrder();
+ this.currentSortDescending = applicationSettings.getLastUsedSortDescending();
+
+ // Set up the list adapter, which allows multi-select and fast scrolling
+ torrentsList.setAdapter(TorrentsAdapter_.getInstance_(getActivity()));
+ torrentsList.setMultiChoiceModeListener(onTorrentsSelected);
+ torrentsList.setFastScrollEnabled(true);
+ if (torrents != null) {
+ updateTorrents(torrents, currentLabels);
+ }
+ // 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();
+ swipeRefreshLayout.setRefreshing(false); // Use our custom indicator
+ }
+ });
+ }
+ nosettingsText.setText(getString(R.string.navigation_nosettings, getString(R.string.app_name)));
+
+ }
+
+ /**
+ * Updates the list adapter to show a new list of torrent objects, replacing the old torrents completely
+ *
+ * @param newTorrents The new, updated list of torrents
+ */
+ public void updateTorrents(ArrayList newTorrents, ArrayList currentLabels) {
+ if (this.isDetached()) {
+ return;
+ }
+
+ torrents = newTorrents;
+ this.currentLabels = currentLabels;
+ applyAllFilters();
+ }
+
+ /**
+ * Just look for a specific torrent in the currently shown list (by its unique id) and update only this
+ *
+ * @param affected The affected torrent to update
+ * @param wasRemoved Whether the affected torrent was indeed removed; otherwise it was updated somehow
+ */
+ public void quickUpdateTorrent(Torrent affected, boolean wasRemoved) {
+ if (this.isDetached()) {
+ return;
+ }
+
+ // Remove the old torrent object first
+ Iterator iter = torrents.iterator();
+ while (iter.hasNext()) {
+ Torrent torrent = iter.next();
+ if (torrent.getUniqueID().equals(affected.getUniqueID())) {
+ iter.remove();
+ break;
+ }
+ }
+ // In case it was an update, add the updated torrent object
+ if (!wasRemoved) {
+ torrents.add(affected);
+ }
+ // Now refresh the screen
+ applyAllFilters();
+ }
+
+ /**
+ * Clears the currently visible list of torrents.
+ *
+ * @param clearError Also clear any error message
+ * @param clearFilter Also clear any selected filter
+ */
+ public void clear(boolean clearError, boolean clearFilter) {
+ torrents = null;
+ if (clearError) {
+ this.connectionErrorMessage = null;
+ }
+ if (clearFilter) {
+ this.currentTextFilter = null;
+ this.currentNavigationFilter = null;
+ }
+ applyAllFilters();
+ }
+
+ /**
+ * Stores the new sort order (for future refreshes) and sorts the current visible list. If the given new sort property equals the existing
+ * property, the list sort order is reversed instead.
+ *
+ * @param newSortOrder The sort order that the user selected.
+ */
+ public void sortBy(TorrentsSortBy newSortOrder) {
+ // Update the sort order property and direction and store this last used setting
+ if (this.currentSortOrder == newSortOrder) {
+ this.currentSortDescending = !this.currentSortDescending;
+ } else {
+ this.currentSortOrder = newSortOrder;
+ this.currentSortDescending = false;
+ }
+ applicationSettings.setLastUsedSortOrder(this.currentSortOrder, this.currentSortDescending);
+ applyAllFilters();
+ }
+
+ public void applyTextFilter(String newTextFilter) {
+ this.currentTextFilter = newTextFilter;
+ // Show the new filtered list
+ applyAllFilters();
+ }
+
+ /**
+ * Apply a filter on the current list of all torrents, showing the appropriate sublist of torrents only
+ *
+ * @param newFilter The new filter to apply to the local list of torrents
+ */
+ public void applyNavigationFilter(NavigationFilter newFilter) {
+ this.currentNavigationFilter = newFilter;
+ applyAllFilters();
+ }
+
+ private void applyAllFilters() {
+
+ // No torrents? Directly update views accordingly
+ if (torrents == null) {
+ updateViewVisibility();
+ return;
+ }
+
+ // Filter the list of torrents to show according to navigation and text filters
+ ArrayList filteredTorrents = new ArrayList<>(torrents);
+ if (currentNavigationFilter != null) {
+ // Remove torrents that do not match the selected navigation filter
+ for (Iterator torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) {
+ if (!currentNavigationFilter.matches(torrentIter.next(), systemSettings.treatDormantAsInactive())) {
+ torrentIter.remove();
+ }
+ }
+ }
+ if (currentTextFilter != null) {
+ // Remove torrents that do not contain the text filter string
+ for (Iterator torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) {
+ if (!torrentIter.next().getName().toLowerCase(Locale.getDefault()).contains(currentTextFilter.toLowerCase(Locale.getDefault()))) {
+ torrentIter.remove();
+ }
+ }
+ }
+
+ // Sort the list of filtered torrents
+ Collections.sort(filteredTorrents, new TorrentsComparator(daemonType, this.currentSortOrder, this.currentSortDescending));
+
+ if (torrentsList.getAdapter() != null) {
+ ((TorrentsAdapter) torrentsList.getAdapter()).update(filteredTorrents);
+ }
+ updateViewVisibility();
+ }
+
+ private MultiChoiceModeListener onTorrentsSelected = new MultiChoiceModeListener() {
+
+ private SelectionManagerMode selectionManagerMode;
+ private ActionMenuView actionsMenu;
+ private Toolbar actionsToolbar;
+ private FloatingActionsMenu addmenuButton;
+
+ @Override
+ public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
+ // Show contextual action bars to start/stop/remove/etc. torrents in batch mode
+ if (actionsMenu == null) {
+ actionsMenu = ((TorrentsActivity) getActivity()).contextualMenu;
+ actionsToolbar = ((TorrentsActivity) getActivity()).actionsToolbar;
+ addmenuButton = ((TorrentsActivity) getActivity()).addmenuButton;
+ }
+ actionsToolbar.setEnabled(false);
+ actionsMenu.setVisibility(View.VISIBLE);
+ addmenuButton.setVisibility(View.GONE);
+ actionsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ return onActionItemClicked(mode, menuItem);
+ }
+ });
+ actionsMenu.getMenu().clear();
+ getActivity().getMenuInflater().inflate(R.menu.fragment_torrents_cab, actionsMenu.getMenu());
+ Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
+ selectionManagerMode = new SelectionManagerMode(themedContext, torrentsList, R.plurals.navigation_torrentsselected);
+ selectionManagerMode.onCreateActionMode(mode, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ selectionManagerMode.onPrepareActionMode(mode, menu);
+ // Hide/show options depending on the type of server we are connected to
+ if (daemonType != null) {
+ actionsMenu.getMenu().findItem(R.id.action_start).setVisible(Daemon.supportsStoppingStarting(daemonType));
+ actionsMenu.getMenu().findItem(R.id.action_stop).setVisible(Daemon.supportsStoppingStarting(daemonType));
+ actionsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(Daemon.supportsSetLabel(daemonType));
+ }
+ // Pause autorefresh
+ if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
+ ((TorrentsActivity) getActivity()).stopRefresh = true;
+ ((TorrentsActivity) getActivity()).stopAutoRefresh();
+ }
+ return true;
+ }
+
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+
+ // Get checked torrents
+ ArrayList checked = new ArrayList<>();
+ for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) {
+ if (torrentsList.getCheckedItemPositions().valueAt(i) && i < torrentsList.getAdapter().getCount()) {
+ checked.add((Torrent) torrentsList.getAdapter().getItem(torrentsList.getCheckedItemPositions().keyAt(i)));
+ }
+ }
+
+ int itemId = item.getItemId();
+ if (itemId == R.id.action_resume) {
+ for (Torrent torrent : checked) {
+ getTasksExecutor().resumeTorrent(torrent);
+ }
+ mode.finish();
+ return true;
+ } else if (itemId == R.id.action_pause) {
+ for (Torrent torrent : checked) {
+ getTasksExecutor().pauseTorrent(torrent);
+ }
+ mode.finish();
+ return true;
+ } else if (itemId == R.id.action_start) {
+ for (Torrent torrent : checked) {
+ getTasksExecutor().startTorrent(torrent, false);
+ }
+ mode.finish();
+ return true;
+ } else if (itemId == R.id.action_stop) {
+ for (Torrent torrent : checked) {
+ getTasksExecutor().stopTorrent(torrent);
+ }
+ mode.finish();
+ return true;
+ } else if (itemId == R.id.action_remove_default) {
+ for (Torrent torrent : checked) {
+ getTasksExecutor().removeTorrent(torrent, false);
+ }
+ mode.finish();
+ return true;
+ } else if (itemId == R.id.action_remove_withdata) {
+ for (Torrent torrent : checked) {
+ getTasksExecutor().removeTorrent(torrent, true);
+ }
+ mode.finish();
+ return true;
+ } else if (itemId == R.id.action_setlabel) {
+ lastMultiSelectedTorrents = checked;
+ if (currentLabels != null) {
+ SetLabelDialog.show(getActivity(), TorrentsFragment.this, currentLabels);
+ }
+ mode.finish();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ // Resume autorefresh
+ if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
+ ((TorrentsActivity) getActivity()).stopRefresh = false;
+ ((TorrentsActivity) getActivity()).startAutoRefresh();
+ }
+ selectionManagerMode.onDestroyActionMode(mode);
+ actionsMenu.setVisibility(View.GONE);
+ actionsToolbar.setEnabled(true);
+ addmenuButton.setVisibility(View.VISIBLE);
+ }
+
+ };
+
+ @Click
+ protected void emptyTextClicked() {
+ // Refresh the activity (that contains this fragment) when the empty view gear is clicked
+ if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
+ ((RefreshableActivity) getActivity()).refreshScreen();
+ }
+ }
+
+ @Click
+ protected void errorTextClicked() {
+ // Refresh the activity (that contains this fragment) when the error view gear is clicked
+ if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
+ ((RefreshableActivity) getActivity()).refreshScreen();
+ }
+ }
+
+ @ItemClick(R.id.torrents_list)
+ protected void torrentsListClicked(Torrent torrent) {
+ // Show the torrent details fragment
+ ((TorrentsActivity) getActivity()).openDetails(torrent);
+ }
+
+ @Override
+ public void onLabelPicked(String newLabel) {
+ for (Torrent torrent : lastMultiSelectedTorrents) {
+ getTasksExecutor().updateLabel(torrent, newLabel);
+ }
+ }
+
+ /**
+ * Updates the shown screen depending on whether we have a connection (so torrents can be shown) or not (in case we need to show a message
+ * suggesting help). This should only ever be called on the UI thread.
+ *
+ * @param hasAConnection True if the user has servers configured and therefore has a connection that can be used
+ */
+ public void updateConnectionStatus(boolean hasAConnection, Daemon daemonType) {
+ if (!isResumed()) return;
+ this.hasAConnection = hasAConnection;
+ this.daemonType = daemonType;
+ if (!hasAConnection) {
+ torrentsList.setVisibility(View.GONE);
+ emptyText.setVisibility(View.GONE);
+ loadingProgress.setVisibility(View.GONE);
+ errorText.setVisibility(View.GONE);
+ nosettingsText.setVisibility(View.VISIBLE);
+ swipeRefreshLayout.setEnabled(false);
+ clear(true, true); // Indirectly also calls updateViewVisibility()
+ } else {
+ updateViewVisibility();
+ }
+ }
+
+ /**
+ * Updates the shown screen depending on whether the torrents are loading. This should only ever be called on the UI thread.
+ *
+ * @param isLoading True if the list of torrents is (re)loading, false otherwise
+ */
+ public void updateIsLoading(boolean isLoading) {
+ if (!isResumed()) return;
+ this.isLoading = isLoading;
+ if (isLoading) {
+ clear(true, false); // Indirectly also calls updateViewVisibility()
+ } else {
+ updateViewVisibility();
+ }
+ }
+
+ /**
+ * Updates the shown screen depending on whether a connection error occurred. This should only ever be called on the UI thread.
+ *
+ * @param connectionErrorMessage The error message from the last failed connection attempt, or null to clear the visible error text
+ */
+ public void updateError(String connectionErrorMessage) {
+ if (!isResumed()) return;
+ this.connectionErrorMessage = connectionErrorMessage;
+ errorText.setText(connectionErrorMessage);
+ if (connectionErrorMessage != null) {
+ clear(false, false); // Indirectly also calls updateViewVisibility()
+ } else {
+ updateViewVisibility();
+ }
+ }
+
+ private void updateViewVisibility() {
+ if (!hasAConnection) {
+ return;
+ }
+ boolean isEmpty = torrents == null || torrentsList.getAdapter() != null && torrentsList.getAdapter().isEmpty();
+ boolean hasError = connectionErrorMessage != null;
+ nosettingsText.setVisibility(View.GONE);
+ errorText.setVisibility(hasError ? View.VISIBLE : View.GONE);
+ torrentsList.setVisibility(!hasError && !isLoading && !isEmpty ? View.VISIBLE : View.GONE);
+ loadingProgress.setVisibility(!hasError && isLoading ? View.VISIBLE : View.GONE);
+ emptyText.setVisibility(!hasError && !isLoading && isEmpty ? View.VISIBLE : View.GONE);
+ swipeRefreshLayout.setEnabled(true);
+ }
+
+ /**
+ * Returns the object responsible for executing torrent tasks against a connected server
+ *
+ * @return The executor for tasks on some torrent
+ */
+ private TorrentTasksExecutor getTasksExecutor() {
+ // NOTE: Assumes the activity implements all the required torrent tasks
+ return (TorrentTasksExecutor) getActivity();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java b/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java
index 0f8299b0..3b704954 100644
--- a/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java
+++ b/app/src/main/java/org/transdroid/core/gui/TransdroidApp.java
@@ -17,11 +17,14 @@
package org.transdroid.core.gui;
import android.app.Application;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
import com.evernote.android.job.JobConfig;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.util.JobLogger;
+
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EApplication;
import org.transdroid.core.gui.log.Log;
@@ -30,21 +33,21 @@ import org.transdroid.core.service.ScheduledJobCreator;
@EApplication
public class TransdroidApp extends Application {
- @Bean
- protected Log log;
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- // Configure Android-Job
- JobConfig.addLogger(new JobLogger() {
- @Override
- public void log(int priority, @NonNull String tag, @NonNull String message, @Nullable Throwable t) {
- log.d(tag, message);
- }
- });
- JobManager.create(this).addJobCreator(new ScheduledJobCreator());
- }
+ @Bean
+ protected Log log;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Configure Android-Job
+ JobConfig.addLogger(new JobLogger() {
+ @Override
+ public void log(int priority, @NonNull String tag, @NonNull String message, @Nullable Throwable t) {
+ log.d(tag, message);
+ }
+ });
+ JobManager.create(this).addJobCreator(new ScheduledJobCreator());
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java
index d04927b7..e22d3644 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/DetailsAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -33,198 +33,204 @@ import android.widget.BaseAdapter;
/**
* List adapter that holds a header view showing torrent details and show the list list contained by the torrent.
+ *
* @author Eric Kok
*/
public class DetailsAdapter extends MergeAdapter {
- private ViewHolderAdapter torrentDetailsViewAdapter = null;
- private TorrentDetailsView torrentDetailsView = null;
- private ViewHolderAdapter piecesSeparatorAdapter = null;
- private ViewHolderAdapter piecesMapViewAdapter = null;
- private PiecesMapView piecesMapView = null;
- private ViewHolderAdapter trackersSeparatorAdapter = null;
- private SimpleListItemAdapter trackersAdapter = null;
- private ViewHolderAdapter errorsSeparatorAdapter = null;
- private SimpleListItemAdapter errorsAdapter = null;
- private ViewHolderAdapter torrentFilesSeparatorAdapter = null;
- private TorrentFilesAdapter torrentFilesAdapter = null;
-
- public DetailsAdapter(Context context) {
- // Immediately bind the adapters, or the MergeAdapter will not be able to determine the view types and instead
- // display nothing at all
-
- // Torrent details header
- torrentDetailsView = TorrentDetailsView_.build(context);
- torrentDetailsViewAdapter = new ViewHolderAdapter(torrentDetailsView);
- torrentDetailsViewAdapter.setViewEnabled(false);
- torrentDetailsViewAdapter.setViewVisibility(View.GONE);
- addAdapter(torrentDetailsViewAdapter);
-
- // Pieces map
- piecesSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
- context.getString(R.string.status_pieces)));
- piecesSeparatorAdapter.setViewEnabled(false);
- piecesSeparatorAdapter.setViewVisibility(View.GONE);
- addAdapter(piecesSeparatorAdapter);
- piecesMapView = new PiecesMapView(context);
- piecesMapViewAdapter = new ViewHolderAdapter(piecesMapView);
- piecesMapViewAdapter.setViewEnabled(false);
- piecesMapViewAdapter.setViewVisibility(View.GONE);
- addAdapter(piecesMapViewAdapter);
-
- // Tracker errors
- errorsSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
- context.getString(R.string.status_errors)));
- errorsSeparatorAdapter.setViewEnabled(false);
- errorsSeparatorAdapter.setViewVisibility(View.GONE);
- addAdapter(errorsSeparatorAdapter);
- this.errorsAdapter = new SimpleListItemAdapter(context, new ArrayList());
- this.errorsAdapter.setAutoLinkMask(Linkify.WEB_URLS);
- addAdapter(errorsAdapter);
-
- // Trackers
- trackersSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
- context.getString(R.string.status_trackers)));
- trackersSeparatorAdapter.setViewEnabled(false);
- trackersSeparatorAdapter.setViewVisibility(View.GONE);
- addAdapter(trackersSeparatorAdapter);
- this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList());
- addAdapter(trackersAdapter);
-
- // Torrent files
- torrentFilesSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
- context.getString(R.string.status_files)));
- torrentFilesSeparatorAdapter.setViewEnabled(false);
- torrentFilesSeparatorAdapter.setViewVisibility(View.GONE);
- addAdapter(torrentFilesSeparatorAdapter);
- this.torrentFilesAdapter = new TorrentFilesAdapter(context, new ArrayList());
- addAdapter(torrentFilesAdapter);
-
- }
-
- /**
- * Update the torrent data in the details header of this merge adapter
- * @param torrent The torrent for which detailed data is shown
- */
- public void updateTorrent(Torrent torrent) {
- torrentDetailsView.update(torrent);
- torrentDetailsViewAdapter.setViewVisibility(torrent == null ? View.GONE : View.VISIBLE);
- }
-
- /**
- * Update the list of files contained in this torrent
- * @param torrentFiles The new list of files, or null if the list and header should be hidden
- */
- public void updateTorrentFiles(List torrentFiles) {
- if (torrentFiles == null) {
- torrentFilesAdapter.update(new ArrayList());
- torrentFilesSeparatorAdapter.setViewVisibility(View.GONE);
- } else {
- torrentFilesAdapter.update(torrentFiles);
- torrentFilesSeparatorAdapter.setViewVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Update the list of trackers
- * @param trackers The new list of trackers known for this torrent, or null if the list and header should be hidden
- */
- public void updateTrackers(List extends SimpleListItem> trackers) {
- if (trackers == null || trackers.isEmpty()) {
- trackersAdapter.update(new ArrayList());
- trackersSeparatorAdapter.setViewVisibility(View.GONE);
- } else {
- trackersAdapter.update(trackers);
- trackersSeparatorAdapter.setViewVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Update the list of errors
- * @param errors The new list of errors known for this torrent, or null if the list and header should be hidden
- */
- public void updateErrors(List extends SimpleListItem> errors) {
- if (errors == null || errors.isEmpty()) {
- errorsAdapter.update(new ArrayList());
- errorsSeparatorAdapter.setViewVisibility(View.GONE);
- } else {
- errorsAdapter.update(errors);
- errorsSeparatorAdapter.setViewVisibility(View.VISIBLE);
- }
- }
-
- public void updatePieces(List pieces) {
- if (pieces == null || pieces.isEmpty()) {
- piecesSeparatorAdapter.setViewEnabled(false);
- piecesSeparatorAdapter.setViewVisibility(View.GONE);
- piecesMapViewAdapter.setViewEnabled(false);
- piecesMapViewAdapter.setViewVisibility(View.GONE);
- } else {
- piecesMapView.setPieces(pieces);
-
- piecesMapViewAdapter.setViewEnabled(true);
- piecesMapViewAdapter.setViewVisibility(View.VISIBLE);
- piecesSeparatorAdapter.setViewEnabled(true);
- piecesSeparatorAdapter.setViewVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Clear currently visible torrent, including header and shown lists
- */
- public void clear() {
- updateTorrent(null);
- updateTorrentFiles(null);
- updateErrors(null);
- updateTrackers(null);
- }
-
- protected static class TorrentFilesAdapter extends BaseAdapter {
-
- private final Context context;
- private List items;
-
- public TorrentFilesAdapter(Context context, List items) {
- this.context = context;
- this.items = items;
- }
-
- /**
- * Allows updating of the full data list underlying this adapter, replacing all items
- * @param newItems The new list of files to display
- */
- public void update(List newItems) {
- this.items = newItems;
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return items.size();
- }
-
- @Override
- public TorrentFile getItem(int position) {
- return items.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- TorrentFileView torrentFileView;
- if (convertView == null) {
- torrentFileView = TorrentFileView_.build(context);
- } else {
- torrentFileView = (TorrentFileView) convertView;
- }
- torrentFileView.bind(getItem(position));
- return torrentFileView;
- }
-
- }
+ private ViewHolderAdapter torrentDetailsViewAdapter = null;
+ private TorrentDetailsView torrentDetailsView = null;
+ private ViewHolderAdapter piecesSeparatorAdapter = null;
+ private ViewHolderAdapter piecesMapViewAdapter = null;
+ private PiecesMapView piecesMapView = null;
+ private ViewHolderAdapter trackersSeparatorAdapter = null;
+ private SimpleListItemAdapter trackersAdapter = null;
+ private ViewHolderAdapter errorsSeparatorAdapter = null;
+ private SimpleListItemAdapter errorsAdapter = null;
+ private ViewHolderAdapter torrentFilesSeparatorAdapter = null;
+ private TorrentFilesAdapter torrentFilesAdapter = null;
+
+ public DetailsAdapter(Context context) {
+ // Immediately bind the adapters, or the MergeAdapter will not be able to determine the view types and instead
+ // display nothing at all
+
+ // Torrent details header
+ torrentDetailsView = TorrentDetailsView_.build(context);
+ torrentDetailsViewAdapter = new ViewHolderAdapter(torrentDetailsView);
+ torrentDetailsViewAdapter.setViewEnabled(false);
+ torrentDetailsViewAdapter.setViewVisibility(View.GONE);
+ addAdapter(torrentDetailsViewAdapter);
+
+ // Pieces map
+ piecesSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
+ context.getString(R.string.status_pieces)));
+ piecesSeparatorAdapter.setViewEnabled(false);
+ piecesSeparatorAdapter.setViewVisibility(View.GONE);
+ addAdapter(piecesSeparatorAdapter);
+ piecesMapView = new PiecesMapView(context);
+ piecesMapViewAdapter = new ViewHolderAdapter(piecesMapView);
+ piecesMapViewAdapter.setViewEnabled(false);
+ piecesMapViewAdapter.setViewVisibility(View.GONE);
+ addAdapter(piecesMapViewAdapter);
+
+ // Tracker errors
+ errorsSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
+ context.getString(R.string.status_errors)));
+ errorsSeparatorAdapter.setViewEnabled(false);
+ errorsSeparatorAdapter.setViewVisibility(View.GONE);
+ addAdapter(errorsSeparatorAdapter);
+ this.errorsAdapter = new SimpleListItemAdapter(context, new ArrayList());
+ this.errorsAdapter.setAutoLinkMask(Linkify.WEB_URLS);
+ addAdapter(errorsAdapter);
+
+ // Trackers
+ trackersSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
+ context.getString(R.string.status_trackers)));
+ trackersSeparatorAdapter.setViewEnabled(false);
+ trackersSeparatorAdapter.setViewVisibility(View.GONE);
+ addAdapter(trackersSeparatorAdapter);
+ this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList());
+ addAdapter(trackersAdapter);
+
+ // Torrent files
+ torrentFilesSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
+ context.getString(R.string.status_files)));
+ torrentFilesSeparatorAdapter.setViewEnabled(false);
+ torrentFilesSeparatorAdapter.setViewVisibility(View.GONE);
+ addAdapter(torrentFilesSeparatorAdapter);
+ this.torrentFilesAdapter = new TorrentFilesAdapter(context, new ArrayList());
+ addAdapter(torrentFilesAdapter);
+
+ }
+
+ /**
+ * Update the torrent data in the details header of this merge adapter
+ *
+ * @param torrent The torrent for which detailed data is shown
+ */
+ public void updateTorrent(Torrent torrent) {
+ torrentDetailsView.update(torrent);
+ torrentDetailsViewAdapter.setViewVisibility(torrent == null ? View.GONE : View.VISIBLE);
+ }
+
+ /**
+ * Update the list of files contained in this torrent
+ *
+ * @param torrentFiles The new list of files, or null if the list and header should be hidden
+ */
+ public void updateTorrentFiles(List torrentFiles) {
+ if (torrentFiles == null) {
+ torrentFilesAdapter.update(new ArrayList());
+ torrentFilesSeparatorAdapter.setViewVisibility(View.GONE);
+ } else {
+ torrentFilesAdapter.update(torrentFiles);
+ torrentFilesSeparatorAdapter.setViewVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Update the list of trackers
+ *
+ * @param trackers The new list of trackers known for this torrent, or null if the list and header should be hidden
+ */
+ public void updateTrackers(List extends SimpleListItem> trackers) {
+ if (trackers == null || trackers.isEmpty()) {
+ trackersAdapter.update(new ArrayList());
+ trackersSeparatorAdapter.setViewVisibility(View.GONE);
+ } else {
+ trackersAdapter.update(trackers);
+ trackersSeparatorAdapter.setViewVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Update the list of errors
+ *
+ * @param errors The new list of errors known for this torrent, or null if the list and header should be hidden
+ */
+ public void updateErrors(List extends SimpleListItem> errors) {
+ if (errors == null || errors.isEmpty()) {
+ errorsAdapter.update(new ArrayList());
+ errorsSeparatorAdapter.setViewVisibility(View.GONE);
+ } else {
+ errorsAdapter.update(errors);
+ errorsSeparatorAdapter.setViewVisibility(View.VISIBLE);
+ }
+ }
+
+ public void updatePieces(List pieces) {
+ if (pieces == null || pieces.isEmpty()) {
+ piecesSeparatorAdapter.setViewEnabled(false);
+ piecesSeparatorAdapter.setViewVisibility(View.GONE);
+ piecesMapViewAdapter.setViewEnabled(false);
+ piecesMapViewAdapter.setViewVisibility(View.GONE);
+ } else {
+ piecesMapView.setPieces(pieces);
+
+ piecesMapViewAdapter.setViewEnabled(true);
+ piecesMapViewAdapter.setViewVisibility(View.VISIBLE);
+ piecesSeparatorAdapter.setViewEnabled(true);
+ piecesSeparatorAdapter.setViewVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Clear currently visible torrent, including header and shown lists
+ */
+ public void clear() {
+ updateTorrent(null);
+ updateTorrentFiles(null);
+ updateErrors(null);
+ updateTrackers(null);
+ }
+
+ protected static class TorrentFilesAdapter extends BaseAdapter {
+
+ private final Context context;
+ private List items;
+
+ public TorrentFilesAdapter(Context context, List items) {
+ this.context = context;
+ this.items = items;
+ }
+
+ /**
+ * Allows updating of the full data list underlying this adapter, replacing all items
+ *
+ * @param newItems The new list of files to display
+ */
+ public void update(List newItems) {
+ this.items = newItems;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getCount() {
+ return items.size();
+ }
+
+ @Override
+ public TorrentFile getItem(int position) {
+ return items.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TorrentFileView torrentFileView;
+ if (convertView == null) {
+ torrentFileView = TorrentFileView_.build(context);
+ } else {
+ torrentFileView = (TorrentFileView) convertView;
+ }
+ torrentFileView.bind(getItem(position));
+ return torrentFileView;
+ }
+
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java b/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java
index 51c5c8b0..76065ab3 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/LocalTorrent.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -30,222 +30,231 @@ import android.content.res.Resources;
/**
* Wrapper around Torrent to provide some addition getters that give translatable or otherwise formatted Strings of
* torrent statistics.
+ *
* @author Eric Kok
*/
public class LocalTorrent {
- /**
- * Creates the LocalTorrent object so that the translatable/formattable version of a Torrent can be used.
- * @param torrent The Torrent object
- * @return The torrent wrapped as LocalTorrent object
- */
- public static LocalTorrent fromTorrent(Torrent torrent) {
- return new LocalTorrent(torrent);
- }
-
- private final Torrent t;
-
- private LocalTorrent(Torrent torrent) {
- this.t = torrent;
- }
-
- private static final String DECIMAL_FORMATTER = "%.1f";
- private static final String DECIMAL_FORMATTER_2 = "%.2f";
-
- /**
- * Builds a string showing the upload/download seed ratio. If not downloading, it will base the ratio on the total
- * size; so if you created the torrent yourself you will have downloaded 0 bytes, but the ratio will pretend you
- * have 100%.
- * @return A nicely formatted string containing the upload/download seed ratio
- */
- public String getRatioString() {
- long baseSize = t.getTotalSize();
- if (t.getStatusCode() == TorrentStatus.Downloading) {
- baseSize = t.getDownloadedEver();
- }
- if (baseSize <= 0) {
- return String.format(Locale.getDefault(), DECIMAL_FORMATTER_2, 0d);
- } else if (t.getRatio() == Double.POSITIVE_INFINITY) {
- return "\u221E";
- } else {
- return String.format(Locale.getDefault(), DECIMAL_FORMATTER_2, t.getRatio());
- }
- }
-
- /**
- * Returns a formatted string indicating the current progress in terms of transferred bytes
- * @param r The context resources, to access translations
- * @param withAvailability Whether to show file availability in-line
- * @return A nicely formatted string indicating torrent status and, if applicable, progress in bytes
- */
- public String getProgressSizeText(Resources r, boolean withAvailability) {
-
- switch (t.getStatusCode()) {
- case Waiting:
- case Error:
- // Not downloading yet
- return r.getString(R.string.status_waitingtodl, FileSizeConverter.getSize(t.getTotalSize()));
- case Checking:
- return r.getString(R.string.status_checking);
- case Downloading:
- // Downloading
- return r.getString(
- R.string.status_size1,
- FileSizeConverter.getSize(t.getDownloadedEver()),
- FileSizeConverter.getSize(t.getTotalSize()),
- String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100)
- + "%"
- + (!withAvailability ? "" : "/"
- + String.format(DECIMAL_FORMATTER, t.getAvailability() * 100) + "%"));
- case Seeding:
- case Paused:
- case Queued:
- // Seeding or paused
- return r.getString(R.string.status_size2, FileSizeConverter.getSize(t.getTotalSize()),
- FileSizeConverter.getSize(t.getUploadedEver()));
- default:
- return "";
- }
-
- }
-
- /**
- * Returns a formatted string indicating either the expected time to download (ETA) or, when seeding, the ratio
- * @param r The context resources, to access translations
- * @return A string like '~ 34 seconds', or 'RATIO 8.2' or an empty string
- */
- public String getProgressEtaRatioText(Resources r) {
- switch (t.getStatusCode()) {
- case Downloading:
- // Downloading
- return getRemainingTimeString(r, true, false);
- case Seeding:
- case Paused:
- case Queued:
- // Seeding or paused
- return r.getString(R.string.status_ratio, getRatioString());
- case Waiting:
- case Checking:
- case Error:
- default:
- return "";
- }
- }
-
- /**
- * Returns a formatted string indicating the torrent status and connected peers
- * @param r The context resources, to access translations
- * @return A string like 'Queued' or, when seeding or leeching, '2 OF 28 PEERS'
- */
- public String getProgressConnectionText(Resources r) {
-
- switch (t.getStatusCode()) {
- case Waiting:
- return r.getString(R.string.status_waiting);
- case Checking:
- return r.getString(R.string.status_checking);
- case Downloading:
- return r.getString(R.string.status_seeders, t.getSeedersConnected(), t.getSeedersKnown());
- case Seeding:
- return r.getString(R.string.status_leechers, t.getLeechersConnected(), t.getLeechersKnown());
- case Paused:
- return r.getString(R.string.status_paused);
- case Queued:
- return r.getString(R.string.status_stopped);
- case Error:
- return r.getString(R.string.status_error);
- default:
- return r.getString(R.string.status_unknown);
- }
-
- }
-
- /**
- * Returns a formatted string indicating current transfer speeds for the torrent
- * @param r The context resources, to access translations
- * @return A string like '↓ 28KB/s ↑ 1.8MB/s', or an empty string when not transferrring
- */
- public String getProgressSpeedText(Resources r) {
-
- switch (t.getStatusCode()) {
- case Waiting:
- case Checking:
- case Paused:
- case Queued:
- return "";
- case Downloading:
- return r.getString(R.string.status_speed_down, FileSizeConverter.getSize(t.getRateDownload()) + "/s") + " "
- + r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
- case Seeding:
- return r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
- default:
- return "";
- }
-
- }
-
- public String getProgressStatusEta(Resources r) {
- switch (t.getStatusCode()) {
- case Waiting:
- return r.getString(R.string.status_waiting).toUpperCase(Locale.getDefault());
- case Checking:
- return r.getString(R.string.status_checking).toUpperCase(Locale.getDefault());
- case Error:
- return r.getString(R.string.status_error).toUpperCase(Locale.getDefault());
- case Downloading:
- // Downloading
- return r.getString(R.string.status_downloading).toUpperCase(Locale.getDefault()) + " ("
- + String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100) + "%), "
- + getRemainingTimeString(r, false, true);
- case Seeding:
- return r.getString(R.string.status_seeding).toUpperCase(Locale.getDefault());
- case Paused:
- return r.getString(R.string.status_paused).toUpperCase(Locale.getDefault());
- case Queued:
- return r.getString(R.string.status_queued).toUpperCase(Locale.getDefault());
- default:
- return r.getString(R.string.status_unknown).toUpperCase(Locale.getDefault());
- }
- }
-
- /**
- * Returns a formatted string indicating the remaining download time
- * @param r The context resources, to access translations
- * @param inDays Whether to show days or use hours for > 24 hours left instead
- * @return A string like '4d 8h 34m 5s' or '2m 3s'
- */
- public String getRemainingTimeString(Resources r, boolean abbreviate, boolean inDays) {
- if (t.getEta() == -1 || t.getEta() == -2) {
- return r.getString(R.string.status_unknowneta);
- }
- return r.getString(abbreviate ? R.string.status_eta : R.string.status_etalong,
- TimespanConverter.getTime(t.getEta(), inDays));
- }
-
- /**
- * Convert a DaemonException to a translatable human-readable error message
- * @param e The exception that was thrown by the server
- * @return A string resource ID to show to the user
- */
- public static int getResourceForDaemonException(DaemonException e) {
- switch (e.getType()) {
- case MethodUnsupported:
- return R.string.error_unsupported;
- case ConnectionError:
- return R.string.error_httperror;
- case UnexpectedResponse:
- return R.string.error_jsonresponseerror;
- case ParsingFailed:
- return R.string.error_jsonrequesterror;
- case NotConnected:
- return R.string.error_daemonnotconnected;
- case AuthenticationFailure:
- return R.string.error_401;
- case FileAccessError:
- return R.string.error_torrentfile;
- default:
- return R.string.error_httperror;
- }
- }
+ /**
+ * Creates the LocalTorrent object so that the translatable/formattable version of a Torrent can be used.
+ *
+ * @param torrent The Torrent object
+ * @return The torrent wrapped as LocalTorrent object
+ */
+ public static LocalTorrent fromTorrent(Torrent torrent) {
+ return new LocalTorrent(torrent);
+ }
+
+ private final Torrent t;
+
+ private LocalTorrent(Torrent torrent) {
+ this.t = torrent;
+ }
+
+ private static final String DECIMAL_FORMATTER = "%.1f";
+ private static final String DECIMAL_FORMATTER_2 = "%.2f";
+
+ /**
+ * Builds a string showing the upload/download seed ratio. If not downloading, it will base the ratio on the total
+ * size; so if you created the torrent yourself you will have downloaded 0 bytes, but the ratio will pretend you
+ * have 100%.
+ *
+ * @return A nicely formatted string containing the upload/download seed ratio
+ */
+ public String getRatioString() {
+ long baseSize = t.getTotalSize();
+ if (t.getStatusCode() == TorrentStatus.Downloading) {
+ baseSize = t.getDownloadedEver();
+ }
+ if (baseSize <= 0) {
+ return String.format(Locale.getDefault(), DECIMAL_FORMATTER_2, 0d);
+ } else if (t.getRatio() == Double.POSITIVE_INFINITY) {
+ return "\u221E";
+ } else {
+ return String.format(Locale.getDefault(), DECIMAL_FORMATTER_2, t.getRatio());
+ }
+ }
+
+ /**
+ * Returns a formatted string indicating the current progress in terms of transferred bytes
+ *
+ * @param r The context resources, to access translations
+ * @param withAvailability Whether to show file availability in-line
+ * @return A nicely formatted string indicating torrent status and, if applicable, progress in bytes
+ */
+ public String getProgressSizeText(Resources r, boolean withAvailability) {
+
+ switch (t.getStatusCode()) {
+ case Waiting:
+ case Error:
+ // Not downloading yet
+ return r.getString(R.string.status_waitingtodl, FileSizeConverter.getSize(t.getTotalSize()));
+ case Checking:
+ return r.getString(R.string.status_checking);
+ case Downloading:
+ // Downloading
+ return r.getString(
+ R.string.status_size1,
+ FileSizeConverter.getSize(t.getDownloadedEver()),
+ FileSizeConverter.getSize(t.getTotalSize()),
+ String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100)
+ + "%"
+ + (!withAvailability ? "" : "/"
+ + String.format(DECIMAL_FORMATTER, t.getAvailability() * 100) + "%"));
+ case Seeding:
+ case Paused:
+ case Queued:
+ // Seeding or paused
+ return r.getString(R.string.status_size2, FileSizeConverter.getSize(t.getTotalSize()),
+ FileSizeConverter.getSize(t.getUploadedEver()));
+ default:
+ return "";
+ }
+
+ }
+
+ /**
+ * Returns a formatted string indicating either the expected time to download (ETA) or, when seeding, the ratio
+ *
+ * @param r The context resources, to access translations
+ * @return A string like '~ 34 seconds', or 'RATIO 8.2' or an empty string
+ */
+ public String getProgressEtaRatioText(Resources r) {
+ switch (t.getStatusCode()) {
+ case Downloading:
+ // Downloading
+ return getRemainingTimeString(r, true, false);
+ case Seeding:
+ case Paused:
+ case Queued:
+ // Seeding or paused
+ return r.getString(R.string.status_ratio, getRatioString());
+ case Waiting:
+ case Checking:
+ case Error:
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Returns a formatted string indicating the torrent status and connected peers
+ *
+ * @param r The context resources, to access translations
+ * @return A string like 'Queued' or, when seeding or leeching, '2 OF 28 PEERS'
+ */
+ public String getProgressConnectionText(Resources r) {
+
+ switch (t.getStatusCode()) {
+ case Waiting:
+ return r.getString(R.string.status_waiting);
+ case Checking:
+ return r.getString(R.string.status_checking);
+ case Downloading:
+ return r.getString(R.string.status_seeders, t.getSeedersConnected(), t.getSeedersKnown());
+ case Seeding:
+ return r.getString(R.string.status_leechers, t.getLeechersConnected(), t.getLeechersKnown());
+ case Paused:
+ return r.getString(R.string.status_paused);
+ case Queued:
+ return r.getString(R.string.status_stopped);
+ case Error:
+ return r.getString(R.string.status_error);
+ default:
+ return r.getString(R.string.status_unknown);
+ }
+
+ }
+
+ /**
+ * Returns a formatted string indicating current transfer speeds for the torrent
+ *
+ * @param r The context resources, to access translations
+ * @return A string like '↓ 28KB/s ↑ 1.8MB/s', or an empty string when not transferrring
+ */
+ public String getProgressSpeedText(Resources r) {
+
+ switch (t.getStatusCode()) {
+ case Waiting:
+ case Checking:
+ case Paused:
+ case Queued:
+ return "";
+ case Downloading:
+ return r.getString(R.string.status_speed_down, FileSizeConverter.getSize(t.getRateDownload()) + "/s") + " "
+ + r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
+ case Seeding:
+ return r.getString(R.string.status_speed_up, FileSizeConverter.getSize(t.getRateUpload()) + "/s");
+ default:
+ return "";
+ }
+
+ }
+
+ public String getProgressStatusEta(Resources r) {
+ switch (t.getStatusCode()) {
+ case Waiting:
+ return r.getString(R.string.status_waiting).toUpperCase(Locale.getDefault());
+ case Checking:
+ return r.getString(R.string.status_checking).toUpperCase(Locale.getDefault());
+ case Error:
+ return r.getString(R.string.status_error).toUpperCase(Locale.getDefault());
+ case Downloading:
+ // Downloading
+ return r.getString(R.string.status_downloading).toUpperCase(Locale.getDefault()) + " ("
+ + String.format(DECIMAL_FORMATTER, t.getDownloadedPercentage() * 100) + "%), "
+ + getRemainingTimeString(r, false, true);
+ case Seeding:
+ return r.getString(R.string.status_seeding).toUpperCase(Locale.getDefault());
+ case Paused:
+ return r.getString(R.string.status_paused).toUpperCase(Locale.getDefault());
+ case Queued:
+ return r.getString(R.string.status_queued).toUpperCase(Locale.getDefault());
+ default:
+ return r.getString(R.string.status_unknown).toUpperCase(Locale.getDefault());
+ }
+ }
+
+ /**
+ * Returns a formatted string indicating the remaining download time
+ *
+ * @param r The context resources, to access translations
+ * @param inDays Whether to show days or use hours for > 24 hours left instead
+ * @return A string like '4d 8h 34m 5s' or '2m 3s'
+ */
+ public String getRemainingTimeString(Resources r, boolean abbreviate, boolean inDays) {
+ if (t.getEta() == -1 || t.getEta() == -2) {
+ return r.getString(R.string.status_unknowneta);
+ }
+ return r.getString(abbreviate ? R.string.status_eta : R.string.status_etalong,
+ TimespanConverter.getTime(t.getEta(), inDays));
+ }
+
+ /**
+ * Convert a DaemonException to a translatable human-readable error message
+ *
+ * @param e The exception that was thrown by the server
+ * @return A string resource ID to show to the user
+ */
+ public static int getResourceForDaemonException(DaemonException e) {
+ switch (e.getType()) {
+ case MethodUnsupported:
+ return R.string.error_unsupported;
+ case ConnectionError:
+ return R.string.error_httperror;
+ case UnexpectedResponse:
+ return R.string.error_jsonresponseerror;
+ case ParsingFailed:
+ return R.string.error_jsonrequesterror;
+ case NotConnected:
+ return R.string.error_daemonnotconnected;
+ case AuthenticationFailure:
+ return R.string.error_401;
+ case FileAccessError:
+ return R.string.error_torrentfile;
+ default:
+ return R.string.error_httperror;
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/MergeAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/MergeAdapter.java
index 49920ba0..d8f16c90 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/MergeAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/MergeAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -31,284 +31,292 @@ import android.widget.TextView;
* An adapter that can contain many other adapters and shows them in sequence. Taken from
* http://stackoverflow.com/questions/7964259/android-attaching-multiple-adapters-to-one-adapter and based on the Apache
* 2-licensed CommonsWare MergeAdapter.
+ *
* @author Eric Kok
* @author Alex Amiryan
* @author Mark Murphy
*/
public class MergeAdapter extends BaseAdapter implements SectionIndexer {
- protected ArrayList pieces = new ArrayList();
- protected String noItemsText;
-
- /**
- * Stock constructor, simply chaining to the superclass.
- */
- public MergeAdapter() {
- super();
- }
-
- /**
- * Adds a new adapter to the roster of things to appear in the aggregate list.
- * @param adapter Source for row views for this section
- */
- public void addAdapter(ListAdapter adapter) {
- pieces.add(adapter);
- adapter.registerDataSetObserver(new CascadeDataSetObserver());
- }
-
- /**
- * Get the data item associated with the specified position in the data set.
- * @param position Position of the item whose data we want
- */
- public Object getItem(int position) {
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
- return (piece.getItem(position));
- }
-
- position -= size;
- }
-
- return (null);
- }
-
- public void setNoItemsText(String text) {
- noItemsText = text;
- }
-
- /**
- * Get the adapter associated with the specified position in the data set.
- * @param position Position of the item whose adapter we want
- */
- public ListAdapter getAdapter(int position) {
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
- return (piece);
- }
-
- position -= size;
- }
-
- return (null);
- }
-
- /**
- * How many items are in the data set represented by this {@link Adapter}.
- */
- public int getCount() {
- int total = 0;
-
- for (ListAdapter piece : pieces) {
- total += piece.getCount();
- }
-
- if (total == 0 && noItemsText != null) {
- total = 1;
- }
-
- return (total);
- }
-
- /**
- * Returns the number of types of {@link View}s that will be created by {@link #getView(int, View, ViewGroup)}.
- */
- @Override
- public int getViewTypeCount() {
- int total = 0;
-
- for (ListAdapter piece : pieces) {
- total += piece.getViewTypeCount();
- }
-
- return (Math.max(total, 1)); // needed for setListAdapter() before
- // content add'
- }
-
- /**
- * Get the type of {@link View} that will be created by {@link #getView(int, View, ViewGroup)} for the specified item.
- * @param position Position of the item whose data we want
- */
- @Override
- public int getItemViewType(int position) {
- int typeOffset = 0;
- int result = -1;
-
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
- result = typeOffset + piece.getItemViewType(position);
- break;
- }
-
- position -= size;
- typeOffset += piece.getViewTypeCount();
- }
-
- return (result);
- }
-
- /**
- * Are all items in this {@link ListAdapter} enabled? If yes it means all items are selectable and clickable.
- */
- @Override
- public boolean areAllItemsEnabled() {
- return (false);
- }
-
- /**
- * Returns true if the item at the specified position is not a separator.
- * @param position Position of the item whose data we want
- */
- @Override
- public boolean isEnabled(int position) {
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
- return (piece.isEnabled(position));
- }
-
- position -= size;
- }
-
- return (false);
- }
-
- /**
- * Get a {@link View} that displays the data at the specified position in the data set.
- * @param position Position of the item whose data we want
- * @param convertView View to recycle, if not null
- * @param parent ViewGroup containing the returned View
- */
- public View getView(int position, View convertView, ViewGroup parent) {
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
-
- return (piece.getView(position, convertView, parent));
- }
-
- position -= size;
- }
-
- if (noItemsText != null) {
- TextView text = new TextView(parent.getContext());
- text.setText(noItemsText);
- return text;
- }
-
- return (null);
- }
-
- /**
- * Get the row id associated with the specified position in the list.
- * @param position Position of the item whose data we want
- */
- public long getItemId(int position) {
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
- return (piece.getItemId(position));
- }
-
- position -= size;
- }
-
- return (-1);
- }
-
- public final int getPositionForSection(int section) {
- int position = 0;
-
- for (ListAdapter piece : pieces) {
- if (piece instanceof SectionIndexer) {
- Object[] sections = ((SectionIndexer) piece).getSections();
- int numSections = 0;
-
- if (sections != null) {
- numSections = sections.length;
- }
-
- if (section < numSections) {
- return (position + ((SectionIndexer) piece).getPositionForSection(section));
- } else if (sections != null) {
- section -= numSections;
- }
- }
-
- position += piece.getCount();
- }
-
- return (0);
- }
-
- public final int getSectionForPosition(int position) {
- int section = 0;
-
- for (ListAdapter piece : pieces) {
- int size = piece.getCount();
-
- if (position < size) {
- if (piece instanceof SectionIndexer) {
- return (section + ((SectionIndexer) piece).getSectionForPosition(position));
- }
-
- return (0);
- } else {
- if (piece instanceof SectionIndexer) {
- Object[] sections = ((SectionIndexer) piece).getSections();
-
- if (sections != null) {
- section += sections.length;
- }
- }
- }
-
- position -= size;
- }
-
- return (0);
- }
-
- public final Object[] getSections() {
- ArrayList sections = new ArrayList();
-
- for (ListAdapter piece : pieces) {
- if (piece instanceof SectionIndexer) {
- Object[] curSections = ((SectionIndexer) piece).getSections();
-
- if (curSections != null) {
- for (Object section : curSections) {
- sections.add(section);
- }
- }
- }
- }
-
- if (sections.size() == 0) {
- return (null);
- }
-
- return (sections.toArray(new Object[0]));
- }
-
- private class CascadeDataSetObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- notifyDataSetInvalidated();
- }
- }
+ protected ArrayList pieces = new ArrayList();
+ protected String noItemsText;
+
+ /**
+ * Stock constructor, simply chaining to the superclass.
+ */
+ public MergeAdapter() {
+ super();
+ }
+
+ /**
+ * Adds a new adapter to the roster of things to appear in the aggregate list.
+ *
+ * @param adapter Source for row views for this section
+ */
+ public void addAdapter(ListAdapter adapter) {
+ pieces.add(adapter);
+ adapter.registerDataSetObserver(new CascadeDataSetObserver());
+ }
+
+ /**
+ * Get the data item associated with the specified position in the data set.
+ *
+ * @param position Position of the item whose data we want
+ */
+ public Object getItem(int position) {
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+ return (piece.getItem(position));
+ }
+
+ position -= size;
+ }
+
+ return (null);
+ }
+
+ public void setNoItemsText(String text) {
+ noItemsText = text;
+ }
+
+ /**
+ * Get the adapter associated with the specified position in the data set.
+ *
+ * @param position Position of the item whose adapter we want
+ */
+ public ListAdapter getAdapter(int position) {
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+ return (piece);
+ }
+
+ position -= size;
+ }
+
+ return (null);
+ }
+
+ /**
+ * How many items are in the data set represented by this {@link Adapter}.
+ */
+ public int getCount() {
+ int total = 0;
+
+ for (ListAdapter piece : pieces) {
+ total += piece.getCount();
+ }
+
+ if (total == 0 && noItemsText != null) {
+ total = 1;
+ }
+
+ return (total);
+ }
+
+ /**
+ * Returns the number of types of {@link View}s that will be created by {@link #getView(int, View, ViewGroup)}.
+ */
+ @Override
+ public int getViewTypeCount() {
+ int total = 0;
+
+ for (ListAdapter piece : pieces) {
+ total += piece.getViewTypeCount();
+ }
+
+ return (Math.max(total, 1)); // needed for setListAdapter() before
+ // content add'
+ }
+
+ /**
+ * Get the type of {@link View} that will be created by {@link #getView(int, View, ViewGroup)} for the specified item.
+ *
+ * @param position Position of the item whose data we want
+ */
+ @Override
+ public int getItemViewType(int position) {
+ int typeOffset = 0;
+ int result = -1;
+
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+ result = typeOffset + piece.getItemViewType(position);
+ break;
+ }
+
+ position -= size;
+ typeOffset += piece.getViewTypeCount();
+ }
+
+ return (result);
+ }
+
+ /**
+ * Are all items in this {@link ListAdapter} enabled? If yes it means all items are selectable and clickable.
+ */
+ @Override
+ public boolean areAllItemsEnabled() {
+ return (false);
+ }
+
+ /**
+ * Returns true if the item at the specified position is not a separator.
+ *
+ * @param position Position of the item whose data we want
+ */
+ @Override
+ public boolean isEnabled(int position) {
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+ return (piece.isEnabled(position));
+ }
+
+ position -= size;
+ }
+
+ return (false);
+ }
+
+ /**
+ * Get a {@link View} that displays the data at the specified position in the data set.
+ *
+ * @param position Position of the item whose data we want
+ * @param convertView View to recycle, if not null
+ * @param parent ViewGroup containing the returned View
+ */
+ public View getView(int position, View convertView, ViewGroup parent) {
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+
+ return (piece.getView(position, convertView, parent));
+ }
+
+ position -= size;
+ }
+
+ if (noItemsText != null) {
+ TextView text = new TextView(parent.getContext());
+ text.setText(noItemsText);
+ return text;
+ }
+
+ return (null);
+ }
+
+ /**
+ * Get the row id associated with the specified position in the list.
+ *
+ * @param position Position of the item whose data we want
+ */
+ public long getItemId(int position) {
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+ return (piece.getItemId(position));
+ }
+
+ position -= size;
+ }
+
+ return (-1);
+ }
+
+ public final int getPositionForSection(int section) {
+ int position = 0;
+
+ for (ListAdapter piece : pieces) {
+ if (piece instanceof SectionIndexer) {
+ Object[] sections = ((SectionIndexer) piece).getSections();
+ int numSections = 0;
+
+ if (sections != null) {
+ numSections = sections.length;
+ }
+
+ if (section < numSections) {
+ return (position + ((SectionIndexer) piece).getPositionForSection(section));
+ } else if (sections != null) {
+ section -= numSections;
+ }
+ }
+
+ position += piece.getCount();
+ }
+
+ return (0);
+ }
+
+ public final int getSectionForPosition(int position) {
+ int section = 0;
+
+ for (ListAdapter piece : pieces) {
+ int size = piece.getCount();
+
+ if (position < size) {
+ if (piece instanceof SectionIndexer) {
+ return (section + ((SectionIndexer) piece).getSectionForPosition(position));
+ }
+
+ return (0);
+ } else {
+ if (piece instanceof SectionIndexer) {
+ Object[] sections = ((SectionIndexer) piece).getSections();
+
+ if (sections != null) {
+ section += sections.length;
+ }
+ }
+ }
+
+ position -= size;
+ }
+
+ return (0);
+ }
+
+ public final Object[] getSections() {
+ ArrayList sections = new ArrayList();
+
+ for (ListAdapter piece : pieces) {
+ if (piece instanceof SectionIndexer) {
+ Object[] curSections = ((SectionIndexer) piece).getSections();
+
+ if (curSections != null) {
+ for (Object section : curSections) {
+ sections.add(section);
+ }
+ }
+ }
+ }
+
+ if (sections.size() == 0) {
+ return (null);
+ }
+
+ return (sections.toArray(new Object[0]));
+ }
+
+ private class CascadeDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ notifyDataSetInvalidated();
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java b/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java
index 805a3cbd..1bf1b4ea 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/PiecesMapView.java
@@ -41,9 +41,9 @@ class PiecesMapView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int ws = MeasureSpec.getSize(widthMeasureSpec);
- int hs = Math.max(getHeight(), MINIMUM_HEIGHT);
- setMeasuredDimension(ws, hs);
+ int ws = MeasureSpec.getSize(widthMeasureSpec);
+ int hs = Math.max(getHeight(), MINIMUM_HEIGHT);
+ setMeasuredDimension(ws, hs);
}
@Override
@@ -65,7 +65,7 @@ class PiecesMapView extends View {
piecesScaled = new ArrayList();
int bucketCount = (int) Math.ceil((double) width / (double) pieceWidth);
- int bucketSize = (int) Math.floor((double)this.pieces.size() / (double) bucketCount);
+ int bucketSize = (int) Math.floor((double) this.pieces.size() / (double) bucketCount);
// loop buckets
for (int i = 0; i < bucketCount; i++) {
@@ -74,7 +74,7 @@ class PiecesMapView extends View {
int start = i * bucketSize;
// If this is the last bucket, throw the remainder of the pieces array into it
- int end = (i == bucketCount-1) ? this.pieces.size() : (i+1) * bucketSize;
+ int end = (i == bucketCount - 1) ? this.pieces.size() : (i + 1) * bucketSize;
ArrayList bucket = new ArrayList(this.pieces.subList(start, end));
@@ -82,7 +82,7 @@ class PiecesMapView extends View {
int downloadingCount = 0;
// loop pieces in bucket
- for(int j = 0; j < bucket.size(); j++) {
+ for (int j = 0; j < bucket.size(); j++) {
// Count downloading pieces
if (bucket.get(j) == 1) {
downloadingCount++;
@@ -115,8 +115,7 @@ class PiecesMapView extends View {
}
String scaledPiecesString = "";
- for (int s : piecesScaled)
- {
+ for (int s : piecesScaled) {
scaledPiecesString += s;
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItem.java b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItem.java
index b05c619a..37e52f0e 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItem.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItem.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -19,10 +19,11 @@ package org.transdroid.core.gui.lists;
/**
* Represents a filter item as shown in the navigation list or spinner.
+ *
* @author Eric Kok
*/
public interface SimpleListItem {
- public String getName();
+ public String getName();
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemAdapter.java
index 0d2975cc..c919a246 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -26,89 +26,92 @@ import android.widget.BaseAdapter;
public class SimpleListItemAdapter extends BaseAdapter {
- private final Context context;
- private List extends SimpleListItem> items;
- private int autoLinkMask = 0;
-
- public SimpleListItemAdapter(Context context, List extends SimpleListItem> items) {
- this.context = context;
- this.items = items;
- }
-
- /**
- * Allows updating of the full data list underlying this adapter, replacing all items
- * @param newItems The new list of simple list items to display
- */
- public void update(List extends SimpleListItem> newItems) {
- this.items = newItems;
- notifyDataSetChanged();
- }
-
- public void setAutoLinkMask(int autoLinkMask) {
- this.autoLinkMask = autoLinkMask;
- }
-
- @Override
- public int getCount() {
- return items.size();
- }
-
- @Override
- public SimpleListItem getItem(int position) {
- return items.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- SimpleListItemView filterItemView;
- if (convertView == null || !(convertView instanceof SimpleListItemView)) {
- filterItemView = SimpleListItemView_.build(context);
- } else {
- filterItemView = (SimpleListItemView) convertView;
- }
- filterItemView.bind(getItem(position), autoLinkMask);
- return filterItemView;
- }
-
- /**
- * Represents a very simple list item that only contains a single string to show in the list. Use wrapStringsList to
- * wrap an existing list of strings into a list of {@link SimpleListItem}s.
- * @author Eric Kok
- */
- public static class SimpleStringItem implements SimpleListItem {
-
- /**
- * Wraps a simple string of strings into a list of SimpleStringItem to add as data to a
- * {@link SimpleListItemAdapter}
- * @param strings A list of string
- * @return A list of SimpleStringItem objects representing the input strings
- */
- public static List wrapStringsList(List strings) {
- ArrayList errors = new ArrayList();
- if (strings != null) {
- for (String string : strings) {
- errors.add(new SimpleStringItem(string));
- }
- }
- return errors;
- }
-
- private final String string;
-
- public SimpleStringItem(String string) {
- this.string = string;
- }
-
- @Override
- public String getName() {
- return this.string;
- }
-
- }
+ private final Context context;
+ private List extends SimpleListItem> items;
+ private int autoLinkMask = 0;
+
+ public SimpleListItemAdapter(Context context, List extends SimpleListItem> items) {
+ this.context = context;
+ this.items = items;
+ }
+
+ /**
+ * Allows updating of the full data list underlying this adapter, replacing all items
+ *
+ * @param newItems The new list of simple list items to display
+ */
+ public void update(List extends SimpleListItem> newItems) {
+ this.items = newItems;
+ notifyDataSetChanged();
+ }
+
+ public void setAutoLinkMask(int autoLinkMask) {
+ this.autoLinkMask = autoLinkMask;
+ }
+
+ @Override
+ public int getCount() {
+ return items.size();
+ }
+
+ @Override
+ public SimpleListItem getItem(int position) {
+ return items.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ SimpleListItemView filterItemView;
+ if (convertView == null || !(convertView instanceof SimpleListItemView)) {
+ filterItemView = SimpleListItemView_.build(context);
+ } else {
+ filterItemView = (SimpleListItemView) convertView;
+ }
+ filterItemView.bind(getItem(position), autoLinkMask);
+ return filterItemView;
+ }
+
+ /**
+ * Represents a very simple list item that only contains a single string to show in the list. Use wrapStringsList to
+ * wrap an existing list of strings into a list of {@link SimpleListItem}s.
+ *
+ * @author Eric Kok
+ */
+ public static class SimpleStringItem implements SimpleListItem {
+
+ /**
+ * Wraps a simple string of strings into a list of SimpleStringItem to add as data to a
+ * {@link SimpleListItemAdapter}
+ *
+ * @param strings A list of string
+ * @return A list of SimpleStringItem objects representing the input strings
+ */
+ public static List wrapStringsList(List strings) {
+ ArrayList errors = new ArrayList();
+ if (strings != null) {
+ for (String string : strings) {
+ errors.add(new SimpleStringItem(string));
+ }
+ }
+ return errors;
+ }
+
+ private final String string;
+
+ public SimpleStringItem(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public String getName() {
+ return this.string;
+ }
+
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemSpinnerAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemSpinnerAdapter.java
index eac5597b..109d8274 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemSpinnerAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemSpinnerAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -27,40 +27,42 @@ import android.widget.TextView;
/**
* A wrapper around {@link ArrayAdapter} that contains {@link SimpleListItem}s which simply show their name in the
* Spinner. The standard Android spinner resources are used for the layout.
+ *
* @author Eric Kok
*/
public class SimpleListItemSpinnerAdapter extends ArrayAdapter {
- /**
- * Constructs the adapter, supplying the {@link SimpleListItem}s to show in the spinner. The given resource will be
- * ignored as the standard Android Spinner layout is used instead.
- * @param context The UI context to inflate the layout in
- * @param resource This is ignored; android.R.layout.simple_spinner_item is always used instead
- * @param objects The items to show in the spinner, which can simply display some name
- */
- public SimpleListItemSpinnerAdapter(Context context, int resource, List objects) {
- super(context, android.R.layout.simple_spinner_item, objects);
- setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // This relies on the ArrayAdapter implementation and the used standard xml layouts that simply return a
- // TextView; this can then be filled with the SimpleListItem's name instead of the standard toString()
- // implementation
- TextView text = (TextView) super.getView(position, convertView, parent);
- text.setText(getItem(position).getName());
- return text;
- }
+ /**
+ * Constructs the adapter, supplying the {@link SimpleListItem}s to show in the spinner. The given resource will be
+ * ignored as the standard Android Spinner layout is used instead.
+ *
+ * @param context The UI context to inflate the layout in
+ * @param resource This is ignored; android.R.layout.simple_spinner_item is always used instead
+ * @param objects The items to show in the spinner, which can simply display some name
+ */
+ public SimpleListItemSpinnerAdapter(Context context, int resource, List objects) {
+ super(context, android.R.layout.simple_spinner_item, objects);
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // This relies on the ArrayAdapter implementation and the used standard xml layouts that simply return a
+ // TextView; this can then be filled with the SimpleListItem's name instead of the standard toString()
+ // implementation
+ TextView text = (TextView) super.getView(position, convertView, parent);
+ text.setText(getItem(position).getName());
+ return text;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ // This relies on the ArrayAdapter implementation and the used standard xml layouts that simply return a
+ // TextView; this can then be filled with the SimpleListItem's name instead of the standard toString()
+ // implementation
+ TextView text = (TextView) super.getDropDownView(position, convertView, parent);
+ text.setText(getItem(position).getName());
+ return text;
+ }
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- // This relies on the ArrayAdapter implementation and the used standard xml layouts that simply return a
- // TextView; this can then be filled with the SimpleListItem's name instead of the standard toString()
- // implementation
- TextView text = (TextView) super.getDropDownView(position, convertView, parent);
- text.setText(getItem(position).getName());
- return text;
- }
-
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java
index 50863f1b..b6d4f243 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -26,23 +26,24 @@ import org.transdroid.R;
/**
* View that represents some {@link SimpleListItem} object and simple prints out the text (in proper style)
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_simple)
public class SimpleListItemView extends FrameLayout {
- @ViewById
- protected TextView itemText;
+ @ViewById
+ protected TextView itemText;
- public SimpleListItemView(Context context) {
- super(context);
- }
+ public SimpleListItemView(Context context) {
+ super(context);
+ }
- public void bind(SimpleListItem filterItem, int autoLinkMask) {
- itemText.setText(filterItem.getName());
- if (autoLinkMask > 0) {
- itemText.setAutoLinkMask(autoLinkMask);
- }
- }
+ public void bind(SimpleListItem filterItem, int autoLinkMask) {
+ itemText.setText(filterItem.getName());
+ if (autoLinkMask > 0) {
+ itemText.setAutoLinkMask(autoLinkMask);
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/SortByListItem.java b/app/src/main/java/org/transdroid/core/gui/lists/SortByListItem.java
index 274ad341..b2939d14 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/SortByListItem.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/SortByListItem.java
@@ -1,78 +1,81 @@
-/*
+/*
* 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.lists;
import android.content.Context;
+
import org.transdroid.R;
import org.transdroid.daemon.TorrentsSortBy;
/**
* Represents a way in which a torrents list can be sorted.
+ *
* @author Eric Kok
*/
public class SortByListItem implements SimpleListItem {
- private final TorrentsSortBy sortBy;
- private final String name;
+ private final TorrentsSortBy sortBy;
+ private final String name;
+
+ public SortByListItem(Context context, TorrentsSortBy sortBy) {
+ this.sortBy = sortBy;
+ switch (sortBy) {
+ case DateAdded:
+ this.name = context.getString(R.string.action_sort_added);
+ break;
+ case DateDone:
+ this.name = context.getString(R.string.action_sort_done);
+ break;
+ case Ratio:
+ this.name = context.getString(R.string.action_sort_ratio);
+ break;
+ case Status:
+ this.name = context.getString(R.string.action_sort_status);
+ break;
+ case UploadSpeed:
+ this.name = context.getString(R.string.action_sort_upspeed);
+ break;
+ case DownloadSpeed:
+ this.name = context.getString(R.string.action_sort_downspeed);
+ break;
+ case Percent:
+ this.name = context.getString(R.string.action_sort_percent);
+ break;
+ case Size:
+ this.name = context.getString(R.string.action_sort_size);
+ break;
+ default:
+ this.name = context.getString(R.string.action_sort_alpha);
+ break;
+ }
+ }
+
+ /**
+ * Returns the contained represented sort order.
+ *
+ * @return The sort by order as its enumeration value
+ */
+ public TorrentsSortBy getSortBy() {
+ return sortBy;
+ }
- public SortByListItem(Context context, TorrentsSortBy sortBy) {
- this.sortBy = sortBy;
- switch (sortBy) {
- case DateAdded:
- this.name = context.getString(R.string.action_sort_added);
- break;
- case DateDone:
- this.name = context.getString(R.string.action_sort_done);
- break;
- case Ratio:
- this.name = context.getString(R.string.action_sort_ratio);
- break;
- case Status:
- this.name = context.getString(R.string.action_sort_status);
- break;
- case UploadSpeed:
- this.name = context.getString(R.string.action_sort_upspeed);
- break;
- case DownloadSpeed:
- this.name = context.getString(R.string.action_sort_downspeed);
- break;
- case Percent:
- this.name = context.getString(R.string.action_sort_percent);
- break;
- case Size:
- this.name = context.getString(R.string.action_sort_size);
- break;
- default:
- this.name = context.getString(R.string.action_sort_alpha);
- break;
- }
- }
-
- /**
- * Returns the contained represented sort order.
- * @return The sort by order as its enumeration value
- */
- public TorrentsSortBy getSortBy() {
- return sortBy;
- }
-
- @Override
- public String getName() {
- return name;
- }
+ @Override
+ public String getName() {
+ return name;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java
index 96052d91..5dbf27b4 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -32,73 +32,75 @@ import org.transdroid.daemon.util.FileSizeConverter;
/**
* Represents a group of views that show torrent status, sizes, speeds and other details.
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.fragment_details_header)
public class TorrentDetailsView extends RelativeLayout {
- @ViewById
- protected TextView labelText, dateaddedText, uploadedText, uploadedunitText, ratioText, upspeedText, seedersText, downloadedunitText,
- downloadedText, totalsizeText, downspeedText, leechersText, statusText;
- @ViewById
- protected TorrentStatusLayout statusLayout;
+ @ViewById
+ protected TextView labelText, dateaddedText, uploadedText, uploadedunitText, ratioText, upspeedText, seedersText, downloadedunitText,
+ downloadedText, totalsizeText, downspeedText, leechersText, statusText;
+ @ViewById
+ protected TorrentStatusLayout statusLayout;
- public TorrentDetailsView(Context context) {
- super(context);
- }
+ public TorrentDetailsView(Context context) {
+ super(context);
+ }
- /**
- * Update the text fields with new/updated torrent details
- * @param torrent The torrent for which to show details
- */
- public void update(Torrent torrent) {
+ /**
+ * Update the text fields with new/updated torrent details
+ *
+ * @param torrent The torrent for which to show details
+ */
+ public void update(Torrent torrent) {
- if (torrent == null) {
- return;
- }
+ if (torrent == null) {
+ return;
+ }
- LocalTorrent local = LocalTorrent.fromTorrent(torrent);
+ LocalTorrent local = LocalTorrent.fromTorrent(torrent);
- // Set label text
- if (Daemon.supportsLabels(torrent.getDaemon())) {
- if (TextUtils.isEmpty(torrent.getLabelName())) {
- labelText.setText(getResources().getString(R.string.labels_unlabeled));
- } else {
- labelText.setText(torrent.getLabelName());
- }
- labelText.setVisibility(View.VISIBLE);
- } else {
- labelText.setVisibility(View.INVISIBLE);
- }
+ // Set label text
+ if (Daemon.supportsLabels(torrent.getDaemon())) {
+ if (TextUtils.isEmpty(torrent.getLabelName())) {
+ labelText.setText(getResources().getString(R.string.labels_unlabeled));
+ } else {
+ labelText.setText(torrent.getLabelName());
+ }
+ labelText.setVisibility(View.VISIBLE);
+ } else {
+ labelText.setVisibility(View.INVISIBLE);
+ }
- // Set status texts
- if (torrent.getDateAdded() != null) {
- dateaddedText.setText(getResources().getString(R.string.status_sincedate, DateUtils
- .getRelativeDateTimeString(getContext(), torrent.getDateAdded().getTime(), DateUtils.SECOND_IN_MILLIS,
- DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_ABBREV_MONTH)));
- dateaddedText.setVisibility(View.VISIBLE);
- } else {
- dateaddedText.setVisibility(View.INVISIBLE);
- }
+ // Set status texts
+ if (torrent.getDateAdded() != null) {
+ dateaddedText.setText(getResources().getString(R.string.status_sincedate, DateUtils
+ .getRelativeDateTimeString(getContext(), torrent.getDateAdded().getTime(), DateUtils.SECOND_IN_MILLIS,
+ DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_ABBREV_MONTH)));
+ dateaddedText.setVisibility(View.VISIBLE);
+ } else {
+ dateaddedText.setVisibility(View.INVISIBLE);
+ }
- statusLayout.setStatus(torrent.getStatusCode());
- statusText.setText(getResources().getString(R.string.status_status, local.getProgressStatusEta(getResources())));
- ratioText.setText(getResources().getString(R.string.status_ratio, local.getRatioString()));
- seedersText.setText(getResources().getString(R.string.status_seeders, torrent.getSeedersConnected(), torrent.getSeedersKnown()));
- leechersText.setText(getResources().getString(R.string.status_leechers, torrent.getLeechersConnected(), torrent.getLeechersKnown()));
- // TODO: Add field that displays torrent errors (as opposed to tracker errors)
- // TODO: Add field that displays availability
+ statusLayout.setStatus(torrent.getStatusCode());
+ statusText.setText(getResources().getString(R.string.status_status, local.getProgressStatusEta(getResources())));
+ ratioText.setText(getResources().getString(R.string.status_ratio, local.getRatioString()));
+ seedersText.setText(getResources().getString(R.string.status_seeders, torrent.getSeedersConnected(), torrent.getSeedersKnown()));
+ leechersText.setText(getResources().getString(R.string.status_leechers, torrent.getLeechersConnected(), torrent.getLeechersKnown()));
+ // TODO: Add field that displays torrent errors (as opposed to tracker errors)
+ // TODO: Add field that displays availability
- // Sizes and speeds texts
- totalsizeText.setText(getResources().getString(R.string.status_ofsize, FileSizeConverter.getSize(torrent.getTotalSize())));
- downloadedText.setText(FileSizeConverter.getSize(torrent.getDownloadedEver(), false));
- downloadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getDownloadedEver()).toString());
- uploadedText.setText(FileSizeConverter.getSize(torrent.getUploadedEver(), false));
- uploadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getUploadedEver()).toString());
- downspeedText
- .setText(getResources().getString(R.string.status_speed_down_details, FileSizeConverter.getSize(torrent.getRateDownload()) + "/s"));
- upspeedText.setText(getResources().getString(R.string.status_speed_up, FileSizeConverter.getSize(torrent.getRateUpload()) + "/s"));
+ // Sizes and speeds texts
+ totalsizeText.setText(getResources().getString(R.string.status_ofsize, FileSizeConverter.getSize(torrent.getTotalSize())));
+ downloadedText.setText(FileSizeConverter.getSize(torrent.getDownloadedEver(), false));
+ downloadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getDownloadedEver()).toString());
+ uploadedText.setText(FileSizeConverter.getSize(torrent.getUploadedEver(), false));
+ uploadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getUploadedEver()).toString());
+ downspeedText
+ .setText(getResources().getString(R.string.status_speed_down_details, FileSizeConverter.getSize(torrent.getRateDownload()) + "/s"));
+ upspeedText.setText(getResources().getString(R.string.status_speed_up, FileSizeConverter.getSize(torrent.getRateUpload()) + "/s"));
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentFilePriorityLayout.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentFilePriorityLayout.java
index 72ed8c69..4fe92549 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentFilePriorityLayout.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentFilePriorityLayout.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -30,71 +30,72 @@ import android.widget.RelativeLayout;
* A relative layout that that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far
* left indicating the priority of the represented file. The darker the green, the higher the priority, while grey means
* the file isn't downloaded at all.
+ *
* @author Eric Kok
*/
public class TorrentFilePriorityLayout extends RelativeLayout {
- private final float scale = getContext().getResources().getDisplayMetrics().density;
- private final int WIDTH = (int) (6 * scale + 0.5f);
+ private final float scale = getContext().getResources().getDisplayMetrics().density;
+ private final int WIDTH = (int) (6 * scale + 0.5f);
- private Priority priority = null;
- private final Paint offPaint = new Paint();
- private final Paint lowPaint = new Paint();
- private final Paint highPaint = new Paint();
- private final Paint normalPaint = new Paint();
- private final RectF fullRect = new RectF();
+ private Priority priority = null;
+ private final Paint offPaint = new Paint();
+ private final Paint lowPaint = new Paint();
+ private final Paint highPaint = new Paint();
+ private final Paint normalPaint = new Paint();
+ private final RectF fullRect = new RectF();
- public TorrentFilePriorityLayout(Context context) {
- super(context);
- initPaints();
- setWillNotDraw(false);
- }
+ public TorrentFilePriorityLayout(Context context) {
+ super(context);
+ initPaints();
+ setWillNotDraw(false);
+ }
- public TorrentFilePriorityLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initPaints();
- setWillNotDraw(false);
- }
+ public TorrentFilePriorityLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initPaints();
+ setWillNotDraw(false);
+ }
- private void initPaints() {
- offPaint.setColor(getResources().getColor(R.color.file_off));
- lowPaint.setColor(getResources().getColor(R.color.file_low));
- normalPaint.setColor(getResources().getColor(R.color.file_normal));
- highPaint.setColor(getResources().getColor(R.color.file_high));
- }
+ private void initPaints() {
+ offPaint.setColor(getResources().getColor(R.color.file_off));
+ lowPaint.setColor(getResources().getColor(R.color.file_low));
+ normalPaint.setColor(getResources().getColor(R.color.file_normal));
+ highPaint.setColor(getResources().getColor(R.color.file_high));
+ }
- public void setPriority(Priority priority) {
- this.priority = priority;
- this.invalidate();
- }
+ public void setPriority(Priority priority) {
+ this.priority = priority;
+ this.invalidate();
+ }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
- int height = getHeight();
- int width = WIDTH;
- fullRect.set(0, 0, width, height);
+ int height = getHeight();
+ int width = WIDTH;
+ fullRect.set(0, 0, width, height);
- if (priority == null) {
- return;
- }
+ if (priority == null) {
+ return;
+ }
- switch (priority) {
- case Low:
- canvas.drawRect(fullRect, lowPaint);
- break;
- case Normal:
- canvas.drawRect(fullRect, normalPaint);
- break;
- case High:
- canvas.drawRect(fullRect, highPaint);
- break;
- default: // Off
- canvas.drawRect(fullRect, offPaint);
- break;
- }
+ switch (priority) {
+ case Low:
+ canvas.drawRect(fullRect, lowPaint);
+ break;
+ case Normal:
+ canvas.drawRect(fullRect, normalPaint);
+ break;
+ case High:
+ canvas.drawRect(fullRect, highPaint);
+ break;
+ default: // Off
+ canvas.drawRect(fullRect, offPaint);
+ break;
+ }
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java
index 4a4ee56e..6a7632cf 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -26,23 +26,24 @@ import org.transdroid.daemon.TorrentFile;
/**
* View that represents some {@link TorrentFile} object and show the file's name, status and priority
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_torrentfile)
public class TorrentFileView extends TorrentFilePriorityLayout {
- @ViewById
- protected TextView nameText, progressText, sizesText;
+ @ViewById
+ protected TextView nameText, progressText, sizesText;
- public TorrentFileView(Context context) {
- super(context, null);
- }
+ public TorrentFileView(Context context) {
+ super(context, null);
+ }
- public void bind(TorrentFile torrentFile) {
- nameText.setText(torrentFile.getName());
- sizesText.setText(torrentFile.getDownloadedAndTotalSizeText());
- progressText.setText(torrentFile.getProgressText());
- setPriority(torrentFile.getPriority());
- }
+ public void bind(TorrentFile torrentFile) {
+ nameText.setText(torrentFile.getName());
+ sizesText.setText(torrentFile.getDownloadedAndTotalSizeText());
+ progressText.setText(torrentFile.getProgressText());
+ setPriority(torrentFile.getPriority());
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java
index 61034ece..c1e84d71 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,98 +28,98 @@ import android.view.View;
/**
* Draws a progress bar indicating the download progress as well as the torrent status.
- *
+ *
* @author Eric Kok
*/
public class TorrentProgressBar extends View {
- private final float scale = getContext().getResources().getDisplayMetrics().density;
- private final int MINIMUM_HEIGHT = (int) (3 * scale + 0.5f);
-
- private int progress;
- private boolean isActive;
- private boolean isError;
- private final Paint notdonePaint = new Paint();
- private final Paint inactiveDonePaint = new Paint();
- private final Paint inactivePaint = new Paint();
- private final Paint progressPaint = new Paint();
- private final Paint donePaint = new Paint();
- private final Paint errorPaint = new Paint();
- private final RectF fullRect = new RectF();
- private final RectF progressRect = new RectF();
-
- public void setProgress(int progress) {
- this.progress = progress;
- this.invalidate();
- }
-
- public void setActive(boolean isActive) {
- this.isActive = isActive;
- this.invalidate();
- }
-
- public void setError(boolean isError) {
- this.isError = isError;
- this.invalidate();
- }
-
- public TorrentProgressBar(Context context) {
- super(context);
- initPaints();
- }
-
- public TorrentProgressBar(Context context, AttributeSet attrs) {
- super(context, attrs);
- initPaints();
-
- // Parse any set attributes from XML
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TorrentProgressBar);
- if (a.hasValue(R.styleable.TorrentProgressBar_progress)) {
- this.progress = a.getIndex(R.styleable.TorrentProgressBar_progress);
- this.isActive = a.getBoolean(R.styleable.TorrentProgressBar_isActive, false);
- }
- a.recycle();
- }
-
- private void initPaints() {
- notdonePaint.setColor(getResources().getColor(R.color.torrent_background));
- inactiveDonePaint.setColor(getResources().getColor(R.color.torrent_paused));
- inactivePaint.setColor(getResources().getColor(R.color.torrent_other));
- progressPaint.setColor(getResources().getColor(R.color.torrent_downloading));
- donePaint.setColor(getResources().getColor(R.color.torrent_seeding));
- errorPaint.setColor(getResources().getColor(R.color.torrent_error));
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int ws = MeasureSpec.getSize(widthMeasureSpec);
- int hs = Math.max(getHeight(), MINIMUM_HEIGHT);
- setMeasuredDimension(ws, hs);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- int height = getHeight();
- int width = getWidth();
- fullRect.set(0, 0, width, height);
-
- // Error?
- if (isError) {
- canvas.drawRect(fullRect, errorPaint);
- } else {
- // Background rounded rectangle
- canvas.drawRect(fullRect, notdonePaint);
-
- // Foreground progress indicator
- if (progress > 0) {
- progressRect.set(0, 0, width * ((float) progress / 100), height);
- canvas.drawRect(progressRect, (isActive ? (progress == 100 ? donePaint : progressPaint)
- : (progress == 100 ? inactiveDonePaint : inactivePaint)));
- }
- }
-
- }
+ private final float scale = getContext().getResources().getDisplayMetrics().density;
+ private final int MINIMUM_HEIGHT = (int) (3 * scale + 0.5f);
+
+ private int progress;
+ private boolean isActive;
+ private boolean isError;
+ private final Paint notdonePaint = new Paint();
+ private final Paint inactiveDonePaint = new Paint();
+ private final Paint inactivePaint = new Paint();
+ private final Paint progressPaint = new Paint();
+ private final Paint donePaint = new Paint();
+ private final Paint errorPaint = new Paint();
+ private final RectF fullRect = new RectF();
+ private final RectF progressRect = new RectF();
+
+ public void setProgress(int progress) {
+ this.progress = progress;
+ this.invalidate();
+ }
+
+ public void setActive(boolean isActive) {
+ this.isActive = isActive;
+ this.invalidate();
+ }
+
+ public void setError(boolean isError) {
+ this.isError = isError;
+ this.invalidate();
+ }
+
+ public TorrentProgressBar(Context context) {
+ super(context);
+ initPaints();
+ }
+
+ public TorrentProgressBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initPaints();
+
+ // Parse any set attributes from XML
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TorrentProgressBar);
+ if (a.hasValue(R.styleable.TorrentProgressBar_progress)) {
+ this.progress = a.getIndex(R.styleable.TorrentProgressBar_progress);
+ this.isActive = a.getBoolean(R.styleable.TorrentProgressBar_isActive, false);
+ }
+ a.recycle();
+ }
+
+ private void initPaints() {
+ notdonePaint.setColor(getResources().getColor(R.color.torrent_background));
+ inactiveDonePaint.setColor(getResources().getColor(R.color.torrent_paused));
+ inactivePaint.setColor(getResources().getColor(R.color.torrent_other));
+ progressPaint.setColor(getResources().getColor(R.color.torrent_downloading));
+ donePaint.setColor(getResources().getColor(R.color.torrent_seeding));
+ errorPaint.setColor(getResources().getColor(R.color.torrent_error));
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int ws = MeasureSpec.getSize(widthMeasureSpec);
+ int hs = Math.max(getHeight(), MINIMUM_HEIGHT);
+ setMeasuredDimension(ws, hs);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int height = getHeight();
+ int width = getWidth();
+ fullRect.set(0, 0, width, height);
+
+ // Error?
+ if (isError) {
+ canvas.drawRect(fullRect, errorPaint);
+ } else {
+ // Background rounded rectangle
+ canvas.drawRect(fullRect, notdonePaint);
+
+ // Foreground progress indicator
+ if (progress > 0) {
+ progressRect.set(0, 0, width * ((float) progress / 100), height);
+ canvas.drawRect(progressRect, (isActive ? (progress == 100 ? donePaint : progressPaint)
+ : (progress == 100 ? inactiveDonePaint : inactivePaint)));
+ }
+ }
+
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java
index bbaac0db..929cf12d 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -30,81 +30,83 @@ import android.widget.RelativeLayout;
* A relative layout that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far left
* indicating the status of the represented torrent. Active downloads are blue, seeding torrents are green, errors are
* red, etc.
+ *
* @author Eric Kok
*/
public class TorrentStatusLayout extends RelativeLayout {
- private final float scale = getContext().getResources().getDisplayMetrics().density;
- private final int WIDTH = (int) (6 * scale + 0.5f);
+ private final float scale = getContext().getResources().getDisplayMetrics().density;
+ private final int WIDTH = (int) (6 * scale + 0.5f);
- private TorrentStatus status = null;
- private final Paint pausedPaint = new Paint();
- private final Paint otherPaint = new Paint();
- private final Paint downloadingPaint = new Paint();
- private final Paint seedingPaint = new Paint();
- private final Paint errorPaint = new Paint();
- private final RectF fullRect = new RectF();
+ private TorrentStatus status = null;
+ private final Paint pausedPaint = new Paint();
+ private final Paint otherPaint = new Paint();
+ private final Paint downloadingPaint = new Paint();
+ private final Paint seedingPaint = new Paint();
+ private final Paint errorPaint = new Paint();
+ private final RectF fullRect = new RectF();
- public TorrentStatusLayout(Context context) {
- super(context);
- initPaints();
- setWillNotDraw(false);
- }
+ public TorrentStatusLayout(Context context) {
+ super(context);
+ initPaints();
+ setWillNotDraw(false);
+ }
- public TorrentStatusLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initPaints();
- setWillNotDraw(false);
- }
+ public TorrentStatusLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initPaints();
+ setWillNotDraw(false);
+ }
- private void initPaints() {
- pausedPaint.setColor(getResources().getColor(R.color.torrent_paused));
- otherPaint.setColor(getResources().getColor(R.color.torrent_other));
- downloadingPaint.setColor(getResources().getColor(R.color.torrent_downloading));
- seedingPaint.setColor(getResources().getColor(R.color.torrent_seeding));
- errorPaint.setColor(getResources().getColor(R.color.torrent_error));
- }
+ private void initPaints() {
+ pausedPaint.setColor(getResources().getColor(R.color.torrent_paused));
+ otherPaint.setColor(getResources().getColor(R.color.torrent_other));
+ downloadingPaint.setColor(getResources().getColor(R.color.torrent_downloading));
+ seedingPaint.setColor(getResources().getColor(R.color.torrent_seeding));
+ errorPaint.setColor(getResources().getColor(R.color.torrent_error));
+ }
- /**
- * Registers the status of the represented torrent and invalidates the view so the status colour will be updated
- * accordingly.
- * @param status The updated torrent status to show
- */
- public void setStatus(TorrentStatus status) {
- this.status = status;
- this.invalidate();
- }
+ /**
+ * Registers the status of the represented torrent and invalidates the view so the status colour will be updated
+ * accordingly.
+ *
+ * @param status The updated torrent status to show
+ */
+ public void setStatus(TorrentStatus status) {
+ this.status = status;
+ this.invalidate();
+ }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
- int height = getHeight();
- int width = WIDTH;
- fullRect.set(0, 0, width, height);
+ int height = getHeight();
+ int width = WIDTH;
+ fullRect.set(0, 0, width, height);
- if (status == null) {
- return;
- }
+ if (status == null) {
+ return;
+ }
- switch (status) {
- case Downloading:
- canvas.drawRect(fullRect, downloadingPaint);
- break;
- case Paused:
- canvas.drawRect(fullRect, pausedPaint);
- break;
- case Seeding:
- canvas.drawRect(fullRect, seedingPaint);
- break;
- case Error:
- canvas.drawRect(fullRect, errorPaint);
- break;
- default: // Checking, Waiting, Queued, Unknown
- canvas.drawRect(fullRect, otherPaint);
- break;
- }
+ switch (status) {
+ case Downloading:
+ canvas.drawRect(fullRect, downloadingPaint);
+ break;
+ case Paused:
+ canvas.drawRect(fullRect, pausedPaint);
+ break;
+ case Seeding:
+ canvas.drawRect(fullRect, seedingPaint);
+ break;
+ case Error:
+ canvas.drawRect(fullRect, errorPaint);
+ break;
+ default: // Checking, Waiting, Queued, Unknown
+ canvas.drawRect(fullRect, otherPaint);
+ break;
+ }
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java
index f01dd03e..4fb07774 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,55 +29,56 @@ import org.transdroid.daemon.TorrentStatus;
/**
* View that represents some {@link Torrent} object and displays progress, status, speeds, etc.
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_torrent)
public class TorrentView extends TorrentStatusLayout {
- @ViewById
- protected ImageView priorityImage;
- @ViewById
- protected TextView nameText, ratioText, progressText, speedText, peersText;
- @ViewById
- protected TorrentProgressBar torrentProgressbar;
+ @ViewById
+ protected ImageView priorityImage;
+ @ViewById
+ protected TextView nameText, ratioText, progressText, speedText, peersText;
+ @ViewById
+ protected TorrentProgressBar torrentProgressbar;
- public TorrentView(Context context) {
- super(context);
- }
+ public TorrentView(Context context) {
+ super(context);
+ }
- public void bind(Torrent torrent) {
- LocalTorrent local = LocalTorrent.fromTorrent(torrent);
- setStatus(torrent.getStatusCode());
- nameText.setText(torrent.getName());
- progressText.setText(local.getProgressSizeText(getResources(), false));
- ratioText.setText(local.getProgressEtaRatioText(getResources()));
- // TODO: Implement per-torrent priority and set priorityImage
- priorityImage.setVisibility(View.INVISIBLE);
+ public void bind(Torrent torrent) {
+ LocalTorrent local = LocalTorrent.fromTorrent(torrent);
+ setStatus(torrent.getStatusCode());
+ nameText.setText(torrent.getName());
+ progressText.setText(local.getProgressSizeText(getResources(), false));
+ ratioText.setText(local.getProgressEtaRatioText(getResources()));
+ // TODO: Implement per-torrent priority and set priorityImage
+ priorityImage.setVisibility(View.INVISIBLE);
- // Only show status bar, peers and speed fields if relevant, i.e. when downloading or actively seeding
- if (torrent.getStatusCode() == TorrentStatus.Downloading ||
- (torrent.getStatusCode() == TorrentStatus.Seeding && torrent.getRateUpload() > 0)) {
- torrentProgressbar.setVisibility(View.VISIBLE);
- torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100));
- torrentProgressbar.setActive(torrent.canPause());
- torrentProgressbar.setError(torrent.getStatusCode() == TorrentStatus.Error);
- peersText.setVisibility(View.VISIBLE);
- peersText.setText(local.getProgressConnectionText(getResources()));
- speedText.setVisibility(View.VISIBLE);
- speedText.setText(local.getProgressSpeedText(getResources()));
- } else if (torrent.getPartDone() < 1) {
- // Not active, but also not complete, so show the status bar
- torrentProgressbar.setVisibility(View.VISIBLE);
- torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100));
- torrentProgressbar.setActive(torrent.canPause());
- torrentProgressbar.setError(torrent.getStatusCode() == TorrentStatus.Error);
- peersText.setVisibility(View.GONE);
- speedText.setVisibility(View.GONE);
- } else {
- torrentProgressbar.setVisibility(View.GONE);
- peersText.setVisibility(View.GONE);
- speedText.setVisibility(View.GONE);
- }
- }
+ // Only show status bar, peers and speed fields if relevant, i.e. when downloading or actively seeding
+ if (torrent.getStatusCode() == TorrentStatus.Downloading ||
+ (torrent.getStatusCode() == TorrentStatus.Seeding && torrent.getRateUpload() > 0)) {
+ torrentProgressbar.setVisibility(View.VISIBLE);
+ torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100));
+ torrentProgressbar.setActive(torrent.canPause());
+ torrentProgressbar.setError(torrent.getStatusCode() == TorrentStatus.Error);
+ peersText.setVisibility(View.VISIBLE);
+ peersText.setText(local.getProgressConnectionText(getResources()));
+ speedText.setVisibility(View.VISIBLE);
+ speedText.setText(local.getProgressSpeedText(getResources()));
+ } else if (torrent.getPartDone() < 1) {
+ // Not active, but also not complete, so show the status bar
+ torrentProgressbar.setVisibility(View.VISIBLE);
+ torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100));
+ torrentProgressbar.setActive(torrent.canPause());
+ torrentProgressbar.setError(torrent.getStatusCode() == TorrentStatus.Error);
+ peersText.setVisibility(View.GONE);
+ speedText.setVisibility(View.GONE);
+ } else {
+ torrentProgressbar.setVisibility(View.GONE);
+ peersText.setVisibility(View.GONE);
+ speedText.setVisibility(View.GONE);
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java
index 3a2b9734..b681479e 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,61 +29,63 @@ import java.util.ArrayList;
/**
* Adapter that contains a list of torrent objects to show.
+ *
* @author Eric Kok
*/
@EBean
public class TorrentsAdapter extends BaseAdapter {
- private ArrayList torrents = null;
+ private ArrayList torrents = null;
- @RootContext
- protected Context context;
+ @RootContext
+ protected Context context;
- /**
- * Allows updating the full internal list of torrents at once, replacing the old list
- * @param newTorrents The new list of torrent objects
- */
- public void update(ArrayList newTorrents) {
- this.torrents = newTorrents;
- notifyDataSetChanged();
- }
+ /**
+ * Allows updating the full internal list of torrents at once, replacing the old list
+ *
+ * @param newTorrents The new list of torrent objects
+ */
+ public void update(ArrayList newTorrents) {
+ this.torrents = newTorrents;
+ notifyDataSetChanged();
+ }
- @Override
- public boolean hasStableIds() {
- return true;
- }
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
- @Override
- public int getCount() {
- if (torrents == null) {
- return 0;
- }
- return torrents.size();
- }
+ @Override
+ public int getCount() {
+ if (torrents == null) {
+ return 0;
+ }
+ return torrents.size();
+ }
- @Override
- public Torrent getItem(int position) {
- if (torrents == null) {
- return null;
- }
- return torrents.get(position);
- }
+ @Override
+ public Torrent getItem(int position) {
+ if (torrents == null) {
+ return null;
+ }
+ return torrents.get(position);
+ }
- @Override
- public long getItemId(int position) {
- return position;
- }
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- TorrentView torrentView;
- if (convertView == null) {
- torrentView = TorrentView_.build(context);
- } else {
- torrentView = (TorrentView) convertView;
- }
- torrentView.bind(getItem(position));
- return torrentView;
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TorrentView torrentView;
+ if (convertView == null) {
+ torrentView = TorrentView_.build(context);
+ } else {
+ torrentView = (TorrentView) convertView;
+ }
+ torrentView.bind(getItem(position));
+ return torrentView;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/lists/ViewHolderAdapter.java b/app/src/main/java/org/transdroid/core/gui/lists/ViewHolderAdapter.java
index fcecc05c..77ce019a 100644
--- a/app/src/main/java/org/transdroid/core/gui/lists/ViewHolderAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/lists/ViewHolderAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,96 +28,100 @@ import android.widget.ListView;
* the view object. This is required since otherwise the adapter's consumer (i.e. a {@link ListView}) does not update
* the list row accordingly. Use {@link #setViewEnabled(boolean)} to enable or disable this contained view for user
* interaction.
+ *
* @author Eric Kok
*/
public class ViewHolderAdapter extends BaseAdapter {
- private final View view;
+ private final View view;
- /**
- * Instantiates this wrapper adapter with the one and only view to show. It can not be updated and view visibility
- * should be set directly on this adapter using {@link #setViewVisibility(int)}. Use
- * {@link #setViewEnabled(boolean)} to enable or disable this contained view for user interaction.
- * @param view The view that will be wrapper in an adapter to show in a list view
- */
- public ViewHolderAdapter(View view) {
- this.view = view;
- }
+ /**
+ * Instantiates this wrapper adapter with the one and only view to show. It can not be updated and view visibility
+ * should be set directly on this adapter using {@link #setViewVisibility(int)}. Use
+ * {@link #setViewEnabled(boolean)} to enable or disable this contained view for user interaction.
+ *
+ * @param view The view that will be wrapper in an adapter to show in a list view
+ */
+ public ViewHolderAdapter(View view) {
+ this.view = view;
+ }
- /**
- * Sets the visibility on the contained view and notifies consumers of this adapter (i.e. a {@link ListView})
- * accordingly. Use {@link View#GONE} to hide this adapter's view altogether.
- * @param visibility The visibility to set on the contained view
- */
- public void setViewVisibility(int visibility) {
- view.setVisibility(visibility);
- notifyDataSetChanged();
- }
+ /**
+ * Sets the visibility on the contained view and notifies consumers of this adapter (i.e. a {@link ListView})
+ * accordingly. Use {@link View#GONE} to hide this adapter's view altogether.
+ *
+ * @param visibility The visibility to set on the contained view
+ */
+ public void setViewVisibility(int visibility) {
+ view.setVisibility(visibility);
+ notifyDataSetChanged();
+ }
- /**
- * Sets whether the contained view should be enabled and notifies consumers of this adapter (i.e. a {@link ListView}
- * ) accordingly. A contained enabled view allows user interaction (clicks, focus), while a disabled view does not.
- * @param enabled Whether the contained view should be enabled
- */
- public void setViewEnabled(boolean enabled) {
- view.setEnabled(enabled);
- notifyDataSetChanged();
- }
+ /**
+ * Sets whether the contained view should be enabled and notifies consumers of this adapter (i.e. a {@link ListView}
+ * ) accordingly. A contained enabled view allows user interaction (clicks, focus), while a disabled view does not.
+ *
+ * @param enabled Whether the contained view should be enabled
+ */
+ public void setViewEnabled(boolean enabled) {
+ view.setEnabled(enabled);
+ notifyDataSetChanged();
+ }
- /**
- * Returns 1 if the contained view is {@link View#VISIBLE} or {@link View#INVISIBLE}, return 0 if {@link View#GONE}.
- */
- @Override
- public int getCount() {
- return view.getVisibility() == View.VISIBLE ? 1 : 0;
- }
+ /**
+ * Returns 1 if the contained view is {@link View#VISIBLE} or {@link View#INVISIBLE}, return 0 if {@link View#GONE}.
+ */
+ @Override
+ public int getCount() {
+ return view.getVisibility() == View.VISIBLE ? 1 : 0;
+ }
- /**
- * Always directly returns the single contained view instance.
- */
- @Override
- public Object getItem(int position) {
- return view;
- }
+ /**
+ * Always directly returns the single contained view instance.
+ */
+ @Override
+ public Object getItem(int position) {
+ return view;
+ }
- /**
- * Always returns the position directly as item id.
- */
- @Override
- public long getItemId(int position) {
- return position;
- }
+ /**
+ * Always returns the position directly as item id.
+ */
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
- /**
- * Always directly returns the single contained view instance.
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- return view;
- }
+ /**
+ * Always directly returns the single contained view instance.
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return view;
+ }
- /**
- * Always returns true, as there is only one contained item and it is never changed.
- */
- @Override
- public boolean hasStableIds() {
- return true;
- }
+ /**
+ * Always returns true, as there is only one contained item and it is never changed.
+ */
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
- /**
- * Returns false, as the contained view can still be enabled and disabled.
- */
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
+ /**
+ * Returns false, as the contained view can still be enabled and disabled.
+ */
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
- /**
- * Returns true if the contained view is enabled, returns false otherwise.
- */
- @Override
- public boolean isEnabled(int position) {
- return view.isEnabled();
- }
+ /**
+ * Returns true if the contained view is enabled, returns false otherwise.
+ */
+ @Override
+ public boolean isEnabled(int position) {
+ return view.isEnabled();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java b/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java
index dda98570..fb449810 100644
--- a/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java
+++ b/app/src/main/java/org/transdroid/core/gui/log/DatabaseHelper.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -20,7 +20,9 @@ import java.sql.SQLException;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
+
import androidx.annotation.Keep;
+
import android.util.Log;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
@@ -29,41 +31,42 @@ import com.j256.ormlite.table.TableUtils;
/**
* Helper to access the database to access persisting objects.
+ *
* @author Eric Kok
*/
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
- private static final String DATABASE_NAME = "transdroid.db";
- private static final int DATABASE_VERSION = 1;
+ private static final String DATABASE_NAME = "transdroid.db";
+ private static final int DATABASE_VERSION = 1;
- @Keep
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
+ @Keep
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
- @Override
- public void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) {
- try {
- TableUtils.createTable(connectionSource, ErrorLogEntry.class);
- } catch (SQLException e) {
- Log.e(org.transdroid.core.gui.log.Log.LOG_NAME, "Could not create new table for ErrorLogEntry", e);
- }
- }
+ @Override
+ public void onCreate(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource) {
+ try {
+ TableUtils.createTable(connectionSource, ErrorLogEntry.class);
+ } catch (SQLException e) {
+ Log.e(org.transdroid.core.gui.log.Log.LOG_NAME, "Could not create new table for ErrorLogEntry", e);
+ }
+ }
- @Override
- public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int oldVersion,
- int newVersion) {
- try {
- switch (oldVersion) {
- case 1:
- TableUtils.createTable(connectionSource, ErrorLogEntry.class);
+ @Override
+ public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int oldVersion,
+ int newVersion) {
+ try {
+ switch (oldVersion) {
+ case 1:
+ TableUtils.createTable(connectionSource, ErrorLogEntry.class);
/*case 1:
etc...*/
- }
+ }
- } catch (SQLException e) {
- Log.e(org.transdroid.core.gui.log.Log.LOG_NAME, "Could not upgrade the table for ErrorLogEntry", e);
- }
- }
+ } catch (SQLException e) {
+ Log.e(org.transdroid.core.gui.log.Log.LOG_NAME, "Could not upgrade the table for ErrorLogEntry", e);
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/log/ErrorLogEntry.java b/app/src/main/java/org/transdroid/core/gui/log/ErrorLogEntry.java
index 91bd4d9f..e4800e76 100644
--- a/app/src/main/java/org/transdroid/core/gui/log/ErrorLogEntry.java
+++ b/app/src/main/java/org/transdroid/core/gui/log/ErrorLogEntry.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -26,83 +26,84 @@ import com.j256.ormlite.table.DatabaseTable;
/**
* Represents an error log entry to be registered in the database.
+ *
* @author Eric Kok
*/
@DatabaseTable(tableName = "ErrorLogEntry")
public class ErrorLogEntry implements Parcelable {
- public static final String ID = "logId";
- public static final String DATEANDTIME = "dateAndTime";
-
- @DatabaseField(id = true, columnName = ID)
- private Integer logId;
- @DatabaseField(columnName = DATEANDTIME)
- private Date dateAndTime;
- @DatabaseField
- private Integer priority;
- @DatabaseField
- private String tag;
- @DatabaseField
- private String message;
-
- public ErrorLogEntry() {
- }
-
- public ErrorLogEntry(Integer priority, String tag, String message) {
- this.dateAndTime = new Date();
- this.priority = priority;
- this.tag = tag;
- this.message = message;
- }
-
- public Integer getLogId() {
- return logId;
- }
-
- public Date getDateAndTime() {
- return dateAndTime;
- }
-
- public Integer getPriority() {
- return priority;
- }
-
- public String getTag() {
- return tag;
- }
-
- public String getMessage() {
- return message;
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(logId);
- out.writeLong(dateAndTime.getTime());
- out.writeInt(priority);
- out.writeString(tag);
- out.writeString(message);
- }
-
- public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
- public ErrorLogEntry createFromParcel(Parcel in) {
- return new ErrorLogEntry(in);
- }
-
- public ErrorLogEntry[] newArray(int size) {
- return new ErrorLogEntry[size];
- }
- };
-
- private ErrorLogEntry(Parcel in) {
- logId = in.readInt();
- dateAndTime = new Date(in.readLong());
- priority = in.readInt();
- tag = in.readString();
- message = in.readString();
- }
+ public static final String ID = "logId";
+ public static final String DATEANDTIME = "dateAndTime";
+
+ @DatabaseField(id = true, columnName = ID)
+ private Integer logId;
+ @DatabaseField(columnName = DATEANDTIME)
+ private Date dateAndTime;
+ @DatabaseField
+ private Integer priority;
+ @DatabaseField
+ private String tag;
+ @DatabaseField
+ private String message;
+
+ public ErrorLogEntry() {
+ }
+
+ public ErrorLogEntry(Integer priority, String tag, String message) {
+ this.dateAndTime = new Date();
+ this.priority = priority;
+ this.tag = tag;
+ this.message = message;
+ }
+
+ public Integer getLogId() {
+ return logId;
+ }
+
+ public Date getDateAndTime() {
+ return dateAndTime;
+ }
+
+ public Integer getPriority() {
+ return priority;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(logId);
+ out.writeLong(dateAndTime.getTime());
+ out.writeInt(priority);
+ out.writeString(tag);
+ out.writeString(message);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public ErrorLogEntry createFromParcel(Parcel in) {
+ return new ErrorLogEntry(in);
+ }
+
+ public ErrorLogEntry[] newArray(int size) {
+ return new ErrorLogEntry[size];
+ }
+ };
+
+ private ErrorLogEntry(Parcel in) {
+ logId = in.readInt();
+ dateAndTime = new Date(in.readLong());
+ priority = in.readInt();
+ tag = in.readString();
+ message = in.readString();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java b/app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java
index c581da90..52e4c7d9 100644
--- a/app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java
+++ b/app/src/main/java/org/transdroid/core/gui/log/ErrorLogSender.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -35,61 +35,61 @@ import com.j256.ormlite.dao.Dao;
@EBean
public class ErrorLogSender {
- @Bean
- protected Log log;
- @Bean
- protected NavigationHelper navigationHelper;
- @OrmLiteDao(helper = DatabaseHelper.class)
- protected Dao errorLogDao;
+ @Bean
+ protected Log log;
+ @Bean
+ protected NavigationHelper navigationHelper;
+ @OrmLiteDao(helper = DatabaseHelper.class)
+ protected Dao errorLogDao;
- public void collectAndSendLog(final Activity callingActivity, final ServerSetting serverSetting) {
+ public void collectAndSendLog(final Activity callingActivity, final ServerSetting serverSetting) {
- try {
+ try {
- // Prepare an email with error logging information
- StringBuilder body = new StringBuilder();
- body.append("Please describe your problem:\n\n\n");
- body.append("\n");
- body.append(navigationHelper.getAppNameAndVersion());
- body.append("\n");
- if (serverSetting == null) {
- body.append("(No server settings)");
- } else {
- body.append(serverSetting.getType().toString());
- body.append(" settings: ");
- body.append(serverSetting.getHumanReadableIdentifier());
- }
- body.append("\n\nConnection and error log:");
+ // Prepare an email with error logging information
+ StringBuilder body = new StringBuilder();
+ body.append("Please describe your problem:\n\n\n");
+ body.append("\n");
+ body.append(navigationHelper.getAppNameAndVersion());
+ body.append("\n");
+ if (serverSetting == null) {
+ body.append("(No server settings)");
+ } else {
+ body.append(serverSetting.getType().toString());
+ body.append(" settings: ");
+ body.append(serverSetting.getHumanReadableIdentifier());
+ }
+ body.append("\n\nConnection and error log:");
- // Print the individual error log messages as stored in the database
- List all = errorLogDao.queryBuilder().orderBy(ErrorLogEntry.ID, true).query();
- for (ErrorLogEntry errorLogEntry : all) {
- body.append("\n");
- body.append(errorLogEntry.getLogId());
- body.append(" -- ");
- body.append(errorLogEntry.getDateAndTime());
- body.append(" -- ");
- body.append(errorLogEntry.getPriority());
- body.append(" -- ");
- body.append(errorLogEntry.getMessage());
- }
+ // Print the individual error log messages as stored in the database
+ List all = errorLogDao.queryBuilder().orderBy(ErrorLogEntry.ID, true).query();
+ for (ErrorLogEntry errorLogEntry : all) {
+ body.append("\n");
+ body.append(errorLogEntry.getLogId());
+ body.append(" -- ");
+ body.append(errorLogEntry.getDateAndTime());
+ body.append(" -- ");
+ body.append(errorLogEntry.getPriority());
+ body.append(" -- ");
+ body.append(errorLogEntry.getMessage());
+ }
- Intent target = new Intent(Intent.ACTION_SEND);
- target.setType("message/rfc822");
- target.putExtra(Intent.EXTRA_EMAIL, new String[] { "transdroid@2312.nl" });
- target.putExtra(Intent.EXTRA_SUBJECT, "Transdroid error report");
- target.putExtra(Intent.EXTRA_TEXT, body.toString());
- try {
- callingActivity.startActivity(Intent.createChooser(target,
- callingActivity.getString(R.string.pref_sendlog)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- } catch (ActivityNotFoundException e) {
- log.i(callingActivity, "Tried to send error log, but there is no email app installed.");
- }
+ Intent target = new Intent(Intent.ACTION_SEND);
+ target.setType("message/rfc822");
+ target.putExtra(Intent.EXTRA_EMAIL, new String[]{"transdroid@2312.nl"});
+ target.putExtra(Intent.EXTRA_SUBJECT, "Transdroid error report");
+ target.putExtra(Intent.EXTRA_TEXT, body.toString());
+ try {
+ callingActivity.startActivity(Intent.createChooser(target,
+ callingActivity.getString(R.string.pref_sendlog)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ } catch (ActivityNotFoundException e) {
+ log.i(callingActivity, "Tried to send error log, but there is no email app installed.");
+ }
- } catch (SQLException e) {
- log.e(callingActivity, "Cannot read the error log to build an error report to send: " + e.toString());
- }
+ } catch (SQLException e) {
+ log.e(callingActivity, "Cannot read the error log to build an error report to send: " + e.toString());
+ }
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/log/Log.java b/app/src/main/java/org/transdroid/core/gui/log/Log.java
index a99c458b..9c3669ea 100644
--- a/app/src/main/java/org/transdroid/core/gui/log/Log.java
+++ b/app/src/main/java/org/transdroid/core/gui/log/Log.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,46 +28,47 @@ import java.util.Date;
/**
* Application-wide logging class that registers entries in the database (for a certain time).
+ *
* @author Eric Kok
*/
@EBean(scope = Scope.Singleton)
public class Log {
- public static final String LOG_NAME = "Transdroid";
- private static final long MAX_LOG_AGE = 15 * 60 * 1000; // 15 minutes
- @OrmLiteDao(helper = DatabaseHelper.class)
- Dao errorLogDao;
+ public static final String LOG_NAME = "Transdroid";
+ private static final long MAX_LOG_AGE = 15 * 60 * 1000; // 15 minutes
+ @OrmLiteDao(helper = DatabaseHelper.class)
+ Dao errorLogDao;
- protected void log(Object object, int priority, String message) {
- log(object instanceof String ? (String) object : object.getClass().getSimpleName(), priority, message);
- }
+ protected void log(Object object, int priority, String message) {
+ log(object instanceof String ? (String) object : object.getClass().getSimpleName(), priority, message);
+ }
- protected void log(String logName, int priority, String message) {
- if (BuildConfig.DEBUG) {
- android.util.Log.println(priority, LOG_NAME, message);
- }
- try {
- // Store this log message to the database
- errorLogDao.create(new ErrorLogEntry(priority, logName, message));
- // Truncate the error log
- DeleteBuilder db = errorLogDao.deleteBuilder();
- db.setWhere(db.where().le(ErrorLogEntry.DATEANDTIME, new Date(new Date().getTime() - MAX_LOG_AGE)));
- errorLogDao.delete(db.prepare());
- } catch (Exception e) {
- android.util.Log.e(LOG_NAME, "Cannot write log message to database: " + e.toString());
- }
- }
+ protected void log(String logName, int priority, String message) {
+ if (BuildConfig.DEBUG) {
+ android.util.Log.println(priority, LOG_NAME, message);
+ }
+ try {
+ // Store this log message to the database
+ errorLogDao.create(new ErrorLogEntry(priority, logName, message));
+ // Truncate the error log
+ DeleteBuilder db = errorLogDao.deleteBuilder();
+ db.setWhere(db.where().le(ErrorLogEntry.DATEANDTIME, new Date(new Date().getTime() - MAX_LOG_AGE)));
+ errorLogDao.delete(db.prepare());
+ } catch (Exception e) {
+ android.util.Log.e(LOG_NAME, "Cannot write log message to database: " + e.toString());
+ }
+ }
- public void d(Object object, String msg) {
- log(object, android.util.Log.DEBUG, msg);
- }
+ public void d(Object object, String msg) {
+ log(object, android.util.Log.DEBUG, msg);
+ }
- public void i(Object object, String msg) {
- log(object, android.util.Log.DEBUG, msg);
- }
+ public void i(Object object, String msg) {
+ log(object, android.util.Log.DEBUG, msg);
+ }
- public void e(Object object, String msg) {
- log(object, android.util.Log.ERROR, msg);
- }
+ public void e(Object object, String msg) {
+ log(object, android.util.Log.ERROR, msg);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/log/LogUncaughtExceptionHandler.java b/app/src/main/java/org/transdroid/core/gui/log/LogUncaughtExceptionHandler.java
index d0b5405a..714d1738 100644
--- a/app/src/main/java/org/transdroid/core/gui/log/LogUncaughtExceptionHandler.java
+++ b/app/src/main/java/org/transdroid/core/gui/log/LogUncaughtExceptionHandler.java
@@ -20,32 +20,32 @@ import android.content.Context;
public class LogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
- private final Context context;
- private final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
-
- public LogUncaughtExceptionHandler(Context context, Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler) {
- this.context = context;
- this.defaultUncaughtExceptionHandler = defaultUncaughtExceptionHandler;
- }
-
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
-
- // Write exception stack trace to the log
- String prefix = "E: ";
- Log_ log = Log_.getInstance_(context);
- log.e(this, prefix + ex.toString());
- if (ex.getCause() != null) {
- for (StackTraceElement e : ex.getCause().getStackTrace()) {
- log.e(this, prefix + e.toString());
- }
- }
- for (StackTraceElement e : ex.getStackTrace()) {
- log.e(this, prefix + e.toString());
- }
-
- // Rely on default Android exception handling
- defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
- }
+ private final Context context;
+ private final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;
+
+ public LogUncaughtExceptionHandler(Context context, Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler) {
+ this.context = context;
+ this.defaultUncaughtExceptionHandler = defaultUncaughtExceptionHandler;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+
+ // Write exception stack trace to the log
+ String prefix = "E: ";
+ Log_ log = Log_.getInstance_(context);
+ log.e(this, prefix + ex.toString());
+ if (ex.getCause() != null) {
+ for (StackTraceElement e : ex.getCause().getStackTrace()) {
+ log.e(this, prefix + e.toString());
+ }
+ }
+ for (StackTraceElement e : ex.getStackTrace()) {
+ log.e(this, prefix + e.toString());
+ }
+
+ // Rely on default Android exception handling
+ defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java b/app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java
index d81f080e..73514904 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -36,78 +36,82 @@ import android.view.Window;
* Helper class that show a dialog either as pop-up or as full screen activity. Should be used by calling
* {@link #showDialog(Context, DialogSpecification)} with in instance of the dialog specification that should be shown,
* from the calling activity's {@link Activity#onCreateDialog(int)}.
+ *
* @author Eric Kok
*/
@EActivity
public class DialogHelper extends Activity {
- @Extra
- protected DialogSpecification dialog;
+ @Extra
+ protected DialogSpecification dialog;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(dialog.getDialogLayoutId());
- // TODO getActionBar().setDisplayHomeAsUpEnabled(true);
- }
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(dialog.getDialogLayoutId());
+ // TODO getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(dialog.getDialogMenuId(), menu);
return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- // Action bar up button clicked; navigate up all the way back to the torrents activity
- TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
- return true;
- }
- return dialog.onMenuItemSelected(this, item.getItemId());
- }
-
- /**
- * Call this from {@link Activity#onCreateDialog(int)}, supplying an instance of the {@link DialogSpecification}
- * that should be shown to the user.
- * @param context The activity that calls this method and which will own the constructed dialog
- * @param dialog An instance of the specification for the dialog that needs to be shown
- * @return Either an instance of a {@link Dialog} that the activity should further control or null if the dialog
- * will instead be opened as a full screen activity
- */
- public static Dialog showDialog(Context context, DialogSpecification dialog) {
-
- // If the device is large (i.e. a tablet) then return a dialog to show
- if (!NavigationHelper_.getInstance_(context).isSmallScreen())
- return new PopupDialog(context, dialog);
-
- // This is a small device; create a full screen dialog (which is just an activity)
- DialogHelper_.intent(context).dialog(dialog).start();
- return null;
-
- }
-
- /**
- * A specific dialog that shows some layout (resource) as contents. It has no buttons or other chrome.
- */
- protected static class PopupDialog extends Dialog {
- public PopupDialog(Context context, DialogSpecification dialog) {
- super(context);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(dialog.getDialogLayoutId());
- }
- }
-
- /**
- * Specification for some dialog that can be show to the user, consisting of a custom layout and possibly an action
- * bar menu. Warning: the action bar, and thus the menu options, is only shown when the dialog is presented as full
- * screen activity. Use only for unimportant actions.
- */
- public interface DialogSpecification extends Serializable {
- int getDialogLayoutId();
- int getDialogMenuId();
- boolean onMenuItemSelected(Activity ownerActivity, int selectedItemId);
- }
-
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ // Action bar up button clicked; navigate up all the way back to the torrents activity
+ TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
+ return true;
+ }
+ return dialog.onMenuItemSelected(this, item.getItemId());
+ }
+
+ /**
+ * Call this from {@link Activity#onCreateDialog(int)}, supplying an instance of the {@link DialogSpecification}
+ * that should be shown to the user.
+ *
+ * @param context The activity that calls this method and which will own the constructed dialog
+ * @param dialog An instance of the specification for the dialog that needs to be shown
+ * @return Either an instance of a {@link Dialog} that the activity should further control or null if the dialog
+ * will instead be opened as a full screen activity
+ */
+ public static Dialog showDialog(Context context, DialogSpecification dialog) {
+
+ // If the device is large (i.e. a tablet) then return a dialog to show
+ if (!NavigationHelper_.getInstance_(context).isSmallScreen())
+ return new PopupDialog(context, dialog);
+
+ // This is a small device; create a full screen dialog (which is just an activity)
+ DialogHelper_.intent(context).dialog(dialog).start();
+ return null;
+
+ }
+
+ /**
+ * A specific dialog that shows some layout (resource) as contents. It has no buttons or other chrome.
+ */
+ protected static class PopupDialog extends Dialog {
+ public PopupDialog(Context context, DialogSpecification dialog) {
+ super(context);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(dialog.getDialogLayoutId());
+ }
+ }
+
+ /**
+ * Specification for some dialog that can be show to the user, consisting of a custom layout and possibly an action
+ * bar menu. Warning: the action bar, and thus the menu options, is only shown when the dialog is presented as full
+ * screen activity. Use only for unimportant actions.
+ */
+ public interface DialogSpecification extends Serializable {
+ int getDialogLayoutId();
+
+ int getDialogMenuId();
+
+ boolean onMenuItemSelected(Activity ownerActivity, int selectedItemId);
+ }
+
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java b/app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java
index 9a90b5a1..05201f75 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -33,81 +33,85 @@ import java.util.List;
/**
* List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where appropriate.
+ *
* @author Eric Kok
*/
@EBean
public class FilterListAdapter extends MergeAdapter {
- @RootContext
- protected Context context;
- private FilterListItemAdapter serverItems = null;
- private FilterListItemAdapter statusTypeItems = null;
- private FilterListItemAdapter labelItems = null;
- protected ViewHolderAdapter statusTypeSeparator;
- protected ViewHolderAdapter labelSeperator;
- protected ViewHolderAdapter serverSeparator;
+ @RootContext
+ protected Context context;
+ private FilterListItemAdapter serverItems = null;
+ private FilterListItemAdapter statusTypeItems = null;
+ private FilterListItemAdapter labelItems = null;
+ protected ViewHolderAdapter statusTypeSeparator;
+ protected ViewHolderAdapter labelSeperator;
+ protected ViewHolderAdapter serverSeparator;
- /**
- * Update the list of available servers
- * @param servers The new list of available servers
- */
- public void updateServers(List servers) {
- if (this.serverItems == null && servers != null) {
- serverSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_servers)));
- serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE);
- addAdapter(serverSeparator);
- this.serverItems = new FilterListItemAdapter(context, servers);
- addAdapter(serverItems);
- } else if (this.serverItems != null && servers != null) {
- serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE);
- this.serverItems.update(servers);
- } else {
- serverSeparator.setViewVisibility(View.GONE);
- this.serverItems.update(new ArrayList());
- }
- notifyDataSetChanged();
- }
+ /**
+ * Update the list of available servers
+ *
+ * @param servers The new list of available servers
+ */
+ public void updateServers(List servers) {
+ if (this.serverItems == null && servers != null) {
+ serverSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_servers)));
+ serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE);
+ addAdapter(serverSeparator);
+ this.serverItems = new FilterListItemAdapter(context, servers);
+ addAdapter(serverItems);
+ } else if (this.serverItems != null && servers != null) {
+ serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE);
+ this.serverItems.update(servers);
+ } else {
+ serverSeparator.setViewVisibility(View.GONE);
+ this.serverItems.update(new ArrayList());
+ }
+ notifyDataSetChanged();
+ }
- /**
- * Update the list of available status types
- * @param statusTypes The new list of available status types
- */
- public void updateStatusTypes(List statusTypes) {
- if (this.statusTypeItems == null && statusTypes != null) {
- statusTypeSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_status)));
- statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE);
- addAdapter(statusTypeSeparator);
- this.statusTypeItems = new FilterListItemAdapter(context, statusTypes);
- addAdapter(statusTypeItems);
- } else if (this.statusTypeItems != null && statusTypes != null) {
- statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE);
- this.statusTypeItems.update(statusTypes);
- } else {
- statusTypeSeparator.setViewVisibility(View.GONE);
- this.statusTypeItems.update(new ArrayList());
- }
- notifyDataSetChanged();
- }
+ /**
+ * Update the list of available status types
+ *
+ * @param statusTypes The new list of available status types
+ */
+ public void updateStatusTypes(List statusTypes) {
+ if (this.statusTypeItems == null && statusTypes != null) {
+ statusTypeSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_status)));
+ statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE);
+ addAdapter(statusTypeSeparator);
+ this.statusTypeItems = new FilterListItemAdapter(context, statusTypes);
+ addAdapter(statusTypeItems);
+ } else if (this.statusTypeItems != null && statusTypes != null) {
+ statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE);
+ this.statusTypeItems.update(statusTypes);
+ } else {
+ statusTypeSeparator.setViewVisibility(View.GONE);
+ this.statusTypeItems.update(new ArrayList());
+ }
+ notifyDataSetChanged();
+ }
- /**
- * Update the list of available labels
- * @param labels The new list of available labels
- */
- public void updateLabels(List labels) {
- if (this.labelItems == null && labels != null) {
- labelSeperator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_labels)));
- labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
- addAdapter(labelSeperator);
- this.labelItems = new FilterListItemAdapter(context, labels);
- addAdapter(labelItems);
- } else if (this.labelItems != null && labels != null) {
- labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
- this.labelItems.update(labels);
- } else {
- labelSeperator.setViewVisibility(View.GONE);
- this.labelItems.update(new ArrayList());
- }
- notifyDataSetChanged();
- }
+ /**
+ * Update the list of available labels
+ *
+ * @param labels The new list of available labels
+ */
+ public void updateLabels(List labels) {
+ if (this.labelItems == null && labels != null) {
+ labelSeperator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_labels)));
+ labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
+ addAdapter(labelSeperator);
+ this.labelItems = new FilterListItemAdapter(context, labels);
+ addAdapter(labelItems);
+ } else if (this.labelItems != null && labels != null) {
+ labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
+ this.labelItems.update(labels);
+ } else {
+ labelSeperator.setViewVisibility(View.GONE);
+ this.labelItems.update(new ArrayList());
+ }
+ notifyDataSetChanged();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java b/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java
index 93611c90..91b4ac83 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,48 +28,49 @@ import java.util.List;
public class FilterListItemAdapter extends BaseAdapter {
- private final Context context;
- private List extends SimpleListItem> items;
+ private final Context context;
+ private List extends SimpleListItem> items;
- public FilterListItemAdapter(Context context, List extends SimpleListItem> items) {
- this.context = context;
- this.items = items;
- }
+ public FilterListItemAdapter(Context context, List extends SimpleListItem> items) {
+ this.context = context;
+ this.items = items;
+ }
- /**
- * Allows updating of the full data list underlying this adapter, replacing all items
- * @param newItems The new list of filter items to display
- */
- public void update(List extends SimpleListItem> newItems) {
- this.items = newItems;
- notifyDataSetChanged();
- }
+ /**
+ * Allows updating of the full data list underlying this adapter, replacing all items
+ *
+ * @param newItems The new list of filter items to display
+ */
+ public void update(List extends SimpleListItem> newItems) {
+ this.items = newItems;
+ notifyDataSetChanged();
+ }
- @Override
- public int getCount() {
- return items.size();
- }
+ @Override
+ public int getCount() {
+ return items.size();
+ }
- @Override
- public SimpleListItem getItem(int position) {
- return items.get(position);
- }
+ @Override
+ public SimpleListItem getItem(int position) {
+ return items.get(position);
+ }
- @Override
- public long getItemId(int position) {
- return position;
- }
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- FilterListItemView filterItemView;
- if (convertView == null || !(convertView instanceof SimpleListItemView)) {
- filterItemView = FilterListItemView_.build(context);
- } else {
- filterItemView = (FilterListItemView) convertView;
- }
- filterItemView.bind(getItem(position));
- return filterItemView;
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ FilterListItemView filterItemView;
+ if (convertView == null || !(convertView instanceof SimpleListItemView)) {
+ filterItemView = FilterListItemView_.build(context);
+ } else {
+ filterItemView = (FilterListItemView) convertView;
+ }
+ filterItemView.bind(getItem(position));
+ return filterItemView;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java b/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java
index 3ee5d9fe..6c72cae8 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -27,20 +27,21 @@ import org.transdroid.core.gui.lists.SimpleListItem;
/**
* View that represents some {@link SimpleListItem} object specifically used to represent a navigation filter item.
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_filter)
public class FilterListItemView extends FrameLayout {
- @ViewById
- protected TextView itemText;
+ @ViewById
+ protected TextView itemText;
- public FilterListItemView(Context context) {
- super(context);
- }
+ public FilterListItemView(Context context) {
+ super(context);
+ }
- public void bind(SimpleListItem filterItem) {
- itemText.setText(filterItem.getName());
- }
+ public void bind(SimpleListItem filterItem) {
+ itemText.setText(filterItem.getName());
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java b/app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java
index 3a2f9b14..d04b3a08 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -27,29 +27,31 @@ import org.transdroid.R;
/**
* A list item that shows a sub header or separator (in underlined Holo style).
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_separator)
public class FilterSeparatorView extends FrameLayout {
- protected String text;
-
- @ViewById
- protected TextView separatorText;
-
- public FilterSeparatorView(Context context) {
- super(context);
- }
-
- /**
- * Sets the text that will be shown in this separator (sub header)
- * @param text The new text to show
- * @return Itself, for convenience of method chaining
- */
- public FilterSeparatorView setText(String text) {
- separatorText.setText(text);
- setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, AbsListView.LayoutParams.WRAP_CONTENT));
- return this;
- }
+ protected String text;
+
+ @ViewById
+ protected TextView separatorText;
+
+ public FilterSeparatorView(Context context) {
+ super(context);
+ }
+
+ /**
+ * Sets the text that will be shown in this separator (sub header)
+ *
+ * @param text The new text to show
+ * @return Itself, for convenience of method chaining
+ */
+ public FilterSeparatorView setText(String text) {
+ separatorText.setText(text);
+ setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, AbsListView.LayoutParams.WRAP_CONTENT));
+ return this;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/Label.java b/app/src/main/java/org/transdroid/core/gui/navigation/Label.java
index 32ecbb2c..10d6e600 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/Label.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/Label.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,118 +29,121 @@ import java.util.List;
/**
* Represents some label that is active or available on the server.
+ *
* @author Eric Kok
*/
public class Label implements SimpleListItem, NavigationFilter, Comparable {
- private static String unnamedLabelText = null;
-
- private final boolean isEmptyLabel;
- private final String name;
- private final int count;
-
- private Label(String name, int count, boolean isEmptyLabel) {
- this.name = name;
- this.count = count;
- this.isEmptyLabel = isEmptyLabel;
- }
-
- public Label(org.transdroid.daemon.Label daemonLabel) {
- this(daemonLabel.getName(), daemonLabel.getCount(), false);
- }
-
- @Override
- public String getName() {
- if (TextUtils.isEmpty(this.name)) {
- return unnamedLabelText;
- }
- return this.name;
- }
-
- @Override
- public String getCode() {
- // Use the class name and label name to provide a unique navigation filter code
- return Label.class.getSimpleName() + "_" + name;
- }
-
- public int getCount() {
- return count;
- }
-
- public boolean isEmptyLabel() {
- return isEmptyLabel;
- }
-
- /**
- * Returns true if the torrent label's name matches this (selected) label's name, false otherwise
- * @param torrent The torrent to match against this label
- * @param dormantAsInactive This property is ignored for label comparisons
- */
- @Override
- public boolean matches(Torrent torrent, boolean dormantAsInactive) {
- if (isEmptyLabel) {
- return TextUtils.isEmpty(torrent.getLabelName());
- }
- return torrent.getLabelName() != null && torrent.getLabelName().equals(name);
- }
-
- @Override
- public int compareTo(Label another) {
- return this.name.compareTo(another.getName());
- }
-
- /**
- * Converts a list of labels as retrieved from a server daemon into a list of labels that can be used in the UI as navigation filters.
- * @param daemonLabels The raw list of labels as received from the server daemon adapter
- * @param unnamedLabel The text to show for the empty label (i.e. the unnamed label)
- * @return A label items that can be used in a filter list such as the action bar spinner
- */
- public static ArrayList convertToNavigationLabels(List daemonLabels, String unnamedLabel) {
- if (daemonLabels == null) {
- return null;
- }
- ArrayList localLabels = new ArrayList<>();
- unnamedLabelText = unnamedLabel;
-
- for (org.transdroid.daemon.Label label : daemonLabels) {
- if (label != null && !TextUtils.isEmpty(label.getName())) {
- localLabels.add(new Label(label));
- }
- }
- Collections.sort(localLabels);
-
- // force unlabelled to be at the top
- localLabels.add(0, new Label(unnamedLabel, -1, true));
-
- return localLabels;
- }
-
- private Label(Parcel in) {
- this.name = in.readString();
- this.count = in.readInt();
- this.isEmptyLabel = in.readInt() == 1;
- }
-
- public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
- public Label createFromParcel(Parcel in) {
- return new Label(in);
- }
-
- public Label[] newArray(int size) {
- return new Label[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(name);
- dest.writeInt(count);
- dest.writeInt(isEmptyLabel ? 1 : 0);
- }
+ private static String unnamedLabelText = null;
+
+ private final boolean isEmptyLabel;
+ private final String name;
+ private final int count;
+
+ private Label(String name, int count, boolean isEmptyLabel) {
+ this.name = name;
+ this.count = count;
+ this.isEmptyLabel = isEmptyLabel;
+ }
+
+ public Label(org.transdroid.daemon.Label daemonLabel) {
+ this(daemonLabel.getName(), daemonLabel.getCount(), false);
+ }
+
+ @Override
+ public String getName() {
+ if (TextUtils.isEmpty(this.name)) {
+ return unnamedLabelText;
+ }
+ return this.name;
+ }
+
+ @Override
+ public String getCode() {
+ // Use the class name and label name to provide a unique navigation filter code
+ return Label.class.getSimpleName() + "_" + name;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public boolean isEmptyLabel() {
+ return isEmptyLabel;
+ }
+
+ /**
+ * Returns true if the torrent label's name matches this (selected) label's name, false otherwise
+ *
+ * @param torrent The torrent to match against this label
+ * @param dormantAsInactive This property is ignored for label comparisons
+ */
+ @Override
+ public boolean matches(Torrent torrent, boolean dormantAsInactive) {
+ if (isEmptyLabel) {
+ return TextUtils.isEmpty(torrent.getLabelName());
+ }
+ return torrent.getLabelName() != null && torrent.getLabelName().equals(name);
+ }
+
+ @Override
+ public int compareTo(Label another) {
+ return this.name.compareTo(another.getName());
+ }
+
+ /**
+ * Converts a list of labels as retrieved from a server daemon into a list of labels that can be used in the UI as navigation filters.
+ *
+ * @param daemonLabels The raw list of labels as received from the server daemon adapter
+ * @param unnamedLabel The text to show for the empty label (i.e. the unnamed label)
+ * @return A label items that can be used in a filter list such as the action bar spinner
+ */
+ public static ArrayList convertToNavigationLabels(List daemonLabels, String unnamedLabel) {
+ if (daemonLabels == null) {
+ return null;
+ }
+ ArrayList localLabels = new ArrayList<>();
+ unnamedLabelText = unnamedLabel;
+
+ for (org.transdroid.daemon.Label label : daemonLabels) {
+ if (label != null && !TextUtils.isEmpty(label.getName())) {
+ localLabels.add(new Label(label));
+ }
+ }
+ Collections.sort(localLabels);
+
+ // force unlabelled to be at the top
+ localLabels.add(0, new Label(unnamedLabel, -1, true));
+
+ return localLabels;
+ }
+
+ private Label(Parcel in) {
+ this.name = in.readString();
+ this.count = in.readInt();
+ this.isEmptyLabel = in.readInt() == 1;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Label createFromParcel(Parcel in) {
+ return new Label(in);
+ }
+
+ public Label[] newArray(int size) {
+ return new Label[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name);
+ dest.writeInt(count);
+ dest.writeInt(isEmptyLabel ? 1 : 0);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java
index 48181c16..8479a852 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -22,29 +22,33 @@ import org.transdroid.daemon.Torrent;
/**
* Represents a filter, used in the app navigation, that can check if some torrent matches the user-set filter
+ *
* @author Eric Kok
*/
public interface NavigationFilter extends Parcelable {
- /**
- * Implementations should check if the supplied torrent matches the filter; for example a label filter should return true if the torrent's label
- * equals this items label name.
- * @param torrent The torrent to check for matches
- * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively downloading or seeding
- * @return True if the torrent matches the filter and should be shown in the current screen, false otherwise
- */
- boolean matches(Torrent torrent, boolean dormantAsInactive);
+ /**
+ * Implementations should check if the supplied torrent matches the filter; for example a label filter should return true if the torrent's label
+ * equals this items label name.
+ *
+ * @param torrent The torrent to check for matches
+ * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively downloading or seeding
+ * @return True if the torrent matches the filter and should be shown in the current screen, false otherwise
+ */
+ boolean matches(Torrent torrent, boolean dormantAsInactive);
- /**
- * Implementations should return a name that can be shown to indicate the active filter
- * @return The name of the filter item as string
- */
- String getName();
+ /**
+ * Implementations should return a name that can be shown to indicate the active filter
+ *
+ * @return The name of the filter item as string
+ */
+ String getName();
- /**
- * Implementations should return a code that (within reasonable expectations) uniquely identifies it in the list of navigation filters
- * @return The code to uniquely identify this specific navigation filter, such as the name with a class name prefix
- */
- String getCode();
+ /**
+ * Implementations should return a code that (within reasonable expectations) uniquely identifies it in the list of navigation filters
+ *
+ * @return The code to uniquely identify this specific navigation filter, such as the name with a class name prefix
+ */
+ String getCode();
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
index c7018530..ad3bc05a 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -26,9 +26,11 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
+
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.TypefaceSpan;
@@ -54,250 +56,260 @@ import java.util.List;
/**
* Helper for activities to make navigation-related decisions, such as when a device can display a larger, tablet style layout or how to display
* errors.
+ *
* @author Eric Kok
*/
@SuppressLint("ResourceAsColor")
@EBean
public class NavigationHelper {
- private static final int REQUEST_TORRENT_READ_PERMISSION = 0;
- private static final int REQUEST_SETTINGS_READ_PERMISSION = 1;
- private static final int REQUEST_SETTINGS_WRITE_PERMISSION = 2;
-
- private static ImageLoader imageCache;
- @RootContext
- protected Context context;
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- public boolean checkTorrentReadPermission(final Activity activity) {
- return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
- checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_TORRENT_READ_PERMISSION);
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- public boolean checkSettingsReadPermission(final Activity activity) {
- return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
- checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_SETTINGS_READ_PERMISSION);
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- public boolean checkSettingsWritePermission(final Activity activity) {
- return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
- checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_SETTINGS_WRITE_PERMISSION);
- }
-
- private boolean checkPermission(final Activity activity, final String permission, final int requestCode) {
- if (hasPermission(permission))
- // Permission already granted
- return true;
- if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
- // Never asked again: show a dialog with an explanation
- activity.runOnUiThread(new Runnable() {
- public void run() {
- new MaterialDialog.Builder(context).content(R.string.permission_readtorrent).positiveText(android.R.string.ok)
- .onPositive(new MaterialDialog.SingleButtonCallback() {
- @Override
- public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
- ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
- }
- }).show();
- }
- });
- return false;
- }
- // Permission not granted (and we asked for it already before)
- ActivityCompat.requestPermissions(activity, new String[]{permission}, REQUEST_TORRENT_READ_PERMISSION);
- return false;
- }
-
- private boolean hasPermission(String requiredPermission) {
- return ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED;
- }
-
- public Boolean handleTorrentReadPermissionResult(int requestCode, int[] grantResults) {
- if (requestCode == REQUEST_TORRENT_READ_PERMISSION) {
- // Return permission granting result
- return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
- }
- return null;
- }
-
- public Boolean handleSettingsReadPermissionResult(int requestCode, int[] grantResults) {
- if (requestCode == REQUEST_SETTINGS_READ_PERMISSION) {
- // Return permission granting result
- return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
- }
- return null;
- }
-
- public Boolean handleSettingsWritePermissionResult(int requestCode, int[] grantResults) {
- if (requestCode == REQUEST_SETTINGS_WRITE_PERMISSION) {
- // Return permission granting result
- return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
- }
- return null;
- }
-
- /**
- * Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font
- * @param string A plain text {@link String}
- * @return A {@link Spannable} that can be applied to supporting views (such as the action bar title) so that the input string will be displayed
- * using the Roboto Condensed font (if the OS has this)
- */
- public static SpannableString buildCondensedFontString(String string) {
- if (string == null) {
- return null;
- }
- SpannableString s = new SpannableString(string);
- s.setSpan(new TypefaceSpan("sans-serif-condensed"), 0, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return s;
- }
-
- /**
- * Analyses a torrent http or magnet URI and tries to come up with a reasonable human-readable name.
- * @param rawTorrentUri The raw http:// or magnet: link to the torrent
- * @return A best-guess, reasonably long name for the linked torrent
- */
- public static String extractNameFromUri(Uri rawTorrentUri) {
-
- if (rawTorrentUri.getScheme() == null) {
- // Probably an incorrect URI; just return the whole thing
- return rawTorrentUri.toString();
- }
-
- if (rawTorrentUri.getScheme().equals("magnet")) {
- // Magnet links might have a dn (display name) parameter
- String dn = getQueryParameter(rawTorrentUri, "dn");
- if (dn != null && !dn.equals("")) {
- return dn;
- }
- // If not, try to return the hash that is specified as xt (exact topci)
- String xt = getQueryParameter(rawTorrentUri, "xt");
- if (xt != null && !xt.equals("")) {
- return xt;
- }
- }
-
- if (rawTorrentUri.isHierarchical()) {
- String path = rawTorrentUri.getPath();
- if (path != null) {
- if (path.contains("/")) {
- path = path.substring(path.lastIndexOf("/"));
- }
- return path;
- }
- }
-
- // No idea what to do with this; return as is
- return rawTorrentUri.toString();
- }
-
- private static String getQueryParameter(Uri uri, String parameter) {
- int start = uri.toString().indexOf(parameter + "=");
- if (start >= 0) {
- int begin = start + (parameter + "=").length();
- int end = uri.toString().indexOf("&", begin);
- return uri.toString().substring(begin, end >= 0 ? end : uri.toString().length());
- }
- return null;
- }
-
- /**
- * Returns (and initialises, if needed) an image cache that uses memory and (1MB) local storage.
- * @return An image cache that loads web images synchronously and transparently
- */
- public ImageLoader getImageCache() {
- if (imageCache == null) {
- imageCache = ImageLoader.getInstance();
- try {
- LruDiskCache diskCache = new LruDiskCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25);
- // @formatter:off
- Builder imageCacheBuilder = new Builder(context)
- .defaultDisplayImageOptions(
- new DisplayImageOptions.Builder()
- .cacheInMemory(true)
- .cacheOnDisk(true)
- .imageScaleType(ImageScaleType.IN_SAMPLE_INT)
- .showImageForEmptyUri(R.drawable.ic_launcher).build())
- .memoryCache(new UsingFreqLimitedMemoryCache(1024 * 1024))
- .diskCache(diskCache);
- imageCache.init(imageCacheBuilder.build());
- // @formatter:on
- } catch (IOException e) {
- // The cache directory is always available on Android; ignore this exception
- }
- }
- return imageCache;
- }
-
- public void forceOpenInBrowser(Uri link) {
- Intent intent = new Intent(Intent.ACTION_VIEW).setData(link);
- List activities = context.getPackageManager().queryIntentActivities(intent, 0);
- for (ResolveInfo resolveInfo : activities) {
- if (activities.size() == 1 || (resolveInfo.isDefault && resolveInfo.activityInfo.packageName.equals(context.getPackageName()))) {
- // There is a default browser; use this
- intent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
- return;
- }
- }
- // No default browser found: open chooser
- try {
- context.startActivity(Intent.createChooser(intent, "Open..."));
- } catch (Exception e) {
- // No browser installed; consume and fail silently
- }
- }
-
- /**
- * Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180).
- * @return The app name and version, such as 'Transdroid 1.5.0 (180)'
- */
- public String getAppNameAndVersion() {
- return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")";
- }
-
- /**
- * Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for example, be used to determine if a
- * dialog should be shown full screen. Currently is true if the device's smallest dimension is 500 dip.
- * @return True if the app runs on a small device, false otherwise
- */
- public boolean isSmallScreen() {
- return context.getResources().getBoolean(R.bool.show_dialog_fullscreen);
- }
-
- /**
- * Whether any search-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite
- * version.
- * @return True if search is enabled, false otherwise
- */
- public boolean enableSearchUi() {
- return context.getResources().getBoolean(R.bool.search_available);
- }
-
- /**
- * Whether any RSS-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite version.
- * @return True if search is enabled, false otherwise
- */
- public boolean enableRssUi() {
- return context.getResources().getBoolean(R.bool.rss_available);
- }
-
- /**
- * Returns whether any seedbox-related components should be shown in the interface; specifically the option to add server settings via easy
- * seedbox-specific screens.
- * @return True if seedbox settings should be shown, false otherwise
- */
- public boolean enableSeedboxes() {
- return context.getResources().getBoolean(R.bool.seedboxes_available);
- }
-
- /**
- * Whether the custom app update checker should be used to check for new app and search module versions.
- * @return True if it should be checked against transdroid.org if there are app updates (as opposed to using the Play Store for updates, for
- * example), false otherwise
- */
- public boolean enableUpdateChecker() {
- return context.getResources().getBoolean(R.bool.updatecheck_available);
- }
+ private static final int REQUEST_TORRENT_READ_PERMISSION = 0;
+ private static final int REQUEST_SETTINGS_READ_PERMISSION = 1;
+ private static final int REQUEST_SETTINGS_WRITE_PERMISSION = 2;
+
+ private static ImageLoader imageCache;
+ @RootContext
+ protected Context context;
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public boolean checkTorrentReadPermission(final Activity activity) {
+ return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
+ checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_TORRENT_READ_PERMISSION);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public boolean checkSettingsReadPermission(final Activity activity) {
+ return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
+ checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_SETTINGS_READ_PERMISSION);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ public boolean checkSettingsWritePermission(final Activity activity) {
+ return Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ||
+ checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_SETTINGS_WRITE_PERMISSION);
+ }
+
+ private boolean checkPermission(final Activity activity, final String permission, final int requestCode) {
+ if (hasPermission(permission))
+ // Permission already granted
+ return true;
+ if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
+ // Never asked again: show a dialog with an explanation
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ new MaterialDialog.Builder(context).content(R.string.permission_readtorrent).positiveText(android.R.string.ok)
+ .onPositive(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
+ }
+ }).show();
+ }
+ });
+ return false;
+ }
+ // Permission not granted (and we asked for it already before)
+ ActivityCompat.requestPermissions(activity, new String[]{permission}, REQUEST_TORRENT_READ_PERMISSION);
+ return false;
+ }
+
+ private boolean hasPermission(String requiredPermission) {
+ return ContextCompat.checkSelfPermission(context, requiredPermission) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ public Boolean handleTorrentReadPermissionResult(int requestCode, int[] grantResults) {
+ if (requestCode == REQUEST_TORRENT_READ_PERMISSION) {
+ // Return permission granting result
+ return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ }
+ return null;
+ }
+
+ public Boolean handleSettingsReadPermissionResult(int requestCode, int[] grantResults) {
+ if (requestCode == REQUEST_SETTINGS_READ_PERMISSION) {
+ // Return permission granting result
+ return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ }
+ return null;
+ }
+
+ public Boolean handleSettingsWritePermissionResult(int requestCode, int[] grantResults) {
+ if (requestCode == REQUEST_SETTINGS_WRITE_PERMISSION) {
+ // Return permission granting result
+ return grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ }
+ return null;
+ }
+
+ /**
+ * Converts a string into a {@link Spannable} that displays the string in the Roboto Condensed font
+ *
+ * @param string A plain text {@link String}
+ * @return A {@link Spannable} that can be applied to supporting views (such as the action bar title) so that the input string will be displayed
+ * using the Roboto Condensed font (if the OS has this)
+ */
+ public static SpannableString buildCondensedFontString(String string) {
+ if (string == null) {
+ return null;
+ }
+ SpannableString s = new SpannableString(string);
+ s.setSpan(new TypefaceSpan("sans-serif-condensed"), 0, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return s;
+ }
+
+ /**
+ * Analyses a torrent http or magnet URI and tries to come up with a reasonable human-readable name.
+ *
+ * @param rawTorrentUri The raw http:// or magnet: link to the torrent
+ * @return A best-guess, reasonably long name for the linked torrent
+ */
+ public static String extractNameFromUri(Uri rawTorrentUri) {
+
+ if (rawTorrentUri.getScheme() == null) {
+ // Probably an incorrect URI; just return the whole thing
+ return rawTorrentUri.toString();
+ }
+
+ if (rawTorrentUri.getScheme().equals("magnet")) {
+ // Magnet links might have a dn (display name) parameter
+ String dn = getQueryParameter(rawTorrentUri, "dn");
+ if (dn != null && !dn.equals("")) {
+ return dn;
+ }
+ // If not, try to return the hash that is specified as xt (exact topci)
+ String xt = getQueryParameter(rawTorrentUri, "xt");
+ if (xt != null && !xt.equals("")) {
+ return xt;
+ }
+ }
+
+ if (rawTorrentUri.isHierarchical()) {
+ String path = rawTorrentUri.getPath();
+ if (path != null) {
+ if (path.contains("/")) {
+ path = path.substring(path.lastIndexOf("/"));
+ }
+ return path;
+ }
+ }
+
+ // No idea what to do with this; return as is
+ return rawTorrentUri.toString();
+ }
+
+ private static String getQueryParameter(Uri uri, String parameter) {
+ int start = uri.toString().indexOf(parameter + "=");
+ if (start >= 0) {
+ int begin = start + (parameter + "=").length();
+ int end = uri.toString().indexOf("&", begin);
+ return uri.toString().substring(begin, end >= 0 ? end : uri.toString().length());
+ }
+ return null;
+ }
+
+ /**
+ * Returns (and initialises, if needed) an image cache that uses memory and (1MB) local storage.
+ *
+ * @return An image cache that loads web images synchronously and transparently
+ */
+ public ImageLoader getImageCache() {
+ if (imageCache == null) {
+ imageCache = ImageLoader.getInstance();
+ try {
+ LruDiskCache diskCache = new LruDiskCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25);
+ // @formatter:off
+ Builder imageCacheBuilder = new Builder(context)
+ .defaultDisplayImageOptions(
+ new DisplayImageOptions.Builder()
+ .cacheInMemory(true)
+ .cacheOnDisk(true)
+ .imageScaleType(ImageScaleType.IN_SAMPLE_INT)
+ .showImageForEmptyUri(R.drawable.ic_launcher).build())
+ .memoryCache(new UsingFreqLimitedMemoryCache(1024 * 1024))
+ .diskCache(diskCache);
+ imageCache.init(imageCacheBuilder.build());
+ // @formatter:on
+ } catch (IOException e) {
+ // The cache directory is always available on Android; ignore this exception
+ }
+ }
+ return imageCache;
+ }
+
+ public void forceOpenInBrowser(Uri link) {
+ Intent intent = new Intent(Intent.ACTION_VIEW).setData(link);
+ List activities = context.getPackageManager().queryIntentActivities(intent, 0);
+ for (ResolveInfo resolveInfo : activities) {
+ if (activities.size() == 1 || (resolveInfo.isDefault && resolveInfo.activityInfo.packageName.equals(context.getPackageName()))) {
+ // There is a default browser; use this
+ intent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
+ return;
+ }
+ }
+ // No default browser found: open chooser
+ try {
+ context.startActivity(Intent.createChooser(intent, "Open..."));
+ } catch (Exception e) {
+ // No browser installed; consume and fail silently
+ }
+ }
+
+ /**
+ * Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180).
+ *
+ * @return The app name and version, such as 'Transdroid 1.5.0 (180)'
+ */
+ public String getAppNameAndVersion() {
+ return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")";
+ }
+
+ /**
+ * Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for example, be used to determine if a
+ * dialog should be shown full screen. Currently is true if the device's smallest dimension is 500 dip.
+ *
+ * @return True if the app runs on a small device, false otherwise
+ */
+ public boolean isSmallScreen() {
+ return context.getResources().getBoolean(R.bool.show_dialog_fullscreen);
+ }
+
+ /**
+ * Whether any search-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite
+ * version.
+ *
+ * @return True if search is enabled, false otherwise
+ */
+ public boolean enableSearchUi() {
+ return context.getResources().getBoolean(R.bool.search_available);
+ }
+
+ /**
+ * Whether any RSS-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite version.
+ *
+ * @return True if search is enabled, false otherwise
+ */
+ public boolean enableRssUi() {
+ return context.getResources().getBoolean(R.bool.rss_available);
+ }
+
+ /**
+ * Returns whether any seedbox-related components should be shown in the interface; specifically the option to add server settings via easy
+ * seedbox-specific screens.
+ *
+ * @return True if seedbox settings should be shown, false otherwise
+ */
+ public boolean enableSeedboxes() {
+ return context.getResources().getBoolean(R.bool.seedboxes_available);
+ }
+
+ /**
+ * Whether the custom app update checker should be used to check for new app and search module versions.
+ *
+ * @return True if it should be checked against transdroid.org if there are app updates (as opposed to using the Play Store for updates, for
+ * example), false otherwise
+ */
+ public boolean enableUpdateChecker() {
+ return context.getResources().getBoolean(R.bool.updatecheck_available);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java b/app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java
index 10a555ba..4204bb49 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -18,10 +18,11 @@ package org.transdroid.core.gui.navigation;
/**
* Interface to be implemented by any activity that allows its content to be refreshed; fragments can ask for user-initiated refreshes.
+ *
* @author Eric Kok
*/
public interface RefreshableActivity {
- void refreshScreen();
+ void refreshScreen();
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java b/app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java
index 8ad3a6b6..18821b26 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -32,115 +32,118 @@ import android.widget.ListView;
* A helper to implement {@link ListView} selection modification behaviour with the {@link SelectionModificationSpinner}
* by implementing the specific actions and providing a title based on the number of currently selected items. It is
* important that the provided list was instantiated already.
+ *
* @author Eric Kok
*/
public class SelectionManagerMode implements MultiChoiceModeListener, OnModificationActionSelectedListener {
- private final Context themedContext;
- private final ListView managedList;
- private final int titleTemplateResource;
- private Class> onlyCheckClass = null;
+ private final Context themedContext;
+ private final ListView managedList;
+ private final int titleTemplateResource;
+ private Class> onlyCheckClass = null;
- /**
- * Instantiates the helper by binding it to a specific {@link ListView} and providing the text resource to display
- * as title in the spinner.
- * @param themedContext The context which is associated with the correct theme to apply when inflating views, i.e. the toolbar context
- * @param managedList The list to manage the selection for and execute selection action to
- * @param titleTemplateResource The string resource id to show as the spinners title; the number of selected items
- * will be supplied as numeric formatting argument
- */
- public SelectionManagerMode(Context themedContext, ListView managedList, int titleTemplateResource) {
- this.themedContext = themedContext;
- this.managedList = managedList;
- this.titleTemplateResource = titleTemplateResource;
- }
+ /**
+ * Instantiates the helper by binding it to a specific {@link ListView} and providing the text resource to display
+ * as title in the spinner.
+ *
+ * @param themedContext The context which is associated with the correct theme to apply when inflating views, i.e. the toolbar context
+ * @param managedList The list to manage the selection for and execute selection action to
+ * @param titleTemplateResource The string resource id to show as the spinners title; the number of selected items
+ * will be supplied as numeric formatting argument
+ */
+ public SelectionManagerMode(Context themedContext, ListView managedList, int titleTemplateResource) {
+ this.themedContext = themedContext;
+ this.managedList = managedList;
+ this.titleTemplateResource = titleTemplateResource;
+ }
- /**
- * Set the class type of items that are allowed to be checked in the {@link ListView}. Defaults to null, which means
- * every list view row can be checked.
- * @param onlyCheckClass The {@link Class} instance to use to check list item types against
- */
- public void setOnlyCheckClass(Class> onlyCheckClass) {
- this.onlyCheckClass = onlyCheckClass;
- }
+ /**
+ * Set the class type of items that are allowed to be checked in the {@link ListView}. Defaults to null, which means
+ * every list view row can be checked.
+ *
+ * @param onlyCheckClass The {@link Class} instance to use to check list item types against
+ */
+ public void setOnlyCheckClass(Class> onlyCheckClass) {
+ this.onlyCheckClass = onlyCheckClass;
+ }
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- // Allow modification of selection through a spinner
- SelectionModificationSpinner selectionSpinner = new SelectionModificationSpinner(themedContext);
- selectionSpinner.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- selectionSpinner.setOnModificationActionSelectedListener(this);
- mode.setCustomView(selectionSpinner);
- return true;
- }
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // Allow modification of selection through a spinner
+ SelectionModificationSpinner selectionSpinner = new SelectionModificationSpinner(themedContext);
+ selectionSpinner.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ selectionSpinner.setOnModificationActionSelectedListener(this);
+ mode.setCustomView(selectionSpinner);
+ return true;
+ }
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
- @Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
- int checkedCount = 0;
- for (int i = 0; i < managedList.getCheckedItemPositions().size(); i++) {
- if (managedList.getCheckedItemPositions().valueAt(i)
- && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(managedList
- .getCheckedItemPositions().keyAt(i)))))
- checkedCount++;
- }
- ((SelectionModificationSpinner) mode.getCustomView()).updateTitle(themedContext.getResources()
- .getQuantityString(titleTemplateResource, checkedCount, checkedCount));
- }
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ int checkedCount = 0;
+ for (int i = 0; i < managedList.getCheckedItemPositions().size(); i++) {
+ if (managedList.getCheckedItemPositions().valueAt(i)
+ && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(managedList
+ .getCheckedItemPositions().keyAt(i)))))
+ checkedCount++;
+ }
+ ((SelectionModificationSpinner) mode.getCustomView()).updateTitle(themedContext.getResources()
+ .getQuantityString(titleTemplateResource, checkedCount, checkedCount));
+ }
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- }
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return false;
- }
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return false;
+ }
- /**
- * Implements the {@link SelectionModificationSpinner}'s invert selection command by flipping the checked status for
- * each (enabled) items in the {@link ListView}.
- */
- @Override
- public void invertSelection() {
- SparseBooleanArray checked = managedList.getCheckedItemPositions();
- for (int i = 0; i < managedList.getAdapter().getCount(); i++) {
- if (managedList.getAdapter().isEnabled(i)
- && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(i))))
- managedList.setItemChecked(i, !checked.get(i, false));
- }
- }
+ /**
+ * Implements the {@link SelectionModificationSpinner}'s invert selection command by flipping the checked status for
+ * each (enabled) items in the {@link ListView}.
+ */
+ @Override
+ public void invertSelection() {
+ SparseBooleanArray checked = managedList.getCheckedItemPositions();
+ for (int i = 0; i < managedList.getAdapter().getCount(); i++) {
+ if (managedList.getAdapter().isEnabled(i)
+ && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(i))))
+ managedList.setItemChecked(i, !checked.get(i, false));
+ }
+ }
- /**
- * Implements the {@link SelectionModificationSpinner}'s select all command by checking each (enabled) item in the
- * {@link ListView}.
- */
- @Override
- public void selectAll() {
- for (int i = 0; i < managedList.getAdapter().getCount(); i++) {
- if (managedList.getAdapter().isEnabled(i)
- && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(i))))
- managedList.setItemChecked(i, true);
- }
- }
+ /**
+ * Implements the {@link SelectionModificationSpinner}'s select all command by checking each (enabled) item in the
+ * {@link ListView}.
+ */
+ @Override
+ public void selectAll() {
+ for (int i = 0; i < managedList.getAdapter().getCount(); i++) {
+ if (managedList.getAdapter().isEnabled(i)
+ && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(i))))
+ managedList.setItemChecked(i, true);
+ }
+ }
- /**
- * Implements the {@link SelectionModificationSpinner}'s select finished command by checking each (enabled) item
- * that represents something that is {@link Finishable} and indeed is finished;
- */
- @Override
- public void selectFinished() {
- for (int i = 0; i < managedList.getAdapter().getCount(); i++) {
- if (managedList.getAdapter().isEnabled(i)
- && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(i)))
- && managedList.getItemAtPosition(i) instanceof Finishable)
- managedList.setItemChecked(i, ((Finishable) managedList.getItemAtPosition(i)).isFinished());
- }
- }
+ /**
+ * Implements the {@link SelectionModificationSpinner}'s select finished command by checking each (enabled) item
+ * that represents something that is {@link Finishable} and indeed is finished;
+ */
+ @Override
+ public void selectFinished() {
+ for (int i = 0; i < managedList.getAdapter().getCount(); i++) {
+ if (managedList.getAdapter().isEnabled(i)
+ && (onlyCheckClass == null || onlyCheckClass.isInstance(managedList.getItemAtPosition(i)))
+ && managedList.getItemAtPosition(i) instanceof Finishable)
+ managedList.setItemChecked(i, ((Finishable) managedList.getItemAtPosition(i)).isFinished());
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/SelectionModificationSpinner.java b/app/src/main/java/org/transdroid/core/gui/navigation/SelectionModificationSpinner.java
index 4905faeb..2487ee31 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/SelectionModificationSpinner.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/SelectionModificationSpinner.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,90 +28,96 @@ import android.widget.TextView;
/**
* Spinner that holds actions that can be performed on list selections. The spinner itself has some title, which can for
* example be used to show the number of selected items.
+ *
* @author Eric Kok
*/
public class SelectionModificationSpinner extends Spinner {
- private SelectionDropDownAdapter selectionAdapter;
- private OnModificationActionSelectedListener onModificationActionSelected = null;
-
- /**
- * Instantiates a spinner that contains some fixed actions for a user to modify selections.
- * @param context The interface context where the spinner will be shown in
- */
- public SelectionModificationSpinner(Context context) {
- super(context);
- selectionAdapter = new SelectionDropDownAdapter(context);
- setAdapter(selectionAdapter);
- }
-
- /**
- * Updates the fixed title text shown in the spinner, regardless of spinner item action selection.
- * @param title The new static string to show, such as the number of selected items
- */
- public void updateTitle(String title) {
- selectionAdapter.titleView.setText(title);
- invalidate();
- }
-
- /**
- * Sets the listener for action selection events.
- * @param onModificationActionSelected The listener that handles performing of the actions as selected in this
- * spinner by the user
- */
- public void setOnModificationActionSelectedListener(OnModificationActionSelectedListener onModificationActionSelected) {
- this.onModificationActionSelected = onModificationActionSelected;
- }
-
- @Override
- public void setSelection(int position) {
- if (position == 0) {
- onModificationActionSelected.selectAll();
- } else if (position == 1) {
- onModificationActionSelected.selectFinished();
- } else if (position == 2) {
- onModificationActionSelected.invertSelection();
- }
- super.setSelection(position);
- }
-
- /**
- * Local adapter that holds the actions which can be performed and a title text view that always shows instead of a
- * list item as in a normal spinner.
- */
- private class SelectionDropDownAdapter extends ArrayAdapter {
-
- protected TextView titleView = null;
-
- public SelectionDropDownAdapter(Context context) {
- super(context, android.R.layout.simple_list_item_1, new String[] {
- context.getString(R.string.navigation_selectall),
- context.getString(R.string.navigation_selectfinished),
- context.getString(R.string.navigation_invertselection) });
- titleView = new TextView(getContext());
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // This returns the singleton text view showing the title with the number of selected items
- return titleView;
- }
-
- @Override
- public View getDropDownView(int position, View convertView, ViewGroup parent) {
- // This returns the actions to show in the spinner list
- return super.getView(position, convertView, parent);
- }
-
- }
-
- /**
- * Interface to implement if an interface want to respond to selection modification actions.
- */
- public interface OnModificationActionSelectedListener {
- public void selectAll();
- public void selectFinished();
- public void invertSelection();
- }
+ private SelectionDropDownAdapter selectionAdapter;
+ private OnModificationActionSelectedListener onModificationActionSelected = null;
+
+ /**
+ * Instantiates a spinner that contains some fixed actions for a user to modify selections.
+ *
+ * @param context The interface context where the spinner will be shown in
+ */
+ public SelectionModificationSpinner(Context context) {
+ super(context);
+ selectionAdapter = new SelectionDropDownAdapter(context);
+ setAdapter(selectionAdapter);
+ }
+
+ /**
+ * Updates the fixed title text shown in the spinner, regardless of spinner item action selection.
+ *
+ * @param title The new static string to show, such as the number of selected items
+ */
+ public void updateTitle(String title) {
+ selectionAdapter.titleView.setText(title);
+ invalidate();
+ }
+
+ /**
+ * Sets the listener for action selection events.
+ *
+ * @param onModificationActionSelected The listener that handles performing of the actions as selected in this
+ * spinner by the user
+ */
+ public void setOnModificationActionSelectedListener(OnModificationActionSelectedListener onModificationActionSelected) {
+ this.onModificationActionSelected = onModificationActionSelected;
+ }
+
+ @Override
+ public void setSelection(int position) {
+ if (position == 0) {
+ onModificationActionSelected.selectAll();
+ } else if (position == 1) {
+ onModificationActionSelected.selectFinished();
+ } else if (position == 2) {
+ onModificationActionSelected.invertSelection();
+ }
+ super.setSelection(position);
+ }
+
+ /**
+ * Local adapter that holds the actions which can be performed and a title text view that always shows instead of a
+ * list item as in a normal spinner.
+ */
+ private class SelectionDropDownAdapter extends ArrayAdapter {
+
+ protected TextView titleView = null;
+
+ public SelectionDropDownAdapter(Context context) {
+ super(context, android.R.layout.simple_list_item_1, new String[]{
+ context.getString(R.string.navigation_selectall),
+ context.getString(R.string.navigation_selectfinished),
+ context.getString(R.string.navigation_invertselection)});
+ titleView = new TextView(getContext());
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // This returns the singleton text view showing the title with the number of selected items
+ return titleView;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ // This returns the actions to show in the spinner list
+ return super.getView(position, convertView, parent);
+ }
+
+ }
+
+ /**
+ * Interface to implement if an interface want to respond to selection modification actions.
+ */
+ public interface OnModificationActionSelectedListener {
+ public void selectAll();
+
+ public void selectFinished();
+
+ public void invertSelection();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java b/app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java
index 5384474b..9ed1bd66 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -37,71 +37,72 @@ import java.util.List;
public class SetLabelDialog {
- /**
- * A dialog fragment that allows picking a label or entering a new label to set this new label to the torrent.
- * @param context The activity context that opens (and owns) this dialog
- * @param onLabelPickedListener The callback when a new label has been entered or picked by the user
- * @param currentLabels The list of labels as currently exist on the server, to present as list for easy selection
- */
- public static void show(final Context context, final OnLabelPickedListener onLabelPickedListener, List currentLabels) {
+ /**
+ * A dialog fragment that allows picking a label or entering a new label to set this new label to the torrent.
+ *
+ * @param context The activity context that opens (and owns) this dialog
+ * @param onLabelPickedListener The callback when a new label has been entered or picked by the user
+ * @param currentLabels The list of labels as currently exist on the server, to present as list for easy selection
+ */
+ public static void show(final Context context, final OnLabelPickedListener onLabelPickedListener, List currentLabels) {
- // Discard the empty label in this list before storing it locally
- for (Iterator iter = currentLabels.iterator(); iter.hasNext(); ) {
- if (iter.next().isEmptyLabel()) {
- iter.remove();
- }
- }
+ // Discard the empty label in this list before storing it locally
+ for (Iterator iter = currentLabels.iterator(); iter.hasNext(); ) {
+ if (iter.next().isEmptyLabel()) {
+ iter.remove();
+ }
+ }
- final View setLabelLayout = LayoutInflater.from(context).inflate(R.layout.dialog_setlabel, null);
- final ListView labelsList = (ListView) setLabelLayout.findViewById(R.id.labels_list);
- final EditText newLabelEdit = (EditText) setLabelLayout.findViewById(R.id.newlabel_edit);
+ final View setLabelLayout = LayoutInflater.from(context).inflate(R.layout.dialog_setlabel, null);
+ final ListView labelsList = (ListView) setLabelLayout.findViewById(R.id.labels_list);
+ final EditText newLabelEdit = (EditText) setLabelLayout.findViewById(R.id.newlabel_edit);
- MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
- .customView(setLabelLayout, false)
- .positiveText(R.string.status_update)
- .neutralText(R.string.status_label_remove)
- .negativeText(android.R.string.cancel)
- .callback(new MaterialDialog.ButtonCallback() {
- @Override
- public void onPositive(MaterialDialog dialog) {
- // User should have provided a new label
- if (TextUtils.isEmpty(newLabelEdit.getText())) {
- SnackbarManager.show(Snackbar.with(context).text(R.string.error_notalabel).colorResource(R.color.red));
- return;
- }
- onLabelPickedListener.onLabelPicked(newLabelEdit.getText().toString());
- }
+ MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
+ .customView(setLabelLayout, false)
+ .positiveText(R.string.status_update)
+ .neutralText(R.string.status_label_remove)
+ .negativeText(android.R.string.cancel)
+ .callback(new MaterialDialog.ButtonCallback() {
+ @Override
+ public void onPositive(MaterialDialog dialog) {
+ // User should have provided a new label
+ if (TextUtils.isEmpty(newLabelEdit.getText())) {
+ SnackbarManager.show(Snackbar.with(context).text(R.string.error_notalabel).colorResource(R.color.red));
+ return;
+ }
+ onLabelPickedListener.onLabelPicked(newLabelEdit.getText().toString());
+ }
- @Override
- public void onNeutral(MaterialDialog dialog) {
- onLabelPickedListener.onLabelPicked(null);
- }
- });
- final MaterialDialog dialog = SettingsUtils
- .applyDialogTheme(builder)
- .build();
+ @Override
+ public void onNeutral(MaterialDialog dialog) {
+ onLabelPickedListener.onLabelPicked(null);
+ }
+ });
+ final MaterialDialog dialog = SettingsUtils
+ .applyDialogTheme(builder)
+ .build();
- if (currentLabels.size() == 0) {
- // Hide the list (and its label) if there are no labels yet
- setLabelLayout.findViewById(R.id.pick_label).setVisibility(View.GONE);
- labelsList.setVisibility(View.GONE);
- } else {
- labelsList.setAdapter(new FilterListItemAdapter(context, currentLabels));
- labelsList.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- onLabelPickedListener.onLabelPicked(((Label) labelsList.getItemAtPosition(position)).getName());
- dialog.dismiss();
- }
- });
- }
+ if (currentLabels.size() == 0) {
+ // Hide the list (and its label) if there are no labels yet
+ setLabelLayout.findViewById(R.id.pick_label).setVisibility(View.GONE);
+ labelsList.setVisibility(View.GONE);
+ } else {
+ labelsList.setAdapter(new FilterListItemAdapter(context, currentLabels));
+ labelsList.setOnItemClickListener(new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ onLabelPickedListener.onLabelPicked(((Label) labelsList.getItemAtPosition(position)).getName());
+ dialog.dismiss();
+ }
+ });
+ }
- dialog.show();
+ dialog.show();
- }
+ }
- public interface OnLabelPickedListener {
- void onLabelPicked(String newLabel);
- }
+ public interface OnLabelPickedListener {
+ void onLabelPicked(String newLabel);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java b/app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java
index 9ecdfabc..74f52264 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,35 +28,36 @@ import org.transdroid.core.app.settings.SettingsUtils;
public class SetStorageLocationDialog {
- /**
- * A dialog fragment that allows changing of the storage location by editing the path text directly.
- * @param context The activity context that opens (and owns) this dialog
- * @param onStorageLocationUpdatedListener The callback for when the user is done updating the storage location
- * @param currentLocation The current storage location that will be available to the user to edit
- */
- public static void show(final Context context, final OnStorageLocationUpdatedListener onStorageLocationUpdatedListener, String currentLocation) {
- View locationLayout = LayoutInflater.from(context).inflate(R.layout.dialog_storagelocation, null);
- final EditText locationText = (EditText) locationLayout.findViewById(R.id.location_edit);
- locationText.setText(currentLocation);
- MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
- .customView(locationLayout, false)
- .positiveText(R.string.status_update)
- .negativeText(android.R.string.cancel)
- .callback(new MaterialDialog.ButtonCallback() {
- @Override
- public void onPositive(MaterialDialog dialog) {
- // User is done editing and requested to update given the text input
- onStorageLocationUpdatedListener.onStorageLocationUpdated(locationText.getText().toString());
- }
- });
-
- SettingsUtils
- .applyDialogTheme(builder)
- .show();
- }
-
- public interface OnStorageLocationUpdatedListener {
- void onStorageLocationUpdated(String newLocation);
- }
+ /**
+ * A dialog fragment that allows changing of the storage location by editing the path text directly.
+ *
+ * @param context The activity context that opens (and owns) this dialog
+ * @param onStorageLocationUpdatedListener The callback for when the user is done updating the storage location
+ * @param currentLocation The current storage location that will be available to the user to edit
+ */
+ public static void show(final Context context, final OnStorageLocationUpdatedListener onStorageLocationUpdatedListener, String currentLocation) {
+ View locationLayout = LayoutInflater.from(context).inflate(R.layout.dialog_storagelocation, null);
+ final EditText locationText = (EditText) locationLayout.findViewById(R.id.location_edit);
+ locationText.setText(currentLocation);
+ MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
+ .customView(locationLayout, false)
+ .positiveText(R.string.status_update)
+ .negativeText(android.R.string.cancel)
+ .callback(new MaterialDialog.ButtonCallback() {
+ @Override
+ public void onPositive(MaterialDialog dialog) {
+ // User is done editing and requested to update given the text input
+ onStorageLocationUpdatedListener.onStorageLocationUpdated(locationText.getText().toString());
+ }
+ });
+
+ SettingsUtils
+ .applyDialogTheme(builder)
+ .show();
+ }
+
+ public interface OnStorageLocationUpdatedListener {
+ void onStorageLocationUpdated(String newLocation);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java b/app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java
index 78e1a0c9..d74c2cd3 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -32,32 +32,33 @@ import java.util.List;
public class SetTrackersDialog extends DialogFragment {
- /**
- * A dialog fragment that allows changing the trackers of a torrent by editing the text directly.
- * @param context The activity context that opens (and owns) this dialog
- * @param onTrackersUpdatedListener The callback for when the user is done updating the trackers list
- * @param currentTrackers The current trackers text/list that will be available to the user to edit
- */
- public static void show(final Context context, final OnTrackersUpdatedListener onTrackersUpdatedListener, String currentTrackers) {
- View trackersLayout = LayoutInflater.from(context).inflate(R.layout.dialog_trackers, null);
- final EditText trackersText = (EditText) trackersLayout.findViewById(R.id.trackers_edit);
- trackersText.setText(currentTrackers);
- MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
- .customView(trackersLayout, false)
- .positiveText(R.string.status_update)
- .negativeText(android.R.string.cancel)
- .callback(new MaterialDialog.ButtonCallback() {
- @Override
- public void onPositive(MaterialDialog dialog) {
- // User is done editing and requested to update given the text input
- onTrackersUpdatedListener.onTrackersUpdated(Arrays.asList(trackersText.getText().toString().split("\n")));
- }
- });
- SettingsUtils.applyDialogTheme(builder).show();
- }
-
- public interface OnTrackersUpdatedListener {
- void onTrackersUpdated(List updatedTrackers);
- }
+ /**
+ * A dialog fragment that allows changing the trackers of a torrent by editing the text directly.
+ *
+ * @param context The activity context that opens (and owns) this dialog
+ * @param onTrackersUpdatedListener The callback for when the user is done updating the trackers list
+ * @param currentTrackers The current trackers text/list that will be available to the user to edit
+ */
+ public static void show(final Context context, final OnTrackersUpdatedListener onTrackersUpdatedListener, String currentTrackers) {
+ View trackersLayout = LayoutInflater.from(context).inflate(R.layout.dialog_trackers, null);
+ final EditText trackersText = (EditText) trackersLayout.findViewById(R.id.trackers_edit);
+ trackersText.setText(currentTrackers);
+ MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
+ .customView(trackersLayout, false)
+ .positiveText(R.string.status_update)
+ .negativeText(android.R.string.cancel)
+ .callback(new MaterialDialog.ButtonCallback() {
+ @Override
+ public void onPositive(MaterialDialog dialog) {
+ // User is done editing and requested to update given the text input
+ onTrackersUpdatedListener.onTrackersUpdated(Arrays.asList(trackersText.getText().toString().split("\n")));
+ }
+ });
+ SettingsUtils.applyDialogTheme(builder).show();
+ }
+
+ public interface OnTrackersUpdatedListener {
+ void onTrackersUpdated(List updatedTrackers);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java b/app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java
index 5f5b2228..2d7bb133 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -30,85 +30,86 @@ import org.transdroid.core.app.settings.SettingsUtils;
public class SetTransferRatesDialog {
- /**
- * A dialog fragment that allow picking of maximum download and upload transfer rates as well as the resetting of these values.
- * @param context The activity context that opens (and owns) this dialog
- * @param onRatesPickedListener The callback for results in this dialog (with newly selected values or a reset)
- */
- public static void show(final Context context, final OnRatesPickedListener onRatesPickedListener) {
-
- View transferRatesLayout = LayoutInflater.from(context).inflate(R.layout.dialog_transferrates, null);
- final TextView maxSpeedDown = (TextView) transferRatesLayout.findViewById(R.id.maxspeeddown_text);
- final TextView maxSpeedUp = (TextView) transferRatesLayout.findViewById(R.id.maxspeedup_text);
-
- MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
- .customView(transferRatesLayout, false)
- .positiveText(R.string.status_update)
- .neutralText(R.string.status_maxspeed_reset)
- .negativeText(android.R.string.cancel)
- .callback(new MaterialDialog.ButtonCallback() {
- @Override
- public void onPositive(MaterialDialog dialog) {
- int maxDown = -1, maxUp = -1;
- try {
- maxDown = Integer.parseInt(maxSpeedDown.getText().toString());
- maxUp = Integer.parseInt(maxSpeedUp.getText().toString());
- } catch (NumberFormatException e) {
- // Impossible as we only input via the number buttons
- }
- if (maxDown <= 0 || maxUp <= 0) {
- onRatesPickedListener.onInvalidNumber();
- return;
- }
- onRatesPickedListener.onRatesPicked(maxDown, maxUp);
- }
-
- @Override
- public void onNeutral(MaterialDialog dialog) {
- onRatesPickedListener.resetRates();
- }
- });
- MaterialDialog dialog = SettingsUtils.applyDialogTheme(builder).build();
-
- bindButtons(dialog.getCustomView(), maxSpeedDown, R.id.down1Button, R.id.down2Button, R.id.down3Button, R.id.down4Button, R.id.down5Button,
- R.id.down6Button, R.id.down7Button, R.id.down8Button, R.id.down9Button, R.id.down0Button);
- bindButtons(dialog.getCustomView(), maxSpeedUp, R.id.up1Button, R.id.up2Button, R.id.up3Button, R.id.up4Button, R.id.up5Button,
- R.id.up6Button, R.id.up7Button, R.id.up8Button, R.id.up9Button, R.id.up0Button);
-
- dialog.show();
-
- }
-
- private static void bindButtons(View transferRatesContent, View numberView, int... buttonResource) {
- for (int i : buttonResource) {
- // Keep the relevant number as reference in the view tag and bind the click listerner
- transferRatesContent.findViewById(i).setTag(numberView);
- transferRatesContent.findViewById(i).setOnClickListener(onNumberClicked);
- }
- }
-
- private static OnClickListener onNumberClicked = new OnClickListener() {
- @Override
- public void onClick(View v) {
- // Append the text contents of the button itself as text to the current number (as reference in the view's
- // tag)
- TextView numberView = (TextView) v.getTag();
- if (numberView.getText().toString().equals(v.getContext().getString(R.string.status_maxspeed_novalue))) {
- numberView.setText("");
- }
- numberView.setText(numberView.getText().toString() + ((Button) v).getText().toString());
- }
- };
-
- /**
- * Listener interface to the user having picked or wanting to resets the current maximum transfer speeds;
- */
- public interface OnRatesPickedListener {
- void onRatesPicked(int maxDownloadSpeed, int maxUploadSpeed);
-
- void resetRates();
-
- void onInvalidNumber();
- }
+ /**
+ * A dialog fragment that allow picking of maximum download and upload transfer rates as well as the resetting of these values.
+ *
+ * @param context The activity context that opens (and owns) this dialog
+ * @param onRatesPickedListener The callback for results in this dialog (with newly selected values or a reset)
+ */
+ public static void show(final Context context, final OnRatesPickedListener onRatesPickedListener) {
+
+ View transferRatesLayout = LayoutInflater.from(context).inflate(R.layout.dialog_transferrates, null);
+ final TextView maxSpeedDown = (TextView) transferRatesLayout.findViewById(R.id.maxspeeddown_text);
+ final TextView maxSpeedUp = (TextView) transferRatesLayout.findViewById(R.id.maxspeedup_text);
+
+ MaterialDialog.Builder builder = new MaterialDialog.Builder(context)
+ .customView(transferRatesLayout, false)
+ .positiveText(R.string.status_update)
+ .neutralText(R.string.status_maxspeed_reset)
+ .negativeText(android.R.string.cancel)
+ .callback(new MaterialDialog.ButtonCallback() {
+ @Override
+ public void onPositive(MaterialDialog dialog) {
+ int maxDown = -1, maxUp = -1;
+ try {
+ maxDown = Integer.parseInt(maxSpeedDown.getText().toString());
+ maxUp = Integer.parseInt(maxSpeedUp.getText().toString());
+ } catch (NumberFormatException e) {
+ // Impossible as we only input via the number buttons
+ }
+ if (maxDown <= 0 || maxUp <= 0) {
+ onRatesPickedListener.onInvalidNumber();
+ return;
+ }
+ onRatesPickedListener.onRatesPicked(maxDown, maxUp);
+ }
+
+ @Override
+ public void onNeutral(MaterialDialog dialog) {
+ onRatesPickedListener.resetRates();
+ }
+ });
+ MaterialDialog dialog = SettingsUtils.applyDialogTheme(builder).build();
+
+ bindButtons(dialog.getCustomView(), maxSpeedDown, R.id.down1Button, R.id.down2Button, R.id.down3Button, R.id.down4Button, R.id.down5Button,
+ R.id.down6Button, R.id.down7Button, R.id.down8Button, R.id.down9Button, R.id.down0Button);
+ bindButtons(dialog.getCustomView(), maxSpeedUp, R.id.up1Button, R.id.up2Button, R.id.up3Button, R.id.up4Button, R.id.up5Button,
+ R.id.up6Button, R.id.up7Button, R.id.up8Button, R.id.up9Button, R.id.up0Button);
+
+ dialog.show();
+
+ }
+
+ private static void bindButtons(View transferRatesContent, View numberView, int... buttonResource) {
+ for (int i : buttonResource) {
+ // Keep the relevant number as reference in the view tag and bind the click listerner
+ transferRatesContent.findViewById(i).setTag(numberView);
+ transferRatesContent.findViewById(i).setOnClickListener(onNumberClicked);
+ }
+ }
+
+ private static OnClickListener onNumberClicked = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Append the text contents of the button itself as text to the current number (as reference in the view's
+ // tag)
+ TextView numberView = (TextView) v.getTag();
+ if (numberView.getText().toString().equals(v.getContext().getString(R.string.status_maxspeed_novalue))) {
+ numberView.setText("");
+ }
+ numberView.setText(numberView.getText().toString() + ((Button) v).getText().toString());
+ }
+ };
+
+ /**
+ * Listener interface to the user having picked or wanting to resets the current maximum transfer speeds;
+ */
+ public interface OnRatesPickedListener {
+ void onRatesPicked(int maxDownloadSpeed, int maxUploadSpeed);
+
+ void resetRates();
+
+ void onInvalidNumber();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/navigation/StatusType.java b/app/src/main/java/org/transdroid/core/gui/navigation/StatusType.java
index 26cc1bab..5818fc4f 100644
--- a/app/src/main/java/org/transdroid/core/gui/navigation/StatusType.java
+++ b/app/src/main/java/org/transdroid/core/gui/navigation/StatusType.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,137 +29,142 @@ import android.os.Parcelable;
/**
* Enumeration of all status types, which filter the list of shown torrents based on transfer activity.
+ *
* @author Eric Kok
*/
public enum StatusType {
- ShowAll {
- public StatusTypeFilter getFilterItem(Context context) {
- return new StatusTypeFilter(StatusType.ShowAll, context.getString(R.string.navigation_status_showall));
- }
- },
- OnlyDownloading {
- public StatusTypeFilter getFilterItem(Context context) {
- return new StatusTypeFilter(StatusType.OnlyDownloading, context.getString(R.string.navigation_status_onlydown));
- }
- },
- OnlyUploading {
- public StatusTypeFilter getFilterItem(Context context) {
- return new StatusTypeFilter(StatusType.OnlyUploading, context.getString(R.string.navigation_status_onlyup));
- }
- },
- OnlyActive {
- public StatusTypeFilter getFilterItem(Context context) {
- return new StatusTypeFilter(StatusType.OnlyActive, context.getString(R.string.navigation_status_onlyactive));
- }
- },
- OnlyInactive {
- public StatusTypeFilter getFilterItem(Context context) {
- return new StatusTypeFilter(StatusType.OnlyInactive, context.getString(R.string.navigation_status_onlyinactive));
- }
- };
-
- /**
- * Returns the status type to show all torrents, represented as filter item to show in the navigation list.
- * @param context The Android UI context, to access translations
- * @return The show ShowAll status type filter item
- */
- public static StatusTypeFilter getShowAllType(Context context) {
- return ShowAll.getFilterItem(context);
- }
-
- /**
- * Returns a list with all status types, represented as filter item that can be shown in the GUI.
- * @param context The Android UI context, to access translations
- * @return A list of filter items for all available status types
- */
- public static List getAllStatusTypes(Context context) {
- return Arrays.asList(ShowAll.getFilterItem(context), OnlyDownloading.getFilterItem(context),
- OnlyUploading.getFilterItem(context), OnlyActive.getFilterItem(context),
- OnlyInactive.getFilterItem(context));
- }
-
- /**
- * Every status type can return a filter item that represents it in the navigation
- * @param context The Android UI context, to access translations
- * @return A filter item object to show in the GUI
- */
- public abstract StatusTypeFilter getFilterItem(Context context);
-
- public static class StatusTypeFilter implements SimpleListItem, NavigationFilter {
-
- private final StatusType statusType;
- private final String name;
-
- StatusTypeFilter(StatusType statusType, String name) {
- this.statusType = statusType;
- this.name = name;
- }
-
- public StatusType getStatusType() {
- return statusType;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public String getCode() {
- // Uses the class name and status type enum to provide a unique navigation filter code
- return StatusTypeFilter.class.getSimpleName() + "_" + statusType.name();
- }
-
- /**
- * Returns true if the torrent status matches this (selected) status type, false otherwise
- * @param torrent The torrent to match against this status type
- * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively
- * downloading or seeding
- */
- @Override
- public boolean matches(Torrent torrent, boolean dormantAsInactive) {
- switch (statusType) {
- case OnlyDownloading:
- return torrent.isDownloading(dormantAsInactive);
- case OnlyUploading:
- return torrent.isSeeding(dormantAsInactive);
- case OnlyActive:
- return torrent.isDownloading(dormantAsInactive)
- || torrent.isSeeding(dormantAsInactive);
- case OnlyInactive:
- return !torrent.isDownloading(dormantAsInactive) && !torrent.isSeeding(dormantAsInactive);
- default:
- return true;
- }
- }
-
- private StatusTypeFilter(Parcel in) {
- this.statusType = StatusType.valueOf(in.readString());
- this.name = in.readString();
- }
-
- public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
- public StatusTypeFilter createFromParcel(Parcel in) {
- return new StatusTypeFilter(in);
- }
-
- public StatusTypeFilter[] newArray(int size) {
- return new StatusTypeFilter[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(statusType.name());
- dest.writeString(name);
- }
-
- }
+ ShowAll {
+ public StatusTypeFilter getFilterItem(Context context) {
+ return new StatusTypeFilter(StatusType.ShowAll, context.getString(R.string.navigation_status_showall));
+ }
+ },
+ OnlyDownloading {
+ public StatusTypeFilter getFilterItem(Context context) {
+ return new StatusTypeFilter(StatusType.OnlyDownloading, context.getString(R.string.navigation_status_onlydown));
+ }
+ },
+ OnlyUploading {
+ public StatusTypeFilter getFilterItem(Context context) {
+ return new StatusTypeFilter(StatusType.OnlyUploading, context.getString(R.string.navigation_status_onlyup));
+ }
+ },
+ OnlyActive {
+ public StatusTypeFilter getFilterItem(Context context) {
+ return new StatusTypeFilter(StatusType.OnlyActive, context.getString(R.string.navigation_status_onlyactive));
+ }
+ },
+ OnlyInactive {
+ public StatusTypeFilter getFilterItem(Context context) {
+ return new StatusTypeFilter(StatusType.OnlyInactive, context.getString(R.string.navigation_status_onlyinactive));
+ }
+ };
+
+ /**
+ * Returns the status type to show all torrents, represented as filter item to show in the navigation list.
+ *
+ * @param context The Android UI context, to access translations
+ * @return The show ShowAll status type filter item
+ */
+ public static StatusTypeFilter getShowAllType(Context context) {
+ return ShowAll.getFilterItem(context);
+ }
+
+ /**
+ * Returns a list with all status types, represented as filter item that can be shown in the GUI.
+ *
+ * @param context The Android UI context, to access translations
+ * @return A list of filter items for all available status types
+ */
+ public static List getAllStatusTypes(Context context) {
+ return Arrays.asList(ShowAll.getFilterItem(context), OnlyDownloading.getFilterItem(context),
+ OnlyUploading.getFilterItem(context), OnlyActive.getFilterItem(context),
+ OnlyInactive.getFilterItem(context));
+ }
+
+ /**
+ * Every status type can return a filter item that represents it in the navigation
+ *
+ * @param context The Android UI context, to access translations
+ * @return A filter item object to show in the GUI
+ */
+ public abstract StatusTypeFilter getFilterItem(Context context);
+
+ public static class StatusTypeFilter implements SimpleListItem, NavigationFilter {
+
+ private final StatusType statusType;
+ private final String name;
+
+ StatusTypeFilter(StatusType statusType, String name) {
+ this.statusType = statusType;
+ this.name = name;
+ }
+
+ public StatusType getStatusType() {
+ return statusType;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getCode() {
+ // Uses the class name and status type enum to provide a unique navigation filter code
+ return StatusTypeFilter.class.getSimpleName() + "_" + statusType.name();
+ }
+
+ /**
+ * Returns true if the torrent status matches this (selected) status type, false otherwise
+ *
+ * @param torrent The torrent to match against this status type
+ * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively
+ * downloading or seeding
+ */
+ @Override
+ public boolean matches(Torrent torrent, boolean dormantAsInactive) {
+ switch (statusType) {
+ case OnlyDownloading:
+ return torrent.isDownloading(dormantAsInactive);
+ case OnlyUploading:
+ return torrent.isSeeding(dormantAsInactive);
+ case OnlyActive:
+ return torrent.isDownloading(dormantAsInactive)
+ || torrent.isSeeding(dormantAsInactive);
+ case OnlyInactive:
+ return !torrent.isDownloading(dormantAsInactive) && !torrent.isSeeding(dormantAsInactive);
+ default:
+ return true;
+ }
+ }
+
+ private StatusTypeFilter(Parcel in) {
+ this.statusType = StatusType.valueOf(in.readString());
+ this.name = in.readString();
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public StatusTypeFilter createFromParcel(Parcel in) {
+ return new StatusTypeFilter(in);
+ }
+
+ public StatusTypeFilter[] newArray(int size) {
+ return new StatusTypeFilter[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(statusType.name());
+ dest.writeString(name);
+ }
+
+ }
}
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 088292c8..19545b4e 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
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -18,6 +18,7 @@ package org.transdroid.core.gui.remoterss;
import androidx.fragment.app.Fragment;
+
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
@@ -44,102 +45,102 @@ import java.util.List;
/**
* Fragment that shows a list of RSS items from the server and allows the user
* to download remotely, without having to set up RSS feeds on the Android device.
+ *
* @author Twig
*/
@EFragment(R.layout.fragment_remoterss)
public class RemoteRssFragment extends Fragment {
- @Bean
- protected Log log;
-
- // Local data
- protected ArrayList remoteRssItems;
-
- // Views
- @ViewById
- protected View detailsContainer;
- @ViewById(R.id.remoterss_filter)
- protected Spinner remoteRssFilter;
- @ViewById
- protected ListView torrentsList;
- @ViewById(R.id.remoterss_status_message)
- protected TextView remoteRssStatusMessage;
-
-
- @AfterViews
- protected void init() {
- // Inject menu options in the actions toolbar
- setHasOptionsMenu(true);
-
- // Set up details adapter
- RemoteRssItemsAdapter adapter = new RemoteRssItemsAdapter(getActivity());
- torrentsList.setAdapter(adapter);
- torrentsList.setFastScrollEnabled(true);
- }
-
- @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);
- }
- else {
- remoteRssStatusMessage.setVisibility(View.VISIBLE);
- remoteRssStatusMessage.setText(R.string.remoterss_no_files);
- }
- }
-
- 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);
- }
-
- /**
- * When the user clicks on an item, prepare to download it.
- */
- @ItemClick(resName = "torrents_list")
- protected void detailsListClicked(int position) {
- RemoteRssItemsAdapter adapter = (RemoteRssItemsAdapter) torrentsList.getAdapter();
- RemoteRssItem item = (RemoteRssItem) adapter.getItem(position);
-
- ((RssFeedsActivity) getActivity()).downloadRemoteRssItem(item);
- }
-
- @ItemSelect(R.id.remoterss_filter)
- protected void onFeedSelected(boolean selected, int position) {
- ((RssFeedsActivity) getActivity()).onFeedSelected(position);
- }
+ @Bean
+ protected Log log;
+
+ // Local data
+ protected ArrayList remoteRssItems;
+
+ // Views
+ @ViewById
+ protected View detailsContainer;
+ @ViewById(R.id.remoterss_filter)
+ protected Spinner remoteRssFilter;
+ @ViewById
+ protected ListView torrentsList;
+ @ViewById(R.id.remoterss_status_message)
+ protected TextView remoteRssStatusMessage;
+
+
+ @AfterViews
+ protected void init() {
+ // Inject menu options in the actions toolbar
+ setHasOptionsMenu(true);
+
+ // Set up details adapter
+ RemoteRssItemsAdapter adapter = new RemoteRssItemsAdapter(getActivity());
+ torrentsList.setAdapter(adapter);
+ torrentsList.setFastScrollEnabled(true);
+ }
+
+ @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);
+ } else {
+ remoteRssStatusMessage.setVisibility(View.VISIBLE);
+ remoteRssStatusMessage.setText(R.string.remoterss_no_files);
+ }
+ }
+
+ 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);
+ }
+
+ /**
+ * When the user clicks on an item, prepare to download it.
+ */
+ @ItemClick(resName = "torrents_list")
+ protected void detailsListClicked(int position) {
+ RemoteRssItemsAdapter adapter = (RemoteRssItemsAdapter) torrentsList.getAdapter();
+ RemoteRssItem item = (RemoteRssItem) adapter.getItem(position);
+
+ ((RssFeedsActivity) getActivity()).downloadRemoteRssItem(item);
+ }
+
+ @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/remoterss/RemoteRssItemView.java b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemView.java
index 587aa592..d3ee2e7d 100644
--- a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemView.java
+++ b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,26 +28,27 @@ import org.transdroid.core.gui.remoterss.data.RemoteRssItem;
/**
* View that represents some {@link RemoteRssItem} object.
+ *
* @author Twig
*/
@EViewGroup(R.layout.list_item_remoterssitem)
public class RemoteRssItemView extends LinearLayout {
- // Views
- @ViewById
- protected TextView nameText, dateText, labelText;
+ // Views
+ @ViewById
+ protected TextView nameText, dateText, labelText;
- public RemoteRssItemView(Context context) {
- super(context);
- }
+ public RemoteRssItemView(Context context) {
+ super(context);
+ }
- public void bind(RemoteRssItem item) {
- labelText.setText(item.getSourceName());
- nameText.setText(item.getName());
- dateText.setText(
- DateFormat.getDateFormat(getContext()).format(item.getTimestamp()) +
- " " +
- DateFormat.getTimeFormat(getContext()).format(item.getTimestamp())
- );
- }
+ public void bind(RemoteRssItem item) {
+ labelText.setText(item.getSourceName());
+ nameText.setText(item.getName());
+ dateText.setText(
+ DateFormat.getDateFormat(getContext()).format(item.getTimestamp()) +
+ " " +
+ DateFormat.getTimeFormat(getContext()).format(item.getTimestamp())
+ );
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemsAdapter.java b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemsAdapter.java
index e9c4b07f..3c4081d9 100644
--- a/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemsAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/remoterss/RemoteRssItemsAdapter.java
@@ -11,47 +11,46 @@ import java.util.ArrayList;
import java.util.List;
public class RemoteRssItemsAdapter extends BaseAdapter {
- protected Context context;
- protected List items;
-
- public RemoteRssItemsAdapter(Context context) {
- this.context = context;
- items = new ArrayList<>();
- }
-
- @Override
- public int getCount() {
- return items.size();
- }
-
- @Override
- public Object getItem(int position) {
- return items.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- RemoteRssItemView itemView;
-
- if (convertView == null) {
- itemView = RemoteRssItemView_.build(context);
- }
- else {
- itemView = (RemoteRssItemView) convertView;
- }
-
- itemView.bind((RemoteRssItem) getItem(position));
-
- return itemView;
- }
-
- public void updateItems(List remoteItems) {
- items = remoteItems;
- notifyDataSetChanged();
- }
+ protected Context context;
+ protected List items;
+
+ public RemoteRssItemsAdapter(Context context) {
+ this.context = context;
+ items = new ArrayList<>();
+ }
+
+ @Override
+ public int getCount() {
+ return items.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return items.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ RemoteRssItemView itemView;
+
+ if (convertView == null) {
+ itemView = RemoteRssItemView_.build(context);
+ } else {
+ itemView = (RemoteRssItemView) convertView;
+ }
+
+ itemView.bind((RemoteRssItem) getItem(position));
+
+ return itemView;
+ }
+
+ public void updateItems(List remoteItems) {
+ items = remoteItems;
+ notifyDataSetChanged();
+ }
}
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 c5eba827..9771aeff 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
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -22,13 +22,17 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+
import com.google.android.material.tabs.TabLayout;
+
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
+
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -78,365 +82,367 @@ 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);
-
- // if local feeds dont have any entries but remote does, show it instead
- int defaultTab = RSS_FEEDS_LOCAL;
-
- if (hasRemoteRss && applicationSettings.getRssfeedSettings().size() == 0) {
- if (currentConnection instanceof RemoteRssSupplier) {
- RemoteRssSupplier remoteConnection = ((RemoteRssSupplier) (currentConnection));
- boolean hasRemoteFeeds = false;
-
- try {
- hasRemoteFeeds = remoteConnection.getRemoteRssChannels(log).size() > 0;
- } catch (DaemonException e) {}
-
- if (hasRemoteFeeds) {
- defaultTab = RSS_FEEDS_REMOTE;
- }
- }
- }
- viewPager.setCurrentItem(defaultTab);
-
- 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);
- }
+ // 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);
+
+ // if local feeds dont have any entries but remote does, show it instead
+ int defaultTab = RSS_FEEDS_LOCAL;
+
+ if (hasRemoteRss && applicationSettings.getRssfeedSettings().size() == 0) {
+ if (currentConnection instanceof RemoteRssSupplier) {
+ RemoteRssSupplier remoteConnection = ((RemoteRssSupplier) (currentConnection));
+ boolean hasRemoteFeeds = false;
+
+ try {
+ hasRemoteFeeds = remoteConnection.getRemoteRssChannels(log).size() > 0;
+ } catch (DaemonException e) {
+ }
+
+ if (hasRemoteFeeds) {
+ defaultTab = RSS_FEEDS_REMOTE;
+ }
+ }
+ }
+ viewPager.setCurrentItem(defaultTab);
+
+ 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);
- }
+ 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
index f60acf9f..ea335303 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
@@ -1,22 +1,23 @@
-/*
+/*
* 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 androidx.fragment.app.Fragment;
+
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -37,71 +38,72 @@ import java.util.List;
/**
* Fragment lists the RSS feeds the user wants to monitor and, if room, the list of items in a feed in a right pane.
+ *
* @author Eric Kok
*/
@EFragment(R.layout.fragment_rssfeeds)
@OptionsMenu(R.menu.fragment_rssfeeds)
public class RssFeedsFragment extends Fragment {
- // Views
- @ViewById(R.id.rssfeeds_list)
- protected ListView feedsList;
- @Bean
- protected RssfeedsAdapter rssfeedsAdapter;
- @ViewById
- protected TextView nosettingsText;
-
- @AfterViews
- protected void init() {
- feedsList.setAdapter(rssfeedsAdapter);
- }
-
- public void update(List loaders) {
- rssfeedsAdapter.update(loaders);
- boolean hasSettings = !(loaders == null || loaders.size() == 0);
- feedsList.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
- nosettingsText.setVisibility(hasSettings ? View.GONE : View.VISIBLE);
- getActivity().invalidateOptionsMenu();
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
- boolean hasFeeds = rssfeedsAdapter != null && rssfeedsAdapter.getCount() > 0;
- menu.findItem(R.id.action_refresh).setVisible(hasFeeds);
- menu.findItem(R.id.action_settings).setShowAsAction(!hasFeeds ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
- }
-
- @OptionsItem(R.id.action_settings)
- protected void openSettings() {
- 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() {
- getRssActivity().refreshFeeds();
- }
-
- @ItemClick(R.id.rssfeeds_list)
- protected void onFeedClicked(RssfeedLoader loader) {
- getRssActivity().openRssfeed(loader, true);
- }
-
- /**
- * Notifies the contained list of RSS feeds that the underlying data has been changed.
- */
- public void notifyDataSetChanged() {
- rssfeedsAdapter.notifyDataSetChanged();
- }
+ // Views
+ @ViewById(R.id.rssfeeds_list)
+ protected ListView feedsList;
+ @Bean
+ protected RssfeedsAdapter rssfeedsAdapter;
+ @ViewById
+ protected TextView nosettingsText;
+
+ @AfterViews
+ protected void init() {
+ feedsList.setAdapter(rssfeedsAdapter);
+ }
+
+ public void update(List loaders) {
+ rssfeedsAdapter.update(loaders);
+ boolean hasSettings = !(loaders == null || loaders.size() == 0);
+ feedsList.setVisibility(hasSettings ? View.VISIBLE : View.GONE);
+ nosettingsText.setVisibility(hasSettings ? View.GONE : View.VISIBLE);
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ boolean hasFeeds = rssfeedsAdapter != null && rssfeedsAdapter.getCount() > 0;
+ menu.findItem(R.id.action_refresh).setVisible(hasFeeds);
+ menu.findItem(R.id.action_settings).setShowAsAction(!hasFeeds ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
+ }
+
+ @OptionsItem(R.id.action_settings)
+ protected void openSettings() {
+ 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() {
+ getRssActivity().refreshFeeds();
+ }
+
+ @ItemClick(R.id.rssfeeds_list)
+ protected void onFeedClicked(RssfeedLoader loader) {
+ getRssActivity().openRssfeed(loader, true);
+ }
+
+ /**
+ * Notifies the contained list of RSS feeds that the underlying data has been changed.
+ */
+ public void notifyDataSetChanged() {
+ rssfeedsAdapter.notifyDataSetChanged();
+ }
}
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
index 7f9794da..bb8f31a6 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
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
@@ -38,45 +39,45 @@ import org.transdroid.core.rssparser.Channel;
@EActivity(R.layout.activity_rssitems)
public class RssItemsActivity extends AppCompatActivity {
- @Extra
- protected Channel rssfeed = null;
- @Extra
- protected String rssfeedName;
- @Extra
- protected boolean requiresExternalAuthentication;
+ @Extra
+ protected Channel rssfeed = null;
+ @Extra
+ protected String rssfeedName;
+ @Extra
+ protected boolean requiresExternalAuthentication;
- @FragmentById(R.id.rssitems_fragment)
- protected RssItemsFragment fragmentItems;
- @ViewById
- protected Toolbar rssfeedsToolbar;
+ @FragmentById(R.id.rssitems_fragment)
+ protected RssItemsFragment fragmentItems;
+ @ViewById
+ protected Toolbar rssfeedsToolbar;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- SettingsUtils.applyDayNightTheme(this);
- super.onCreate(savedInstanceState);
- }
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ SettingsUtils.applyDayNightTheme(this);
+ super.onCreate(savedInstanceState);
+ }
- @AfterViews
- protected void init() {
+ @AfterViews
+ protected void init() {
- // We require an RSS feed to be specified; otherwise close the activity
- if (rssfeed == null) {
- finish();
- return;
- }
+ // We require an RSS feed to be specified; otherwise close the activity
+ if (rssfeed == null) {
+ finish();
+ return;
+ }
- setSupportActionBar(rssfeedsToolbar);
- getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(rssfeedName));
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setSupportActionBar(rssfeedsToolbar);
+ getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(rssfeedName));
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- // Get the intent extras and show them to the already loaded fragment
- fragmentItems.update(rssfeed, false, requiresExternalAuthentication);
- }
+ // Get the intent extras and show them to the already loaded fragment
+ fragmentItems.update(rssfeed, false, requiresExternalAuthentication);
+ }
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- @OptionsItem(android.R.id.home)
- protected void navigateUp() {
- TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
- }
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @OptionsItem(android.R.id.home)
+ protected void navigateUp() {
+ TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
+ }
}
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
index 48133995..22705970 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
@@ -23,8 +23,10 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+
import androidx.fragment.app.Fragment;
import androidx.appcompat.app.AppCompatActivity;
+
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.Menu;
@@ -57,184 +59,186 @@ import java.util.List;
/**
* Fragment that lists the items in a specific RSS feed
+ *
* @author Eric Kok
*/
@EFragment(R.layout.fragment_rssitems)
public class RssItemsFragment extends Fragment {
- @InstanceState
- protected Channel rssFeed = null;
- @InstanceState
- protected boolean hasError = false;
- @InstanceState
- protected boolean requiresExternalAuthentication = false;
-
- @Bean
- protected NavigationHelper navigationHelper;
-
- // Views
- @ViewById(R.id.rssitems_list)
- protected ListView rssItemsList;
- private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
-
- SelectionManagerMode selectionManagerMode;
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- // 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.onCreateActionMode(mode, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return selectionManagerMode.onPrepareActionMode(mode, menu);
- }
-
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-
- // 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)));
- }
- }
-
- int itemId = item.getItemId();
- if (itemId == R.id.action_addall) {
-
- // Start an Intent that adds multiple items at once, by supplying the urls and titles as string array
- // extras and setting the Intent action to ADD_MULTIPLE
- Intent intent = new Intent("org.transdroid.ADD_MULTIPLE");
- String[] urls = new String[checked.size()];
- String[] titles = new String[checked.size()];
- for (int i = 0; i < checked.size(); i++) {
- urls[i] = checked.get(i).getTheLink();
- titles[i] = checked.get(i).getTitle();
- }
- intent.putExtra("TORRENT_URLS", urls);
- intent.putExtra("TORRENT_TITLES", titles);
- startActivity(intent);
- mode.finish();
- return true;
-
- } else if (itemId == R.id.action_copytoclipboard) {
-
- StringBuilder names = new StringBuilder();
- for (int f = 0; f < checked.size(); f++) {
- if (f != 0) {
- names.append("\n");
- }
- names.append(checked.get(f).getTitle());
- }
- ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
- clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
- mode.finish();
- return true;
-
- } else {
-
- // The other items only operate on one (the first) selected item
- if (checked.size() < 1) {
- return false;
- }
- final Item first = checked.get(0);
- if (itemId == R.id.action_showdetails) {
- // Show a dialog box with the RSS item description text
- new AlertDialog.Builder(getActivity()).setMessage(first.getDescription())
- .setPositiveButton(R.string.action_close, null).show();
- } else if (itemId == R.id.action_openwebsite) {
- // Open the browser to show the website contained in the item's link tag
- Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getTitle()), Toast.LENGTH_LONG).show();
- if (!TextUtils.isEmpty(first.getLink())) {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getLink())));
- } else {
- // No URL was specified in the RSS feed item link tag (or no link tag was present)
- SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_no_link).colorResource(R.color.red));
- }
- } else if (itemId == R.id.action_useassearch) {
- // Use the RSS item title to start a new search (mimicking the search manager style)
- Intent search = SearchActivity_.intent(getActivity()).get();
- search.setAction(Intent.ACTION_SEARCH);
- search.putExtra(SearchManager.QUERY, first.getTitle());
- startActivity(search);
- }
- mode.finish();
- return true;
-
- }
- }
-
- @Override
- public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
- selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- selectionManagerMode.onDestroyActionMode(mode);
- }
-
- };
- @Bean
- protected RssitemsAdapter rssitemsAdapter;
- @ViewById
- protected TextView emptyText;
-
- @AfterViews
- protected void init() {
-
- // Set up the list adapter, which allows multi-select
- rssItemsList.setAdapter(rssitemsAdapter);
- rssItemsList.setMultiChoiceModeListener(onItemsSelected);
- update(rssFeed, hasError, requiresExternalAuthentication);
-
- }
-
- /**
- * Update the shown RSS items in the list.
- * @param channel The loaded RSS content channel object
- * @param hasError True if there were errors in loading the channel, in which case an error text is shown; false otherwise
- * @param requiresExternalAuthentication Whether this RSS feed requires external authentication and should thus be redirected to a browser
- */
- public void update(Channel channel, boolean hasError, boolean requiresExternalAuthentication) {
- this.requiresExternalAuthentication = requiresExternalAuthentication;
- rssitemsAdapter.update(channel);
- rssItemsList.setVisibility(View.GONE);
- emptyText.setVisibility(View.VISIBLE);
- if (hasError) {
- emptyText.setText(R.string.rss_error);
- return;
- }
- if (channel == null) {
- emptyText.setText(R.string.rss_noselection);
- return;
- }
- if (channel.getItems().size() == 0) {
- emptyText.setText(R.string.rss_empty);
- return;
- }
- rssItemsList.setVisibility(View.VISIBLE);
- emptyText.setVisibility(View.INVISIBLE);
- }
-
- @ItemClick(resName = "rssitems_list")
- protected void onItemClicked(Item item) {
- if (requiresExternalAuthentication) {
- // Redirect to the browser, as this feed requires cookie authentication which we piggy-back on using the browser cookies
- navigationHelper.forceOpenInBrowser(item.getTheLinkUri());
- return;
- }
-
- // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
- Intent i = TorrentsActivity_.intent(getActivity()).get();
- i.setData(item.getTheLinkUri());
- i.putExtra("TORRENT_TITLE", item.getTitle());
- startActivity(i);
- }
+ @InstanceState
+ protected Channel rssFeed = null;
+ @InstanceState
+ protected boolean hasError = false;
+ @InstanceState
+ protected boolean requiresExternalAuthentication = false;
+
+ @Bean
+ protected NavigationHelper navigationHelper;
+
+ // Views
+ @ViewById(R.id.rssitems_list)
+ protected ListView rssItemsList;
+ private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
+
+ SelectionManagerMode selectionManagerMode;
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // 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.onCreateActionMode(mode, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return selectionManagerMode.onPrepareActionMode(mode, menu);
+ }
+
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+
+ // 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)));
+ }
+ }
+
+ int itemId = item.getItemId();
+ if (itemId == R.id.action_addall) {
+
+ // Start an Intent that adds multiple items at once, by supplying the urls and titles as string array
+ // extras and setting the Intent action to ADD_MULTIPLE
+ Intent intent = new Intent("org.transdroid.ADD_MULTIPLE");
+ String[] urls = new String[checked.size()];
+ String[] titles = new String[checked.size()];
+ for (int i = 0; i < checked.size(); i++) {
+ urls[i] = checked.get(i).getTheLink();
+ titles[i] = checked.get(i).getTitle();
+ }
+ intent.putExtra("TORRENT_URLS", urls);
+ intent.putExtra("TORRENT_TITLES", titles);
+ startActivity(intent);
+ mode.finish();
+ return true;
+
+ } else if (itemId == R.id.action_copytoclipboard) {
+
+ StringBuilder names = new StringBuilder();
+ for (int f = 0; f < checked.size(); f++) {
+ if (f != 0) {
+ names.append("\n");
+ }
+ names.append(checked.get(f).getTitle());
+ }
+ ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
+ mode.finish();
+ return true;
+
+ } else {
+
+ // The other items only operate on one (the first) selected item
+ if (checked.size() < 1) {
+ return false;
+ }
+ final Item first = checked.get(0);
+ if (itemId == R.id.action_showdetails) {
+ // Show a dialog box with the RSS item description text
+ new AlertDialog.Builder(getActivity()).setMessage(first.getDescription())
+ .setPositiveButton(R.string.action_close, null).show();
+ } else if (itemId == R.id.action_openwebsite) {
+ // Open the browser to show the website contained in the item's link tag
+ Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getTitle()), Toast.LENGTH_LONG).show();
+ if (!TextUtils.isEmpty(first.getLink())) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getLink())));
+ } else {
+ // No URL was specified in the RSS feed item link tag (or no link tag was present)
+ SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_no_link).colorResource(R.color.red));
+ }
+ } else if (itemId == R.id.action_useassearch) {
+ // Use the RSS item title to start a new search (mimicking the search manager style)
+ Intent search = SearchActivity_.intent(getActivity()).get();
+ search.setAction(Intent.ACTION_SEARCH);
+ search.putExtra(SearchManager.QUERY, first.getTitle());
+ startActivity(search);
+ }
+ mode.finish();
+ return true;
+
+ }
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ selectionManagerMode.onItemCheckedStateChanged(mode, position, id, checked);
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ selectionManagerMode.onDestroyActionMode(mode);
+ }
+
+ };
+ @Bean
+ protected RssitemsAdapter rssitemsAdapter;
+ @ViewById
+ protected TextView emptyText;
+
+ @AfterViews
+ protected void init() {
+
+ // Set up the list adapter, which allows multi-select
+ rssItemsList.setAdapter(rssitemsAdapter);
+ rssItemsList.setMultiChoiceModeListener(onItemsSelected);
+ update(rssFeed, hasError, requiresExternalAuthentication);
+
+ }
+
+ /**
+ * Update the shown RSS items in the list.
+ *
+ * @param channel The loaded RSS content channel object
+ * @param hasError True if there were errors in loading the channel, in which case an error text is shown; false otherwise
+ * @param requiresExternalAuthentication Whether this RSS feed requires external authentication and should thus be redirected to a browser
+ */
+ public void update(Channel channel, boolean hasError, boolean requiresExternalAuthentication) {
+ this.requiresExternalAuthentication = requiresExternalAuthentication;
+ rssitemsAdapter.update(channel);
+ rssItemsList.setVisibility(View.GONE);
+ emptyText.setVisibility(View.VISIBLE);
+ if (hasError) {
+ emptyText.setText(R.string.rss_error);
+ return;
+ }
+ if (channel == null) {
+ emptyText.setText(R.string.rss_noselection);
+ return;
+ }
+ if (channel.getItems().size() == 0) {
+ emptyText.setText(R.string.rss_empty);
+ return;
+ }
+ rssItemsList.setVisibility(View.VISIBLE);
+ emptyText.setVisibility(View.INVISIBLE);
+ }
+
+ @ItemClick(resName = "rssitems_list")
+ protected void onItemClicked(Item item) {
+ if (requiresExternalAuthentication) {
+ // Redirect to the browser, as this feed requires cookie authentication which we piggy-back on using the browser cookies
+ navigationHelper.forceOpenInBrowser(item.getTheLinkUri());
+ return;
+ }
+
+ // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
+ Intent i = TorrentsActivity_.intent(getActivity()).get();
+ i.setData(item.getTheLinkUri());
+ i.putExtra("TORRENT_TITLE", item.getTitle());
+ startActivity(i);
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java
index 946321cb..d4137e22 100644
--- a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java
+++ b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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
.
*/
@@ -28,82 +28,83 @@ import java.util.List;
/**
* A container class that holds RSS feed settings and, after they have been retrieved, the contents as {@link Channel}, the number of new items and an
* indication of a connection error.
+ *
* @author Eric Kok
*/
public class RssfeedLoader {
- private final RssfeedSetting setting;
- private Channel channel = null;
- private int newCount = -1;
- private boolean hasError = false;
+ private final RssfeedSetting setting;
+ private Channel channel = null;
+ private int newCount = -1;
+ private boolean hasError = false;
- public RssfeedLoader(RssfeedSetting setting) {
- this.setting = setting;
- }
+ public RssfeedLoader(RssfeedSetting setting) {
+ this.setting = setting;
+ }
- public void update(Channel channel, boolean hasError) {
- this.channel = channel;
- this.hasError = hasError;
- if (channel == null || channel.getItems() == null || hasError) {
- this.hasError = true;
- newCount = -1;
- return;
- }
- // Peek if this feed properly supports publish dates
- boolean usePublishDate = false;
- if (channel.getItems().size() > 0) {
- Date pubDate = channel.getItems().get(0).getPubdate();
- usePublishDate = pubDate != null && pubDate.getTime() > 0;
- }
- if (usePublishDate) {
- // Count the number of new items, based on the date that this RSS feed was last viewed by the user
- newCount = 0;
- List- items = channel.getItems();
- // Reverse-order sort the items on their published date
- Collections.sort(items, new Comparator
- () {
- @Override
- public int compare(Item lhs, Item rhs) {
- return 0 - lhs.getPubdate().compareTo(rhs.getPubdate());
- }
- });
- for (Item item : items) {
- if (item.getPubdate() == null || setting.getLastViewed() == null || item.getPubdate().after(setting.getLastViewed())) {
- newCount++;
- item.setIsNew(true);
- } else {
- item.setIsNew(false);
- }
- }
- } else {
- // Use the url of the last RSS item the last time the feed was viewed by the user to count new items
- newCount = 0;
- boolean isNew = true;
- for (Item item : channel.getItems()) {
- if (item.getTheLink() != null && setting.getLastViewedItemUrl() != null && item.getTheLink().equals(setting.getLastViewedItemUrl())) {
- isNew = false;
- }
- if (isNew) {
- newCount++;
- }
- item.setIsNew(isNew);
- }
- }
- }
+ public void update(Channel channel, boolean hasError) {
+ this.channel = channel;
+ this.hasError = hasError;
+ if (channel == null || channel.getItems() == null || hasError) {
+ this.hasError = true;
+ newCount = -1;
+ return;
+ }
+ // Peek if this feed properly supports publish dates
+ boolean usePublishDate = false;
+ if (channel.getItems().size() > 0) {
+ Date pubDate = channel.getItems().get(0).getPubdate();
+ usePublishDate = pubDate != null && pubDate.getTime() > 0;
+ }
+ if (usePublishDate) {
+ // Count the number of new items, based on the date that this RSS feed was last viewed by the user
+ newCount = 0;
+ List
- items = channel.getItems();
+ // Reverse-order sort the items on their published date
+ Collections.sort(items, new Comparator
- () {
+ @Override
+ public int compare(Item lhs, Item rhs) {
+ return 0 - lhs.getPubdate().compareTo(rhs.getPubdate());
+ }
+ });
+ for (Item item : items) {
+ if (item.getPubdate() == null || setting.getLastViewed() == null || item.getPubdate().after(setting.getLastViewed())) {
+ newCount++;
+ item.setIsNew(true);
+ } else {
+ item.setIsNew(false);
+ }
+ }
+ } else {
+ // Use the url of the last RSS item the last time the feed was viewed by the user to count new items
+ newCount = 0;
+ boolean isNew = true;
+ for (Item item : channel.getItems()) {
+ if (item.getTheLink() != null && setting.getLastViewedItemUrl() != null && item.getTheLink().equals(setting.getLastViewedItemUrl())) {
+ isNew = false;
+ }
+ if (isNew) {
+ newCount++;
+ }
+ item.setIsNew(isNew);
+ }
+ }
+ }
- public Channel getChannel() {
- return channel;
- }
+ public Channel getChannel() {
+ return channel;
+ }
- public RssfeedSetting getSetting() {
- return setting;
- }
+ public RssfeedSetting getSetting() {
+ return setting;
+ }
- public int getNewCount() {
- return newCount;
- }
+ public int getNewCount() {
+ return newCount;
+ }
- public boolean hasError() {
- return hasError;
- }
+ public boolean hasError() {
+ return hasError;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java
index 949f305a..ef1abb4a 100644
--- a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java
+++ b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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
.
*/
@@ -33,46 +33,47 @@ import org.transdroid.core.gui.navigation.NavigationHelper;
/**
* View that represents some {@link RssfeedSetting} object and displays name as well as loads a favicon for the feed's site and can load how many new
* items are available.
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_rssfeed)
public class RssfeedView extends LinearLayout {
- private static final String GRABICON_URL = "https://besticon-demo.herokuapp.com/icon?url=%1$s&size=72";
+ private static final String GRABICON_URL = "https://besticon-demo.herokuapp.com/icon?url=%1$s&size=72";
- @Bean
- protected NavigationHelper navigationHelper;
+ @Bean
+ protected NavigationHelper navigationHelper;
- // Views
- @ViewById
- protected ImageView faviconImage;
- @ViewById
- protected TextView nameText, newcountText;
- @ViewById
- protected ProgressBar loadingProgress;
+ // Views
+ @ViewById
+ protected ImageView faviconImage;
+ @ViewById
+ protected TextView nameText, newcountText;
+ @ViewById
+ protected ProgressBar loadingProgress;
- public RssfeedView(Context context) {
- super(context);
- }
+ public RssfeedView(Context context) {
+ super(context);
+ }
- public void bind(RssfeedLoader rssfeedLoader) {
+ public void bind(RssfeedLoader rssfeedLoader) {
- // Show the RSS feed name and either a loading indicator or the number of new items
- nameText.setText(rssfeedLoader.getSetting().getName());
- if (rssfeedLoader.hasError() || rssfeedLoader.getChannel() != null) {
- loadingProgress.setVisibility(View.GONE);
- newcountText.setVisibility(View.VISIBLE);
- newcountText.setText(rssfeedLoader.hasError() ? "?" : Integer.toString(rssfeedLoader.getNewCount()));
- } else {
- loadingProgress.setVisibility(View.VISIBLE);
- newcountText.setVisibility(View.GONE);
- }
+ // Show the RSS feed name and either a loading indicator or the number of new items
+ nameText.setText(rssfeedLoader.getSetting().getName());
+ if (rssfeedLoader.hasError() || rssfeedLoader.getChannel() != null) {
+ loadingProgress.setVisibility(View.GONE);
+ newcountText.setVisibility(View.VISIBLE);
+ newcountText.setText(rssfeedLoader.hasError() ? "?" : Integer.toString(rssfeedLoader.getNewCount()));
+ } else {
+ loadingProgress.setVisibility(View.VISIBLE);
+ newcountText.setVisibility(View.GONE);
+ }
- // Clear and then asynchronously load the RSS feed site' favicon
- // Uses the g.etfv.co service to resolve the favicon of any feed URL
- faviconImage.setImageDrawable(null);
- navigationHelper.getImageCache().displayImage(String.format(GRABICON_URL, rssfeedLoader.getSetting().getUrl()), faviconImage);
+ // Clear and then asynchronously load the RSS feed site' favicon
+ // Uses the g.etfv.co service to resolve the favicon of any feed URL
+ faviconImage.setImageDrawable(null);
+ navigationHelper.getImageCache().displayImage(String.format(GRABICON_URL, rssfeedLoader.getSetting().getUrl()), faviconImage);
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java
index 589cf291..2193db2a 100644
--- a/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -29,61 +29,63 @@ import java.util.List;
/**
* Adapter that contains a list of {@link RssfeedSetting}s, each with associated loaded RSS feed {@link org.transdroid.core.rssparser.Channel}.
+ *
* @author Eric Kok
*/
@EBean
public class RssfeedsAdapter extends BaseAdapter {
- private List loaders = null;
+ private List loaders = null;
- @RootContext
- protected Context context;
+ @RootContext
+ protected Context context;
- /**
- * Allows updating the full internal list of feed loaders at once, replacing the old list
- * @param loaders The new list of RSS feed loader objects, which pair settings and a loaded channel
- */
- public void update(List loaders) {
- this.loaders = loaders;
- notifyDataSetChanged();
- }
+ /**
+ * Allows updating the full internal list of feed loaders at once, replacing the old list
+ *
+ * @param loaders The new list of RSS feed loader objects, which pair settings and a loaded channel
+ */
+ public void update(List loaders) {
+ this.loaders = loaders;
+ notifyDataSetChanged();
+ }
- @Override
- public boolean hasStableIds() {
- return true;
- }
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
- @Override
- public int getCount() {
- if (loaders == null) {
- return 0;
- }
- return loaders.size();
- }
+ @Override
+ public int getCount() {
+ if (loaders == null) {
+ return 0;
+ }
+ return loaders.size();
+ }
- @Override
- public RssfeedLoader getItem(int position) {
- if (loaders == null) {
- return null;
- }
- return loaders.get(position);
- }
+ @Override
+ public RssfeedLoader getItem(int position) {
+ if (loaders == null) {
+ return null;
+ }
+ return loaders.get(position);
+ }
- @Override
- public long getItemId(int position) {
- return position;
- }
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- RssfeedView rssfeedView;
- if (convertView == null) {
- rssfeedView = RssfeedView_.build(context);
- } else {
- rssfeedView = (RssfeedView) convertView;
- }
- rssfeedView.bind(getItem(position));
- return rssfeedView;
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ RssfeedView rssfeedView;
+ if (convertView == null) {
+ rssfeedView = RssfeedView_.build(context);
+ } else {
+ rssfeedView = (RssfeedView) convertView;
+ }
+ rssfeedView.bind(getItem(position));
+ return rssfeedView;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java b/app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java
index 7a7eda57..b9a31ae3 100644
--- a/app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java
+++ b/app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,53 +28,54 @@ import org.transdroid.R;
/**
* A relative layout that that is checkable (to be used in a contextual action bar) and shows a coloured bar in the far left indicating the view
* status, that is, if the item is new to the user or was viewed earlier.
+ *
* @author Eric Kok
*/
public class RssitemStatusLayout extends RelativeLayout {
- private final float scale = getContext().getResources().getDisplayMetrics().density;
- private final int WIDTH = (int) (6 * scale + 0.5f);
- private final Paint oldPaint = new Paint();
- private final Paint newPaint = new Paint();
- private final RectF fullRect = new RectF();
- private Boolean isNew = null;
+ private final float scale = getContext().getResources().getDisplayMetrics().density;
+ private final int WIDTH = (int) (6 * scale + 0.5f);
+ private final Paint oldPaint = new Paint();
+ private final Paint newPaint = new Paint();
+ private final RectF fullRect = new RectF();
+ private Boolean isNew = null;
- public RssitemStatusLayout(Context context) {
- super(context);
- initPaints();
- setWillNotDraw(false);
- }
+ public RssitemStatusLayout(Context context) {
+ super(context);
+ initPaints();
+ setWillNotDraw(false);
+ }
- public RssitemStatusLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initPaints();
- setWillNotDraw(false);
- }
+ public RssitemStatusLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initPaints();
+ setWillNotDraw(false);
+ }
- private void initPaints() {
- oldPaint.setColor(getResources().getColor(R.color.file_off)); // Grey
- newPaint.setColor(getResources().getColor(R.color.file_normal)); // Normal green
- }
+ private void initPaints() {
+ oldPaint.setColor(getResources().getColor(R.color.file_off)); // Grey
+ newPaint.setColor(getResources().getColor(R.color.file_normal)); // Normal green
+ }
- public void setIsNew(Boolean isNew) {
- this.isNew = isNew;
- this.invalidate();
- }
+ public void setIsNew(Boolean isNew) {
+ this.isNew = isNew;
+ this.invalidate();
+ }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
- int height = getHeight();
- int width = WIDTH;
- fullRect.set(0, 0, width, height);
+ int height = getHeight();
+ int width = WIDTH;
+ fullRect.set(0, 0, width, height);
- if (isNew == null) {
- return;
- }
+ if (isNew == null) {
+ return;
+ }
- canvas.drawRect(fullRect, isNew ? newPaint : oldPaint);
+ canvas.drawRect(fullRect, isNew ? newPaint : oldPaint);
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java b/app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java
index 191090ae..fad0d5d5 100644
--- a/app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java
+++ b/app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -27,27 +27,28 @@ import org.transdroid.core.rssparser.Item;
/**
* View that represents some {@link Item} object, which is a single item in some RSS feed.
+ *
* @author Eric Kok
*/
@EViewGroup(R.layout.list_item_rssitem)
public class RssitemView extends RssitemStatusLayout {
- // Views
- @ViewById
- protected TextView nameText, dateText;
+ // Views
+ @ViewById
+ protected TextView nameText, dateText;
- public RssitemView(Context context) {
- super(context);
- }
+ public RssitemView(Context context) {
+ super(context);
+ }
- public void bind(Item rssitem) {
+ public void bind(Item rssitem) {
- nameText.setText(rssitem.getTitle());
- dateText.setText(rssitem.getPubdate() == null ? "" : DateUtils
- .getRelativeDateTimeString(getContext(), rssitem.getPubdate().getTime(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_MONTH));
- setIsNew(rssitem.isNew());
+ nameText.setText(rssitem.getTitle());
+ dateText.setText(rssitem.getPubdate() == null ? "" : DateUtils
+ .getRelativeDateTimeString(getContext(), rssitem.getPubdate().getTime(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_MONTH));
+ setIsNew(rssitem.isNew());
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java b/app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java
index d122a41a..ab9b2715 100644
--- a/app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -28,61 +28,63 @@ import org.transdroid.core.rssparser.Item;
/**
* Adapter that contains a list of {@link Item}s in an RSS feed.
+ *
* @author Eric Kok
*/
@EBean
public class RssitemsAdapter extends BaseAdapter {
- private Channel rssfeed = null;
+ private Channel rssfeed = null;
- @RootContext
- protected Context context;
+ @RootContext
+ protected Context context;
- /**
- * Allows updating the full RSS feed (channel and contained items), replacing the old data
- * @param rssfeed The new RSS feed contents
- */
- public void update(Channel rssfeed) {
- this.rssfeed = rssfeed;
- notifyDataSetChanged();
- }
+ /**
+ * Allows updating the full RSS feed (channel and contained items), replacing the old data
+ *
+ * @param rssfeed The new RSS feed contents
+ */
+ public void update(Channel rssfeed) {
+ this.rssfeed = rssfeed;
+ notifyDataSetChanged();
+ }
- @Override
- public boolean hasStableIds() {
- return true;
- }
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
- @Override
- public int getCount() {
- if (rssfeed == null) {
- return 0;
- }
- return rssfeed.getItems().size();
- }
+ @Override
+ public int getCount() {
+ if (rssfeed == null) {
+ return 0;
+ }
+ return rssfeed.getItems().size();
+ }
- @Override
- public Item getItem(int position) {
- if (rssfeed == null) {
- return null;
- }
- return rssfeed.getItems().get(position);
- }
+ @Override
+ public Item getItem(int position) {
+ if (rssfeed == null) {
+ return null;
+ }
+ return rssfeed.getItems().get(position);
+ }
- @Override
- public long getItemId(int position) {
- return position;
- }
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- RssitemView rssitemView;
- if (convertView == null) {
- rssitemView = RssitemView_.build(context);
- } else {
- rssitemView = (RssitemView) convertView;
- }
- rssitemView.bind(getItem(position));
- return rssitemView;
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ RssitemView rssitemView;
+ if (convertView == null) {
+ rssitemView = RssitemView_.build(context);
+ } else {
+ rssitemView = (RssitemView) convertView;
+ }
+ rssitemView.bind(getItem(position));
+ return rssitemView;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java b/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
index d67e3b57..81ddce5c 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -31,57 +31,59 @@ import java.lang.ref.WeakReference;
public class BarcodeHelper {
- // A 'random' ID to identify QR-encoded settings scan intents
- public static final int ACTIVITY_BARCODE_QRSETTINGS = 0x0000c0df;
- private static final Uri SCANNER_MARKET_URI = Uri.parse("market://search?q=pname:com.google.zxing.client.android");
+ // A 'random' ID to identify QR-encoded settings scan intents
+ public static final int ACTIVITY_BARCODE_QRSETTINGS = 0x0000c0df;
+ private static final Uri SCANNER_MARKET_URI = Uri.parse("market://search?q=pname:com.google.zxing.client.android");
- /**
- * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with the given
- * request code.
- * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
- * the bar code scanner
- * @param requestCode {@link #ACTIVITY_BARCODE_QRSETTINGS}
- */
- public static void startBarcodeScanner(final Activity activity, int requestCode) {
- // Start a bar code scanner that can handle the SCAN intent (specifically ZXing)
- startBarcodeIntent(activity, new Intent("com.google.zxing.client.android.SCAN"), requestCode);
- }
+ /**
+ * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with the given
+ * request code.
+ *
+ * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
+ * the bar code scanner
+ * @param requestCode {@link #ACTIVITY_BARCODE_QRSETTINGS}
+ */
+ public static void startBarcodeScanner(final Activity activity, int requestCode) {
+ // Start a bar code scanner that can handle the SCAN intent (specifically ZXing)
+ startBarcodeIntent(activity, new Intent("com.google.zxing.client.android.SCAN"), requestCode);
+ }
- /**
- * Call this to share content encoded in a QR code, specially used to share settings. The calling activity will
- * receive an Intent result with ID {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there the returned intent will
- * contain the data as SCAN_RESULT String extra.
- * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
- * the bar code scanner
- * @param content The content to share, that is, the raw data (Transdroid settings encoded as JSON data structure)
- * to share as QR code
- */
- public static void shareContentBarcode(final Activity activity, final String content) {
- // Start a bar code encoded that can handle the ENCODE intent (specifically ZXing)
- Intent encodeIntent = new Intent("com.google.zxing.client.android.ENCODE");
- encodeIntent.putExtra("ENCODE_TYPE", "TEXT_TYPE");
- encodeIntent.putExtra("ENCODE_DATA", content);
- encodeIntent.putExtra("ENCODE_SHOW_CONTENTS", false);
- startBarcodeIntent(activity, encodeIntent, -1);
- }
+ /**
+ * Call this to share content encoded in a QR code, specially used to share settings. The calling activity will
+ * receive an Intent result with ID {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there the returned intent will
+ * contain the data as SCAN_RESULT String extra.
+ *
+ * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
+ * the bar code scanner
+ * @param content The content to share, that is, the raw data (Transdroid settings encoded as JSON data structure)
+ * to share as QR code
+ */
+ public static void shareContentBarcode(final Activity activity, final String content) {
+ // Start a bar code encoded that can handle the ENCODE intent (specifically ZXing)
+ Intent encodeIntent = new Intent("com.google.zxing.client.android.ENCODE");
+ encodeIntent.putExtra("ENCODE_TYPE", "TEXT_TYPE");
+ encodeIntent.putExtra("ENCODE_DATA", content);
+ encodeIntent.putExtra("ENCODE_SHOW_CONTENTS", false);
+ startBarcodeIntent(activity, encodeIntent, -1);
+ }
- @SuppressLint("ValidFragment")
- private static void startBarcodeIntent(final Activity activity, final Intent intent, int requestCode) {
- try {
- activity.startActivityForResult(intent, requestCode);
- } catch (Exception e) {
- // Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present
- final WeakReference intentStartContext = new WeakReference(activity);
- new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(activity.getString(R.string.search_barcodescannernotfound))
- .setPositiveButton(android.R.string.yes, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (intentStartContext.get() != null)
- intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI));
- }
- }).setNegativeButton(android.R.string.no, null).show();
- }
- }
+ @SuppressLint("ValidFragment")
+ private static void startBarcodeIntent(final Activity activity, final Intent intent, int requestCode) {
+ try {
+ activity.startActivityForResult(intent, requestCode);
+ } catch (Exception e) {
+ // Can't start the bar code scanner, for example with a SecurityException or when ZXing is not present
+ final WeakReference intentStartContext = new WeakReference(activity);
+ new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(activity.getString(R.string.search_barcodescannernotfound))
+ .setPositiveButton(android.R.string.yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (intentStartContext.get() != null)
+ intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, SCANNER_MARKET_URI));
+ }
+ }).setNegativeButton(android.R.string.no, null).show();
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java b/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java
index b0a9fe02..e2e56925 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/FilePickerHelper.java
@@ -1,16 +1,16 @@
-/*
+/*
* 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 .
*/
@@ -31,39 +31,40 @@ import java.lang.ref.WeakReference;
public class FilePickerHelper {
- public static final int ACTIVITY_FILEPICKER = 0x0000c0df; // A 'random' ID to identify file picker intents
- public static final Uri FILEMANAGER_MARKET_URI = Uri.parse("market://search?q=pname:org.openintents.filemanager");
+ public static final int ACTIVITY_FILEPICKER = 0x0000c0df; // A 'random' ID to identify file picker intents
+ public static final Uri FILEMANAGER_MARKET_URI = Uri.parse("market://search?q=pname:org.openintents.filemanager");
- /**
- * Call this to start a file picker intent. The calling activity will receive an Intent result with ID
- * {@link #ACTIVITY_FILEPICKER} with an Intent that contains the selected local file as data Intent.
- * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
- * the file picker
- */
- @SuppressLint("ValidFragment")
- public static void startFilePicker(final Activity activity) {
- try {
- // Start a file manager that can handle the file/* file/* intents
- activity.startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("application/x-bittorrent"),
- ACTIVITY_FILEPICKER);
- } catch (Exception e1) {
- try {
- // Start a file manager that can handle the PICK_FILE intent (specifically IO File Manager)
- activity.startActivityForResult(new Intent("org.openintents.action.PICK_FILE"), ACTIVITY_FILEPICKER);
- } catch (Exception e2) {
- // Can't start the file manager, for example with a SecurityException or when IO File Manager is not present
- final WeakReference intentStartContext = new WeakReference(activity);
- new AlertDialog.Builder(activity).setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(activity.getString(R.string.search_filemanagernotfound))
- .setPositiveButton(android.R.string.yes, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (intentStartContext.get() != null)
- intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, FILEMANAGER_MARKET_URI));
- }
- }).setNegativeButton(android.R.string.no, null).show();
- }
- }
- }
+ /**
+ * Call this to start a file picker intent. The calling activity will receive an Intent result with ID
+ * {@link #ACTIVITY_FILEPICKER} with an Intent that contains the selected local file as data Intent.
+ *
+ * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
+ * the file picker
+ */
+ @SuppressLint("ValidFragment")
+ public static void startFilePicker(final Activity activity) {
+ try {
+ // Start a file manager that can handle the file/* file/* intents
+ activity.startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("application/x-bittorrent"),
+ ACTIVITY_FILEPICKER);
+ } catch (Exception e1) {
+ try {
+ // Start a file manager that can handle the PICK_FILE intent (specifically IO File Manager)
+ activity.startActivityForResult(new Intent("org.openintents.action.PICK_FILE"), ACTIVITY_FILEPICKER);
+ } catch (Exception e2) {
+ // Can't start the file manager, for example with a SecurityException or when IO File Manager is not present
+ final WeakReference