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/README.md b/README.md
index 5309aa4a..c16aef7c 100644
--- a/README.md
+++ b/README.md
@@ -1,89 +1,122 @@
Transdroid
==========
-[www.transdroid.org](http://www.transdroid.org)
+[www.transdroid.org](https://www.transdroid.org/)
[Twitter](https://twitter.com/transdroid) - [transdroid@2312.nl](transdroid@2312.nl)
-"Manage your torrents from your Android device"
-
-
-
-
-
-
-
-
-
-
-
-Manage your torrents from your Android device with Transdroid. All popular clients are supported: µTorrent, Transmission, rTorrent, Vuze, Deluge, BitTorrent 6, qBittorrent and many more. You can view and manage the running torrents and individual files. Adding is easy via the integrated search or RSS feeds (full version required). Monitor progress using the home screen widget or background alarm service.
+Manage torrents from your Android device.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Manage your torrents from your Android device with Transdroid.
+All popular clients are supported: µTorrent, Transmission, rTorrent, Vuze, Deluge, BitTorrent 6, qBittorrent, and many more.
+You can view and manage running torrents and individual files.
+Adding is easy via the integrated search or RSS feeds (full version required).
+Monitor progress using the home screen widget or background alarm service.
Contributions
=============
-Code and design contributions are very welcome. You might want to contact me via social networks (G+, Twitter) or e-mail first. Please note all code will be GNU GPL v3 licensed.
+Code and design contributions are very welcome.
+You might want to contact me via social networks (Twitter) or e-mail first.
+Please note that all code will be licensed in GNU GPLv3.
-Please respect the coding standards for easier merging. master contains the current release version of Transdroid while dev contains the active development version. However, larger, new features are developed in their own branch.
+Please respect the coding standards for easier merging.
+`master` contains the current release version of Transdroid while `dev` contains the active development version.
+However, larger and new features will be developed in their own branch.
Code structure
==============
-Starting with version 2.3.0, Transdroid is developed in Android Studio, fully integrating with the Gradle build system. It is (since version 2.5.0) compiled against Android 5.1 (API level 22) and (since version 2.2.0) supporting ICS (API level 15) and up only. To support lite (Transdrone, specially for the Play Store) and full (Transdroid) versions of the app, build flavours are defined in gradle, which contain version-specific resources. Dependencies are managed via JCentral et al. in the app's build.gradle file.
+Starting with version 2.3.0, Transdroid is developed in Android Studio, fully integrating with the Gradle build system.
+It is (since version 2.5.18) compiled against Android 10 (API level 29) and (since version 2.2.0) supporting Android ICS (API level 15) and up only.
+To support lite (Transdrone, specially for the Play Store) and full (Transdroid) versions of the app, build flavours are defined in gradle, which contain version-specific resources.
+Dependencies are managed via JCentral et al. in the app's build.gradle file.
Developed By
============
-Designed and developed by [Eric Kok](eric@2312.nl) of [2312 development](http://2312.nl). Contributions by various others (see commit log).
+Designed and developed by [Eric Kok](eric@2312.nl) of [2312 development](https://2312.nl/).
+Contributions by various others (see commit log).
License
=======
-
- Copyright 2010-2018 Eric Kok et al.
-
+
+ Copyright 2010-2020 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 .
-
-Some code/libraries/resources are used in the project:
+ along with Transdroid. If not, see .
-* [AndroidAnnotations](http://androidannotations.org/)
- Pierre-Yves Ricau (eBusinessInformations) et al.
+Some code/libraries/resources are used in the project:
+* [Android Jetpack (AndroidX)](https://developer.android.com/jetpack)
+ The Android Open Source Project
Apache License, Version 2.0
-* [ActionBar-PullToRefresh](https://github.com/chrisbanes/ActionBar-PullToRefresh)
- Chris Banes
+* [AndroidAnnotations](http://androidannotations.org/)
+ Pierre-Yves Ricau (eBusinessInformations) et al.
Apache License, Version 2.0
-* [Crouton](https://github.com/keyboardsurfer/Crouton)
- Code: Benjamin Weiss (Neofonie Mobile Gmbh) et al.
- Idea: Cyril Mottier
+* [ORMLite](https://github.com/j256/ormlite-core) and [ORMLite Android](https://github.com/j256/ormlite-android)
+ Gray Watson
+ ISC License
+* [Android Universal Image Loader](https://github.com/nostra13/Android-Universal-Image-Loader)
+ Sergey Tarasevich
Apache License, Version 2.0
-* [Base16Encoder](http://openjpa.apache.org/)
- Marc Prud'hommeaux
- Apache OpenJPA
-* MultipartEntity
- Apache Software Foundation
+* [FloatingActionButton](https://github.com/zendesk/android-floating-action-button)
+ Oleksandr Melnykov, Zendesk
Apache License, Version 2.0
-* RssParser ([learning-android](http://github.com/digitalspaghetti/learning-android))
- Tane Piper
- Public Domain
-* [Base64](http://iharder.net/base64)
- Robert Harder
+* [Snackbar](https://github.com/nispok/snackbar)
+ William Mora
+ MIT License
+* [Java implementation of Rencode](https://github.com/aegnor/rencode-java)
+ Daniel Dimovski
+ MIT License
+* [OpenJPA's Base16Encoder](https://github.com/apache/openjpa)
+ Marc Prud'hommeaux
+ Apache OpenJPA
+* [Base64](http://iharder.sourceforge.net/current/java/base64/)
+ Robert Harder
Public Domain
-* [aXMLRPC](https://github.com/timroes/aXMLRPC)
- Tim Roes
+* [aXMLRPC](https://github.com/gturri/aXMLRPC)
+ Tim Roes
MIT License
-* [android-ColorPickerPreference](https://github.com/attenzione/android-ColorPickerPreference)
- Daniel Nilsson and Sergey Margaritov
+* [Material Dialogs](https://github.com/afollestad/material-dialogs)
+ Aidan Follestad
Apache License, Version 2.0
-* [Funnel icon](http://thenounproject.com/noun/funnel/#icon-No5608)
- Naomi Atkinson from The Noun Project
+* [Android-Job](https://github.com/evernote/android-job)
+ Evernote Corporation
+ Apache License, Version 2.0
+* [android-ColorPickerPreference](https://github.com/attenzione/android-ColorPickerPreference)
+ Daniel Nilsson and Sergey Margaritov
+ Apache License, Version 2.0
+* RssParser ([learning-android](https://github.com/tanepiper/learning-android))
+ Tane Piper
+ Public Domain
+* [Funnel icon](https://thenounproject.com/term/funnel/5608/)
+ Naomi Atkinson from The Noun Project
Creative Commons Attribution 3.0
-
diff --git a/app/build.gradle b/app/build.gradle
index ce166215..3e302185 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -79,7 +79,7 @@ dependencies {
// Android support
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
- implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
+ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
// Other
diff --git a/app/src/full/res/values/bools.xml b/app/src/full/res/values/bools.xml
index 427a2c88..68b5601f 100644
--- a/app/src/full/res/values/bools.xml
+++ b/app/src/full/res/values/bools.xml
@@ -1,29 +1,28 @@
-
-
-
- true
-
- true
-
- true
-
- true
+
+ true
+
+ true
+
+ true
+
+ true
diff --git a/app/src/full/res/values/strings.xml b/app/src/full/res/values/strings.xml
index d50334ce..9ac8c21e 100644
--- a/app/src/full/res/values/strings.xml
+++ b/app/src/full/res/values/strings.xml
@@ -1,24 +1,24 @@
- Transdroid
+ Transdroid
- Donate with PayPal
- https://paypal.me/erickoknl
+ Donate with PayPal
+ https://paypal.me/erickoknl
diff --git a/app/src/lite/res/values/bools.xml b/app/src/lite/res/values/bools.xml
index be7fce50..389bb1d6 100644
--- a/app/src/lite/res/values/bools.xml
+++ b/app/src/lite/res/values/bools.xml
@@ -1,29 +1,28 @@
-
-
-
- false
-
- false
-
- false
-
- true
+
+ false
+
+ false
+
+ false
+
+ true
diff --git a/app/src/lite/res/values/strings.xml b/app/src/lite/res/values/strings.xml
index 36912a08..631a0965 100644
--- a/app/src/lite/res/values/strings.xml
+++ b/app/src/lite/res/values/strings.xml
@@ -1,24 +1,24 @@
- Transdrone
+ Transdrone
-
-
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1fae5479..2873ea14 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,333 +1,331 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ 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..9e381185 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,21 +1,30 @@
-/*
+/*
* 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.app.search;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.androidannotations.annotations.EBean;
+import org.androidannotations.annotations.EBean.Scope;
+import org.androidannotations.annotations.RootContext;
+
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@@ -23,146 +32,141 @@ import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
-import org.androidannotations.annotations.EBean;
-import org.androidannotations.annotations.EBean.Scope;
-import org.androidannotations.annotations.RootContext;
-
-import android.content.ContentProviderClient;
-import android.content.Context;
-import android.database.Cursor;
-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;
+
+ /**
+ * 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;
+ }
+ }
+
+ public enum SearchSortOrder {
+ Combined, BySeeders
+ }
}
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..49b2144e 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,122 +1,122 @@
-/*
+/*
* 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.app.search;
-import java.util.Date;
-
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Date;
+
/**
* 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();
- }
+ 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];
+ }
+ };
+ 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 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();
+ }
+
+ 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);
+ }
}
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..7c0bc0a0 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,763 @@ 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;
+ @Bean
+ protected SearchHelper searchHelper;
+ private SharedPreferences prefs;
+
+ 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<>(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);
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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 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()));
+ }
+
+ /**
+ * 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()))];
+ }
+
+ /**
+ * 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 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;
+ }
+
+ /**
+ * 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 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);
+ }
+
+ /**
+ * 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..22fd62d6 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,122 +1,126 @@
-/*
+/*
* 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.app.settings;
-import java.util.Date;
+import android.net.Uri;
+import android.text.TextUtils;
import org.transdroid.core.gui.lists.SimpleListItem;
-import android.net.Uri;
-import android.text.TextUtils;
+import java.util.Date;
/**
* 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 final String lastViewedItemUrl;
+ private Date lastViewed;
+
+ 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..6b639ec5 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,50 @@ 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 {
+ 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);
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @Bean
+ protected SystemSettings systemSettings;
+
+ /**
+ * 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 +90,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..bf54a1ce 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
@@ -1,7 +1,6 @@
package org.transdroid.core.app.settings;
-import android.content.Context;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
@@ -33,6 +32,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..d1bfd2c3 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,81 +1,82 @@
-/*
+/*
* 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.app.settings;
-import org.transdroid.core.gui.lists.SimpleListItem;
-import org.transdroid.core.gui.search.SearchSetting;
-
import android.net.Uri;
import android.text.TextUtils;
+import org.transdroid.core.gui.lists.SimpleListItem;
+import org.transdroid.core.gui.search.SearchSetting;
+
/**
* 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;
+ public static final String KEY_PREFIX = "websearch_";
+ private static final String DEFAULT_NAME = "Default";
+ private final int order;
+ private final String name;
+ private final String baseUrl;
+ private final String cookies;
+
+ public WebsearchSetting(int order, String name, String baseUrl, String cookies) {
+ this.order = order;
+ this.name = name;
+ this.baseUrl = baseUrl;
+ this.cookies = cookies;
+ }
+
+ public int getOrder() {
+ return order;
+ }
+
+ @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 WebsearchSetting(int order, String name, String baseUrl, String cookies) {
- this.order = order;
- this.name = name;
- this.baseUrl = baseUrl;
- this.cookies = cookies;
- }
+ public String getBaseUrl() {
+ return baseUrl;
+ }
- public int getOrder() {
- return order;
- }
+ public String getCookies() {
+ return 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 String getKey() {
+ return KEY_PREFIX + getOrder();
+ }
- 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..41fdd85f 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;
@@ -56,8 +57,6 @@ import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
-import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
-import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
import org.transdroid.daemon.task.ForceRecheckTask;
import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
@@ -74,6 +73,8 @@ import org.transdroid.daemon.task.SetLabelTask;
import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.StartTask;
import org.transdroid.daemon.task.StopTask;
+import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
+import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
import java.util.ArrayList;
import java.util.List;
@@ -82,318 +83,318 @@ 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;
+ // Details view components
+ @ViewById
+ protected Toolbar selectionToolbar;
+ @FragmentById(R.id.torrentdetails_fragment)
+ protected DetailsFragment fragmentDetails;
+ private IDaemonAdapter currentConnection = null;
+
+ @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..19578638 100644
--- a/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
+++ b/app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
@@ -1,31 +1,27 @@
-/*
+/*
* 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;
import android.annotation.SuppressLint;
-import android.app.Fragment;
import android.content.ClipData;
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;
@@ -35,6 +31,11 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.ActionMenuView;
+import androidx.fragment.app.Fragment;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import com.nispok.snackbar.enums.SnackbarType;
@@ -48,7 +49,6 @@ import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting;
-import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.lists.DetailsAdapter;
import org.transdroid.core.gui.lists.SimpleListItemAdapter;
import org.transdroid.core.gui.navigation.Label;
@@ -75,616 +75,611 @@ 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;
+ // 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;
+ private ServerSetting currentServerSettings = null;
+ 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(menuItem -> 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);
+ }
+
+ };
+
+ @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(() -> {
+ ((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(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();
+ }
+ }
+
+ /**
+ * 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..dd889321 100644
--- a/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java
+++ b/app/src/main/java/org/transdroid/core/gui/ServerPickerDialog.java
@@ -16,50 +16,47 @@
*/
package org.transdroid.core.gui;
-import java.util.List;
-
+import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.DialogFragment;
import android.os.Bundle;
+
+import androidx.fragment.app.DialogFragment;
+
import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
+import java.util.List;
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();
- }
+ /**
+ * 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.getSupportFragmentManager(), "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");
- }
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ String[] serverNames = getArguments().getStringArray("serverNames");
+ return new AlertDialog.Builder(getActivity()).setTitle(R.string.navigation_pickserver)
+ .setItems(serverNames, (dialog, which) -> {
+ if (getActivity() != null && getActivity() instanceof TorrentsActivity)
+ ((TorrentsActivity) getActivity()).switchServerAndAddFromIntent(which);
+ }).create();
+ }
}
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..3740c288 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,84 @@ 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;
+ private OnClickListener onStartDownPickerClicked = v ->
+ SetTransferRatesDialog.show(getContext(), ServerStatusView.this);
+
+ 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);
+
+ }
+
+ @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..7b12ce53 100644
--- a/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
+++ b/app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
@@ -1,24 +1,21 @@
-/*
+/*
* 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;
-import androidx.appcompat.widget.ActionMenuView;
-import androidx.appcompat.widget.Toolbar;
-
import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile;
@@ -27,34 +24,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..791eba05 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,23 +24,22 @@ 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;
-import androidx.appcompat.app.ActionBarDrawerToggle;
-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;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.ActionMenuView;
+import androidx.appcompat.widget.SearchView;
+import androidx.appcompat.widget.Toolbar;
+import androidx.drawerlayout.widget.DrawerLayout;
+
import com.getbase.floatingactionbutton.FloatingActionButton;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import com.nispok.snackbar.Snackbar;
@@ -86,7 +85,10 @@ import org.transdroid.core.gui.rss.RssFeedsActivity_;
import org.transdroid.core.gui.search.FilePickerHelper;
import org.transdroid.core.gui.search.UrlEntryDialog;
import org.transdroid.core.gui.settings.MainSettingsActivity_;
-import org.transdroid.core.service.*;
+import org.transdroid.core.service.AppUpdateJob;
+import org.transdroid.core.service.ConnectivityHelper;
+import org.transdroid.core.service.RssCheckerJob;
+import org.transdroid.core.service.ServerCheckerJob;
import org.transdroid.core.widget.ListWidgetProvider;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
@@ -102,8 +104,6 @@ import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
-import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
-import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
import org.transdroid.daemon.task.ForceRecheckTask;
import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
@@ -124,6 +124,8 @@ import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.task.StartTask;
import org.transdroid.daemon.task.StopTask;
+import org.transdroid.daemon.task.ToggleFirstLastPieceDownloadTask;
+import org.transdroid.daemon.task.ToggleSequentialDownloadTask;
import org.transdroid.daemon.util.HttpHelper;
import java.io.File;
@@ -142,1244 +144,1235 @@ 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;
+ // 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 ListView navigationList;
+ private FilterListAdapter navigationListAdapter;
+ private ServerSelectionView serverSelectionView;
+ private ServerStatusView serverStatusView;
+ private ActionBarDrawerToggle drawerToggle;
+ private MenuItem searchMenu = null;
+ private IDaemonAdapter currentConnection = null;
+
+ // Auto refresh task
+ private AsyncTask autoRefreshTask;
+
+ private String awaitingAddLocalFile;
+ private String awaitingAddTitle;
+ /**
+ * 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);
+ }
+ };
+ 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;
+ }
+ };
+
+ @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);
+ // Redirect to the classic activity implementation so we can use @OptionsItem methods
+ actionsToolbar.setOnMenuItemClickListener(this::onOptionsItemSelected);
+ 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.addDrawerListener(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(v -> {
+ // Pause autorefresh
+ stopRefresh = true;
+ stopAutoRefresh();
+ });
+ item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ stopRefresh = false;
+ startAutoRefresh();
+ return true;
+ }
+ });
+ item.setActionView(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);
+ }
+
+ /**
+ * 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) {
+ getSupportFragmentManager().beginTransaction().show(fragmentDetails).commit();
+ } else {
+ getSupportFragmentManager().beginTransaction().hide(fragmentDetails).commit();
+ }
+ }
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(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);
+ }
+
+ /**
+ * 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());
+ try (FileOutputStream output = new FileOutputStream(tempFile)) {
+ 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);
+ }
+ } 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) {
+ 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..978d10a4 100644
--- a/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
+++ b/app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
@@ -1,27 +1,22 @@
-/*
+/*
* 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;
-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;
@@ -31,6 +26,12 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.ActionMenuView;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.Fragment;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.androidannotations.annotations.AfterViews;
@@ -64,436 +65,437 @@ 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();
- }
+ // 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;
+ // Local data
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @Bean
+ protected SystemSettings systemSettings;
+ @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;
+ 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(menuItem -> 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);
+ }
+
+ };
+
+ @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(() -> {
+ ((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();
+ }
+
+ @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..af0d0c29 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,10 @@
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 +29,16 @@ 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((priority, tag, message, 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..d612bc9f 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,230 +1,235 @@
-/*
+/*
* 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 java.util.ArrayList;
-import java.util.List;
-
-import org.transdroid.R;
-import org.transdroid.core.gui.navigation.*;
-import org.transdroid.core.gui.lists.PiecesMapView;
-import org.transdroid.daemon.Torrent;
-import org.transdroid.daemon.TorrentFile;
-
import android.content.Context;
import android.text.util.Linkify;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import org.transdroid.R;
+import org.transdroid.core.gui.navigation.FilterSeparatorView_;
+import org.transdroid.daemon.Torrent;
+import org.transdroid.daemon.TorrentFile;
+
+import java.util.ArrayList;
+import java.util.List;
+
/**
* 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..9ab09507 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,22 +1,22 @@
-/*
+/*
* 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 java.util.Locale;
+import android.content.res.Resources;
import org.transdroid.R;
import org.transdroid.daemon.DaemonException;
@@ -25,227 +25,233 @@ import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.util.FileSizeConverter;
import org.transdroid.daemon.util.TimespanConverter;
-import android.content.res.Resources;
+import java.util.Locale;
/**
* 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;
- }
- }
+ private static final String DECIMAL_FORMATTER = "%.1f";
+ private static final String DECIMAL_FORMATTER_2 = "%.2f";
+ private final Torrent t;
+
+ private LocalTorrent(Torrent torrent) {
+ this.t = torrent;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 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;
+ case ConnectionError:
+ default:
+ return R.string.error_httperror;
+ }
+ }
+
+ /**
+ * 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 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");
+ case Waiting:
+ case Checking:
+ case Paused:
+ case Queued:
+ 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));
+ }
}
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..254361f2 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,23 +1,21 @@
-/*
+/*
* 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 java.util.ArrayList;
-
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
@@ -27,288 +25,297 @@ import android.widget.ListAdapter;
import android.widget.SectionIndexer;
import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* 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) {
+ sections.addAll(Arrays.asList(curSections));
+ }
+ }
+ }
+
+ 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..ecb042ee 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
@@ -1,27 +1,24 @@
package org.transdroid.core.gui.lists;
-import org.transdroid.R;
-
import android.content.Context;
-import android.view.View;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.view.View;
+
+import org.transdroid.R;
import java.util.ArrayList;
import java.util.List;
-import java.lang.Math;
class PiecesMapView extends View {
private final float scale = getContext().getResources().getDisplayMetrics().density;
private final int MINIMUM_HEIGHT = (int) (25 * scale);
private final int MINIMUM_PIECE_WIDTH = (int) (2 * scale);
-
- private ArrayList pieces = null;
-
private final Paint downloadingPaint = new Paint();
private final Paint donePaint = new Paint();
private final Paint partialDonePaint = new Paint();
+ private ArrayList pieces = null;
public PiecesMapView(Context context) {
super(context);
@@ -35,15 +32,15 @@ class PiecesMapView extends View {
}
public void setPieces(List pieces) {
- this.pieces = new ArrayList(pieces);
+ this.pieces = new ArrayList<>(pieces);
invalidate();
}
@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
@@ -62,10 +59,10 @@ class PiecesMapView extends View {
int pieceWidth;
pieceWidth = MINIMUM_PIECE_WIDTH;
- piecesScaled = new ArrayList();
+ 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,15 +71,15 @@ 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));
+ ArrayList bucket = new ArrayList<>(this.pieces.subList(start, end));
int doneCount = 0;
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++;
@@ -114,10 +111,9 @@ class PiecesMapView extends View {
piecesScaled.add(state);
}
- String scaledPiecesString = "";
- for (int s : piecesScaled)
- {
- scaledPiecesString += s;
+ StringBuilder scaledPiecesString = new StringBuilder();
+ for (int s : piecesScaled) {
+ scaledPiecesString.append(s);
}
// Draw downscaled peices
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..747824fc 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();
+ 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..2a023abb 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,114 +1,117 @@
-/*
+/*
* 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 java.util.ArrayList;
-import java.util.List;
-
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
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 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 {
+
+ private final String string;
+
+ public SimpleStringItem(String string) {
+ this.string = string;
+ }
+
+ /**
+ * 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;
+ }
+
+ @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..4000e710 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,66 +1,68 @@
-/*
+/*
* 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 java.util.List;
-
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import java.util.List;
+
/**
* 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..a87e75e7 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,24 +1,21 @@
-/*
+/*
* 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 org.transdroid.R;
-import org.transdroid.daemon.Priority;
-
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -26,75 +23,76 @@ import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
+import org.transdroid.R;
+import org.transdroid.daemon.Priority;
+
/**
* 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 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 final float scale = getContext().getResources().getDisplayMetrics().density;
+ private final int WIDTH = (int) (6 * scale + 0.5f);
+ 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;
- 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);
+ fullRect.set(0, 0, WIDTH, getHeight());
- 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..7dec796b 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,23 +1,21 @@
-/*
+/*
* 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 org.transdroid.R;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -26,100 +24,101 @@ import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
+import org.transdroid.R;
+
/**
* 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 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();
+ private int progress;
+ private boolean isActive;
+ private boolean isError;
+
+ 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();
+ }
+
+ 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();
+ }
+
+ 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..340cfe6b 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,24 +1,21 @@
-/*
+/*
* 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 org.transdroid.R;
-import org.transdroid.daemon.TorrentStatus;
-
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -26,85 +23,87 @@ import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
+import org.transdroid.R;
+import org.transdroid.daemon.TorrentStatus;
+
/**
* 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 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 final float scale = getContext().getResources().getDisplayMetrics().density;
+ private final int WIDTH = (int) (6 * scale + 0.5f);
+ 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;
- 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);
+ fullRect.set(0, 0, WIDTH, getHeight());
- 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..02381ca2 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,62 @@ 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;
+ @RootContext
+ protected Context context;
+ private ArrayList torrents = null;
- @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..6bd7e965 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,69 +1,71 @@
-/*
+/*
* 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.log;
-import java.sql.SQLException;
-
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
-import androidx.annotation.Keep;
import android.util.Log;
+import androidx.annotation.Keep;
+
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
+import java.sql.SQLException;
+
/**
* 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);
- /*case 1:
+ @Override
+ public void onUpgrade(SQLiteDatabase sqLiteDatabase, ConnectionSource connectionSource, int oldVersion,
+ int newVersion) {
+ try {
+ switch (oldVersion) {
+ case 1:
+ TableUtils.createTable(connectionSource, ErrorLogEntry.class);
+ /*case 2:
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..bf28ec82 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,108 +1,107 @@
-/*
+/*
* 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.log;
-import java.util.Date;
-
import android.os.Parcel;
import android.os.Parcelable;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
+import java.util.Date;
+
/**
* 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";
+ 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];
+ }
+ };
+ @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;
+ }
+
+ private ErrorLogEntry(Parcel in) {
+ logId = in.readInt();
+ dateAndTime = new Date(in.readLong());
+ priority = in.readInt();
+ tag = in.readString();
+ message = in.readString();
+ }
+
+ 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);
+ }
}
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..817f566a 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,23 +1,26 @@
-/*
+/*
* 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.log;
-import java.sql.SQLException;
-import java.util.List;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+
+import com.j256.ormlite.dao.Dao;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
@@ -26,70 +29,67 @@ import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.navigation.NavigationHelper;
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-
-import com.j256.ormlite.dao.Dao;
+import java.sql.SQLException;
+import java.util.List;
@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..a8deff86 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,27 +1,21 @@
-/*
+/*
* 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.navigation;
-import java.io.Serializable;
-
-import org.androidannotations.annotations.EActivity;
-import org.androidannotations.annotations.Extra;
-import org.transdroid.core.gui.*;
-
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
@@ -32,82 +26,92 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Window;
+import org.androidannotations.annotations.EActivity;
+import org.androidannotations.annotations.Extra;
+import org.transdroid.core.gui.TorrentsActivity_;
+
+import java.io.Serializable;
+
/**
* 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);
- }
+ /**
+ * 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) {
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ // 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;
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(dialog.getDialogLayoutId());
+ // TODO getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @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());
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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());
+ }
+ }
+
}
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..22f02b73 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 .
*/
@@ -24,7 +24,6 @@ import org.androidannotations.annotations.RootContext;
import org.transdroid.R;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.gui.lists.MergeAdapter;
-import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.lists.ViewHolderAdapter;
import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter;
@@ -33,81 +32,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;
+ protected ViewHolderAdapter statusTypeSeparator;
+ protected ViewHolderAdapter labelSeperator;
+ protected ViewHolderAdapter serverSeparator;
+ private FilterListItemAdapter serverItems = null;
+ private FilterListItemAdapter statusTypeItems = null;
+ private FilterListItemAdapter labelItems = null;
- /**
- * 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..6ba17f95 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 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..adab87e7 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,119 @@ 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);
- }
+ 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];
+ }
+ };
+ 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);
+ }
+
+ private Label(Parcel in) {
+ this.name = in.readString();
+ this.count = in.readInt();
+ this.isEmptyLabel = in.readInt() == 1;
+ }
+
+ /**
+ * 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;
+ }
+
+ @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());
+ }
+
+ @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..a51557de 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,14 +26,13 @@ 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;
-import com.afollestad.materialdialogs.DialogAction;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
import com.afollestad.materialdialogs.MaterialDialog;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
@@ -54,250 +53,255 @@ 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;
+
+ /**
+ * 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;
+ }
+
+ @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 MaterialDialog.Builder(context)
+ .content(R.string.permission_readtorrent)
+ .positiveText(android.R.string.ok)
+ .onPositive((dialog, 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;
+ }
+
+ /**
+ * 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 + " (" + 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..79789d17 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,24 +1,21 @@
-/*
+/*
* 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.navigation;
-import org.transdroid.core.gui.navigation.SelectionModificationSpinner.OnModificationActionSelectedListener;
-import org.transdroid.daemon.Finishable;
-
import android.content.Context;
import android.util.SparseBooleanArray;
import android.view.ActionMode;
@@ -28,119 +25,125 @@ import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
+import org.transdroid.core.gui.navigation.SelectionModificationSpinner.OnModificationActionSelectedListener;
+import org.transdroid.daemon.Finishable;
+
/**
* 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..b96588f9 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,117 +1,124 @@
-/*
+/*
* 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.navigation;
-import org.transdroid.R;
-
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import android.widget.Spinner;
import android.widget.TextView;
+import androidx.appcompat.widget.AppCompatSpinner;
+
+import org.transdroid.R;
+
/**
* 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();
- }
+public class SelectionModificationSpinner extends AppCompatSpinner {
+
+ 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);
+ }
+
+ /**
+ * Interface to implement if an interface want to respond to selection modification actions.
+ */
+ public interface OnModificationActionSelectedListener {
+ void selectAll();
+
+ void selectFinished();
+
+ void invertSelection();
+ }
+
+ /**
+ * 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 static 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);
+ }
+
+ }
}
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..bb12e5ad 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 .
*/
@@ -20,8 +20,6 @@ import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ListView;
@@ -37,71 +35,64 @@ 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)
+ .onPositive((dialog, which) -> {
+ // 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());
+ })
+ .onNeutral((dialog, which) ->
+ onLabelPickedListener.onLabelPicked(null));
- @Override
- public void onNeutral(MaterialDialog dialog) {
- onLabelPickedListener.onLabelPicked(null);
- }
- });
- final MaterialDialog dialog = SettingsUtils
- .applyDialogTheme(builder)
- .build();
+ 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((parent, view, position, 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..088b83c1 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,33 @@ 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)
+ .onPositive((dialog, which) -> {
+ // 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..5aa41424 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,27 +1,28 @@
-/*
+/*
* 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.navigation;
-import android.app.DialogFragment;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
+import androidx.fragment.app.DialogFragment;
+
import com.afollestad.materialdialogs.MaterialDialog;
import org.transdroid.R;
@@ -32,32 +33,30 @@ 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)
+ .onPositive((dialog, which) -> {
+ // 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..52e3ee16 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,78 @@ 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();
- }
+ private static OnClickListener onNumberClicked = 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());
+ };
+
+ /**
+ * 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)
+ .onPositive((dialog, which) -> {
+ 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);
+ })
+ .onNeutral((dialog, which) ->
+ 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);
+ }
+ }
+
+ /**
+ * 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..146ac815 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,165 +1,169 @@
-/*
+/*
* 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.navigation;
-import java.util.Arrays;
-import java.util.List;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
import org.transdroid.R;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.daemon.Torrent;
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
+import java.util.Arrays;
+import java.util.List;
/**
* 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 {
+
+ 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];
+ }
+ };
+ private final StatusType statusType;
+ private final String name;
+
+ StatusTypeFilter(StatusType statusType, String name) {
+ this.statusType = statusType;
+ this.name = name;
+ }
+
+ private StatusTypeFilter(Parcel in) {
+ this.statusType = StatusType.valueOf(in.readString());
+ this.name = in.readString();
+ }
+
+ 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;
+ }
+ }
+
+ @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..1b6d00d1 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,29 +1,30 @@
-/*
+/*
* Copyright 2010-2018 Eric Kok et al.
- *
+ *
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see .
*/
package org.transdroid.core.gui.remoterss;
-import androidx.fragment.app.Fragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
+import androidx.fragment.app.Fragment;
+
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
@@ -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..11af9ad5 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,17 +22,18 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+
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;
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+import com.google.android.material.tabs.TabLayout;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import com.nispok.snackbar.enums.SnackbarType;
@@ -71,372 +72,364 @@ import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
import java.util.List;
@EActivity(R.layout.activity_rssfeeds)
public class RssFeedsActivity extends AppCompatActivity {
- // Settings and local data
- @Bean
- protected Log log;
- @Bean
- protected ApplicationSettings applicationSettings;
-
- protected static final int RSS_FEEDS_LOCAL = 0;
- protected static final int RSS_FEEDS_REMOTE = 1;
-
- @FragmentById(R.id.rssfeeds_fragment)
- protected RssFeedsFragment fragmentLocalFeeds;
- @FragmentById(R.id.rssitems_fragment)
- protected RssItemsFragment fragmentItems;
- @FragmentById(R.id.remoterss_fragment)
- protected RemoteRssFragment fragmentRemoteFeeds;
-
- @ViewById(R.id.rssfeeds_toolbar)
- protected Toolbar rssFeedsToolbar;
- @ViewById(R.id.rssfeeds_tabs)
- protected TabLayout tabLayout;
- @ViewById(R.id.rssfeeds_pager)
- protected ViewPager viewPager;
-
- // remote RSS stuff
- @NonConfigurationInstance
- protected ArrayList feeds;
- @InstanceState
- protected int selectedFilter;
- @NonConfigurationInstance
- protected ArrayList recentItems;
- @Bean
- protected ConnectivityHelper connectivityHelper;
-
-
- protected class LayoutPagerAdapter extends PagerAdapter {
- boolean hasRemoteRss;
- String serverName;
-
- public LayoutPagerAdapter(boolean hasRemoteRss, String name) {
- super();
-
- this.hasRemoteRss = hasRemoteRss;
- this.serverName = (name.length() > 0 ? name : getString(R.string.navigation_rss_tabs_remote));
- }
-
- @NonNull
- @Override
- public Object instantiateItem(@NonNull ViewGroup container, int position) {
- int resId = 0;
-
- if (position == RSS_FEEDS_LOCAL) {
- resId = R.id.layout_rssfeeds_local;
- }
- else if (position == RSS_FEEDS_REMOTE) {
- resId = R.id.layout_rss_feeds_remote;
- }
-
- return findViewById(resId);
- }
-
- @Override
- public int getCount() {
- return (this.hasRemoteRss ? 2 : 1);
- }
-
- @Override
- public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
- return (view == o);
- }
-
- @Override
- public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
- container.removeView((View) object);
- }
-
- @Nullable
- @Override
- public CharSequence getPageTitle(int position) {
- switch (position) {
- case RSS_FEEDS_LOCAL:
- return getString(R.string.navigation_rss_tabs_local);
- case RSS_FEEDS_REMOTE:
- return this.serverName;
- }
-
- return super.getPageTitle(position);
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- SettingsUtils.applyDayNightTheme(this);
- super.onCreate(savedInstanceState);
- }
-
- @AfterViews
- protected void init() {
- setSupportActionBar(rssFeedsToolbar);
- getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.rss_feeds)));
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- IDaemonAdapter currentConnection = this.getCurrentConnection();
- boolean hasRemoteRss = Daemon.supportsRemoteRssManagement(currentConnection.getType());
-
- PagerAdapter pagerAdapter = new LayoutPagerAdapter(hasRemoteRss, currentConnection.getSettings().getName());
- viewPager.setAdapter(pagerAdapter);
- tabLayout.setupWithViewPager(viewPager);
-
- // 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);
- }
+ protected static final int RSS_FEEDS_LOCAL = 0;
+ protected static final int RSS_FEEDS_REMOTE = 1;
+ // Settings and local data
+ @Bean
+ protected Log log;
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @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;
+
+ @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 ignored) {
+ }
+
+ 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)) {
+ 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, (lhs, rhs) ->
+ 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) {
+ 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);
+ }
+
+ 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);
+ }
+ }
}
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..0fd0ba98 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,28 +1,29 @@
-/*
+/*
* 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;
import android.widget.ListView;
import android.widget.TextView;
+import androidx.fragment.app.Fragment;
+
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment;
@@ -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..c6d66e21 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,6 @@ 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;
@@ -35,6 +33,9 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
@@ -57,184 +58,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;
+ @Bean
+ protected RssitemsAdapter rssitemsAdapter;
+ @ViewById
+ protected TextView emptyText;
+ 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);
+ }
+
+ };
+
+ @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..a1fa8c0f 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
.
*/
@@ -21,89 +21,84 @@ import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
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;
+ }
+ newCount = 0;
+ if (usePublishDate) {
+ // Count the number of new items, based on the date that this RSS feed was last viewed by the user
+ List
- items = channel.getItems();
+ // Reverse-order sort the items on their published date
+ Collections.sort(items, (lhs, rhs) ->
+ -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
+ 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..b3237e94 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,62 @@ 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;
+ @RootContext
+ protected Context context;
+ private List loaders = null;
- @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..c26cc11a 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,52 @@ 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);
+ fullRect.set(0, 0, WIDTH, getHeight());
- 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..3c0b2f96 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,62 @@ 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;
+ @RootContext
+ protected Context context;
+ private Channel rssfeed = null;
- @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..6ff8406d 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 .
*/
@@ -20,8 +20,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
@@ -31,57 +29,56 @@ 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, (dialog, 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..8d5471d1 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,69 +1,65 @@
-/*
+/*
* 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.search;
-import org.transdroid.R;
-
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
+import org.transdroid.R;
+
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 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, (dialog, which) -> {
+ if (intentStartContext.get() != null)
+ intentStartContext.get().startActivity(new Intent(Intent.ACTION_VIEW, FILEMANAGER_MARKET_URI));
+ }).setNegativeButton(android.R.string.no, null).show();
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java b/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java
index 254840fe..37e3b18a 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/SearchActivity.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,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
-import androidx.core.view.MenuItemCompat;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -36,6 +33,9 @@ import android.widget.SearchView;
import android.widget.Spinner;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
@@ -58,284 +58,282 @@ import java.util.List;
/**
* An activity that shows search results to the user (after a query was supplied by the standard Android search manager) and either shows the list of
* search sites on the left (e.g. on tablets) or allows switching between search sites via the action bar spinner.
+ *
* @author Eric Kok
*/
@EActivity(R.layout.activity_search)
public class SearchActivity extends AppCompatActivity {
- @ViewById
- protected Toolbar searchToolbar;
- @ViewById
- protected Spinner sitesSpinner;
- @FragmentById(R.id.searchresults_fragment)
- protected SearchResultsFragment fragmentResults;
- @ViewById
- protected ListView searchsitesList;
- @ViewById
- protected TextView installmoduleText;
- @Bean
- protected ApplicationSettings applicationSettings;
- @Bean
- protected SearchHelper searchHelper;
- @SystemService
- protected SearchManager searchManager;
- private MenuItem searchMenu = null;
- private SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE);
-
- private List searchSites;
- private SearchSetting lastUsedSite;
- private String lastUsedQuery;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- SettingsUtils.applyDayNightTheme(this);
- super.onCreate(savedInstanceState);
- }
-
- @AfterViews
- protected void init() {
-
- searchToolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material);
- searchToolbar.setNavigationOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- TorrentsActivity_.intent(SearchActivity.this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
- }
- });
- setSupportActionBar(searchToolbar);
-
- // Get the user query, as coming from the standard SearchManager
- handleIntent(getIntent());
-
- if (!searchHelper.isTorrentSearchInstalled()) {
- // The module install text will be shown instead (in onPrepareOptionsMenu)
- return;
- }
-
- // Load sites and find the last used (or set as default) search site
- searchSites = applicationSettings.getSearchSettings();
- lastUsedSite = applicationSettings.getLastUsedSearchSite();
- int lastUsedPosition = -1;
- if (lastUsedSite != null) {
- for (int i = 0; i < searchSites.size(); i++) {
- if (searchSites.get(i).getKey().equals(lastUsedSite.getKey())) {
- lastUsedPosition = i;
- break;
- }
- }
- }
-
- // Allow site selection via list (on large screens) or action bar spinner
- if (searchsitesList != null) {
- // The current layout has a dedicated list view to select the search site
- SearchSitesAdapter searchSitesAdapter = SearchSitesAdapter_.getInstance_(this);
- searchSitesAdapter.update(searchSites);
- searchsitesList.setAdapter(searchSitesAdapter);
- searchsitesList.setOnItemClickListener(onSearchSiteClicked);
- // Select the last used site and start the search
- if (lastUsedPosition >= 0) {
- searchsitesList.setItemChecked(lastUsedPosition, true);
- lastUsedSite = searchSites.get(lastUsedPosition);
- refreshSearch();
- } else {
- fragmentResults.clearResults();
- }
- } else {
- // Use the action bar spinner to select sites
- if (getSupportActionBar() != null)
- getSupportActionBar().setTitle("");
- sitesSpinner.setVisibility(View.VISIBLE);
- sitesSpinner.setAdapter(new SearchSettingsDropDownAdapter(searchToolbar.getContext(), searchSites));
- sitesSpinner.setOnItemSelectedListener(onSearchSiteSelected);
- // Select the last used site; this also starts the search!
- if (lastUsedPosition >= 0) {
- sitesSpinner.setSelection(lastUsedPosition);
- lastUsedSite = searchSites.get(lastUsedPosition);
- refreshSearch();
- } else {
- fragmentResults.clearResults();
- }
- }
- invalidateOptionsMenu();
-
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- // Manually insert the actions into the main torrent and secondary actions toolbars
- searchToolbar.inflateMenu(R.menu.activity_search);
- // Add an expandable SearchView to the action bar
- MenuItem item = menu.findItem(R.id.action_search);
- final SearchView searchView = new SearchView(searchToolbar.getContext());
- searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
- searchView.setQueryRefinementEnabled(true);
- searchView.setIconified(false);
- searchView.setIconifiedByDefault(false);
- MenuItemCompat.setActionView(item, searchView);
- searchMenu = item;
- final MenuItem sortBySeeders = menu.findItem(R.id.action_sort_seeders);
- final MenuItem sortByAdded = menu.findItem(R.id.action_sort_added);
- final SearchSortOrder sortOrder = applicationSettings.getLastUsedSearchSortOrder();
- if (sortOrder == SearchSortOrder.BySeeders) {
- sortBySeeders.setChecked(true);
- } else {
- sortByAdded.setChecked(true);
- }
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- boolean searchInstalled = searchHelper.isTorrentSearchInstalled();
- searchToolbar.getMenu().findItem(R.id.action_search).setVisible(searchInstalled);
- searchToolbar.getMenu().findItem(R.id.action_refresh).setVisible(searchInstalled);
- searchToolbar.getMenu().findItem(R.id.action_downloadsearch).setVisible(!searchInstalled);
- if (searchsitesList != null) {
- searchsitesList.setVisibility(searchInstalled ? View.VISIBLE : View.GONE);
- }
- if (searchInstalled) {
- getFragmentManager().beginTransaction().show(fragmentResults).commit();
- } else {
- getFragmentManager().beginTransaction().hide(fragmentResults).commit();
- }
- installmoduleText.setVisibility(searchInstalled ? View.GONE : View.VISIBLE);
-
- return true;
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- handleIntent(intent);
- refreshSearch();
- }
-
- private void handleIntent(Intent intent) {
-
- lastUsedQuery = parseQuery(intent);
-
- // Is this actually a full HTTP URL? Then redirect this request to add the URL directly
- if (lastUsedQuery != null && (lastUsedQuery.startsWith("http") || lastUsedQuery.startsWith("https") ||
- lastUsedQuery.startsWith("magnet") || lastUsedQuery.startsWith("file"))) {
- // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
- Intent i = TorrentsActivity_.intent(this).get();
- i.setData(Uri.parse(lastUsedQuery));
- startActivity(i);
- finish();
- }
-
- }
-
- @Override
- public boolean onSearchRequested() {
- if (searchMenu != null) {
- searchMenu.expandActionView();
- }
- return true;
- }
-
- private OnItemClickListener onSearchSiteClicked = new OnItemClickListener() {
-
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- lastUsedSite = searchSites.get(position);
- refreshSearch();
- }
- };
- private AdapterView.OnItemSelectedListener onSearchSiteSelected = new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView> parent, View view, int position, long id) {
- lastUsedSite = searchSites.get(position);
- refreshSearch();
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {
- }
- };
-
- /**
- * Extracts the query string from the search {@link Intent}
- * @return The query string that was entered by the user
- */
- private String parseQuery(Intent intent) {
-
- String query = null;
- if (intent.getAction().equals(Intent.ACTION_SEARCH)) {
- query = intent.getStringExtra(SearchManager.QUERY).trim();
- } else if (intent.getAction().equals(Intent.ACTION_SEND)) {
- query = SendIntentHelper.cleanUpText(intent).trim();
- }
- if (query != null && query.length() > 0) {
-
- // Remember this search query to later show as a suggestion
- suggestions.saveRecentQuery(query, null);
- return query;
-
- }
- return null;
-
- }
-
- @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)
- protected void refreshSearch() {
-
- if (searchMenu != null) {
- // Close the search view in the action bar
- searchMenu.collapseActionView();
- }
-
- if (lastUsedSite instanceof WebsearchSetting) {
-
- // Start a browser page directly to the requested search results
- WebsearchSetting websearch = (WebsearchSetting) lastUsedSite;
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(websearch.getBaseUrl().replace("%s", lastUsedQuery))));
- finish();
-
- } else if (lastUsedSite instanceof SearchSite) {
-
- // Save the search site currently used to search for future usage
- applicationSettings.setLastUsedSearchSite(lastUsedSite);
- // Update the activity title (only shown on large devices)
- if (sitesSpinner == null && getSupportActionBar() != null)
- getSupportActionBar()
- .setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.search_queryonsite, lastUsedQuery, lastUsedSite.getName())));
- // Ask the results fragment to start a search for the specified query
- fragmentResults.startSearch(lastUsedQuery, (SearchSite) lastUsedSite, applicationSettings.getLastUsedSearchSortOrder());
-
- }
- }
-
- @OptionsItem(R.id.action_downloadsearch)
- protected void downloadSearchModule() {
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/latest-search")));
- }
-
- @OptionsItem(R.id.action_sort_added)
- protected void sortByDateAdded() {
- if (applicationSettings.getLastUsedSearchSortOrder() == SearchSortOrder.Combined) {
- return;
- }
- invalidateOptionsMenu();
- applicationSettings.setLastUsedSearchSortOrder(SearchSortOrder.Combined);
- refreshSearch();
- }
-
- @OptionsItem(R.id.action_sort_seeders)
- protected void sortBySeeders() {
- if (applicationSettings.getLastUsedSearchSortOrder() == SearchSortOrder.BySeeders) {
- return;
- }
- invalidateOptionsMenu();
- applicationSettings.setLastUsedSearchSortOrder(SearchSortOrder.BySeeders);
- refreshSearch();
- }
+ @ViewById
+ protected Toolbar searchToolbar;
+ @ViewById
+ protected Spinner sitesSpinner;
+ @FragmentById(R.id.searchresults_fragment)
+ protected SearchResultsFragment fragmentResults;
+ @ViewById
+ protected ListView searchsitesList;
+ @ViewById
+ protected TextView installmoduleText;
+ @Bean
+ protected ApplicationSettings applicationSettings;
+ @Bean
+ protected SearchHelper searchHelper;
+ @SystemService
+ protected SearchManager searchManager;
+ private MenuItem searchMenu = null;
+ private SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE);
+
+ private List searchSites;
+ private SearchSetting lastUsedSite;
+ private String lastUsedQuery;
+ private OnItemClickListener onSearchSiteClicked = new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ lastUsedSite = searchSites.get(position);
+ refreshSearch();
+ }
+ };
+ private AdapterView.OnItemSelectedListener onSearchSiteSelected = new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ lastUsedSite = searchSites.get(position);
+ refreshSearch();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ SettingsUtils.applyDayNightTheme(this);
+ super.onCreate(savedInstanceState);
+ }
+
+ @AfterViews
+ protected void init() {
+
+ searchToolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material);
+ searchToolbar.setNavigationOnClickListener(v ->
+ TorrentsActivity_.intent(SearchActivity.this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start());
+ setSupportActionBar(searchToolbar);
+
+ // Get the user query, as coming from the standard SearchManager
+ handleIntent(getIntent());
+
+ if (!searchHelper.isTorrentSearchInstalled()) {
+ // The module install text will be shown instead (in onPrepareOptionsMenu)
+ return;
+ }
+
+ // Load sites and find the last used (or set as default) search site
+ searchSites = applicationSettings.getSearchSettings();
+ lastUsedSite = applicationSettings.getLastUsedSearchSite();
+ int lastUsedPosition = -1;
+ if (lastUsedSite != null) {
+ for (int i = 0; i < searchSites.size(); i++) {
+ if (searchSites.get(i).getKey().equals(lastUsedSite.getKey())) {
+ lastUsedPosition = i;
+ break;
+ }
+ }
+ }
+
+ // Allow site selection via list (on large screens) or action bar spinner
+ if (searchsitesList != null) {
+ // The current layout has a dedicated list view to select the search site
+ SearchSitesAdapter searchSitesAdapter = SearchSitesAdapter_.getInstance_(this);
+ searchSitesAdapter.update(searchSites);
+ searchsitesList.setAdapter(searchSitesAdapter);
+ searchsitesList.setOnItemClickListener(onSearchSiteClicked);
+ // Select the last used site and start the search
+ if (lastUsedPosition >= 0) {
+ searchsitesList.setItemChecked(lastUsedPosition, true);
+ lastUsedSite = searchSites.get(lastUsedPosition);
+ refreshSearch();
+ } else {
+ fragmentResults.clearResults();
+ }
+ } else {
+ // Use the action bar spinner to select sites
+ if (getSupportActionBar() != null)
+ getSupportActionBar().setTitle("");
+ sitesSpinner.setVisibility(View.VISIBLE);
+ sitesSpinner.setAdapter(new SearchSettingsDropDownAdapter(searchToolbar.getContext(), searchSites));
+ sitesSpinner.setOnItemSelectedListener(onSearchSiteSelected);
+ // Select the last used site; this also starts the search!
+ if (lastUsedPosition >= 0) {
+ sitesSpinner.setSelection(lastUsedPosition);
+ lastUsedSite = searchSites.get(lastUsedPosition);
+ refreshSearch();
+ } else {
+ fragmentResults.clearResults();
+ }
+ }
+ invalidateOptionsMenu();
+
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ // Manually insert the actions into the main torrent and secondary actions toolbars
+ searchToolbar.inflateMenu(R.menu.activity_search);
+ // Add an expandable SearchView to the action bar
+ MenuItem item = menu.findItem(R.id.action_search);
+ final SearchView searchView = new SearchView(searchToolbar.getContext());
+ searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
+ searchView.setQueryRefinementEnabled(true);
+ searchView.setIconified(false);
+ searchView.setIconifiedByDefault(false);
+ item.setActionView(searchView);
+ searchMenu = item;
+ final MenuItem sortBySeeders = menu.findItem(R.id.action_sort_seeders);
+ final MenuItem sortByAdded = menu.findItem(R.id.action_sort_added);
+ final SearchSortOrder sortOrder = applicationSettings.getLastUsedSearchSortOrder();
+ if (sortOrder == SearchSortOrder.BySeeders) {
+ sortBySeeders.setChecked(true);
+ } else {
+ sortByAdded.setChecked(true);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ boolean searchInstalled = searchHelper.isTorrentSearchInstalled();
+ searchToolbar.getMenu().findItem(R.id.action_search).setVisible(searchInstalled);
+ searchToolbar.getMenu().findItem(R.id.action_refresh).setVisible(searchInstalled);
+ searchToolbar.getMenu().findItem(R.id.action_downloadsearch).setVisible(!searchInstalled);
+ if (searchsitesList != null) {
+ searchsitesList.setVisibility(searchInstalled ? View.VISIBLE : View.GONE);
+ }
+ if (searchInstalled) {
+ getSupportFragmentManager().beginTransaction().show(fragmentResults).commit();
+ } else {
+ getSupportFragmentManager().beginTransaction().hide(fragmentResults).commit();
+ }
+ installmoduleText.setVisibility(searchInstalled ? View.GONE : View.VISIBLE);
+
+ return true;
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ refreshSearch();
+ }
+
+ private void handleIntent(Intent intent) {
+
+ lastUsedQuery = parseQuery(intent);
+
+ // Is this actually a full HTTP URL? Then redirect this request to add the URL directly
+ if (lastUsedQuery != null && (lastUsedQuery.startsWith("http") || lastUsedQuery.startsWith("https") ||
+ lastUsedQuery.startsWith("magnet") || lastUsedQuery.startsWith("file"))) {
+ // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
+ Intent i = TorrentsActivity_.intent(this).get();
+ i.setData(Uri.parse(lastUsedQuery));
+ startActivity(i);
+ finish();
+ }
+
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ if (searchMenu != null) {
+ searchMenu.expandActionView();
+ }
+ return true;
+ }
+
+ /**
+ * Extracts the query string from the search {@link Intent}
+ *
+ * @return The query string that was entered by the user
+ */
+ private String parseQuery(Intent intent) {
+
+ String query = null;
+ if (intent.getAction().equals(Intent.ACTION_SEARCH)) {
+ query = intent.getStringExtra(SearchManager.QUERY).trim();
+ } else if (intent.getAction().equals(Intent.ACTION_SEND)) {
+ query = SendIntentHelper.cleanUpText(intent).trim();
+ }
+ if (query != null && query.length() > 0) {
+
+ // Remember this search query to later show as a suggestion
+ suggestions.saveRecentQuery(query, null);
+ return query;
+
+ }
+ return null;
+
+ }
+
+ @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)
+ protected void refreshSearch() {
+
+ if (searchMenu != null) {
+ // Close the search view in the action bar
+ searchMenu.collapseActionView();
+ }
+
+ if (lastUsedSite instanceof WebsearchSetting) {
+
+ // Start a browser page directly to the requested search results
+ WebsearchSetting websearch = (WebsearchSetting) lastUsedSite;
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(websearch.getBaseUrl().replace("%s", lastUsedQuery))));
+ finish();
+
+ } else if (lastUsedSite instanceof SearchSite) {
+
+ // Save the search site currently used to search for future usage
+ applicationSettings.setLastUsedSearchSite(lastUsedSite);
+ // Update the activity title (only shown on large devices)
+ if (sitesSpinner == null && getSupportActionBar() != null)
+ getSupportActionBar()
+ .setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.search_queryonsite, lastUsedQuery, lastUsedSite.getName())));
+ // Ask the results fragment to start a search for the specified query
+ fragmentResults.startSearch(lastUsedQuery, (SearchSite) lastUsedSite, applicationSettings.getLastUsedSearchSortOrder());
+
+ }
+ }
+
+ @OptionsItem(R.id.action_downloadsearch)
+ protected void downloadSearchModule() {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/latest-search")));
+ }
+
+ @OptionsItem(R.id.action_sort_added)
+ protected void sortByDateAdded() {
+ if (applicationSettings.getLastUsedSearchSortOrder() == SearchSortOrder.Combined) {
+ return;
+ }
+ invalidateOptionsMenu();
+ applicationSettings.setLastUsedSearchSortOrder(SearchSortOrder.Combined);
+ refreshSearch();
+ }
+
+ @OptionsItem(R.id.action_sort_seeders)
+ protected void sortBySeeders() {
+ if (applicationSettings.getLastUsedSearchSortOrder() == SearchSortOrder.BySeeders) {
+ return;
+ }
+ invalidateOptionsMenu();
+ applicationSettings.setLastUsedSearchSortOrder(SearchSortOrder.BySeeders);
+ refreshSearch();
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchHistoryProvider.java b/app/src/main/java/org/transdroid/core/gui/search/SearchHistoryProvider.java
index 9e09ab6e..574ab227 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/SearchHistoryProvider.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/SearchHistoryProvider.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,19 +24,20 @@ import org.transdroid.BuildConfig;
/**
* Provides search suggestions by simply returning previous user entries.
+ *
* @author Eric Kok
*/
public class SearchHistoryProvider extends SearchRecentSuggestionsProvider {
- public final static String AUTHORITY = BuildConfig.APPLICATION_ID + ".search.SearchHistoryProvider";
- public final static int MODE = DATABASE_MODE_QUERIES;
+ public final static String AUTHORITY = BuildConfig.APPLICATION_ID + ".search.SearchHistoryProvider";
+ public final static int MODE = DATABASE_MODE_QUERIES;
- public SearchHistoryProvider() {
- setupSuggestions(AUTHORITY, MODE);
- }
+ public SearchHistoryProvider() {
+ setupSuggestions(AUTHORITY, MODE);
+ }
+
+ public static void clearHistory(Context context) {
+ new SearchRecentSuggestions(context, AUTHORITY, MODE).clearHistory();
+ }
- public static void clearHistory(Context context) {
- new SearchRecentSuggestions(context, AUTHORITY, MODE).clearHistory();
- }
-
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchResultView.java b/app/src/main/java/org/transdroid/core/gui/search/SearchResultView.java
index 60e53ada..da3e2909 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/SearchResultView.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/SearchResultView.java
@@ -1,56 +1,57 @@
-/*
+/*
* 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.search;
-import org.androidannotations.annotations.EViewGroup;
-import org.androidannotations.annotations.ViewById;
-import org.transdroid.R;
-import org.transdroid.core.app.search.SearchResult;
-
import android.content.Context;
import android.text.format.DateUtils;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import org.androidannotations.annotations.EViewGroup;
+import org.androidannotations.annotations.ViewById;
+import org.transdroid.R;
+import org.transdroid.core.app.search.SearchResult;
+
/**
* View that represents a {@link SearchResult} object from an in-app search
+ *
* @author Eric Kok
*/
@EViewGroup(resName = "list_item_searchresult")
public class SearchResultView extends RelativeLayout {
- // Views
- @ViewById
- protected TextView nameText, seedersText, leechersText, sizeText, dateText;
+ // Views
+ @ViewById
+ protected TextView nameText, seedersText, leechersText, sizeText, dateText;
- public SearchResultView(Context context) {
- super(context);
- }
+ public SearchResultView(Context context) {
+ super(context);
+ }
- public void bind(SearchResult result) {
+ public void bind(SearchResult result) {
- nameText.setText(result.getName());
- sizeText.setText(result.getSize());
- dateText.setText(result.getAddedOn() == null ? "" : DateUtils.getRelativeDateTimeString(getContext(), result
- .getAddedOn().getTime(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_MONTH));
- seedersText.setText(getContext().getString(R.string.search_seeders, result.getSeeders()));
- leechersText.setText(getContext().getString(R.string.search_leechers, result.getLeechers()));
+ nameText.setText(result.getName());
+ sizeText.setText(result.getSize());
+ dateText.setText(result.getAddedOn() == null ? "" : DateUtils.getRelativeDateTimeString(getContext(), result
+ .getAddedOn().getTime(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS,
+ DateUtils.FORMAT_ABBREV_MONTH));
+ seedersText.setText(getContext().getString(R.string.search_seeders, result.getSeeders()));
+ leechersText.setText(getContext().getString(R.string.search_leechers, result.getLeechers()));
- }
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java b/app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java
index 224097e9..710c6b6f 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.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,62 @@ import java.util.List;
/**
* Adapter that contains a list of {@link SearchResult}s.
+ *
* @author Eric Kok
*/
@EBean
public class SearchResultsAdapter extends BaseAdapter {
- private List results = null;
+ @RootContext
+ protected Context context;
+ private List results = null;
- @RootContext
- protected Context context;
+ /**
+ * Allows updating the search results, replacing the old data
+ *
+ * @param results The new list of search results
+ */
+ public void update(List results) {
+ this.results = results;
+ notifyDataSetChanged();
+ }
- /**
- * Allows updating the search results, replacing the old data
- * @param results The new list of search results
- */
- public void update(List results) {
- this.results = results;
- notifyDataSetChanged();
- }
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
- @Override
- public boolean hasStableIds() {
- return true;
- }
+ @Override
+ public int getCount() {
+ if (results == null) {
+ return 0;
+ }
+ return results.size();
+ }
- @Override
- public int getCount() {
- if (results == null) {
- return 0;
- }
- return results.size();
- }
+ @Override
+ public SearchResult getItem(int position) {
+ if (results == null) {
+ return null;
+ }
+ return results.get(position);
+ }
- @Override
- public SearchResult getItem(int position) {
- if (results == null) {
- return null;
- }
- return results.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) {
- SearchResultView rssitemView;
- if (convertView == null) {
- rssitemView = SearchResultView_.build(context);
- } else {
- rssitemView = (SearchResultView) convertView;
- }
- rssitemView.bind(getItem(position));
- return rssitemView;
- }
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ SearchResultView rssitemView;
+ if (convertView == null) {
+ rssitemView = SearchResultView_.build(context);
+ } else {
+ rssitemView = (SearchResultView) convertView;
+ }
+ rssitemView.bind(getItem(position));
+ return rssitemView;
+ }
}
diff --git a/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java b/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
index 59d19702..702af9fe 100644
--- a/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
+++ b/app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
@@ -1,26 +1,24 @@
-/*
+/*
* 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.search;
-import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.Menu;
@@ -32,6 +30,9 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
@@ -48,7 +49,6 @@ import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchHelper.SearchSortOrder;
import org.transdroid.core.app.search.SearchResult;
import org.transdroid.core.app.search.SearchSite;
-import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper_;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
@@ -58,170 +58,170 @@ import java.util.List;
/**
* Fragment that lists the items in a specific RSS feed
+ *
* @author Eric Kok
*/
@EFragment(R.layout.fragment_searchresults)
public class SearchResultsFragment extends Fragment {
- @InstanceState
- protected ArrayList results = null;
- @InstanceState
- protected String resultsSource;
- @Bean
- protected SearchHelper searchHelper;
-
- // Views
- @ViewById(R.id.searchresults_list)
- protected ListView resultsList;
- @Bean
- protected SearchResultsAdapter resultsAdapter;
- @ViewById
- protected TextView emptyText;
- @ViewById
- protected ProgressBar loadingProgress;
-
- @AfterViews
- protected void init() {
-
- // On large screens where this fragment is shown next to the sites list; we show a continues grey vertical line
- // to separate the lists visually
- if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) {
- resultsList.setBackgroundResource(R.drawable.details_list_background);
- }
-
- // Set up the list adapter, which allows multi-select
- resultsList.setAdapter(resultsAdapter);
- resultsList.setMultiChoiceModeListener(onItemsSelected);
- if (results != null) {
- showResults();
- }
-
- }
-
- public void startSearch(String query, SearchSite site, SearchSortOrder sortBy) {
- loadingProgress.setVisibility(View.VISIBLE);
- resultsList.setVisibility(View.GONE);
- emptyText.setVisibility(View.GONE);
- performSearch(query, site, sortBy);
- }
-
- @Background
- protected void performSearch(String query, SearchSite site, SearchSortOrder sortBy) {
- results = searchHelper.search(query, site, sortBy);
- resultsSource = site.isPrivate() ? site.getKey() : null;
- showResults();
- }
-
- @UiThread
- protected void showResults() {
- loadingProgress.setVisibility(View.GONE);
- if (results == null || results.size() == 0) {
- resultsList.setVisibility(View.GONE);
- emptyText.setVisibility(View.VISIBLE);
- return;
- }
- resultsAdapter.update(results);
- resultsList.setVisibility(View.VISIBLE);
- emptyText.setVisibility(View.GONE);
- }
-
- public void clearResults() {
- loadingProgress.setVisibility(View.GONE);
- resultsList.setVisibility(View.GONE);
- emptyText.setVisibility(View.VISIBLE);
- }
-
- @ItemClick(R.id.searchresults_list)
- protected void onItemClicked(SearchResult item) {
- if (item.getTorrentUrl() == null) {
- SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_notorrentfile).colorResource(R.color.red));
- return;
- }
- // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
- Intent i = TorrentsActivity_.intent(getActivity()).get();
- i.setData(Uri.parse(item.getTorrentUrl()));
- i.putExtra("TORRENT_TITLE", item.getName());
- if (resultsSource != null) {
- i.putExtra("PRIVATE_SOURCE", resultsSource);
- }
- startActivity(i);
- }
-
- 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_searchresults_cab, menu);
- Context themedContext = ((AppCompatActivity) getActivity()).getSupportActionBar().getThemedContext();
- selectionManagerMode = new SelectionManagerMode(themedContext, resultsList, R.plurals.search_resutlsselected);
- 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