Browse Source

Merging upstream and fix conflicts

pull/222/head
Kumaresan Rajeswaran 9 years ago
parent
commit
d26973468e
  1. 2
      README.md
  2. 38
      app/build.gradle
  3. 4
      app/src/main/AndroidManifest.xml
  4. 8
      app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java
  5. 5
      app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java
  6. 2
      app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java
  7. 126
      app/src/main/java/org/transdroid/core/gui/DetailsActivity.java
  8. 351
      app/src/main/java/org/transdroid/core/gui/DetailsFragment.java
  9. 59
      app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java
  10. 29
      app/src/main/java/org/transdroid/core/gui/ServerStatusView.java
  11. 19
      app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java
  12. 660
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  13. 151
      app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java
  14. 17
      app/src/main/java/org/transdroid/core/gui/lists/NoProgressHeaderTransformer.java
  15. 12
      app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java
  16. 43
      app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java
  17. 9
      app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java
  18. 12
      app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java
  19. 2
      app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java
  20. 17
      app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java
  21. 16
      app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java
  22. 2
      app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java
  23. 20
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java
  24. 70
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListDropDownAdapter.java
  25. 10
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java
  26. 11
      app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java
  27. 12
      app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java
  28. 32
      app/src/main/java/org/transdroid/core/gui/navigation/Label.java
  29. 14
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java
  30. 181
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java
  31. 52
      app/src/main/java/org/transdroid/core/gui/navigation/NavigationSelectionView.java
  32. 9
      app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java
  33. 14
      app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java
  34. 117
      app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java
  35. 70
      app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java
  36. 73
      app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java
  37. 94
      app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java
  38. 23
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java
  39. 24
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java
  40. 75
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsActivity.java
  41. 16
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java
  42. 34
      app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java
  43. 13
      app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java
  44. 15
      app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java
  45. 33
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java
  46. 16
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java
  47. 33
      app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java
  48. 4
      app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
  49. 69
      app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java
  50. 18
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java
  51. 172
      app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java
  52. 9
      app/src/main/java/org/transdroid/core/gui/search/SearchSettingSelectionView.java
  53. 11
      app/src/main/java/org/transdroid/core/gui/search/SearchSettingsDropDownAdapter.java
  54. 20
      app/src/main/java/org/transdroid/core/gui/search/SearchSiteView.java
  55. 16
      app/src/main/java/org/transdroid/core/gui/search/SearchSitesAdapter.java
  56. 57
      app/src/main/java/org/transdroid/core/gui/search/UrlEntryDialog.java
  57. 4
      app/src/main/java/org/transdroid/core/gui/settings/HelpSettingsActivity.java
  58. 9
      app/src/main/java/org/transdroid/core/gui/settings/KeyBoundPreferencesActivity.java
  59. 77
      app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java
  60. 20
      app/src/main/java/org/transdroid/core/gui/settings/NotificationSettingsActivity.java
  61. 117
      app/src/main/java/org/transdroid/core/gui/settings/OverflowPreference.java
  62. 72
      app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java
  63. 7
      app/src/main/java/org/transdroid/core/gui/settings/RssfeedSettingsActivity.java
  64. 7
      app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java
  65. 49
      app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java
  66. 6
      app/src/main/java/org/transdroid/core/gui/settings/WebsearchPreference.java
  67. 7
      app/src/main/java/org/transdroid/core/gui/settings/WebsearchSettingsActivity.java
  68. 2
      app/src/main/java/org/transdroid/core/seedbox/SeedstuffSettingsActivity.java
  69. 2
      app/src/main/java/org/transdroid/core/seedbox/XirvikDediSettingsActivity.java
  70. 2
      app/src/main/java/org/transdroid/core/seedbox/XirvikSemiSettingsActivity.java
  71. 48
      app/src/main/java/org/transdroid/core/seedbox/XirvikSharedSettingsActivity.java
  72. 10
      app/src/main/java/org/transdroid/core/service/ConnectivityHelper.java
  73. 10
      app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java
  74. 21
      app/src/main/java/org/transdroid/daemon/Daemon.java
  75. 192
      app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java
  76. 106
      app/src/main/java/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java
  77. 450
      app/src/main/java/org/transdroid/daemon/Ttorrent/TtorrentAdapter.java
  78. BIN
      app/src/main/res/drawable-hdpi/ab_bottom_solid_transdroid.9.png
  79. BIN
      app/src/main/res/drawable-hdpi/ab_bottom_solid_transdroid2.9.png
  80. BIN
      app/src/main/res/drawable-hdpi/ab_solid_transdroid.9.png
  81. BIN
      app/src/main/res/drawable-hdpi/ab_solid_transdroid2.9.png
  82. BIN
      app/src/main/res/drawable-hdpi/ab_stacked_solid_transdroid.9.png
  83. BIN
      app/src/main/res/drawable-hdpi/ab_stacked_solid_transdroid2.9.png
  84. BIN
      app/src/main/res/drawable-hdpi/ab_texture_tile_transdroid2.png
  85. BIN
      app/src/main/res/drawable-hdpi/ab_transparent_transdroid.9.png
  86. BIN
      app/src/main/res/drawable-hdpi/ab_transparent_transdroid2.9.png
  87. BIN
      app/src/main/res/drawable-hdpi/abc_list_focused_holo.9.png
  88. BIN
      app/src/main/res/drawable-hdpi/abc_list_longpressed_holo.9.png
  89. BIN
      app/src/main/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
  90. BIN
      app/src/main/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
  91. BIN
      app/src/main/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png
  92. BIN
      app/src/main/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png
  93. BIN
      app/src/main/res/drawable-hdpi/btn_cab_done_default_transdroid2.9.png
  94. BIN
      app/src/main/res/drawable-hdpi/btn_cab_done_focused_transdroid2.9.png
  95. BIN
      app/src/main/res/drawable-hdpi/btn_cab_done_pressed_transdroid2.9.png
  96. BIN
      app/src/main/res/drawable-hdpi/cab_background_bottom_transdroid2.9.png
  97. BIN
      app/src/main/res/drawable-hdpi/cab_background_top_transdroid2.9.png
  98. BIN
      app/src/main/res/drawable-hdpi/ic_action_add.png
  99. BIN
      app/src/main/res/drawable-hdpi/ic_action_barcode.png
  100. BIN
      app/src/main/res/drawable-hdpi/ic_action_copy.png
  101. Some files were not shown because too many files have changed in this diff Show More

2
README.md

@ -20,7 +20,7 @@ Please respect the coding standards for easier merging. master contains the curr
Code structure Code structure
============== ==============
Starting with version 2.3.0, Transdroid is developed in Android Studio, fully integrating with the Gradle build system. It is compiled against Android 4.4 (API level 19) 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 Maven Central 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.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 in the app's build.gradle file.
Developed By Developed By
============ ============

38
app/build.gradle

@ -1,29 +1,19 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'android-apt' apply plugin: 'android-apt'
apply from: '../signing.gradle'
android { android {
compileSdkVersion 19 compileSdkVersion 22
buildToolsVersion '20.0.0' buildToolsVersion '21.1.2'
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 19 targetSdkVersion 22
versionCode 217 versionCode 221
versionName '2.3.0' versionName '2.5.1'
}
signingConfigs {
release {
storeFile STORE_FILE
storePassword STORE_PASSWORD
keyAlias KEY_ALIAS
keyPassword KEY_PASSWORD
}
} }
buildTypes { buildTypes {
release { release {
runProguard false minifyEnabled false
signingConfig signingConfigs.release
} }
} }
productFlavors { productFlavors {
@ -43,19 +33,21 @@ android {
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.androidannotations:androidannotations-api:3.1' compile 'org.androidannotations:androidannotations-api:3.2'
compile 'com.j256.ormlite:ormlite-core:4.48' compile 'com.j256.ormlite:ormlite-core:4.48'
compile 'com.j256.ormlite:ormlite-android:4.48' compile 'com.j256.ormlite:ormlite-android:4.48'
compile 'com.github.chrisbanes.actionbarpulltorefresh:library:0.8' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
compile 'de.keyboardsurfer.android.widget:crouton:1.8.+' compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.+' compile 'com.android.support:support-annotations:22.1.1'
compile 'com.android.support:support-annotations:20.0.0' compile 'com.getbase:floatingactionbutton:1.8.0'
apt "org.androidannotations:androidannotations:3.1" compile 'com.afollestad:material-dialogs:0.7.6.0'
compile 'com.nispok:snackbar:2.10.6'
apt 'org.androidannotations:androidannotations:3.2'
} }
apt { apt {
arguments { arguments {
androidManifestFile variant.processResources.manifestFile androidManifestFile variant.outputs[0].processResources.manifestFile
resourcePackageName 'org.transdroid' resourcePackageName 'org.transdroid'
//logLevel 'INFO' //logLevel 'INFO'
//logFile '/Users/erickok/Dev/transdroid/transdroid/app/build/aa-log.txt' //logFile '/Users/erickok/Dev/transdroid/transdroid/app/build/aa-log.txt'

4
app/src/main/AndroidManifest.xml

@ -44,7 +44,7 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@android:style/Theme.Holo" > android:theme="@style/Theme.AppCompat" >
<!-- Main activities --> <!-- Main activities -->
<activity <activity
@ -53,7 +53,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/TransdroidTheme" android:theme="@style/TransdroidTheme"
android:uiOptions="splitActionBarWhenNarrow" > android:windowSoftInputMode="stateHidden" >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

8
app/src/main/java/org/transdroid/core/app/settings/ApplicationSettings.java

@ -97,15 +97,15 @@ public class ApplicationSettings {
* @return The server settings object, loaded from shared preferences * @return The server settings object, loaded from shared preferences
*/ */
public ServerSetting getServerSetting(int order) { public ServerSetting getServerSetting(int order) {
int max = getMaxNormalServer(); int max = getMaxNormalServer() + 1;
if (order <= max) { if (order < max) {
return getNormalServerSetting(order); return getNormalServerSetting(order);
} }
for (SeedboxProvider provider : SeedboxProvider.values()) { for (SeedboxProvider provider : SeedboxProvider.values()) {
int offset = max; int offset = max;
max += provider.getSettings().getMaxSeedboxOrder(prefs) + 1; max += provider.getSettings().getMaxSeedboxOrder(prefs) + 1;
if (order <= max) { if (order < max) {
return provider.getSettings().getServerSetting(prefs, offset, order - offset - 1); return provider.getSettings().getServerSetting(prefs, offset, order - offset);
} }
} }
return null; return null;

5
app/src/main/java/org/transdroid/core/app/settings/ServerSetting.java

@ -259,6 +259,11 @@ public class ServerSetting implements SimpleListItem {
return false; 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 * 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 * @param connectedToNetwork The name of the (wifi) network we are currently connected to, or null if this could not

2
app/src/main/java/org/transdroid/core/app/settings/SystemSettings.java

@ -76,7 +76,7 @@ public class SystemSettings {
* @param lastChecked The date/time at which the {@link AppUpdateService} last checked the server for updates * @param lastChecked The date/time at which the {@link AppUpdateService} last checked the server for updates
*/ */
public void setLastCheckedForAppUpdates(Date lastChecked) { public void setLastCheckedForAppUpdates(Date lastChecked) {
prefs.edit().putLong("system_lastappupdatecheck", lastChecked == null ? -1L : lastChecked.getTime()).commit(); prefs.edit().putLong("system_lastappupdatecheck", lastChecked == null ? -1L : lastChecked.getTime()).apply();
} }
} }

126
app/src/main/java/org/transdroid/core/gui/DetailsActivity.java

@ -16,8 +16,15 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.ArrayList; import android.annotation.TargetApi;
import java.util.List; import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
@ -29,10 +36,12 @@ import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.*; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.lists.LocalTorrent; import org.transdroid.core.gui.lists.LocalTorrent;
import org.transdroid.core.gui.lists.NoProgressHeaderTransformer;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.navigation.Label; import org.transdroid.core.gui.navigation.Label;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
@ -64,27 +73,18 @@ import org.transdroid.daemon.task.SetTrackersTask;
import org.transdroid.daemon.task.StartTask; import org.transdroid.daemon.task.StartTask;
import org.transdroid.daemon.task.StopTask; import org.transdroid.daemon.task.StopTask;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher; import java.util.ArrayList;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher.OnRefreshListener; import java.util.List;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher.Options;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
* An activity that holds a single torrents details fragment. It is used on devices (i.e. phones) where there is no room * 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
* to show details in the {@link TorrentsActivity} directly. Task execution, such as loading of more details and * TorrentsActivity} directly. Task execution, such as loading of more details and updating file priorities, is performed in this activity via
* updating file priorities, is performed in this activity via background methods. * background methods.
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity(resName = "activity_details") @EActivity(R.layout.activity_details)
@OptionsMenu(resName = "activity_details") @OptionsMenu(R.menu.activity_details)
public class DetailsActivity extends Activity implements TorrentTasksExecutor, RefreshableActivity { public class DetailsActivity extends ActionBarActivity implements TorrentTasksExecutor, RefreshableActivity {
@Extra @Extra
@InstanceState @InstanceState
@ -103,10 +103,11 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
private IDaemonAdapter currentConnection = null; private IDaemonAdapter currentConnection = null;
private PullToRefreshAttacher pullToRefreshAttacher = null;
// Details view components // Details view components
@FragmentById(resName = "torrentdetails_fragment") @ViewById
protected Toolbar selectionToolbar;
@FragmentById(R.id.torrentdetails_fragment)
protected DetailsFragment fragmentDetails; protected DetailsFragment fragmentDetails;
@Override @Override
@ -114,7 +115,6 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
// Set the theme according to the user preference // Set the theme according to the user preference
if (SystemSettings_.getInstance_(this).useDarkTheme()) { if (SystemSettings_.getInstance_(this).useDarkTheme()) {
setTheme(R.style.TransdroidTheme_Dark); setTheme(R.style.TransdroidTheme_Dark);
getActionBar().setIcon(R.drawable.ic_activity_torrents);
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@ -129,8 +129,9 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
} }
// Simple action bar with up, torrent name as title and refresh button // Simple action bar with up, torrent name as title and refresh button
getActionBar().setDisplayHomeAsUpEnabled(true); setSupportActionBar(selectionToolbar);
getActionBar().setTitle(NavigationHelper.buildCondensedFontString(torrent.getName())); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(torrent.getName()));
// Connect to the last used server // Connect to the last used server
ServerSetting lastUsed = applicationSettings.getLastUsedServer(); ServerSetting lastUsed = applicationSettings.getLastUsedServer();
@ -143,41 +144,13 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
} }
@Override
protected void onDestroy() {
Crouton.cancelAllCroutons();
super.onDestroy();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@OptionsItem(android.R.id.home) @OptionsItem(android.R.id.home)
protected void navigateUp() { protected void navigateUp() {
TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start(); TorrentsActivity_.intent(this).flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
} }
/** @OptionsItem(R.id.action_refresh)
* Attaches some view (perhaps contained in a fragment) to this activity's pull to refresh support
* @param view The view to attach
*/
@Override
public void addRefreshableView(View view) {
if (pullToRefreshAttacher == null) {
// Still need to initialise the PullToRefreshAttacher
Options options = new PullToRefreshAttacher.Options();
options.headerTransformer = new NoProgressHeaderTransformer();
pullToRefreshAttacher = PullToRefreshAttacher.get(this, options);
}
pullToRefreshAttacher.addRefreshableView(view, new OnRefreshListener() {
@Override
public void onRefreshStarted(View view) {
// Just refresh the full screen, now that the user has pulled to refresh
pullToRefreshAttacher.setRefreshComplete();
refreshScreen();
}
});
}
@OptionsItem(resName = "action_refresh")
public void refreshScreen() { public void refreshScreen() {
fragmentDetails.updateIsLoading(true, null); fragmentDetails.updateIsLoading(true, null);
refreshTorrent(); refreshTorrent();
@ -189,8 +162,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
protected void refreshTorrent() { protected void refreshTorrent() {
DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log); DaemonTaskResult result = RetrieveTask.create(currentConnection).execute(log);
if (result instanceof RetrieveTaskSuccessResult) { if (result instanceof RetrieveTaskSuccessResult) {
onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), onTorrentsRetrieved(((RetrieveTaskSuccessResult) result).getTorrents(), ((RetrieveTaskSuccessResult) result).getLabels());
((RetrieveTaskSuccessResult) result).getLabels());
} else { } else {
onCommunicationError((DaemonTaskFailureResult) result, true); onCommunicationError((DaemonTaskFailureResult) result, true);
} }
@ -198,8 +170,9 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
public void refreshTorrentDetails(Torrent torrent) { public void refreshTorrentDetails(Torrent torrent) {
if (!Daemon.supportsFineDetails(torrent.getDaemon())) if (!Daemon.supportsFineDetails(torrent.getDaemon())) {
return; return;
}
DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log); DaemonTaskResult result = GetTorrentDetailsTask.create(currentConnection, torrent).execute(log);
if (result instanceof GetTorrentDetailsTaskSuccessResult) { if (result instanceof GetTorrentDetailsTaskSuccessResult) {
onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails()); onTorrentDetailsRetrieved(torrent, ((GetTorrentDetailsTaskSuccessResult) result).getTorrentDetails());
@ -210,8 +183,9 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
public void refreshTorrentFiles(Torrent torrent) { public void refreshTorrentFiles(Torrent torrent) {
if (!Daemon.supportsFileListing(torrent.getDaemon())) if (!Daemon.supportsFileListing(torrent.getDaemon())) {
return; return;
}
DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log); DaemonTaskResult result = GetFileListTask.create(currentConnection, torrent).execute(log);
if (result instanceof GetFileListTaskSuccessResult) { if (result instanceof GetFileListTaskSuccessResult) {
onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles()); onTorrentFilesRetrieved(torrent, ((GetFileListTaskSuccessResult) result).getFiles());
@ -274,8 +248,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log); DaemonTaskResult result = RemoveTask.create(currentConnection, torrent, withData).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
// Close the details activity (as the torrent is now removed) // Close the details activity (as the torrent is now removed)
closeActivity(getString(withData ? R.string.result_removed_with_data : R.string.result_removed, closeActivity(getString(withData ? R.string.result_removed_with_data : R.string.result_removed, torrent.getName()));
torrent.getName()));
} else { } else {
onCommunicationError((DaemonTaskFailureResult) result, false); onCommunicationError((DaemonTaskFailureResult) result, false);
} }
@ -283,19 +256,18 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@UiThread @UiThread
protected void closeActivity(String closeText) { protected void closeActivity(String closeText) {
setResult(RESULT_OK, setResult(RESULT_OK, new Intent().putExtra("torrent_removed", true).putExtra("affected_torrent", torrent));
new Intent().putExtra("torrent_removed", true).putExtra("affected_torrent", torrent));
finish(); finish();
if (closeText != null) if (closeText != null) {
Toast.makeText(this, closeText, Toast.LENGTH_LONG).show(); SnackbarManager.show(Snackbar.with(this).text(closeText));
}
} }
@Background @Background
@Override @Override
public void updateLabel(Torrent torrent, String newLabel) { public void updateLabel(Torrent torrent, String newLabel) {
torrent.mimicNewLabel(newLabel); torrent.mimicNewLabel(newLabel);
DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel) DaemonTaskResult result = SetLabelTask.create(currentConnection, torrent, newLabel == null ? "" : newLabel).execute(log);
.execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_labelset, newLabel)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_labelset, newLabel));
} else { } else {
@ -309,8 +281,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
torrent.mimicCheckingStatus(); torrent.mimicCheckingStatus();
DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log); DaemonTaskResult result = ForceRecheckTask.create(currentConnection, torrent).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_recheckedstarted, torrent.getName()));
getString(R.string.result_recheckedstarted, torrent.getName()));
} else { } else {
onCommunicationError((DaemonTaskFailureResult) result, false); onCommunicationError((DaemonTaskFailureResult) result, false);
} }
@ -341,8 +312,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@Background @Background
@Override @Override
public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) { public void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority) {
DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, DaemonTaskResult result = SetFilePriorityTask.create(currentConnection, torrent, priority, new ArrayList<>(files)).execute(log);
new ArrayList<TorrentFile>(files)).execute(log);
if (result instanceof DaemonTaskSuccessResult) { if (result instanceof DaemonTaskSuccessResult) {
onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset)); onTaskSucceeded((DaemonTaskSuccessResult) result, getString(R.string.result_priotitiesset));
} else { } else {
@ -353,12 +323,11 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@UiThread @UiThread
protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) { protected void onTaskSucceeded(DaemonTaskSuccessResult result, String successMessage) {
// Set the activity result so the calling activity knows it needs to update its view // Set the activity result so the calling activity knows it needs to update its view
setResult(RESULT_OK, setResult(RESULT_OK, new Intent().putExtra("torrent_updated", true).putExtra("affected_torrent", torrent));
new Intent().putExtra("torrent_updated", true).putExtra("affected_torrent", torrent));
// Refresh the screen as well // Refresh the screen as well
refreshTorrent(); refreshTorrent();
refreshTorrentDetails(torrent); refreshTorrentDetails(torrent);
Crouton.showText(this, successMessage, NavigationHelper.CROUTON_INFO_STYLE); SnackbarManager.show(Snackbar.with(this).text(successMessage));
} }
@UiThread @UiThread
@ -370,7 +339,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
@UiThread @UiThread
protected void onTorrentFilesRetrieved(Torrent torrent, List<TorrentFile> torrentFiles) { protected void onTorrentFilesRetrieved(Torrent torrent, List<TorrentFile> torrentFiles) {
// Update the details fragment with the newly retrieved list of files // Update the details fragment with the newly retrieved list of files
fragmentDetails.updateTorrentFiles(torrent, new ArrayList<TorrentFile>(torrentFiles)); fragmentDetails.updateTorrentFiles(torrent, new ArrayList<>(torrentFiles));
} }
@UiThread @UiThread
@ -378,8 +347,8 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
log.i(this, result.getException().toString()); log.i(this, result.getException().toString());
String error = getString(LocalTorrent.getResourceForDaemonException(result.getException())); String error = getString(LocalTorrent.getResourceForDaemonException(result.getException()));
fragmentDetails.updateIsLoading(false, isCritical ? error : null); fragmentDetails.updateIsLoading(false, isCritical ? error : null);
Crouton.showText(this, getString(LocalTorrent.getResourceForDaemonException(result.getException())), SnackbarManager.show(Snackbar.with(this).text(getString(LocalTorrent.getResourceForDaemonException(result.getException())))
NavigationHelper.CROUTON_ERROR_STYLE); .colorResource(R.color.red));
} }
@UiThread @UiThread
@ -387,8 +356,7 @@ public class DetailsActivity extends Activity implements TorrentTasksExecutor, R
// Update the details fragment accordingly // Update the details fragment accordingly
fragmentDetails.updateIsLoading(false, null); fragmentDetails.updateIsLoading(false, null);
fragmentDetails.perhapsUpdateTorrent(torrents); fragmentDetails.perhapsUpdateTorrent(torrents);
fragmentDetails.updateLabels(Label.convertToNavigationLabels(labels, fragmentDetails.updateLabels(Label.convertToNavigationLabels(labels, getResources().getString(R.string.labels_unlabeled)));
getResources().getString(R.string.labels_unlabeled)));
} }
} }

351
app/src/main/java/org/transdroid/core/gui/DetailsFragment.java

@ -16,10 +16,28 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.ArrayList; import android.annotation.SuppressLint;
import java.util.Arrays; import android.app.Fragment;
import java.util.Collections; import android.content.ClipData;
import java.util.List; import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.ActionMenuView;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import com.nispok.snackbar.enums.SnackbarType;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click; import org.androidannotations.annotations.Click;
@ -27,13 +45,17 @@ import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.InstanceState; import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.ItemClick; import org.androidannotations.annotations.ItemClick;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.*; 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.DetailsAdapter;
import org.transdroid.core.gui.lists.SimpleListItemAdapter; import org.transdroid.core.gui.lists.SimpleListItemAdapter;
import org.transdroid.core.gui.navigation.*; import org.transdroid.core.gui.navigation.Label;
import org.transdroid.core.gui.navigation.NavigationHelper_;
import org.transdroid.core.gui.navigation.RefreshableActivity;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
import org.transdroid.core.gui.navigation.SetLabelDialog;
import org.transdroid.core.gui.navigation.SetLabelDialog.OnLabelPickedListener; import org.transdroid.core.gui.navigation.SetLabelDialog.OnLabelPickedListener;
import org.transdroid.core.gui.navigation.SetStorageLocationDialog; import org.transdroid.core.gui.navigation.SetStorageLocationDialog;
import org.transdroid.core.gui.navigation.SetStorageLocationDialog.OnStorageLocationUpdatedListener; import org.transdroid.core.gui.navigation.SetStorageLocationDialog.OnStorageLocationUpdatedListener;
@ -45,33 +67,19 @@ import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentDetails; import org.transdroid.daemon.TorrentDetails;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import android.annotation.SuppressLint; import java.util.ArrayList;
import android.app.Fragment; import java.util.Arrays;
import android.content.ClipData; import java.util.Collections;
import android.content.ClipboardManager; import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
* Fragment that shows detailed statistics about some torrent. These come from some already fetched {@link Torrent} * Fragment that shows detailed statistics about some torrent. These come from some already fetched {@link Torrent} object, but it also retrieves
* object, but it also retrieves further detailed statistics. The actual execution of tasks is performed by the activity * further detailed statistics. The actual execution of tasks is performed by the activity that contains this fragment, as per the {@link
* that contains this fragment, as per the {@link TorrentTasksExecutor} interface. * TorrentTasksExecutor} interface.
* @author Eric Kok * @author Eric Kok
*/ */
@EFragment(resName = "fragment_details") @EFragment(R.layout.fragment_details)
@OptionsMenu(resName = "fragment_details") public class DetailsFragment extends Fragment implements OnTrackersUpdatedListener, OnLabelPickedListener, OnStorageLocationUpdatedListener {
public class DetailsFragment extends Fragment implements OnTrackersUpdatedListener, OnLabelPickedListener,
OnStorageLocationUpdatedListener {
// Local data // Local data
@InstanceState @InstanceState
@ -91,9 +99,15 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
private ServerSetting currentServerSettings = null; private ServerSetting currentServerSettings = null;
// Views // Views
@ViewById(resName = "details_container") @ViewById
protected View detailsContainer; protected View detailsContainer;
@ViewById(resName = "details_list") @ViewById(R.id.details_menu)
protected ActionMenuView detailsMenu;
@ViewById(R.id.contextual_menu)
protected ActionMenuView contextualMenu;
@ViewById
protected SwipeRefreshLayout swipeRefreshLayout;
@ViewById
protected ListView detailsList; protected ListView detailsList;
@ViewById @ViewById
protected TextView emptyText, errorText; protected TextView emptyText, errorText;
@ -103,6 +117,9 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
@AfterViews @AfterViews
protected void init() { 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 // 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 // line to separate the lists visually
if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) { if (!NavigationHelper_.getInstance_(getActivity()).isSmallScreen()) {
@ -113,22 +130,33 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
} }
} }
createMenuOptions();
// Set up details adapter (itself containing the actual lists to show), which allows multi-select and fast // Set up details adapter (itself containing the actual lists to show), which allows multi-select and fast
// scrolling // scrolling
detailsList.setAdapter(new DetailsAdapter(getActivity())); detailsList.setAdapter(new DetailsAdapter(getActivity()));
detailsList.setMultiChoiceModeListener(onDetailsSelected); detailsList.setMultiChoiceModeListener(onDetailsSelected);
detailsList.setFastScrollEnabled(true); detailsList.setFastScrollEnabled(true);
if (getActivity() != null && getActivity() instanceof RefreshableActivity) { if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
((RefreshableActivity) getActivity()).addRefreshableView(detailsList); 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.) // Restore the fragment state (on orientation changes et al.)
if (torrent != null) if (torrent != null) {
updateTorrent(torrent); updateTorrent(torrent);
if (torrentDetails != null) }
if (torrentDetails != null) {
updateTorrentDetails(torrent, torrentDetails); updateTorrentDetails(torrent, torrentDetails);
if (torrentFiles != null) }
if (torrentFiles != null) {
updateTorrentFiles(torrent, torrentFiles); updateTorrentFiles(torrent, torrentFiles);
}
} }
@ -151,7 +179,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
errorText.setVisibility(View.GONE); errorText.setVisibility(View.GONE);
loadingProgress.setVisibility(View.GONE); loadingProgress.setVisibility(View.GONE);
// Also update the available actions in the action bar // Also update the available actions in the action bar
getActivity().invalidateOptionsMenu(); updateMenuOptions();
// Refresh the detailed statistics (errors) and list of files // Refresh the detailed statistics (errors) and list of files
torrentDetails = null; torrentDetails = null;
torrentFiles = null; torrentFiles = null;
@ -166,13 +194,14 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
*/ */
public void updateTorrentDetails(Torrent checkTorrent, TorrentDetails newTorrentDetails) { public void updateTorrentDetails(Torrent checkTorrent, TorrentDetails newTorrentDetails) {
// Check if these are actually the details of the torrent we are now showing // Check if these are actually the details of the torrent we are now showing
if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) {
return; return;
}
this.torrentDetails = newTorrentDetails; this.torrentDetails = newTorrentDetails;
((DetailsAdapter) detailsList.getAdapter()).updateTrackers( ((DetailsAdapter) detailsList.getAdapter())
SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getTrackers())); .updateTrackers(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getTrackers()));
((DetailsAdapter) detailsList.getAdapter()).updateErrors( ((DetailsAdapter) detailsList.getAdapter())
SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getErrors())); .updateErrors(SimpleListItemAdapter.SimpleStringItem.wrapStringsList(newTorrentDetails.getErrors()));
} }
/** /**
@ -182,22 +211,23 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
*/ */
public void updateTorrentFiles(Torrent checkTorrent, ArrayList<TorrentFile> newTorrentFiles) { public void updateTorrentFiles(Torrent checkTorrent, ArrayList<TorrentFile> newTorrentFiles) {
// Check if these are actually the details of the torrent we are now showing // Check if these are actually the details of the torrent we are now showing
if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) if (torrentId == null || !torrentId.equals(checkTorrent.getUniqueID())) {
return; return;
}
Collections.sort(newTorrentFiles); Collections.sort(newTorrentFiles);
this.torrentFiles = newTorrentFiles; this.torrentFiles = newTorrentFiles;
((DetailsAdapter) detailsList.getAdapter()).updateTorrentFiles(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 * Can be called if some outside activity returned new torrents, so we can perhaps piggyback on this by update our data as well.
* data as well.
* @param torrents The last of retrieved torrents * @param torrents The last of retrieved torrents
*/ */
public void perhapsUpdateTorrent(List<Torrent> torrents) { public void perhapsUpdateTorrent(List<Torrent> torrents) {
// Only try to update if we actually were showing a torrent // Only try to update if we actually were showing a torrent
if (this.torrentId == null || torrents == null) if (this.torrentId == null || torrents == null) {
return; return;
}
for (Torrent newTorrent : torrents) { for (Torrent newTorrent : torrents) {
if (newTorrent.getUniqueID().equals(torrentId)) { if (newTorrent.getUniqueID().equals(torrentId)) {
// Found, so we can update our data as well // Found, so we can update our data as well
@ -208,12 +238,12 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
} }
/** /**
* Updates the locally maintained list of labels that are active on the server. Used in the label picking dialog and * 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
* should be updated every time after the list of torrents was retrieved to keep it updated. * after the list of torrents was retrieved to keep it updated.
* @param currentLabels The list of known server labels * @param currentLabels The list of known server labels
*/ */
public void updateLabels(ArrayList<Label> currentLabels) { public void updateLabels(ArrayList<Label> currentLabels) {
this.currentLabels = currentLabels == null ? null : new ArrayList<Label>(currentLabels); this.currentLabels = currentLabels == null ? null : new ArrayList<>(currentLabels);
} }
/** /**
@ -239,133 +269,178 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
this.isLoadingTorrent = isLoading; this.isLoadingTorrent = isLoading;
this.hasCriticalError = connectionErrorMessage != null; this.hasCriticalError = connectionErrorMessage != null;
errorText.setText(connectionErrorMessage); errorText.setText(connectionErrorMessage);
if (isLoading || hasCriticalError) if (isLoading || hasCriticalError) {
clear(); clear();
} }
}
@ItemClick(resName = "details_list") @ItemClick(resName = "details_list")
protected void detailsListClicked(int position) { protected void detailsListClicked(int position) {
detailsList.setItemChecked(position, false); detailsList.setItemChecked(position, false);
} }
public void createMenuOptions() {
getActivity().getMenuInflater().inflate(R.menu.fragment_details, detailsMenu.getMenu());
detailsMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
@Override @Override
public void onPrepareOptionsMenu(Menu menu) { public boolean onMenuItemClick(MenuItem menuItem) {
super.onPrepareOptionsMenu(menu); 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_stop:
stopTorrent();
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:
removeTorrent();
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) { if (torrent == null) {
menu.findItem(R.id.action_resume).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_resume).setVisible(false);
menu.findItem(R.id.action_pause).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_pause).setVisible(false);
menu.findItem(R.id.action_start).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_start).setVisible(false);
menu.findItem(R.id.action_start_direct).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_start_direct).setVisible(false);
menu.findItem(R.id.action_stop).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_stop).setVisible(false);
menu.findItem(R.id.action_remove).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_remove).setVisible(false);
menu.findItem(R.id.action_setlabel).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(false);
menu.findItem(R.id.action_forcerecheck).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(false);
menu.findItem(R.id.action_updatetrackers).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(false);
menu.findItem(R.id.action_changelocation).setVisible(false); detailsMenu.getMenu().findItem(R.id.action_changelocation).setVisible(false);
return; return;
} }
// Update action availability // Update action availability
boolean startStop = Daemon.supportsStoppingStarting(torrent.getDaemon()); boolean startStop = Daemon.supportsStoppingStarting(torrent.getDaemon());
menu.findItem(R.id.action_resume).setVisible(torrent.canResume()); detailsMenu.getMenu().findItem(R.id.action_resume).setVisible(torrent.canResume());
menu.findItem(R.id.action_pause).setVisible(torrent.canPause()); detailsMenu.getMenu().findItem(R.id.action_pause).setVisible(torrent.canPause());
boolean forcedStart = Daemon.supportsForcedStarting(torrent.getDaemon()); boolean forcedStart = Daemon.supportsForcedStarting(torrent.getDaemon());
menu.findItem(R.id.action_start).setVisible(startStop && forcedStart && torrent.canStart()); detailsMenu.getMenu().findItem(R.id.action_start).setVisible(startStop && forcedStart && torrent.canStart());
menu.findItem(R.id.action_start_direct).setVisible(startStop && !forcedStart && torrent.canStart()); detailsMenu.getMenu().findItem(R.id.action_start_direct).setVisible(startStop && !forcedStart && torrent.canStart());
menu.findItem(R.id.action_stop).setVisible(startStop && torrent.canStop()); detailsMenu.getMenu().findItem(R.id.action_stop).setVisible(startStop && torrent.canStop());
menu.findItem(R.id.action_remove).setVisible(true); detailsMenu.getMenu().findItem(R.id.action_remove).setVisible(true);
boolean setLabel = Daemon.supportsSetLabel(torrent.getDaemon()); boolean setLabel = Daemon.supportsSetLabel(torrent.getDaemon());
menu.findItem(R.id.action_setlabel).setVisible(setLabel); detailsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(setLabel);
boolean forceRecheck = Daemon.supportsForceRecheck(torrent.getDaemon()); boolean forceRecheck = Daemon.supportsForceRecheck(torrent.getDaemon());
menu.findItem(R.id.action_forcerecheck).setVisible(forceRecheck); detailsMenu.getMenu().findItem(R.id.action_forcerecheck).setVisible(forceRecheck);
boolean setTrackers = Daemon.supportsSetTrackers(torrent.getDaemon()); boolean setTrackers = Daemon.supportsSetTrackers(torrent.getDaemon());
menu.findItem(R.id.action_updatetrackers).setVisible(setTrackers); detailsMenu.getMenu().findItem(R.id.action_updatetrackers).setVisible(setTrackers);
boolean setLocation = Daemon.supportsSetDownloadLocation(torrent.getDaemon()); boolean setLocation = Daemon.supportsSetDownloadLocation(torrent.getDaemon());
menu.findItem(R.id.action_changelocation).setVisible(setLocation); detailsMenu.getMenu().findItem(R.id.action_changelocation).setVisible(setLocation);
} }
@OptionsItem(resName = "action_resume") @OptionsItem(R.id.action_resume)
protected void resumeTorrent() { protected void resumeTorrent() {
getTasksExecutor().resumeTorrent(torrent); getTasksExecutor().resumeTorrent(torrent);
} }
@OptionsItem(resName = "action_pause") @OptionsItem(R.id.action_pause)
protected void pauseTorrent() { protected void pauseTorrent() {
getTasksExecutor().pauseTorrent(torrent); getTasksExecutor().pauseTorrent(torrent);
} }
@OptionsItem(resName = "action_start_direct") @OptionsItem(R.id.action_start_direct)
protected void startTorrentDirect() { protected void startTorrentDirect() {
getTasksExecutor().startTorrent(torrent, false); getTasksExecutor().startTorrent(torrent, false);
} }
@OptionsItem(resName = "action_start_default") @OptionsItem(R.id.action_start_default)
protected void startTorrentDefault() { protected void startTorrentDefault() {
getTasksExecutor().startTorrent(torrent, false); getTasksExecutor().startTorrent(torrent, false);
} }
@OptionsItem(resName = "action_start_forced") @OptionsItem(R.id.action_start_forced)
protected void startTorrentForced() { protected void startTorrentForced() {
getTasksExecutor().startTorrent(torrent, true); getTasksExecutor().startTorrent(torrent, true);
} }
@OptionsItem(resName = "action_stop") @OptionsItem(R.id.action_stop)
protected void stopTorrent() { protected void stopTorrent() {
getTasksExecutor().stopTorrent(torrent); getTasksExecutor().stopTorrent(torrent);
} }
@OptionsItem(resName = "action_remove") @OptionsItem(resName = "action_remove")
protected void removeTorrentDefault() { protected void removeTorrent() {
ConfirmRemoveDialog.startConfirmRemove((TorrentsActivity) getActivity(), Arrays.asList(torrent)); ConfirmRemoveDialog.startConfirmRemove((TorrentsActivity) getActivity(), Arrays.asList(torrent));
} }
@OptionsItem(resName = "action_setlabel") @OptionsItem(R.id.action_setlabel)
protected void setLabel() { protected void setLabel() {
if (currentLabels != null) if (currentLabels != null) {
new SetLabelDialog().setOnLabelPickedListener(this).setCurrentLabels(currentLabels) SetLabelDialog.show(getActivity(), this, currentLabels);
.show(getFragmentManager(), "SetLabelDialog"); }
} }
@OptionsItem(resName = "action_forcerecheck") @OptionsItem(R.id.action_forcerecheck)
protected void setForceRecheck() { protected void setForceRecheck() {
getTasksExecutor().forceRecheckTorrent(torrent); getTasksExecutor().forceRecheckTorrent(torrent);
} }
@OptionsItem(resName = "action_updatetrackers") @OptionsItem(R.id.action_updatetrackers)
protected void updateTrackers() { protected void updateTrackers() {
if (torrentDetails == null) { if (torrentDetails == null) {
Crouton.showText(getActivity(), R.string.error_stillloadingdetails, NavigationHelper.CROUTON_INFO_STYLE); SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_stillloadingdetails));
return; return;
} }
new SetTrackersDialog().setOnTrackersUpdated(this).setCurrentTrackers(torrentDetails.getTrackersText()) SetTrackersDialog.show(getActivity(), this, torrentDetails.getTrackersText());
.show(getFragmentManager(), "SetTrackersDialog");
} }
@OptionsItem(resName = "action_changelocation") @OptionsItem(R.id.action_changelocation)
protected void changeStorageLocation() { protected void changeStorageLocation() {
new SetStorageLocationDialog().setOnStorageLocationUpdated(this).setCurrentLocation(torrent.getLocationDir()) SetStorageLocationDialog.show(getActivity(), this, torrent.getLocationDir());
.show(getFragmentManager(), "SetStorageLocationDialog");
} }
@Override @Override
public void onLabelPicked(String newLabel) { public void onLabelPicked(String newLabel) {
if (torrent == null) if (torrent == null) {
return; return;
}
getTasksExecutor().updateLabel(torrent, newLabel); getTasksExecutor().updateLabel(torrent, newLabel);
} }
@Override @Override
public void onTrackersUpdated(List<String> updatedTrackers) { public void onTrackersUpdated(List<String> updatedTrackers) {
if (torrent == null) if (torrent == null) {
return; return;
}
getTasksExecutor().updateTrackers(torrent, updatedTrackers); getTasksExecutor().updateTrackers(torrent, updatedTrackers);
} }
@Override @Override
public void onStorageLocationUpdated(String newLocation) { public void onStorageLocationUpdated(String newLocation) {
if (torrent == null) if (torrent == null) {
return; return;
}
getTasksExecutor().updateLocation(torrent, newLocation); getTasksExecutor().updateLocation(torrent, newLocation);
} }
@ -390,10 +465,21 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
SelectionManagerMode selectionManagerMode; SelectionManagerMode selectionManagerMode;
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
// Show contextual action bar to start/stop/remove/etc. torrents in batch mode // Show contextual action bar to start/stop/remove/etc. torrents in batch mode
mode.getMenuInflater().inflate(R.menu.fragment_details_cab, menu); detailsMenu.setEnabled(false);
selectionManagerMode = new SelectionManagerMode(detailsList, R.plurals.navigation_filesselected); 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 = ((ActionBarActivity) 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.setOnlyCheckClass(TorrentFile.class);
selectionManagerMode.onCreateActionMode(mode, menu); selectionManagerMode.onCreateActionMode(mode, menu);
return true; return true;
@ -401,21 +487,20 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
@Override @Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
selectionManagerMode.onPrepareActionMode(mode, menu);
// Pause autorefresh // Pause autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) { if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
((TorrentsActivity) getActivity()).stopRefresh = true; ((TorrentsActivity) getActivity()).stopRefresh = true;
((TorrentsActivity) getActivity()).stopAutoRefresh(); ((TorrentsActivity) getActivity()).stopAutoRefresh();
} }
boolean filePaths = boolean filePaths = currentServerSettings != null && Daemon.supportsFilePaths(currentServerSettings.getType());
currentServerSettings != null && Daemon.supportsFilePaths(currentServerSettings.getType()); contextualMenu.getMenu().findItem(R.id.action_download).setVisible(filePaths);
menu.findItem(R.id.action_download).setVisible(filePaths); boolean filePriorities = currentServerSettings != null && Daemon.supportsFilePrioritySetting(currentServerSettings.getType());
boolean filePriorities = currentServerSettings != null && contextualMenu.getMenu().findItem(R.id.action_priority_off).setVisible(filePriorities);
Daemon.supportsFilePrioritySetting(currentServerSettings.getType()); contextualMenu.getMenu().findItem(R.id.action_priority_low).setVisible(filePriorities);
menu.findItem(R.id.action_priority_off).setVisible(filePriorities); contextualMenu.getMenu().findItem(R.id.action_priority_normal).setVisible(filePriorities);
menu.findItem(R.id.action_priority_low).setVisible(filePriorities); contextualMenu.getMenu().findItem(R.id.action_priority_high).setVisible(filePriorities);
menu.findItem(R.id.action_priority_normal).setVisible(filePriorities); return true;
menu.findItem(R.id.action_priority_high).setVisible(filePriorities);
return selectionManagerMode.onPrepareActionMode(mode, menu);
} }
@SuppressLint("SdCardPath") @SuppressLint("SdCardPath")
@ -423,35 +508,36 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents // Get checked torrents
List<TorrentFile> checked = new ArrayList<TorrentFile>(); List<TorrentFile> checked = new ArrayList<>();
for (int i = 0; i < detailsList.getCheckedItemPositions().size(); i++) { for (int i = 0; i < detailsList.getCheckedItemPositions().size(); i++) {
if (detailsList.getCheckedItemPositions().valueAt(i) if (detailsList.getCheckedItemPositions().valueAt(i) && i < detailsList.getAdapter().getCount() &&
&& i < detailsList.getAdapter().getCount() detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)) instanceof TorrentFile) {
&& detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)) instanceof TorrentFile) checked.add((TorrentFile) detailsList.getAdapter().getItem(detailsList.getCheckedItemPositions().keyAt(i)));
checked.add((TorrentFile) detailsList.getAdapter().getItem( }
detailsList.getCheckedItemPositions().keyAt(i)));
} }
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.action_download) { if (itemId == R.id.action_download) {
if (checked.size() < 1 || currentServerSettings == null) if (checked.size() < 1 || currentServerSettings == null) {
return true; return true;
}
String urlBase = currentServerSettings.getFtpUrl(); String urlBase = currentServerSettings.getFtpUrl();
if (urlBase == null || urlBase.equals("")) if (urlBase == null || urlBase.equals("")) {
urlBase = "ftp://" + currentServerSettings.getAddress(); urlBase = "ftp://" + currentServerSettings.getAddress();
}
// Try using AndFTP intents // Try using AndFTP intents
Intent andftpStart = new Intent(Intent.ACTION_PICK); Intent andftpStart = new Intent(Intent.ACTION_PICK);
andftpStart.setDataAndType(Uri.parse(urlBase), "vnd.android.cursor.dir/lysesoft.andftp.uri"); andftpStart.setDataAndType(Uri.parse(urlBase), "vnd.android.cursor.dir/lysesoft.andftp.uri");
andftpStart.putExtra("command_type", "download"); andftpStart.putExtra("command_type", "download");
andftpStart.putExtra("ftp_pasv", "true"); andftpStart.putExtra("ftp_pasv", "true");
if (Uri.parse(urlBase).getUserInfo() != null) if (Uri.parse(urlBase).getUserInfo() != null) {
andftpStart.putExtra("ftp_username", Uri.parse(urlBase).getUserInfo()); andftpStart.putExtra("ftp_username", Uri.parse(urlBase).getUserInfo());
else } else {
andftpStart.putExtra("ftp_username", currentServerSettings.getUsername()); andftpStart.putExtra("ftp_username", currentServerSettings.getUsername());
if (currentServerSettings.getFtpPassword() != null }
&& !currentServerSettings.getFtpPassword().equals("")) { if (currentServerSettings.getFtpPassword() != null && !currentServerSettings.getFtpPassword().equals("")) {
andftpStart.putExtra("ftp_password", currentServerSettings.getFtpPassword()); andftpStart.putExtra("ftp_password", currentServerSettings.getFtpPassword());
} else { } else {
andftpStart.putExtra("ftp_password", currentServerSettings.getPassword()); andftpStart.putExtra("ftp_password", currentServerSettings.getPassword());
@ -465,8 +551,9 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
// If the file is directly in the root, AndFTP fails if we supply the proper path (like // If the file is directly in the root, AndFTP fails if we supply the proper path (like
// /file.pdf) // /file.pdf)
// Work around this bug by removing the leading / if no further directories are used in the path // Work around this bug by removing the leading / if no further directories are used in the path
if (file.startsWith("/") && file.indexOf("/", 1) < 0) if (file.startsWith("/") && file.indexOf("/", 1) < 0) {
file = file.substring(1); file = file.substring(1);
}
andftpStart.putExtra("remote_file" + (f + 1), file); andftpStart.putExtra("remote_file" + (f + 1), file);
} }
} }
@ -478,8 +565,7 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
// Try using a VIEW intent given an ftp:// scheme URI // Try using a VIEW intent given an ftp:// scheme URI
String url = urlBase + checked.get(0).getFullPath(); String url = urlBase + checked.get(0).getFullPath();
Intent simpleStart = new Intent(Intent.ACTION_VIEW, Uri.parse(url)) Intent simpleStart = new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (simpleStart.resolveActivity(getActivity().getPackageManager()) != null) { if (simpleStart.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(simpleStart); startActivity(simpleStart);
mode.finish(); mode.finish();
@ -487,8 +573,8 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
} }
// No app is available that can handle FTP downloads // No app is available that can handle FTP downloads
Crouton.showText(getActivity(), getString(R.string.error_noftpapp, url), SnackbarManager.show(Snackbar.with(getActivity()).text(getString(R.string.error_noftpapp, url)).type(SnackbarType.MULTI_LINE)
NavigationHelper.CROUTON_ERROR_STYLE); .colorResource(R.color.red));
mode.finish(); mode.finish();
return true; return true;
@ -496,24 +582,27 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
StringBuilder names = new StringBuilder(); StringBuilder names = new StringBuilder();
for (int f = 0; f < checked.size(); f++) { for (int f = 0; f < checked.size(); f++) {
if (f != 0) if (f != 0) {
names.append("\n"); names.append("\n");
}
names.append(checked.get(f).getName()); names.append(checked.get(f).getName());
} }
ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService( ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString())); clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
mode.finish(); mode.finish();
return true; return true;
} else { } else {
Priority priority = Priority.Off; Priority priority = Priority.Off;
if (itemId == R.id.action_priority_low) if (itemId == R.id.action_priority_low) {
priority = Priority.Low; priority = Priority.Low;
if (itemId == R.id.action_priority_normal) }
if (itemId == R.id.action_priority_normal) {
priority = Priority.Normal; priority = Priority.Normal;
if (itemId == R.id.action_priority_high) }
if (itemId == R.id.action_priority_high) {
priority = Priority.High; priority = Priority.High;
}
getTasksExecutor().updatePriority(torrent, checked, priority); getTasksExecutor().updatePriority(torrent, checked, priority);
mode.finish(); mode.finish();
return true; return true;
@ -533,6 +622,8 @@ public class DetailsFragment extends Fragment implements OnTrackersUpdatedListen
((TorrentsActivity) getActivity()).startAutoRefresh(); ((TorrentsActivity) getActivity()).startAutoRefresh();
} }
selectionManagerMode.onDestroyActionMode(mode); selectionManagerMode.onDestroyActionMode(mode);
contextualMenu.setVisibility(View.GONE);
detailsMenu.setEnabled(true);
} }
}; };

59
app/src/main/java/org/transdroid/core/gui/ServerSelectionView.java

@ -0,0 +1,59 @@
/*
* Copyright 2010-2013 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.core.gui;
import android.content.Context;
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.gui.navigation.NavigationFilter;
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());
}
}

29
app/src/main/java/org/transdroid/core/gui/ServerStatusView.java

@ -16,24 +16,25 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.List; import android.content.Context;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.EViewGroup; import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.navigation.SetTransferRatesDialog; import org.transdroid.core.gui.navigation.SetTransferRatesDialog;
import org.transdroid.core.gui.navigation.SetTransferRatesDialog.OnRatesPickedListener; import org.transdroid.core.gui.navigation.SetTransferRatesDialog.OnRatesPickedListener;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.util.FileSizeConverter; import org.transdroid.daemon.util.FileSizeConverter;
import android.content.Context; import java.util.List;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import de.keyboardsurfer.android.widget.crouton.Crouton;
@EViewGroup(resName = "actionbar_serverstatus") @EViewGroup(R.layout.actionbar_serverstatus)
public class ServerStatusView extends RelativeLayout implements OnRatesPickedListener { public class ServerStatusView extends RelativeLayout implements OnRatesPickedListener {
@ViewById @ViewById
@ -57,7 +58,7 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
* @param dormantAsInactive Whether to treat dormant (0KB/s) torrent as inactive state 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 * @param supportsSetTransferRates Whether the connected torrent client supports setting of max transfer speeds
*/ */
public void update(List<Torrent> torrents, boolean dormantAsInactive, boolean supportsSetTransferRates) { public void updateStatus(List<Torrent> torrents, boolean dormantAsInactive, boolean supportsSetTransferRates) {
if (torrents == null) { if (torrents == null) {
downcountText.setText(null); downcountText.setText(null);
@ -91,14 +92,16 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
upspeedText.setText(FileSizeConverter.getSize(upspeed) + "/s"); upspeedText.setText(FileSizeConverter.getSize(upspeed) + "/s");
downcountSign.setVisibility(View.VISIBLE); downcountSign.setVisibility(View.VISIBLE);
upcountSign.setVisibility(View.VISIBLE); upcountSign.setVisibility(View.VISIBLE);
speedswrapperLayout.setOnClickListener(supportsSetTransferRates ? onStartDownPickerClicked : null); if (supportsSetTransferRates)
speedswrapperLayout.setOnClickListener(onStartDownPickerClicked);
else
speedswrapperLayout.setBackgroundDrawable(null);
} }
private OnClickListener onStartDownPickerClicked = new OnClickListener() { private OnClickListener onStartDownPickerClicked = new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
new SetTransferRatesDialog().setOnRatesPickedListener(ServerStatusView.this).show( SetTransferRatesDialog.show(getContext(), ServerStatusView.this);
activity.getFragmentManager(), "SetTransferRatesDialog");
} }
}; };
@ -114,7 +117,7 @@ public class ServerStatusView extends RelativeLayout implements OnRatesPickedLis
@Override @Override
public void onInvalidNumber() { public void onInvalidNumber() {
Crouton.showText(activity, R.string.error_notanumber, NavigationHelper.CROUTON_ERROR_STYLE); SnackbarManager.show(Snackbar.with(activity).text(R.string.error_notanumber).colorResource(R.color.red));
} }
} }

19
app/src/main/java/org/transdroid/core/gui/TorrentTasksExecutor.java

@ -16,28 +16,41 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.List; import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import java.util.List;
/** /**
* Interface to be implemented by any activity that wants containing fragments to be able to load data and execute * Interface to be implemented by any activity that wants containing fragments to be able to load data and execute commands against a torrent server.
* commands against a torrent server.
* @author Eric Kok * @author Eric Kok
*/ */
public interface TorrentTasksExecutor { 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 forceRecheckTorrent(Torrent torrent); void forceRecheckTorrent(Torrent torrent);
void updateLabel(Torrent torrent, String newLabel); void updateLabel(Torrent torrent, String newLabel);
void updateTrackers(Torrent torrent, List<String> newTrackers); void updateTrackers(Torrent torrent, List<String> 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<TorrentFile> files, Priority priority); void updatePriority(Torrent torrent, List<TorrentFile> files, Priority priority);
} }

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

File diff suppressed because it is too large Load Diff

151
app/src/main/java/org/transdroid/core/gui/TorrentsFragment.java

@ -16,10 +16,22 @@
*/ */
package org.transdroid.core.gui; package org.transdroid.core.gui;
import java.util.ArrayList; import android.app.Fragment;
import java.util.Collections; import android.content.Context;
import java.util.Iterator; import android.support.v4.widget.SwipeRefreshLayout;
import java.util.Locale; import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
@ -31,7 +43,8 @@ import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.ApplicationSettings; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.SystemSettings; import org.transdroid.core.app.settings.SystemSettings;
import org.transdroid.core.gui.lists.*; import org.transdroid.core.gui.lists.TorrentsAdapter;
import org.transdroid.core.gui.lists.TorrentsAdapter_;
import org.transdroid.core.gui.navigation.Label; import org.transdroid.core.gui.navigation.Label;
import org.transdroid.core.gui.navigation.NavigationFilter; import org.transdroid.core.gui.navigation.NavigationFilter;
import org.transdroid.core.gui.navigation.RefreshableActivity; import org.transdroid.core.gui.navigation.RefreshableActivity;
@ -43,23 +56,17 @@ import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentsComparator; import org.transdroid.daemon.TorrentsComparator;
import org.transdroid.daemon.TorrentsSortBy; import org.transdroid.daemon.TorrentsSortBy;
import android.app.Fragment; import java.util.ArrayList;
import android.view.ActionMode; import java.util.Collections;
import android.view.Menu; import java.util.Iterator;
import android.view.MenuItem; import java.util.Locale;
import android.view.View;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
/** /**
* Fragment that shows a list of torrents that are active on the server. It supports sorting and filtering and can show * Fragment that shows a list of torrents that are active on the server. It supports sorting and filtering and can show connection progress and
* connection progress and issues. However, actual task starting and execution and overall navigation elements are part * issues. However, actual task starting and execution and overall navigation elements are part of the containing activity, not this fragment.
* of the containing activity, not this fragment.
* @author Eric Kok * @author Eric Kok
*/ */
@EFragment(resName = "fragment_torrents") @EFragment(R.layout.fragment_torrents)
public class TorrentsFragment extends Fragment implements OnLabelPickedListener { public class TorrentsFragment extends Fragment implements OnLabelPickedListener {
// Local data // Local data
@ -91,7 +98,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
protected Daemon daemonType; protected Daemon daemonType;
// Views // Views
@ViewById(resName = "torrents_list") @ViewById
protected SwipeRefreshLayout swipeRefreshLayout;
@ViewById
protected ListView torrentsList; protected ListView torrentsList;
@ViewById @ViewById
protected TextView emptyText; protected TextView emptyText;
@ -113,11 +122,18 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
torrentsList.setAdapter(TorrentsAdapter_.getInstance_(getActivity())); torrentsList.setAdapter(TorrentsAdapter_.getInstance_(getActivity()));
torrentsList.setMultiChoiceModeListener(onTorrentsSelected); torrentsList.setMultiChoiceModeListener(onTorrentsSelected);
torrentsList.setFastScrollEnabled(true); torrentsList.setFastScrollEnabled(true);
if (torrents != null) if (torrents != null) {
updateTorrents(torrents, currentLabels); updateTorrents(torrents, currentLabels);
}
// Allow pulls on the list view to refresh the torrents // Allow pulls on the list view to refresh the torrents
if (getActivity() != null && getActivity() instanceof RefreshableActivity) { if (getActivity() != null && getActivity() instanceof RefreshableActivity) {
((RefreshableActivity) getActivity()).addRefreshableView(torrentsList); 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))); nosettingsText.setText(getString(R.string.navigation_nosettings, getString(R.string.app_name)));
@ -149,8 +165,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
} }
// In case it was an update, add the updated torrent object // In case it was an update, add the updated torrent object
if (!wasRemoved) if (!wasRemoved) {
this.torrents.add(affected); this.torrents.add(affected);
}
// Now refresh the screen // Now refresh the screen
applyAllFilters(); applyAllFilters();
} }
@ -162,8 +179,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
*/ */
public void clear(boolean clearError, boolean clearFilter) { public void clear(boolean clearError, boolean clearFilter) {
this.torrents = null; this.torrents = null;
if (clearError) if (clearError) {
this.connectionErrorMessage = null; this.connectionErrorMessage = null;
}
if (clearFilter) { if (clearFilter) {
this.currentTextFilter = null; this.currentTextFilter = null;
this.currentNavigationFilter = null; this.currentNavigationFilter = null;
@ -172,8 +190,8 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
/** /**
* Stores the new sort order (for future refreshes) and sorts the current visible list. If the given new sort * Stores the new sort order (for future refreshes) and sorts the current visible list. If the given new sort property equals the existing
* property equals the existing property, the list sort order is reversed instead. * property, the list sort order is reversed instead.
* @param newSortOrder The sort order that the user selected. * @param newSortOrder The sort order that the user selected.
*/ */
public void sortBy(TorrentsSortBy newSortOrder) { public void sortBy(TorrentsSortBy newSortOrder) {
@ -212,26 +230,26 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
// Filter the list of torrents to show according to navigation and text filters // Filter the list of torrents to show according to navigation and text filters
ArrayList<Torrent> filteredTorrents = new ArrayList<Torrent>(torrents); ArrayList<Torrent> filteredTorrents = new ArrayList<>(torrents);
if (currentNavigationFilter != null) { if (currentNavigationFilter != null) {
// Remove torrents that do not match the selected navigation filter // Remove torrents that do not match the selected navigation filter
for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) { for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) {
if (!currentNavigationFilter.matches(torrentIter.next(), systemSettings.treatDormantAsInactive())) if (!currentNavigationFilter.matches(torrentIter.next(), systemSettings.treatDormantAsInactive())) {
torrentIter.remove(); torrentIter.remove();
} }
} }
}
if (currentTextFilter != null) { if (currentTextFilter != null) {
// Remove torrent that do not contain the text filter string // Remove torrents that do not contain the text filter string
for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) { for (Iterator<Torrent> torrentIter = filteredTorrents.iterator(); torrentIter.hasNext(); ) {
if (!torrentIter.next().getName().toLowerCase(Locale.getDefault()) if (!torrentIter.next().getName().toLowerCase(Locale.getDefault()).contains(currentTextFilter.toLowerCase(Locale.getDefault()))) {
.contains(currentTextFilter.toLowerCase(Locale.getDefault())))
torrentIter.remove(); torrentIter.remove();
} }
} }
}
// Sort the list of filtered torrents // Sort the list of filtered torrents
Collections.sort(filteredTorrents, new TorrentsComparator(daemonType, this.currentSortOrder, Collections.sort(filteredTorrents, new TorrentsComparator(daemonType, this.currentSortOrder, this.currentSortDescending));
this.currentSortDescending));
((TorrentsAdapter) torrentsList.getAdapter()).update(filteredTorrents); ((TorrentsAdapter) torrentsList.getAdapter()).update(filteredTorrents);
updateViewVisibility(); updateViewVisibility();
@ -239,13 +257,32 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
private MultiChoiceModeListener onTorrentsSelected = new MultiChoiceModeListener() { private MultiChoiceModeListener onTorrentsSelected = new MultiChoiceModeListener() {
SelectionManagerMode selectionManagerMode; private SelectionManagerMode selectionManagerMode;
private ActionMenuView actionsMenu;
private Toolbar actionsToolbar;
private FloatingActionsMenu addmenuButton;
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(final ActionMode mode, Menu menu) {
// Show contextual action bar to start/stop/remove/etc. torrents in batch mode // Show contextual action bars to start/stop/remove/etc. torrents in batch mode
mode.getMenuInflater().inflate(R.menu.fragment_torrents_cab, menu); if (actionsMenu == null) {
selectionManagerMode = new SelectionManagerMode(torrentsList, R.plurals.navigation_torrentsselected); 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 = ((ActionBarActivity) getActivity()).getSupportActionBar().getThemedContext();
selectionManagerMode = new SelectionManagerMode(themedContext, torrentsList, R.plurals.navigation_torrentsselected);
selectionManagerMode.onCreateActionMode(mode, menu); selectionManagerMode.onCreateActionMode(mode, menu);
return true; return true;
} }
@ -255,9 +292,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
selectionManagerMode.onPrepareActionMode(mode, menu); selectionManagerMode.onPrepareActionMode(mode, menu);
// Hide/show options depending on the type of server we are connected to // Hide/show options depending on the type of server we are connected to
if (daemonType != null) { if (daemonType != null) {
menu.findItem(R.id.action_start).setVisible(Daemon.supportsStoppingStarting(daemonType)); actionsMenu.getMenu().findItem(R.id.action_start).setVisible(Daemon.supportsStoppingStarting(daemonType));
menu.findItem(R.id.action_stop).setVisible(Daemon.supportsStoppingStarting(daemonType)); actionsMenu.getMenu().findItem(R.id.action_stop).setVisible(Daemon.supportsStoppingStarting(daemonType));
menu.findItem(R.id.action_setlabel).setVisible(Daemon.supportsSetLabel(daemonType)); actionsMenu.getMenu().findItem(R.id.action_setlabel).setVisible(Daemon.supportsSetLabel(daemonType));
} }
// Pause autorefresh // Pause autorefresh
if (getActivity() != null && getActivity() instanceof TorrentsActivity) { if (getActivity() != null && getActivity() instanceof TorrentsActivity) {
@ -270,11 +307,11 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents // Get checked torrents
ArrayList<Torrent> checked = new ArrayList<Torrent>(); ArrayList<Torrent> checked = new ArrayList<>();
for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) { for (int i = 0; i < torrentsList.getCheckedItemPositions().size(); i++) {
if (torrentsList.getCheckedItemPositions().valueAt(i) && i < torrentsList.getAdapter().getCount()) if (torrentsList.getCheckedItemPositions().valueAt(i) && i < torrentsList.getAdapter().getCount()) {
checked.add((Torrent) torrentsList.getAdapter().getItem( checked.add((Torrent) torrentsList.getAdapter().getItem(torrentsList.getCheckedItemPositions().keyAt(i)));
torrentsList.getCheckedItemPositions().keyAt(i))); }
} }
int itemId = item.getItemId(); int itemId = item.getItemId();
@ -308,9 +345,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
return true; return true;
} else if (itemId == R.id.action_setlabel) { } else if (itemId == R.id.action_setlabel) {
lastMultiSelectedTorrents = checked; lastMultiSelectedTorrents = checked;
if (currentLabels != null) if (currentLabels != null) {
new SetLabelDialog().setOnLabelPickedListener(TorrentsFragment.this).setCurrentLabels(currentLabels) SetLabelDialog.show(getActivity(), TorrentsFragment.this,currentLabels);
.show(getFragmentManager(), "SetLabelDialog"); }
mode.finish(); mode.finish();
return true; return true;
} else { } else {
@ -331,6 +368,9 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
((TorrentsActivity) getActivity()).startAutoRefresh(); ((TorrentsActivity) getActivity()).startAutoRefresh();
} }
selectionManagerMode.onDestroyActionMode(mode); selectionManagerMode.onDestroyActionMode(mode);
actionsMenu.setVisibility(View.GONE);
actionsToolbar.setEnabled(true);
addmenuButton.setVisibility(View.VISIBLE);
} }
}; };
@ -351,7 +391,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
} }
@ItemClick(resName = "torrents_list") @ItemClick(R.id.torrents_list)
protected void torrentsListClicked(Torrent torrent) { protected void torrentsListClicked(Torrent torrent) {
// Show the torrent details fragment // Show the torrent details fragment
((TorrentsActivity) getActivity()).openDetails(torrent); ((TorrentsActivity) getActivity()).openDetails(torrent);
@ -365,8 +405,8 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
/** /**
* Updates the shown screen depending on whether we have a connection (so torrents can be shown) or not (in case we * 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
* need to show a message suggesting help). This should only ever be called on the UI thread. * 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 * @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) { public void updateConnectionStatus(boolean hasAConnection, Daemon daemonType) {
@ -378,6 +418,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
loadingProgress.setVisibility(View.GONE); loadingProgress.setVisibility(View.GONE);
errorText.setVisibility(View.GONE); errorText.setVisibility(View.GONE);
nosettingsText.setVisibility(View.VISIBLE); nosettingsText.setVisibility(View.VISIBLE);
swipeRefreshLayout.setEnabled(false);
clear(true, true); // Indirectly also calls updateViewVisibility() clear(true, true); // Indirectly also calls updateViewVisibility()
} else { } else {
updateViewVisibility(); updateViewVisibility();
@ -385,8 +426,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
/** /**
* Updates the shown screen depending on whether the torrents are loading. This should only ever be called on the UI * Updates the shown screen depending on whether the torrents are loading. This should only ever be called on the UI thread.
* thread.
* @param isLoading True if the list of torrents is (re)loading, false otherwise * @param isLoading True if the list of torrents is (re)loading, false otherwise
*/ */
public void updateIsLoading(boolean isLoading) { public void updateIsLoading(boolean isLoading) {
@ -399,10 +439,8 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
} }
/** /**
* Updates the shown screen depending on whether a connection error occurred. This should only ever be called on the * Updates the shown screen depending on whether a connection error occurred. This should only ever be called on the UI thread.
* UI thread. * @param connectionErrorMessage The error message from the last failed connection attempt, or null to clear the visible error text
* @param connectionErrorMessage The error message from the last failed connection attempt, or null to clear the
* visible error text
*/ */
public void updateError(String connectionErrorMessage) { public void updateError(String connectionErrorMessage) {
this.connectionErrorMessage = connectionErrorMessage; this.connectionErrorMessage = connectionErrorMessage;
@ -425,6 +463,7 @@ public class TorrentsFragment extends Fragment implements OnLabelPickedListener
torrentsList.setVisibility(!hasError && !isLoading && !isEmpty ? View.VISIBLE : View.GONE); torrentsList.setVisibility(!hasError && !isLoading && !isEmpty ? View.VISIBLE : View.GONE);
loadingProgress.setVisibility(!hasError && isLoading ? View.VISIBLE : View.GONE); loadingProgress.setVisibility(!hasError && isLoading ? View.VISIBLE : View.GONE);
emptyText.setVisibility(!hasError && !isLoading && isEmpty ? View.VISIBLE : View.GONE); emptyText.setVisibility(!hasError && !isLoading && isEmpty ? View.VISIBLE : View.GONE);
swipeRefreshLayout.setEnabled(true);
} }
/** /**

17
app/src/main/java/org/transdroid/core/gui/lists/NoProgressHeaderTransformer.java

@ -1,17 +0,0 @@
package org.transdroid.core.gui.lists;
import org.transdroid.R;
import uk.co.senab.actionbarpulltorefresh.library.DefaultHeaderTransformer;
import android.app.Activity;
import android.view.View;
public class NoProgressHeaderTransformer extends DefaultHeaderTransformer {
@Override
public void onViewCreated(Activity activity, View headerView) {
super.onViewCreated(activity, headerView);
setProgressBarColor(activity.getResources().getColor(R.color.green));
}
}

12
app/src/main/java/org/transdroid/core/gui/lists/SimpleListItemView.java

@ -16,18 +16,19 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import android.content.Context; import android.content.Context;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
/** /**
* View that represents some {@link SimpleListItem} object and simple prints out the text (in proper style) * View that represents some {@link SimpleListItem} object and simple prints out the text (in proper style)
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName="list_item_simple") @EViewGroup(R.layout.list_item_simple)
public class SimpleListItemView extends FrameLayout { public class SimpleListItemView extends FrameLayout {
@ViewById @ViewById
@ -39,8 +40,9 @@ public class SimpleListItemView extends FrameLayout {
public void bind(SimpleListItem filterItem, int autoLinkMask) { public void bind(SimpleListItem filterItem, int autoLinkMask) {
itemText.setText(filterItem.getName()); itemText.setText(filterItem.getName());
if (autoLinkMask > 0) if (autoLinkMask > 0) {
itemText.setAutoLinkMask(autoLinkMask); itemText.setAutoLinkMask(autoLinkMask);
} }
}
} }

43
app/src/main/java/org/transdroid/core/gui/lists/TorrentDetailsView.java

@ -16,13 +16,6 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.util.FileSizeConverter;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
@ -30,16 +23,23 @@ import android.view.View;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.util.FileSizeConverter;
/** /**
* Represents a group of views that show torrent status, sizes, speeds and other details. * Represents a group of views that show torrent status, sizes, speeds and other details.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName="fragment_details_header") @EViewGroup(R.layout.fragment_details_header)
public class TorrentDetailsView extends RelativeLayout { public class TorrentDetailsView extends RelativeLayout {
@ViewById @ViewById
protected TextView labelText, dateaddedText, uploadedText, uploadedunitText, ratioText, upspeedText, seedersText, protected TextView labelText, dateaddedText, uploadedText, uploadedunitText, ratioText, upspeedText, seedersText, downloadedunitText,
downloadedunitText, downloadedText, totalsizeText, downspeedText, leechersText, statusText; downloadedText, totalsizeText, downspeedText, leechersText, statusText;
@ViewById @ViewById
protected TorrentStatusLayout statusLayout; protected TorrentStatusLayout statusLayout;
@ -73,10 +73,9 @@ public class TorrentDetailsView extends RelativeLayout {
// Set status texts // Set status texts
if (torrent.getDateAdded() != null) { if (torrent.getDateAdded() != null) {
dateaddedText.setText(getResources().getString( dateaddedText.setText(getResources().getString(R.string.status_sincedate, DateUtils
R.string.status_sincedate, .getRelativeDateTimeString(getContext(), torrent.getDateAdded().getTime(), DateUtils.SECOND_IN_MILLIS,
DateUtils.getRelativeDateTimeString(getContext(), torrent.getDateAdded().getTime(), DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_ABBREV_MONTH)));
DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_ABBREV_MONTH)));
dateaddedText.setVisibility(View.VISIBLE); dateaddedText.setVisibility(View.VISIBLE);
} else { } else {
dateaddedText.setVisibility(View.INVISIBLE); dateaddedText.setVisibility(View.INVISIBLE);
@ -85,24 +84,20 @@ public class TorrentDetailsView extends RelativeLayout {
statusLayout.setStatus(torrent.getStatusCode()); statusLayout.setStatus(torrent.getStatusCode());
statusText.setText(getResources().getString(R.string.status_status, local.getProgressStatusEta(getResources()))); statusText.setText(getResources().getString(R.string.status_status, local.getProgressStatusEta(getResources())));
ratioText.setText(getResources().getString(R.string.status_ratio, local.getRatioString())); ratioText.setText(getResources().getString(R.string.status_ratio, local.getRatioString()));
seedersText.setText(getResources().getString(R.string.status_seeders, torrent.getSeedersConnected(), seedersText.setText(getResources().getString(R.string.status_seeders, torrent.getSeedersConnected(), torrent.getSeedersKnown()));
torrent.getSeedersKnown())); leechersText.setText(getResources().getString(R.string.status_leechers, torrent.getLeechersConnected(), torrent.getLeechersKnown()));
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 torrent errors (as opposed to tracker errors)
// TODO: Add field that displays availability // TODO: Add field that displays availability
// Sizes and speeds texts // Sizes and speeds texts
totalsizeText.setText(getResources().getString(R.string.status_ofsize, totalsizeText.setText(getResources().getString(R.string.status_ofsize, FileSizeConverter.getSize(torrent.getTotalSize())));
FileSizeConverter.getSize(torrent.getTotalSize())));
downloadedText.setText(FileSizeConverter.getSize(torrent.getDownloadedEver(), false)); downloadedText.setText(FileSizeConverter.getSize(torrent.getDownloadedEver(), false));
downloadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getDownloadedEver()).toString()); downloadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getDownloadedEver()).toString());
uploadedText.setText(FileSizeConverter.getSize(torrent.getUploadedEver(), false)); uploadedText.setText(FileSizeConverter.getSize(torrent.getUploadedEver(), false));
uploadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getUploadedEver()).toString()); uploadedunitText.setText(FileSizeConverter.getSizeUnit(torrent.getUploadedEver()).toString());
downspeedText.setText(getResources().getString(R.string.status_speed_down_details, downspeedText
FileSizeConverter.getSize(torrent.getRateDownload()) + "/s")); .setText(getResources().getString(R.string.status_speed_down_details, FileSizeConverter.getSize(torrent.getRateDownload()) + "/s"));
upspeedText.setText(getResources().getString(R.string.status_speed_up, upspeedText.setText(getResources().getString(R.string.status_speed_up, FileSizeConverter.getSize(torrent.getRateUpload()) + "/s"));
FileSizeConverter.getSize(torrent.getRateUpload()) + "/s"));
} }

9
app/src/main/java/org/transdroid/core/gui/lists/TorrentFileView.java

@ -16,18 +16,19 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import android.content.Context;
import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup; import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import android.content.Context;
import android.widget.TextView;
/** /**
* View that represents some {@link TorrentFile} object and show the file's name, status and priority * View that represents some {@link TorrentFile} object and show the file's name, status and priority
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName="list_item_torrentfile") @EViewGroup(R.layout.list_item_torrentfile)
public class TorrentFileView extends TorrentFilePriorityLayout { public class TorrentFileView extends TorrentFilePriorityLayout {
@ViewById @ViewById

12
app/src/main/java/org/transdroid/core/gui/lists/TorrentProgressBar.java

@ -82,12 +82,12 @@ public class TorrentProgressBar extends View {
} }
private void initPaints() { private void initPaints() {
notdonePaint.setColor(0xFFEEEEEE); // Light grey notdonePaint.setColor(getResources().getColor(R.color.torrent_background));
inactiveDonePaint.setColor(0xFFA759D4); // Purple inactiveDonePaint.setColor(getResources().getColor(R.color.torrent_paused));
inactivePaint.setColor(0xFF9E9E9E); // Grey inactivePaint.setColor(getResources().getColor(R.color.torrent_other));
progressPaint.setColor(0xFF42A8FA); // Blue progressPaint.setColor(getResources().getColor(R.color.torrent_downloading));
donePaint.setColor(0xFF8ACC12); // Green donePaint.setColor(getResources().getColor(R.color.torrent_seeding));
errorPaint.setColor(0xFFDE3939); // Red errorPaint.setColor(getResources().getColor(R.color.torrent_error));
} }
@Override @Override

2
app/src/main/java/org/transdroid/core/gui/lists/TorrentStatusLayout.java

@ -68,7 +68,7 @@ public class TorrentStatusLayout extends RelativeLayout {
/** /**
* Registers the status of the represented torrent and invalidates the view so the status colour will be updated * Registers the status of the represented torrent and invalidates the view so the status colour will be updated
* accordingly. * accordingly.
* @param status * @param status The updated torrent status to show
*/ */
public void setStatus(TorrentStatus status) { public void setStatus(TorrentStatus status) {
this.status = status; this.status = status;

17
app/src/main/java/org/transdroid/core/gui/lists/TorrentView.java

@ -16,21 +16,22 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentStatus;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentStatus;
/** /**
* View that represents some {@link Torrent} object and displays progress, status, speeds, etc. * View that represents some {@link Torrent} object and displays progress, status, speeds, etc.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName = "list_item_torrent") @EViewGroup(R.layout.list_item_torrent)
public class TorrentView extends TorrentStatusLayout { public class TorrentView extends TorrentStatusLayout {
@ViewById @ViewById
@ -54,8 +55,8 @@ public class TorrentView extends TorrentStatusLayout {
priorityImage.setVisibility(View.INVISIBLE); priorityImage.setVisibility(View.INVISIBLE);
// Only show status bar, peers and speed fields if relevant, i.e. when downloading or actively seeding // Only show status bar, peers and speed fields if relevant, i.e. when downloading or actively seeding
if (torrent.getStatusCode() == TorrentStatus.Downloading if (torrent.getStatusCode() == TorrentStatus.Downloading ||
|| (torrent.getStatusCode() == TorrentStatus.Seeding && torrent.getRateUpload() > 0)) { (torrent.getStatusCode() == TorrentStatus.Seeding && torrent.getRateUpload() > 0)) {
torrentProgressbar.setVisibility(View.VISIBLE); torrentProgressbar.setVisibility(View.VISIBLE);
torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100)); torrentProgressbar.setProgress((int) (torrent.getDownloadedPercentage() * 100));
torrentProgressbar.setActive(torrent.canPause()); torrentProgressbar.setActive(torrent.canPause());

16
app/src/main/java/org/transdroid/core/gui/lists/TorrentsAdapter.java

@ -16,16 +16,16 @@
*/ */
package org.transdroid.core.gui.lists; package org.transdroid.core.gui.lists;
import java.util.ArrayList; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext; import org.androidannotations.annotations.RootContext;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import android.content.Context; import java.util.ArrayList;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/** /**
* Adapter that contains a list of torrent objects to show. * Adapter that contains a list of torrent objects to show.
@ -55,15 +55,17 @@ public class TorrentsAdapter extends BaseAdapter {
@Override @Override
public int getCount() { public int getCount() {
if (torrents == null) if (torrents == null) {
return 0; return 0;
}
return torrents.size(); return torrents.size();
} }
@Override @Override
public Torrent getItem(int position) { public Torrent getItem(int position) {
if (torrents == null) if (torrents == null) {
return null; return null;
}
return torrents.get(position); return torrents.get(position);
} }

2
app/src/main/java/org/transdroid/core/gui/navigation/DialogHelper.java

@ -48,7 +48,7 @@ public class DialogHelper extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(dialog.getDialogLayoutId()); setContentView(dialog.getDialogLayoutId());
getActionBar().setDisplayHomeAsUpEnabled(true); // TODO getActionBar().setDisplayHomeAsUpEnabled(true);
} }
@Override @Override

20
app/src/main/java/org/transdroid/core/gui/navigation/FilterListAdapter.java

@ -16,8 +16,8 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.util.ArrayList; import android.content.Context;
import java.util.List; import android.view.View;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext; import org.androidannotations.annotations.RootContext;
@ -28,12 +28,11 @@ import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.lists.ViewHolderAdapter; import org.transdroid.core.gui.lists.ViewHolderAdapter;
import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter; import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter;
import android.content.Context; import java.util.ArrayList;
import android.view.View; import java.util.List;
/** /**
* List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where * List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where appropriate.
* appropriate.
* @author Eric Kok * @author Eric Kok
*/ */
@EBean @EBean
@ -54,8 +53,7 @@ public class FilterListAdapter extends MergeAdapter {
*/ */
public void updateServers(List<ServerSetting> servers) { public void updateServers(List<ServerSetting> servers) {
if (this.serverItems == null && servers != null) { if (this.serverItems == null && servers != null) {
serverSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( serverSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_servers)));
context.getString(R.string.navigation_servers)));
serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE); serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE);
addAdapter(serverSeparator); addAdapter(serverSeparator);
this.serverItems = new FilterListItemAdapter(context, servers); this.serverItems = new FilterListItemAdapter(context, servers);
@ -76,8 +74,7 @@ public class FilterListAdapter extends MergeAdapter {
*/ */
public void updateStatusTypes(List<StatusTypeFilter> statusTypes) { public void updateStatusTypes(List<StatusTypeFilter> statusTypes) {
if (this.statusTypeItems == null && statusTypes != null) { if (this.statusTypeItems == null && statusTypes != null) {
statusTypeSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( statusTypeSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_status)));
context.getString(R.string.navigation_status)));
statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE); statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE);
addAdapter(statusTypeSeparator); addAdapter(statusTypeSeparator);
this.statusTypeItems = new FilterListItemAdapter(context, statusTypes); this.statusTypeItems = new FilterListItemAdapter(context, statusTypes);
@ -98,8 +95,7 @@ public class FilterListAdapter extends MergeAdapter {
*/ */
public void updateLabels(List<Label> labels) { public void updateLabels(List<Label> labels) {
if (this.labelItems == null && labels != null) { if (this.labelItems == null && labels != null) {
labelSeperator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( labelSeperator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_labels)));
context.getString(R.string.navigation_labels)));
labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE); labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
addAdapter(labelSeperator); addAdapter(labelSeperator);
this.labelItems = new FilterListItemAdapter(context, labels); this.labelItems = new FilterListItemAdapter(context, labels);

70
app/src/main/java/org/transdroid/core/gui/navigation/FilterListDropDownAdapter.java

@ -1,70 +0,0 @@
/*
* Copyright 2010-2013 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.core.gui.navigation;
import org.androidannotations.annotations.EBean;
import org.transdroid.daemon.IDaemonAdapter;
import android.view.View;
import android.view.ViewGroup;
/**
* List adapter that holds filter items, that is, servers, view types and labels and is displayed as content to a
* Spinner instead of a ListView.
* @author Eric Kok
*/
@EBean
public class FilterListDropDownAdapter extends FilterListAdapter {
protected NavigationSelectionView navigationSelectionView = null;
private String currentServer = null;
private String currentFilter = null;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// This returns the singleton navigation spinner view
if (navigationSelectionView == null) {
navigationSelectionView = NavigationSelectionView_.build(context);
}
navigationSelectionView.bind(currentServer, currentFilter);
return navigationSelectionView;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
// This returns the item to show in the drop down list
return super.getView(position, convertView, parent);
}
public void updateCurrentFilter(NavigationFilter currentFilter) {
this.currentFilter = currentFilter.getName();
if (navigationSelectionView != null)
navigationSelectionView.bind(this.currentServer, this.currentFilter);
}
public void updateCurrentServer(IDaemonAdapter currentConnection) {
this.currentServer = currentConnection.getSettings().getName();
if (navigationSelectionView != null)
navigationSelectionView.bind(this.currentServer, this.currentFilter);
}
public void hideServersLabel() {
serverSeparator.setViewVisibility(View.GONE);
notifyDataSetInvalidated();
}
}

10
app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemAdapter.java

@ -16,16 +16,16 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.util.List;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.lists.SimpleListItemView;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.lists.SimpleListItemView;
import java.util.List;
public class FilterListItemAdapter extends BaseAdapter { public class FilterListItemAdapter extends BaseAdapter {
private final Context context; private final Context context;

11
app/src/main/java/org/transdroid/core/gui/navigation/FilterListItemView.java

@ -16,19 +16,20 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.core.gui.lists.SimpleListItem;
import android.content.Context; import android.content.Context;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.gui.lists.SimpleListItem;
/** /**
* View that represents some {@link SimpleListItem} object specifically used to represent a navigation filter item. * View that represents some {@link SimpleListItem} object specifically used to represent a navigation filter item.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName="list_item_filter") @EViewGroup(R.layout.list_item_filter)
public class FilterListItemView extends FrameLayout { public class FilterListItemView extends FrameLayout {
@ViewById @ViewById

12
app/src/main/java/org/transdroid/core/gui/navigation/FilterSeparatorView.java

@ -16,19 +16,20 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import android.content.Context; import android.content.Context;
import android.widget.AbsListView; import android.widget.AbsListView;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
/** /**
* A list item that shows a sub header or separator (in underlined Holo style). * A list item that shows a sub header or separator (in underlined Holo style).
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName="list_item_separator") @EViewGroup(R.layout.list_item_separator)
public class FilterSeparatorView extends FrameLayout { public class FilterSeparatorView extends FrameLayout {
protected String text; protected String text;
@ -47,8 +48,7 @@ public class FilterSeparatorView extends FrameLayout {
*/ */
public FilterSeparatorView setText(String text) { public FilterSeparatorView setText(String text) {
separatorText.setText(text); separatorText.setText(text);
setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, AbsListView.LayoutParams.WRAP_CONTENT));
AbsListView.LayoutParams.WRAP_CONTENT));
return this; return this;
} }

32
app/src/main/java/org/transdroid/core/gui/navigation/Label.java

@ -16,16 +16,16 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.util.ArrayList; import android.os.Parcel;
import java.util.Collections; import android.os.Parcelable;
import java.util.List; import android.text.TextUtils;
import org.transdroid.core.gui.lists.SimpleListItem; import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.daemon.Torrent; import org.transdroid.daemon.Torrent;
import android.os.Parcel; import java.util.ArrayList;
import android.os.Parcelable; import java.util.Collections;
import android.text.TextUtils; import java.util.List;
/** /**
* Represents some label that is active or available on the server. * Represents some label that is active or available on the server.
@ -51,8 +51,9 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
@Override @Override
public String getName() { public String getName() {
if (TextUtils.isEmpty(this.name)) if (TextUtils.isEmpty(this.name)) {
return unnamedLabelText; return unnamedLabelText;
}
return this.name; return this.name;
} }
@ -77,8 +78,9 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
*/ */
@Override @Override
public boolean matches(Torrent torrent, boolean dormantAsInactive) { public boolean matches(Torrent torrent, boolean dormantAsInactive) {
if (isEmptyLabel) if (isEmptyLabel) {
return TextUtils.isEmpty(torrent.getLabelName()); return TextUtils.isEmpty(torrent.getLabelName());
}
return torrent.getLabelName() != null && torrent.getLabelName().equals(name); return torrent.getLabelName() != null && torrent.getLabelName().equals(name);
} }
@ -88,23 +90,23 @@ public class Label implements SimpleListItem, NavigationFilter, Comparable<Label
} }
/** /**
* Converts a list of labels as retrieved from a server daemon into a list of labels that can be used in the UI as * 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.
* navigation filters.
* @param daemonLabels The raw list of labels as received from the server daemon adapter * @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) * @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 * @return A label items that can be used in a filter list such as the action bar spinner
*/ */
public static ArrayList<Label> convertToNavigationLabels(List<org.transdroid.daemon.Label> daemonLabels, public static ArrayList<Label> convertToNavigationLabels(List<org.transdroid.daemon.Label> daemonLabels, String unnamedLabel) {
String unnamedLabel) { if (daemonLabels == null) {
if (daemonLabels == null)
return null; return null;
ArrayList<Label> localLabels = new ArrayList<Label>(); }
ArrayList<Label> localLabels = new ArrayList<>();
unnamedLabelText = unnamedLabel; unnamedLabelText = unnamedLabel;
localLabels.add(new Label(unnamedLabel, -1, true)); localLabels.add(new Label(unnamedLabel, -1, true));
for (org.transdroid.daemon.Label label : daemonLabels) { for (org.transdroid.daemon.Label label : daemonLabels) {
if (!TextUtils.isEmpty(label.getName())) if (!TextUtils.isEmpty(label.getName())) {
localLabels.add(new Label(label)); localLabels.add(new Label(label));
} }
}
Collections.sort(localLabels); Collections.sort(localLabels);
return localLabels; return localLabels;
} }

14
app/src/main/java/org/transdroid/core/gui/navigation/NavigationFilter.java

@ -16,10 +16,10 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import org.transdroid.daemon.Torrent;
import android.os.Parcelable; import android.os.Parcelable;
import org.transdroid.daemon.Torrent;
/** /**
* Represents a filter, used in the app navigation, that can check if some torrent matches the user-set filter * Represents a filter, used in the app navigation, that can check if some torrent matches the user-set filter
* @author Eric Kok * @author Eric Kok
@ -27,11 +27,10 @@ import android.os.Parcelable;
public interface NavigationFilter extends Parcelable { public interface NavigationFilter extends Parcelable {
/** /**
* Implementations should check if the supplied torrent matches the filter; for example a label filter should return * Implementations should check if the supplied torrent matches the filter; for example a label filter should return true if the torrent's label
* true if the torrent's label equals this items label name. * equals this items label name.
* @param torrent The torrent to check for matches * @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 * @param dormantAsInactive If true, dormant (0KB/s, so no data transfer) torrents are never actively downloading or seeding
* seeding
* @return True if the torrent matches the filter and should be shown in the current screen, false otherwise * @return True if the torrent matches the filter and should be shown in the current screen, false otherwise
*/ */
boolean matches(Torrent torrent, boolean dormantAsInactive); boolean matches(Torrent torrent, boolean dormantAsInactive);
@ -43,8 +42,7 @@ public interface NavigationFilter extends Parcelable {
String getName(); String getName();
/** /**
* Implementations should return a code that (within reasonable expectations) uniquely identifies it in the list of * Implementations should return a code that (within reasonable expectations) uniquely identifies it in the list of navigation filters
* navigation filters
* @return The code to uniquely identify this specific navigation filter, such as the name with a class name prefix * @return The code to uniquely identify this specific navigation filter, such as the name with a class name prefix
*/ */
String getCode(); String getCode();

181
app/src/main/java/org/transdroid/core/gui/navigation/NavigationHelper.java

@ -18,15 +18,12 @@ package org.transdroid.core.gui.navigation;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri; import android.net.Uri;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.TypefaceSpan; import android.text.style.TypefaceSpan;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache; import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache; import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
@ -41,34 +38,82 @@ import org.transdroid.R;
import java.io.IOException; import java.io.IOException;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import de.keyboardsurfer.android.widget.crouton.Style;
/** /**
* Helper for activities to make navigation-related decisions, such as when a device can display a larger, tablet style * Helper for activities to make navigation-related decisions, such as when a device can display a larger, tablet style layout or how to display
* layout or how to display errors. * errors.
* @author Eric Kok * @author Eric Kok
*/ */
@SuppressLint("ResourceAsColor") @SuppressLint("ResourceAsColor")
@EBean @EBean
public class NavigationHelper { public class NavigationHelper {
private static ImageLoader imageCache;
@RootContext @RootContext
protected Context context; protected Context context;
private Boolean inDebugMode;
private static ImageLoader imageCache;
/** /**
* Use with {@link Crouton#showText(android.app.Activity, int, Style)} (and variants) to display error messages. * 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 Style CROUTON_ERROR_STYLE = public static SpannableString buildCondensedFontString(String string) {
new Style.Builder().setBackgroundColor(R.color.crouton_error).setTextSize(13).build(); 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;
}
/** /**
* Use with {@link Crouton#showText(android.app.Activity, int, Style)} (and variants) to display info messages. * 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 Style CROUTON_INFO_STYLE = public static String extractNameFromUri(Uri rawTorrentUri) {
new Style.Builder().setBackgroundColor(R.color.crouton_info).setTextSize(13).build();
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. * Returns (and initialises, if needed) an image cache that uses memory and (1MB) local storage.
@ -78,8 +123,9 @@ public class NavigationHelper {
if (imageCache == null) { if (imageCache == null) {
imageCache = ImageLoader.getInstance(); imageCache = ImageLoader.getInstance();
try { try {
LruDiskCache diskCache = // LruDiskCache diskCache =
new LruDiskCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25); // new LruDiskCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25);
LruDiscCache diskCache = new LruDiscCache(context.getCacheDir(), null, new Md5FileNameGenerator(), 640000, 25);
// @formatter:off // @formatter:off
Builder imageCacheBuilder = new Builder(context) Builder imageCacheBuilder = new Builder(context)
.defaultDisplayImageOptions( .defaultDisplayImageOptions(
@ -100,24 +146,16 @@ public class NavigationHelper {
} }
/** /**
* Whether any search-related UI components should be shown in the interface. At the moment returns false only if we * Returns the application name (like Transdroid) and version name (like 1.5.0), appended by the version code (like 180).
* run as Transdroid Lite version. * @return The app name and version, such as 'Transdroid 1.5.0 (180)'
* @return True if search is enabled, false otherwise
*/ */
public String getAppNameAndVersion() { public String getAppNameAndVersion() {
String appName = context.getString(R.string.app_name); return context.getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")";
try {
PackageInfo m = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return appName + " " + m.versionName + " (" + m.versionCode + ")";
} catch (NameNotFoundException e) {
return appName;
}
} }
/** /**
* Returns whether the device is considered small (i.e. a phone) rather than large (i.e. a tablet). Can, for * 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
* example, be used to determine if a dialog should be shown full screen. Currently is true if the device's smallest * dialog should be shown full screen. Currently is true if the device's smallest dimension is 500 dip.
* dimension is 500 dip.
* @return True if the app runs on a small device, false otherwise * @return True if the app runs on a small device, false otherwise
*/ */
public boolean isSmallScreen() { public boolean isSmallScreen() {
@ -125,8 +163,8 @@ public class NavigationHelper {
} }
/** /**
* Whether any search-related UI components should be shown in the interface. At the moment returns false only if we * Whether any search-related UI components should be shown in the interface. At the moment returns false only if we run as Transdroid Lite
* run as Transdroid Lite version. * version.
* @return True if search is enabled, false otherwise * @return True if search is enabled, false otherwise
*/ */
public boolean enableSearchUi() { public boolean enableSearchUi() {
@ -134,8 +172,7 @@ public class NavigationHelper {
} }
/** /**
* Whether any RSS-related UI components should be shown in the interface. At the moment returns false only if we * 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.
* run as Transdroid Lite version.
* @return True if search is enabled, false otherwise * @return True if search is enabled, false otherwise
*/ */
public boolean enableRssUi() { public boolean enableRssUi() {
@ -143,8 +180,8 @@ public class NavigationHelper {
} }
/** /**
* Returns whether any seedbox-related components should be shown in the interface; specifically the option to add * Returns whether any seedbox-related components should be shown in the interface; specifically the option to add server settings via easy
* server settings via easy seedbox-specific screens. * seedbox-specific screens.
* @return True if seedbox settings should be shown, false otherwise * @return True if seedbox settings should be shown, false otherwise
*/ */
public boolean enableSeedboxes() { public boolean enableSeedboxes() {
@ -153,75 +190,11 @@ public class NavigationHelper {
/** /**
* Whether the custom app update checker should be used to check for new app and search module versions. * 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 * @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
* Play Store for updates, for example), false otherwise * example), false otherwise
*/ */
public boolean enableUpdateChecker() { public boolean enableUpdateChecker() {
return context.getResources().getBoolean(R.bool.updatecheck_available); return context.getResources().getBoolean(R.bool.updatecheck_available);
} }
/**
* 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;
}
} }

52
app/src/main/java/org/transdroid/core/gui/navigation/NavigationSelectionView.java

@ -1,52 +0,0 @@
/*
* Copyright 2010-2013 Eric Kok et al.
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*/
package org.transdroid.core.gui.navigation;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import android.content.Context;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* View that displays the user-selected server and display filter inside the action bar list navigation spinner
* @author Eric Kok
*/
@EViewGroup(resName="actionbar_navigation")
public class NavigationSelectionView extends LinearLayout {
@ViewById
protected TextView filterText;
@ViewById
protected TextView serverText;
public NavigationSelectionView(Context context) {
super(context);
}
/**
* Binds the names of the current connected server and selected filter to this navigation view.
* @param currentServer The name of the server currently connected to
* @param currentFilter The name of the filter that is currently selected
*/
public void bind(String currentServer, String currentFilter) {
serverText.setText(currentServer);
filterText.setText(currentFilter);
}
}

9
app/src/main/java/org/transdroid/core/gui/navigation/RefreshableActivity.java

@ -16,17 +16,12 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import android.view.View;
/** /**
* Interface to be implemented by any activity that allows its content to be refreshed; fragments can ask for * Interface to be implemented by any activity that allows its content to be refreshed; fragments can ask for user-initiated refreshes.
* user-initiated refreshes.
* @author Eric Kok * @author Eric Kok
*/ */
public interface RefreshableActivity { public interface RefreshableActivity {
public void refreshScreen(); void refreshScreen();
public void addRefreshableView(View view);
} }

14
app/src/main/java/org/transdroid/core/gui/navigation/SelectionManagerMode.java

@ -19,6 +19,7 @@ package org.transdroid.core.gui.navigation;
import org.transdroid.core.gui.navigation.SelectionModificationSpinner.OnModificationActionSelectedListener; import org.transdroid.core.gui.navigation.SelectionModificationSpinner.OnModificationActionSelectedListener;
import org.transdroid.daemon.Finishable; import org.transdroid.daemon.Finishable;
import android.content.Context;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
@ -35,18 +36,21 @@ import android.widget.ListView;
*/ */
public class SelectionManagerMode implements MultiChoiceModeListener, OnModificationActionSelectedListener { public class SelectionManagerMode implements MultiChoiceModeListener, OnModificationActionSelectedListener {
private ListView managedList; private final Context themedContext;
private int titleTemplateResource; private final ListView managedList;
private final int titleTemplateResource;
private Class<?> onlyCheckClass = null; private Class<?> onlyCheckClass = null;
/** /**
* Instantiates the helper by binding it to a specific {@link ListView} and providing the text resource to display * Instantiates the helper by binding it to a specific {@link ListView} and providing the text resource to display
* as title in the spinner. * 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 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 * @param titleTemplateResource The string resource id to show as the spinners title; the number of selected items
* will be supplied as numeric formatting argument * will be supplied as numeric formatting argument
*/ */
public SelectionManagerMode(ListView managedList, int titleTemplateResource) { public SelectionManagerMode(Context themedContext, ListView managedList, int titleTemplateResource) {
this.themedContext = themedContext;
this.managedList = managedList; this.managedList = managedList;
this.titleTemplateResource = titleTemplateResource; this.titleTemplateResource = titleTemplateResource;
} }
@ -63,7 +67,7 @@ public class SelectionManagerMode implements MultiChoiceModeListener, OnModifica
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Allow modification of selection through a spinner // Allow modification of selection through a spinner
SelectionModificationSpinner selectionSpinner = new SelectionModificationSpinner(managedList.getContext()); SelectionModificationSpinner selectionSpinner = new SelectionModificationSpinner(themedContext);
selectionSpinner.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, selectionSpinner.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT)); ViewGroup.LayoutParams.MATCH_PARENT));
selectionSpinner.setOnModificationActionSelectedListener(this); selectionSpinner.setOnModificationActionSelectedListener(this);
@ -85,7 +89,7 @@ public class SelectionManagerMode implements MultiChoiceModeListener, OnModifica
.getCheckedItemPositions().keyAt(i))))) .getCheckedItemPositions().keyAt(i)))))
checkedCount++; checkedCount++;
} }
((SelectionModificationSpinner) mode.getCustomView()).updateTitle(managedList.getContext().getResources() ((SelectionModificationSpinner) mode.getCustomView()).updateTitle(themedContext.getResources()
.getQuantityString(titleTemplateResource, checkedCount, checkedCount)); .getQuantityString(titleTemplateResource, checkedCount, checkedCount));
} }

117
app/src/main/java/org/transdroid/core/gui/navigation/SetLabelDialog.java

@ -16,109 +16,80 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.security.InvalidParameterException; import android.content.Context;
import java.util.Iterator; import android.text.TextUtils;
import java.util.List; import android.view.LayoutInflater;
import org.transdroid.R;
import org.transdroid.core.gui.lists.SimpleListItem;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
import de.keyboardsurfer.android.widget.crouton.Crouton;
/** import com.afollestad.materialdialogs.MaterialDialog;
* A dialog fragment that allows picking a label or entering a new label to set this new label to the torrent. import com.nispok.snackbar.Snackbar;
* @author Eric Kok import com.nispok.snackbar.SnackbarManager;
*/
public class SetLabelDialog extends DialogFragment { import org.transdroid.R;
private OnLabelPickedListener onLabelPickedListener = null; import java.util.Iterator;
private List<? extends SimpleListItem> currentLabels = null; import java.util.List;
public SetLabelDialog() { public class SetLabelDialog {
setRetainInstance(true);
}
/** /**
* Sets the callback for when the user is has picked a label for the target torrent. * A dialog fragment that allows picking a label or entering a new label to set this new label to the torrent.
* @param onLabelPickedListener The event listener to this dialog * @param context The activity context that opens (and owns) this dialog
* @return This dialog, for method chaining * @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 SetLabelDialog setOnLabelPickedListener(OnLabelPickedListener onLabelPickedListener) { public static void show(final Context context, final OnLabelPickedListener onLabelPickedListener, List<Label> currentLabels) {
this.onLabelPickedListener = onLabelPickedListener;
return this;
}
/**
* Sets the list of currently known labels as are active on the server. These are offered to the user to pick a new
* label for the target torrents.
* @param currentLabels The list of torrent labels
* @return This dialog, for method chaining
*/
public SetLabelDialog setCurrentLabels(List<Label> currentLabels) {
// Discard the empty label in this list before storing it locally // Discard the empty label in this list before storing it locally
for (Iterator<Label> iter = currentLabels.iterator(); iter.hasNext(); ) { for (Iterator<Label> iter = currentLabels.iterator(); iter.hasNext(); ) {
if (iter.next().isEmptyLabel()) if (iter.next().isEmptyLabel()) {
iter.remove(); iter.remove();
} }
this.currentLabels = currentLabels;
return this;
} }
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 MaterialDialog dialog = 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 @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public void onPositive(MaterialDialog dialog) {
if (onLabelPickedListener == null) // User should have provided a new label
throw new InvalidParameterException( if (TextUtils.isEmpty(newLabelEdit.getText())) {
"Please first set the callback listener using setOnLabelPickedListener before opening the dialog."); SnackbarManager.show(Snackbar.with(context).text(R.string.error_notalabel).colorResource(R.color.red));
if (currentLabels == null) return;
throw new InvalidParameterException( }
"Please first set the list of currently known labels before opening the dialog, even if the list is empty."); onLabelPickedListener.onLabelPicked(newLabelEdit.getText().toString());
final View setlabelFrame = getActivity().getLayoutInflater().inflate(R.layout.dialog_setlabel, null, false); }
final ListView labelsList = (ListView) setlabelFrame.findViewById(R.id.labels_list);
final EditText newlabelEdit = (EditText) setlabelFrame.findViewById(R.id.newlabel_edit); @Override
public void onNeutral(MaterialDialog dialog) {
onLabelPickedListener.onLabelPicked(null);
}
}).build();
if (currentLabels.size() == 0) { if (currentLabels.size() == 0) {
// Hide the list (and its label) if there are no labels yet // Hide the list (and its label) if there are no labels yet
setlabelFrame.findViewById(R.id.pick_label).setVisibility(View.GONE); setLabelLayout.findViewById(R.id.pick_label).setVisibility(View.GONE);
setlabelFrame.findViewById(R.id.line1).setVisibility(View.GONE);
setlabelFrame.findViewById(R.id.line2).setVisibility(View.GONE);
labelsList.setVisibility(View.GONE); labelsList.setVisibility(View.GONE);
} else { } else {
labelsList.setAdapter(new FilterListItemAdapter(getActivity(), currentLabels)); labelsList.setAdapter(new FilterListItemAdapter(context, currentLabels));
labelsList.setOnItemClickListener(new OnItemClickListener() { labelsList.setOnItemClickListener(new OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
onLabelPickedListener.onLabelPicked(((Label) labelsList.getItemAtPosition(position)).getName()); onLabelPickedListener.onLabelPicked(((Label) labelsList.getItemAtPosition(position)).getName());
dismiss(); dialog.dismiss();
} }
}); });
} }
return new AlertDialog.Builder(getActivity()).setView(setlabelFrame)
.setPositiveButton(R.string.status_update, new OnClickListener() { dialog.show();
@Override
public void onClick(DialogInterface dialog, int which) {
// User should have provided a new label
if (newlabelEdit.getText().toString().equals("")) {
Crouton.showText(getActivity(), R.string.error_notalabel,
NavigationHelper.CROUTON_ERROR_STYLE);
}
onLabelPickedListener.onLabelPicked(newlabelEdit.getText().toString());
}
}).setNeutralButton(R.string.status_label_remove, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onLabelPickedListener.onLabelPicked(null);
}
}).setNegativeButton(android.R.string.cancel, null).show();
} }
public interface OnLabelPickedListener { public interface OnLabelPickedListener {

70
app/src/main/java/org/transdroid/core/gui/navigation/SetStorageLocationDialog.java

@ -16,73 +16,35 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.security.InvalidParameterException; import android.content.Context;
import android.view.LayoutInflater;
import org.transdroid.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
/** import com.afollestad.materialdialogs.MaterialDialog;
* A dialog fragment that allows changing of the storage location by editing the path text directly.
* @author Eric Kok
*/
public class SetStorageLocationDialog extends DialogFragment {
private OnStorageLocationUpdatedListener onStorageLocationUpdatedListener = null;
private String currentLocation = null;
public SetStorageLocationDialog() { import org.transdroid.R;
setRetainInstance(true);
}
/** public class SetStorageLocationDialog {
* Sets the callback for when the user is done updating the storage location.
* @param onStorageLocationUpdatedListener The event listener to this dialog
* @return This dialog, for method chaining
*/
public SetStorageLocationDialog setOnStorageLocationUpdated(
OnStorageLocationUpdatedListener onStorageLocationUpdatedListener) {
this.onStorageLocationUpdatedListener = onStorageLocationUpdatedListener;
return this;
}
/** /**
* Sets the current storage location that will be available to the user to edit * A dialog fragment that allows changing of the storage location by editing the path text directly.
* @param currentLocation The current storage location path as text * @param context The activity context that opens (and owns) this dialog
* @return This dialog, for method chaining * @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 SetStorageLocationDialog setCurrentLocation(String currentLocation) { public static void show(final Context context, final OnStorageLocationUpdatedListener onStorageLocationUpdatedListener, String currentLocation) {
this.currentLocation = currentLocation; View locationLayout = LayoutInflater.from(context).inflate(R.layout.dialog_storagelocation, null);
return this; final EditText locationText = (EditText) locationLayout.findViewById(R.id.location_edit);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (currentLocation == null)
throw new InvalidParameterException(
"Please first set the current trackers text using setCurrentLocation before opening the dialog.");
if (onStorageLocationUpdatedListener == null)
throw new InvalidParameterException(
"Please first set the callback listener using setOnStorageLocationUpdated before opening the dialog.");
final View locationFrame = getActivity().getLayoutInflater().inflate(R.layout.dialog_storagelocation, null,
false);
final EditText locationText = (EditText) locationFrame.findViewById(R.id.location_edit);
locationText.setText(currentLocation); locationText.setText(currentLocation);
return new AlertDialog.Builder(getActivity()).setView(locationFrame) new MaterialDialog.Builder(context).customView(locationLayout, false).positiveText(R.string.status_update)
.setPositiveButton(R.string.status_update, new OnClickListener() { .negativeText(android.R.string.cancel).callback(new MaterialDialog.ButtonCallback() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onPositive(MaterialDialog dialog) {
// User is done editing and requested to update given the text input // User is done editing and requested to update given the text input
onStorageLocationUpdatedListener.onStorageLocationUpdated(locationText.getText().toString()); onStorageLocationUpdatedListener.onStorageLocationUpdated(locationText.getText().toString());
} }
}).setNegativeButton(android.R.string.cancel, null).show(); }).show();
} }
public interface OnStorageLocationUpdatedListener { public interface OnStorageLocationUpdatedListener {

73
app/src/main/java/org/transdroid/core/gui/navigation/SetTrackersDialog.java

@ -16,74 +16,39 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.List;
import org.transdroid.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.DialogInterface; import android.content.Context;
import android.content.DialogInterface.OnClickListener; import android.view.LayoutInflater;
import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.EditText; import android.widget.EditText;
/** import com.afollestad.materialdialogs.MaterialDialog;
* A dialog fragment that allows changing the trackers of a torrent by editing the text directly.
* @author Eric Kok
*/
public class SetTrackersDialog extends DialogFragment {
private OnTrackersUpdatedListener onTrackersUpdatedListener = null; import org.transdroid.R;
private String currentTrackers = null;
public SetTrackersDialog() { import java.util.Arrays;
setRetainInstance(true); import java.util.List;
}
/** public class SetTrackersDialog extends DialogFragment {
* Sets the callback for when the user is done updating the trackers list.
* @param onTrackersUpdatedListener The event listener to this dialog
* @return This dialog, for method chaining
*/
public SetTrackersDialog setOnTrackersUpdated(OnTrackersUpdatedListener onTrackersUpdatedListener) {
this.onTrackersUpdatedListener = onTrackersUpdatedListener;
return this;
}
/** /**
* Sets the current trackers text/list that will be available to the user to edit * A dialog fragment that allows changing the trackers of a torrent by editing the text directly.
* @param currentTrackers The current trackers for the target torrent * @param context The activity context that opens (and owns) this dialog
* @return This dialog, for method chaining * @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 SetTrackersDialog setCurrentTrackers(String currentTrackers) { public static void show(final Context context, final OnTrackersUpdatedListener onTrackersUpdatedListener, String currentTrackers) {
this.currentTrackers = currentTrackers; View trackersLayout = LayoutInflater.from(context).inflate(R.layout.dialog_trackers, null);
return this; final EditText trackersText = (EditText) trackersLayout.findViewById(R.id.trackers_edit);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
if (currentTrackers == null)
throw new InvalidParameterException(
"Please first set the current trackers text using setCurrentTrackers before opening the dialog.");
if (onTrackersUpdatedListener == null)
throw new InvalidParameterException(
"Please first set the callback listener using setOnTrackersUpdated before opening the dialog.");
final View trackersFrame = getActivity().getLayoutInflater().inflate(R.layout.dialog_trackers, null, false);
final EditText trackersText = (EditText) trackersFrame.findViewById(R.id.trackers_edit);
trackersText.setText(currentTrackers); trackersText.setText(currentTrackers);
return new AlertDialog.Builder(getActivity()).setView(trackersFrame) new MaterialDialog.Builder(context).customView(trackersLayout, false).positiveText(R.string.status_update)
.setPositiveButton(R.string.status_update, new OnClickListener() { .negativeText(android.R.string.cancel).callback(new MaterialDialog.ButtonCallback() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onPositive(MaterialDialog dialog) {
// User is done editing and requested to update given the text input // User is done editing and requested to update given the text input
onTrackersUpdatedListener.onTrackersUpdated(Arrays.asList(trackersText.getText().toString() onTrackersUpdatedListener.onTrackersUpdated(Arrays.asList(trackersText.getText().toString().split("\n")));
.split("\n")));
} }
}).setNegativeButton(android.R.string.cancel, null).show(); }).show();
} }
public interface OnTrackersUpdatedListener { public interface OnTrackersUpdatedListener {

94
app/src/main/java/org/transdroid/core/gui/navigation/SetTransferRatesDialog.java

@ -16,93 +16,64 @@
*/ */
package org.transdroid.core.gui.navigation; package org.transdroid.core.gui.navigation;
import java.security.InvalidParameterException; import android.content.Context;
import android.view.LayoutInflater;
import org.transdroid.R;
import android.app.Dialog;
import android.app.DialogFragment;
import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
/** import com.afollestad.materialdialogs.MaterialDialog;
* A dialog fragment that allow picking of maximum download and upload transfer rates as well as the resetting of these
* values.
* @author Eric Kok
*/
public class SetTransferRatesDialog extends DialogFragment {
private OnRatesPickedListener onRatesPickedListener = null; import org.transdroid.R;
private TextView maxSpeedDown, maxSpeedUp;
public SetTransferRatesDialog() { public class SetTransferRatesDialog {
setRetainInstance(true);
}
/** /**
* Sets the callback for results in this dialog (with newly selected values or a reset). * A dialog fragment that allow picking of maximum download and upload transfer rates as well as the resetting of these values.
* @param onRatesPickedListener The event listener to this dialog * @param context The activity context that opens (and owns) this dialog
* @return This dialog, for method chaining * @param onRatesPickedListener The callback for results in this dialog (with newly selected values or a reset)
*/ */
public SetTransferRatesDialog setOnRatesPickedListener(OnRatesPickedListener onRatesPickedListener) { public static void show(final Context context, final OnRatesPickedListener onRatesPickedListener) {
this.onRatesPickedListener = onRatesPickedListener;
return this;
}
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 dialog = 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 @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public void onPositive(MaterialDialog dialog) {
if (onRatesPickedListener == null)
throw new InvalidParameterException(
"Please first set the callback listener using setOnRatesPickedListener before opening the dialog.");
final View transferRatesContent = getActivity().getLayoutInflater().inflate(R.layout.dialog_transferrates,
null, false);
maxSpeedDown = (TextView) transferRatesContent.findViewById(R.id.maxspeeddown_text);
maxSpeedUp = (TextView) transferRatesContent.findViewById(R.id.maxspeedup_text);
bindButtons(transferRatesContent, 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(transferRatesContent, 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);
((Button) transferRatesContent.findViewById(R.id.ok_button)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int maxDown = -1, maxUp = -1; int maxDown = -1, maxUp = -1;
try { try {
maxDown = Integer.parseInt(maxSpeedDown.getText().toString()); maxDown = Integer.parseInt(maxSpeedDown.getText().toString());
maxUp = Integer.parseInt(maxSpeedUp.getText().toString()); maxUp = Integer.parseInt(maxSpeedUp.getText().toString());
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// Impossible as we only input via the number buttons
} }
if (maxDown <= 0 || maxUp <= 0) { if (maxDown <= 0 || maxUp <= 0) {
onRatesPickedListener.onInvalidNumber(); onRatesPickedListener.onInvalidNumber();
return;
} }
onRatesPickedListener.onRatesPicked(maxDown, maxUp); onRatesPickedListener.onRatesPicked(maxDown, maxUp);
dismiss();
} }
});
((Button) transferRatesContent.findViewById(R.id.reset_button)).setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onNeutral(MaterialDialog dialog) {
onRatesPickedListener.resetRates(); onRatesPickedListener.resetRates();
dismiss();
}
});
((Button) transferRatesContent.findViewById(R.id.cancel_button)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
} }
}); }).build();
Dialog dialog = new Dialog(getActivity());
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); bindButtons(dialog.getCustomView(), maxSpeedDown, R.id.down1Button, R.id.down2Button, R.id.down3Button, R.id.down4Button, R.id.down5Button,
dialog.setContentView(transferRatesContent); R.id.down6Button, R.id.down7Button, R.id.down8Button, R.id.down9Button, R.id.down0Button);
return dialog; 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 void bindButtons(View transferRatesContent, View numberView, int... buttonResource) { private static void bindButtons(View transferRatesContent, View numberView, int... buttonResource) {
for (int i : buttonResource) { for (int i : buttonResource) {
// Keep the relevant number as reference in the view tag and bind the click listerner // Keep the relevant number as reference in the view tag and bind the click listerner
transferRatesContent.findViewById(i).setTag(numberView); transferRatesContent.findViewById(i).setTag(numberView);
@ -110,14 +81,15 @@ public class SetTransferRatesDialog extends DialogFragment {
} }
} }
private android.view.View.OnClickListener onNumberClicked = new android.view.View.OnClickListener() { private static OnClickListener onNumberClicked = new OnClickListener() {
@Override @Override
public void onClick(View v) { 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 // Append the text contents of the button itself as text to the current number (as reference in the view's
// tag) // tag)
TextView numberView = (TextView) v.getTag(); TextView numberView = (TextView) v.getTag();
if (numberView.getText().toString().equals(getString(R.string.status_maxspeed_novalue))) if (numberView.getText().toString().equals(v.getContext().getString(R.string.status_maxspeed_novalue))) {
numberView.setText(""); numberView.setText("");
}
numberView.setText(numberView.getText().toString() + ((Button) v).getText().toString()); numberView.setText(numberView.getText().toString() + ((Button) v).getText().toString());
} }
}; };

23
app/src/main/java/org/transdroid/core/gui/rss/RssfeedLoader.java

@ -16,18 +16,18 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
/** /**
* A container class that holds RSS feed settings and, after they have been retrieved, the contents as {@link Channel}, * 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
* the number of new items and an indication of a connection error. * indication of a connection error.
* @author Eric Kok * @author Eric Kok
*/ */
public class RssfeedLoader { public class RssfeedLoader {
@ -45,7 +45,7 @@ public class RssfeedLoader {
this.channel = channel; this.channel = channel;
this.hasError = hasError; this.hasError = hasError;
if (channel == null || channel.getItems() == null || hasError) { if (channel == null || channel.getItems() == null || hasError) {
hasError = true; this.hasError = true;
newCount = -1; newCount = -1;
return; return;
} }
@ -67,8 +67,7 @@ public class RssfeedLoader {
} }
}); });
for (Item item : items) { for (Item item : items) {
if (item.getPubdate() == null || setting.getLastViewed() == null if (item.getPubdate() == null || setting.getLastViewed() == null || item.getPubdate().after(setting.getLastViewed())) {
|| item.getPubdate().after(setting.getLastViewed())) {
newCount++; newCount++;
item.setIsNew(true); item.setIsNew(true);
} else { } else {
@ -79,12 +78,12 @@ public class RssfeedLoader {
// Use the url of the last RSS item the last time the feed was viewed by the user to count new items // 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; boolean isNew = true;
for (Item item : channel.getItems()) { for (Item item : channel.getItems()) {
if (item.getTheLink() != null && setting.getLastViewedItemUrl() != null if (item.getTheLink() != null && setting.getLastViewedItemUrl() != null && item.getTheLink().equals(setting.getLastViewedItemUrl())) {
&& item.getTheLink().equals(setting.getLastViewedItemUrl())) {
isNew = false; isNew = false;
} }
if (isNew) if (isNew) {
newCount++; newCount++;
}
item.setIsNew(isNew); item.setIsNew(isNew);
} }
} }

24
app/src/main/java/org/transdroid/core/gui/rss/RssfeedView.java

@ -16,12 +16,6 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.gui.navigation.NavigationHelper;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
@ -29,15 +23,22 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.app.settings.RssfeedSetting;
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 * 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
* site and can load how many new items are available. * items are available.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName = "list_item_rssfeed") @EViewGroup(R.layout.list_item_rssfeed)
public class RssfeedView extends LinearLayout { public class RssfeedView extends LinearLayout {
private static final String GETFVO_URL = "http://g.etfv.co/%1$s"; private static final String GRABICON_URL = "http://grabicon.com/icon?origin=www.transdroid.org&domain=%1$s";
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@ -70,8 +71,7 @@ public class RssfeedView extends LinearLayout {
// Clear and then asynchronously load the RSS feed site' favicon // Clear and then asynchronously load the RSS feed site' favicon
// Uses the g.etfv.co service to resolve the favicon of any feed URL // Uses the g.etfv.co service to resolve the favicon of any feed URL
faviconImage.setImageDrawable(null); faviconImage.setImageDrawable(null);
navigationHelper.getImageCache().displayImage(String.format(GETFVO_URL, rssfeedLoader.getSetting().getUrl()), navigationHelper.getImageCache().displayImage(String.format(GRABICON_URL, rssfeedLoader.getSetting().getUrl()), faviconImage);
faviconImage);
} }

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

@ -16,9 +16,17 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import java.util.ArrayList; import android.annotation.TargetApi;
import java.util.Date; import android.content.Intent;
import java.util.List; import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
@ -27,25 +35,23 @@ import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.FragmentById; import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.UiThread;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.*; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.gui.*; import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.rssparser.Channel; import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.RssParser; import org.transdroid.core.rssparser.RssParser;
import android.annotation.TargetApi; import java.util.ArrayList;
import android.app.Activity; import java.util.Date;
import android.content.Intent; import java.util.List;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import de.keyboardsurfer.android.widget.crouton.Crouton;
@EActivity(resName = "activity_rssfeeds") @EActivity(R.layout.activity_rssfeeds)
public class RssfeedsActivity extends Activity { public class RssfeedsActivity extends ActionBarActivity {
// Settings and local data // Settings and local data
@Bean @Bean
@ -55,26 +61,27 @@ public class RssfeedsActivity extends Activity {
protected List<RssfeedLoader> loaders; protected List<RssfeedLoader> loaders;
// Contained feeds and items fragments // Contained feeds and items fragments
@FragmentById(resName = "rssfeeds_fragment") @FragmentById(R.id.rssfeeds_fragment)
protected RssfeedsFragment fragmentFeeds; protected RssfeedsFragment fragmentFeeds;
@FragmentById(resName = "rssitems_fragment") @FragmentById(R.id.rssitems_fragment)
protected RssitemsFragment fragmentItems; protected RssitemsFragment fragmentItems;
@ViewById
protected Toolbar rssfeedsToolbar;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
// Set the theme according to the user preference // Set the theme according to the user preference
if (SystemSettings_.getInstance_(this).useDarkTheme()) { if (SystemSettings_.getInstance_(this).useDarkTheme()) {
setTheme(R.style.TransdroidTheme_Dark); setTheme(R.style.TransdroidTheme_Dark);
getActionBar().setIcon(R.drawable.ic_activity_torrents);
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@AfterViews @AfterViews
protected void init() { protected void init() {
// Simple action bar with up button and correct title font setSupportActionBar(rssfeedsToolbar);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.rss_feeds)));
getActionBar().setTitle(NavigationHelper.buildCondensedFontString(getString(R.string.rss_feeds))); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@ -93,7 +100,7 @@ public class RssfeedsActivity extends Activity {
* Reload the RSS feed settings and start loading all the feeds. To be called from contained fragments. * Reload the RSS feed settings and start loading all the feeds. To be called from contained fragments.
*/ */
public void refreshFeeds() { public void refreshFeeds() {
loaders = new ArrayList<RssfeedLoader>(); loaders = new ArrayList<>();
// For each RSS feed setting the user created, start a loader that retrieved the RSS feed (via a background // 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 // thread) and, on success, determines the new items in the feed
for (RssfeedSetting setting : applicationSettings.getRssfeedSettings()) { for (RssfeedSetting setting : applicationSettings.getRssfeedSettings()) {
@ -124,8 +131,7 @@ public class RssfeedsActivity extends Activity {
} }
/** /**
* Stores the retrieved RSS feed content channel into the loader and updates the RSS feed in the feeds list * Stores the retrieved RSS feed content channel into the loader and updates the RSS feed in the feeds list fragment.
* fragment.
* @param loader The RSS feed loader that was executed * @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 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 * @param hasError True if a connection error occurred in the loading of the feed; false otherwise
@ -137,12 +143,10 @@ public class RssfeedsActivity extends Activity {
} }
/** /**
* Opens an RSS feed in the dedicated fragment (if there was space in the UI) or a new {@link RssitemsActivity}. * 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
* Optionally this also registers in the user preferences that the feed was now viewed, so that in the future the * the user preferences that the feed was now viewed, so that in the future the new items can be properly marked.
* new items can be properly marked.
* @param loader The RSS feed loader (with settings and the loaded content channel) to show * @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 * @param markAsViewedNow True if the user settings should be updated to reflect this feed's last viewed date; false otherwise
* otherwise
*/ */
public void openRssfeed(RssfeedLoader loader, boolean markAsViewedNow) { public void openRssfeed(RssfeedLoader loader, boolean markAsViewedNow) {
@ -153,8 +157,9 @@ public class RssfeedsActivity extends Activity {
// be loaded until the RSS feeds screen in opened again. // be loaded until the RSS feeds screen in opened again.
if (!loader.hasError() && loader.getChannel() != null && markAsViewedNow) { if (!loader.hasError() && loader.getChannel() != null && markAsViewedNow) {
String lastViewedItemUrl = null; String lastViewedItemUrl = null;
if (loader.getChannel().getItems() != null && loader.getChannel().getItems().size() > 0) if (loader.getChannel().getItems() != null && loader.getChannel().getItems().size() > 0) {
lastViewedItemUrl = loader.getChannel().getItems().get(0).getTheLink(); lastViewedItemUrl = loader.getChannel().getItems().get(0).getTheLink();
}
applicationSettings.setRssfeedLastViewer(loader.getSetting().getOrder(), new Date(), lastViewedItemUrl); applicationSettings.setRssfeedLastViewer(loader.getSetting().getOrder(), new Date(), lastViewedItemUrl);
} }
fragmentItems.update(loader.getChannel(), loader.hasError()); fragmentItems.update(loader.getChannel(), loader.hasError());
@ -163,11 +168,11 @@ public class RssfeedsActivity extends Activity {
// Error message or not yet loaded? Show a toast message instead of opening the items activity // Error message or not yet loaded? Show a toast message instead of opening the items activity
if (loader.hasError()) { if (loader.hasError()) {
Crouton.showText(this, R.string.rss_error, NavigationHelper.CROUTON_INFO_STYLE); SnackbarManager.show(Snackbar.with(this).text(R.string.rss_error).colorResource(R.color.red));
return; return;
} }
if (loader.getChannel() == null || loader.getChannel().getItems().size() == 0) { if (loader.getChannel() == null || loader.getChannel().getItems().size() == 0) {
Crouton.showText(this, R.string.rss_notloaded, NavigationHelper.CROUTON_INFO_STYLE); SnackbarManager.show(Snackbar.with(this).text(R.string.rss_notloaded).colorResource(R.color.red));
return; return;
} }
@ -175,14 +180,16 @@ public class RssfeedsActivity extends Activity {
// be loaded until the RSS feeds screen in opened again // be loaded until the RSS feeds screen in opened again
if (markAsViewedNow) { if (markAsViewedNow) {
String lastViewedItemUrl = null; String lastViewedItemUrl = null;
if (loader.getChannel().getItems() != null && loader.getChannel().getItems().size() > 0) if (loader.getChannel().getItems() != null && loader.getChannel().getItems().size() > 0) {
lastViewedItemUrl = loader.getChannel().getItems().get(0).getTheLink(); lastViewedItemUrl = loader.getChannel().getItems().get(0).getTheLink();
}
applicationSettings.setRssfeedLastViewer(loader.getSetting().getOrder(), new Date(), lastViewedItemUrl); applicationSettings.setRssfeedLastViewer(loader.getSetting().getOrder(), new Date(), lastViewedItemUrl);
} }
String name = loader.getChannel().getTitle(); String name = loader.getChannel().getTitle();
if (TextUtils.isEmpty(name)) if (TextUtils.isEmpty(name)) {
name = loader.getSetting().getName(); name = loader.getSetting().getName();
}
if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(loader.getSetting().getUrl())) { if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(loader.getSetting().getUrl())) {
name = Uri.parse(loader.getSetting().getUrl()).getHost(); name = Uri.parse(loader.getSetting().getUrl()).getHost();
} }

16
app/src/main/java/org/transdroid/core/gui/rss/RssfeedsAdapter.java

@ -16,16 +16,16 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import java.util.List; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext; import org.androidannotations.annotations.RootContext;
import org.transdroid.core.app.settings.RssfeedSetting; import org.transdroid.core.app.settings.RssfeedSetting;
import android.content.Context; import java.util.List;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/** /**
* Adapter that contains a list of {@link RssfeedSetting}s, each with associated loaded RSS feed {@link org.transdroid.core.rssparser.Channel}. * Adapter that contains a list of {@link RssfeedSetting}s, each with associated loaded RSS feed {@link org.transdroid.core.rssparser.Channel}.
@ -55,15 +55,17 @@ public class RssfeedsAdapter extends BaseAdapter {
@Override @Override
public int getCount() { public int getCount() {
if (loaders == null) if (loaders == null) {
return 0; return 0;
}
return loaders.size(); return loaders.size();
} }
@Override @Override
public RssfeedLoader getItem(int position) { public RssfeedLoader getItem(int position) {
if (loaders == null) if (loaders == null) {
return null; return null;
}
return loaders.get(position); return loaders.get(position);
} }

34
app/src/main/java/org/transdroid/core/gui/rss/RssfeedsFragment.java

@ -16,7 +16,12 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import java.util.List; import android.app.Fragment;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
@ -26,25 +31,20 @@ import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu; import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.gui.settings.*; import org.transdroid.core.gui.settings.MainSettingsActivity_;
import android.app.Fragment; import java.util.List;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
/** /**
* Fragment lists the RSS feeds the user wants to monitor and, if room, the list of items in a feed in a right pane. * 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 * @author Eric Kok
*/ */
@EFragment(resName = "fragment_rssfeeds") @EFragment(R.layout.fragment_rssfeeds)
@OptionsMenu(resName = "fragment_rssfeeds") @OptionsMenu(R.menu.fragment_rssfeeds)
public class RssfeedsFragment extends Fragment { public class RssfeedsFragment extends Fragment {
// Views // Views
@ViewById(resName = "rssfeeds_list") @ViewById(R.id.rssfeeds_list)
protected ListView feedsList; protected ListView feedsList;
@Bean @Bean
protected RssfeedsAdapter rssfeedsAdapter; protected RssfeedsAdapter rssfeedsAdapter;
@ -67,22 +67,22 @@ public class RssfeedsFragment extends Fragment {
@Override @Override
public void onPrepareOptionsMenu(Menu menu) { public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu); super.onPrepareOptionsMenu(menu);
menu.findItem(R.id.action_settings).setShowAsAction( boolean hasFeeds = rssfeedsAdapter != null && rssfeedsAdapter.getCount() > 0;
rssfeedsAdapter == null || rssfeedsAdapter.getCount() == 0 ? MenuItem.SHOW_AS_ACTION_ALWAYS menu.findItem(R.id.action_refresh).setVisible(hasFeeds);
: MenuItem.SHOW_AS_ACTION_NEVER); menu.findItem(R.id.action_settings).setShowAsAction(!hasFeeds ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
} }
@OptionsItem(resName = "action_settings") @OptionsItem(R.id.action_settings)
protected void openSettings() { protected void openSettings() {
MainSettingsActivity_.intent(getActivity()).start(); MainSettingsActivity_.intent(getActivity()).start();
} }
@OptionsItem(resName = "action_refresh") @OptionsItem(R.id.action_refresh)
protected void refreshScreen() { protected void refreshScreen() {
((RssfeedsActivity) getActivity()).refreshFeeds(); ((RssfeedsActivity) getActivity()).refreshFeeds();
} }
@ItemClick(resName = "rssfeeds_list") @ItemClick(R.id.rssfeeds_list)
protected void onFeedClicked(RssfeedLoader loader) { protected void onFeedClicked(RssfeedLoader loader) {
((RssfeedsActivity) getActivity()).openRssfeed(loader, true); ((RssfeedsActivity) getActivity()).openRssfeed(loader, true);
} }

13
app/src/main/java/org/transdroid/core/gui/rss/RssitemStatusLayout.java

@ -23,20 +23,21 @@ import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
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 * 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
* left indicating the view status, that is, if the item is new to the user or was viewed earlier. * status, that is, if the item is new to the user or was viewed earlier.
* @author Eric Kok * @author Eric Kok
*/ */
public class RssitemStatusLayout extends RelativeLayout { public class RssitemStatusLayout extends RelativeLayout {
private final float scale = getContext().getResources().getDisplayMetrics().density; private final float scale = getContext().getResources().getDisplayMetrics().density;
private final int WIDTH = (int) (6 * scale + 0.5f); private final int WIDTH = (int) (6 * scale + 0.5f);
private Boolean isNew = null;
private final Paint oldPaint = new Paint(); private final Paint oldPaint = new Paint();
private final Paint newPaint = new Paint(); private final Paint newPaint = new Paint();
private final RectF fullRect = new RectF(); private final RectF fullRect = new RectF();
private Boolean isNew = null;
public RssitemStatusLayout(Context context) { public RssitemStatusLayout(Context context) {
super(context); super(context);
@ -51,8 +52,8 @@ public class RssitemStatusLayout extends RelativeLayout {
} }
private void initPaints() { private void initPaints() {
oldPaint.setColor(0xFF9E9E9E); // Grey oldPaint.setColor(getResources().getColor(R.color.file_off)); // Grey
newPaint.setColor(0xFF8ACC12); // Normal green newPaint.setColor(getResources().getColor(R.color.file_normal)); // Normal green
} }
public void setIsNew(Boolean isNew) { public void setIsNew(Boolean isNew) {

15
app/src/main/java/org/transdroid/core/gui/rss/RssitemView.java

@ -16,19 +16,20 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.core.rssparser.Item;
import android.content.Context; import android.content.Context;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.rssparser.Item;
/** /**
* View that represents some {@link Item} object, which is a single item in some RSS feed. * View that represents some {@link Item} object, which is a single item in some RSS feed.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName = "list_item_rssitem") @EViewGroup(R.layout.list_item_rssitem)
public class RssitemView extends RssitemStatusLayout { public class RssitemView extends RssitemStatusLayout {
// Views // Views
@ -42,8 +43,8 @@ public class RssitemView extends RssitemStatusLayout {
public void bind(Item rssitem) { public void bind(Item rssitem) {
nameText.setText(rssitem.getTitle()); nameText.setText(rssitem.getTitle());
dateText.setText(rssitem.getPubdate() == null ? "" : DateUtils.getRelativeDateTimeString(getContext(), rssitem dateText.setText(rssitem.getPubdate() == null ? "" : DateUtils
.getPubdate().getTime(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, .getRelativeDateTimeString(getContext(), rssitem.getPubdate().getTime(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS,
DateUtils.FORMAT_ABBREV_MONTH)); DateUtils.FORMAT_ABBREV_MONTH));
setIsNew(rssitem.isNew()); setIsNew(rssitem.isNew());

33
app/src/main/java/org/transdroid/core/gui/rss/RssitemsActivity.java

@ -16,40 +16,43 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.Extra; import org.androidannotations.annotations.Extra;
import org.androidannotations.annotations.FragmentById; import org.androidannotations.annotations.FragmentById;
import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.settings.*; import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.*; import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.rssparser.Channel; import org.transdroid.core.rssparser.Channel;
import android.annotation.TargetApi; @EActivity(R.layout.activity_rssitems)
import android.app.Activity; public class RssitemsActivity extends ActionBarActivity {
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
@EActivity(resName = "activity_rssitems")
public class RssitemsActivity extends Activity {
@Extra @Extra
protected Channel rssfeed = null; protected Channel rssfeed = null;
@Extra @Extra
protected String rssfeedName; protected String rssfeedName;
@FragmentById(resName = "rssitems_fragment") @FragmentById(R.id.rssitems_fragment)
protected RssitemsFragment fragmentItems; protected RssitemsFragment fragmentItems;
@ViewById
protected Toolbar rssfeedsToolbar;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
// Set the theme according to the user preference // Set the theme according to the user preference
if (SystemSettings_.getInstance_(this).useDarkTheme()) { if (SystemSettings_.getInstance_(this).useDarkTheme()) {
setTheme(R.style.TransdroidTheme_Dark); setTheme(R.style.TransdroidTheme_Dark);
getActionBar().setIcon(R.drawable.ic_activity_torrents);
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@ -63,9 +66,9 @@ public class RssitemsActivity extends Activity {
return; return;
} }
// Simple action bar with up button and torrent name as title setSupportActionBar(rssfeedsToolbar);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(NavigationHelper.buildCondensedFontString(rssfeedName));
getActionBar().setTitle(NavigationHelper.buildCondensedFontString(rssfeedName)); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Get the intent extras and show them to the already loaded fragment // Get the intent extras and show them to the already loaded fragment
fragmentItems.update(rssfeed, false); fragmentItems.update(rssfeed, false);

16
app/src/main/java/org/transdroid/core/gui/rss/RssitemsAdapter.java

@ -16,16 +16,16 @@
*/ */
package org.transdroid.core.gui.rss; package org.transdroid.core.gui.rss;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
import android.content.Context; import android.content.Context;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext;
import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item;
/** /**
* Adapter that contains a list of {@link Item}s in an RSS feed. * Adapter that contains a list of {@link Item}s in an RSS feed.
* @author Eric Kok * @author Eric Kok
@ -54,15 +54,17 @@ public class RssitemsAdapter extends BaseAdapter {
@Override @Override
public int getCount() { public int getCount() {
if (rssfeed == null) if (rssfeed == null) {
return 0; return 0;
}
return rssfeed.getItems().size(); return rssfeed.getItems().size();
} }
@Override @Override
public Item getItem(int position) { public Item getItem(int position) {
if (rssfeed == null) if (rssfeed == null) {
return null; return null;
}
return rssfeed.getItems().get(position); return rssfeed.getItems().get(position);
} }

33
app/src/main/java/org/transdroid/core/gui/rss/RssitemsFragment.java

@ -27,6 +27,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
@ -37,6 +38,9 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.EFragment;
@ -44,23 +48,20 @@ import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.ItemClick; import org.androidannotations.annotations.ItemClick;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.gui.*; import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.navigation.SelectionManagerMode; import org.transdroid.core.gui.navigation.SelectionManagerMode;
import org.transdroid.core.gui.search.*; import org.transdroid.core.gui.search.SearchActivity_;
import org.transdroid.core.rssparser.Channel; import org.transdroid.core.rssparser.Channel;
import org.transdroid.core.rssparser.Item; import org.transdroid.core.rssparser.Item;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
* Fragment that lists the items in a specific RSS feed * Fragment that lists the items in a specific RSS feed
* @author Eric Kok * @author Eric Kok
*/ */
@EFragment(resName = "fragment_rssitems") @EFragment(R.layout.fragment_rssitems)
public class RssitemsFragment extends Fragment { public class RssitemsFragment extends Fragment {
@InstanceState @InstanceState
@ -69,7 +70,7 @@ public class RssitemsFragment extends Fragment {
protected boolean hasError = false; protected boolean hasError = false;
// Views // Views
@ViewById(resName = "rssitems_list") @ViewById(R.id.rssitems_list)
protected ListView rssitemsList; protected ListView rssitemsList;
private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() { private MultiChoiceModeListener onItemsSelected = new MultiChoiceModeListener() {
@ -79,7 +80,8 @@ public class RssitemsFragment extends Fragment {
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Show contextual action bar to add items in batch mode // Show contextual action bar to add items in batch mode
mode.getMenuInflater().inflate(R.menu.fragment_rssitems_cab, menu); mode.getMenuInflater().inflate(R.menu.fragment_rssitems_cab, menu);
selectionManagerMode = new SelectionManagerMode(rssitemsList, R.plurals.rss_itemsselected); Context themedContext = ((ActionBarActivity) getActivity()).getSupportActionBar().getThemedContext();
selectionManagerMode = new SelectionManagerMode(themedContext, rssitemsList, R.plurals.rss_itemsselected);
selectionManagerMode.onCreateActionMode(mode, menu); selectionManagerMode.onCreateActionMode(mode, menu);
return true; return true;
} }
@ -92,7 +94,7 @@ public class RssitemsFragment extends Fragment {
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// Get checked torrents // Get checked torrents
List<Item> checked = new ArrayList<Item>(); List<Item> checked = new ArrayList<>();
for (int i = 0; i < rssitemsList.getCheckedItemPositions().size(); i++) { for (int i = 0; i < rssitemsList.getCheckedItemPositions().size(); i++) {
if (rssitemsList.getCheckedItemPositions().valueAt(i)) { if (rssitemsList.getCheckedItemPositions().valueAt(i)) {
checked.add(rssitemsAdapter.getItem(rssitemsList.getCheckedItemPositions().keyAt(i))); checked.add(rssitemsAdapter.getItem(rssitemsList.getCheckedItemPositions().keyAt(i)));
@ -126,8 +128,7 @@ public class RssitemsFragment extends Fragment {
} }
names.append(checked.get(f).getTitle()); names.append(checked.get(f).getTitle());
} }
ClipboardManager clipboardManager = ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
(ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString())); clipboardManager.setPrimaryClip(ClipData.newPlainText("Transdroid", names.toString()));
mode.finish(); mode.finish();
return true; return true;
@ -146,18 +147,15 @@ public class RssitemsFragment extends Fragment {
return new AlertDialog.Builder(getActivity()).setMessage(first.getDescription()) return new AlertDialog.Builder(getActivity()).setMessage(first.getDescription())
.setPositiveButton(R.string.action_close, null).create(); .setPositiveButton(R.string.action_close, null).create();
} }
;
}.show(getFragmentManager(), "RssItemDescription"); }.show(getFragmentManager(), "RssItemDescription");
} else if (itemId == R.id.action_openwebsite) { } else if (itemId == R.id.action_openwebsite) {
// Open the browser to show the website contained in the item's link tag // 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.makeText(getActivity(), getString(R.string.search_openingdetails, first.getTitle()), Toast.LENGTH_LONG).show();
Toast.LENGTH_LONG).show();
if (!TextUtils.isEmpty(first.getLink())) { if (!TextUtils.isEmpty(first.getLink())) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getLink()))); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getLink())));
} else { } else {
// No URL was specified in the RSS feed item link tag (or no link tag was present) // No URL was specified in the RSS feed item link tag (or no link tag was present)
Crouton.showText(getActivity(), R.string.error_no_link, NavigationHelper.CROUTON_ERROR_STYLE); SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_no_link).colorResource(R.color.red));
} }
} else if (itemId == R.id.action_useassearch) { } else if (itemId == R.id.action_useassearch) {
// Use the RSS item title to start a new search (mimicking the search manager style) // Use the RSS item title to start a new search (mimicking the search manager style)
@ -201,8 +199,7 @@ public class RssitemsFragment extends Fragment {
/** /**
* Update the shown RSS items in the list. * Update the shown RSS items in the list.
* @param channel The loaded RSS content channel object * @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 * @param hasError True if there were errors in loading the channel, in which case an error text is shown; false otherwise
* otherwise
*/ */
public void update(Channel channel, boolean hasError) { public void update(Channel channel, boolean hasError) {
rssitemsAdapter.update(channel); rssitemsAdapter.update(channel);

4
app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java

@ -106,8 +106,8 @@ public class BarcodeHelper {
* can be constructed for it * can be constructed for it
*/ */
public static String handleScanResult(int resultCode, Intent data, boolean supportsSearch) { public static String handleScanResult(int resultCode, Intent data, boolean supportsSearch) {
String contents = data.getStringExtra("SCAN_RESULT"); String contents = data != null ? data.getStringExtra("SCAN_RESULT") : null;
String formatName = data.getStringExtra("SCAN_RESULT_FORMAT"); String formatName = data != null ? data.getStringExtra("SCAN_RESULT_FORMAT") : null;
if (formatName != null && formatName.equals("QR_CODE")) { if (formatName != null && formatName.equals("QR_CODE")) {
// Scanned barcode was a QR code: return the contents directly // Scanned barcode was a QR code: return the contents directly
return contents; return contents;

69
app/src/main/java/org/transdroid/core/gui/search/SearchActivity.java

@ -17,15 +17,16 @@
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
import android.app.Activity;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.SearchRecentSuggestions; import android.provider.SearchRecentSuggestions;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -46,23 +47,26 @@ import org.androidannotations.annotations.ViewById;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.app.search.SearchHelper; import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchSite; import org.transdroid.core.app.search.SearchSite;
import org.transdroid.core.app.settings.*; import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.gui.*; import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.app.settings.WebsearchSetting;
import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import java.util.List; import java.util.List;
/** /**
* An activity that shows search results to the user (after a query was supplied by the standard Android search manager) * 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
* and either shows the list of search sites on the left (e.g. on tablets) or allows switching between search sites via * search sites on the left (e.g. on tablets) or allows switching between search sites via the action bar spinner.
* the action bar spinner.
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity(resName = "activity_search") @EActivity(R.layout.activity_search)
@OptionsMenu(resName = "activity_search") @OptionsMenu(R.menu.activity_search)
public class SearchActivity extends Activity implements OnNavigationListener { public class SearchActivity extends ActionBarActivity implements ActionBar.OnNavigationListener {
@FragmentById(resName = "searchresults_fragment") @ViewById
protected Toolbar searchToolbar;
@FragmentById(R.id.searchresults_fragment)
protected SearchResultsFragment fragmentResults; protected SearchResultsFragment fragmentResults;
@ViewById @ViewById
protected ListView searchsitesList; protected ListView searchsitesList;
@ -71,14 +75,11 @@ public class SearchActivity extends Activity implements OnNavigationListener {
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
@Bean @Bean
protected NavigationHelper navigationHelper;
@Bean
protected SearchHelper searchHelper; protected SearchHelper searchHelper;
@SystemService @SystemService
protected SearchManager searchManager; protected SearchManager searchManager;
private MenuItem searchMenu = null; private MenuItem searchMenu = null;
private SearchRecentSuggestions suggestions = private SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE);
new SearchRecentSuggestions(this, SearchHistoryProvider.AUTHORITY, SearchHistoryProvider.MODE);
private List<SearchSetting> searchSites; private List<SearchSetting> searchSites;
private SearchSetting lastUsedSite; private SearchSetting lastUsedSite;
@ -97,7 +98,6 @@ public class SearchActivity extends Activity implements OnNavigationListener {
// Set the theme according to the user preference // Set the theme according to the user preference
if (SystemSettings_.getInstance_(this).useDarkTheme()) { if (SystemSettings_.getInstance_(this).useDarkTheme()) {
setTheme(R.style.TransdroidTheme_Dark); setTheme(R.style.TransdroidTheme_Dark);
getActionBar().setIcon(R.drawable.ic_activity_torrents);
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@ -105,6 +105,9 @@ public class SearchActivity extends Activity implements OnNavigationListener {
@AfterViews @AfterViews
protected void init() { protected void init() {
setSupportActionBar(searchToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Get the user query, as coming from the standard SearchManager // Get the user query, as coming from the standard SearchManager
handleIntent(getIntent()); handleIntent(getIntent());
@ -127,25 +130,26 @@ public class SearchActivity extends Activity implements OnNavigationListener {
} }
// Allow site selection via list (on large screens) or action bar spinner // Allow site selection via list (on large screens) or action bar spinner
getActionBar().setDisplayHomeAsUpEnabled(true);
if (searchsitesList != null) { if (searchsitesList != null) {
// The current layout has a dedicated list view to select the search site // The current layout has a dedicated list view to select the search site
SearchSitesAdapter searchSitesAdapter = SearchSitesAdapter_.getInstance_(this); SearchSitesAdapter searchSitesAdapter = SearchSitesAdapter_.getInstance_(this);
searchSitesAdapter.update(searchSites); searchSitesAdapter.update(searchSites);
searchsitesList.setAdapter(searchSitesAdapter); searchsitesList.setAdapter(searchSitesAdapter);
searchsitesList.setOnItemClickListener(onSearchSiteClicked); searchsitesList.setOnItemClickListener(onSearchSiteClicked);
// Select the last used site; this also starts the search! // Select the last used site and start the search
if (lastUsedPosition >= 0) { if (lastUsedPosition >= 0) {
searchsitesList.setItemChecked(lastUsedPosition, true); searchsitesList.setItemChecked(lastUsedPosition, true);
lastUsedSite = searchSites.get(lastUsedPosition);
refreshSearch();
} }
} else { } else {
// Use the action bar spinner to select sites // Use the action bar spinner to select sites
getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
getActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(false);
getActionBar().setListNavigationCallbacks(new SearchSettingsDropDownAdapter(this, searchSites), this); getSupportActionBar().setListNavigationCallbacks(new SearchSettingsDropDownAdapter(searchToolbar.getContext(), searchSites), this);
// Select the last used site; this also starts the search! // Select the last used site; this also starts the search!
if (lastUsedPosition >= 0) { if (lastUsedPosition >= 0) {
getActionBar().setSelectedNavigationItem(lastUsedPosition); getSupportActionBar().setSelectedNavigationItem(lastUsedPosition);
} }
} }
@ -154,15 +158,15 @@ public class SearchActivity extends Activity implements OnNavigationListener {
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
if (navigationHelper.enableSearchUi()) {
// Add an expandable SearchView to the action bar // Add an expandable SearchView to the action bar
MenuItem item = menu.findItem(R.id.action_search); MenuItem item = menu.findItem(R.id.action_search);
final SearchView searchView = new SearchView(this); final SearchView searchView = new SearchView(searchToolbar.getContext());
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setQueryRefinementEnabled(true); searchView.setQueryRefinementEnabled(true);
item.setActionView(searchView); //searchView.setIconified(false);
searchView.setIconifiedByDefault(false);
MenuItemCompat.setActionView(item, searchView);
searchMenu = item; searchMenu = item;
}
return true; return true;
} }
@ -253,7 +257,7 @@ public class SearchActivity extends Activity implements OnNavigationListener {
} }
@OptionsItem(resName = "action_refresh") @OptionsItem(R.id.action_refresh)
protected void refreshSearch() { protected void refreshSearch() {
if (searchMenu != null) { if (searchMenu != null) {
@ -265,8 +269,7 @@ public class SearchActivity extends Activity implements OnNavigationListener {
// Start a browser page directly to the requested search results // Start a browser page directly to the requested search results
WebsearchSetting websearch = (WebsearchSetting) lastUsedSite; WebsearchSetting websearch = (WebsearchSetting) lastUsedSite;
startActivity( startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(websearch.getBaseUrl().replace("%s", lastUsedQuery))));
new Intent(Intent.ACTION_VIEW, Uri.parse(websearch.getBaseUrl().replace("%s", lastUsedQuery))));
finish(); finish();
} else if (lastUsedSite instanceof SearchSite) { } else if (lastUsedSite instanceof SearchSite) {
@ -274,15 +277,15 @@ public class SearchActivity extends Activity implements OnNavigationListener {
// Save the search site currently used to search for future usage // Save the search site currently used to search for future usage
applicationSettings.setLastUsedSearchSite(lastUsedSite); applicationSettings.setLastUsedSearchSite(lastUsedSite);
// Update the activity title (only shown on large devices) // Update the activity title (only shown on large devices)
getActionBar().setTitle(NavigationHelper.buildCondensedFontString( getSupportActionBar().setTitle(
getString(R.string.search_queryonsite, lastUsedQuery, lastUsedSite.getName()))); NavigationHelper.buildCondensedFontString(getString(R.string.search_queryonsite, lastUsedQuery, lastUsedSite.getName())));
// Ask the results fragment to start a search for the specified query // Ask the results fragment to start a search for the specified query
fragmentResults.startSearch(lastUsedQuery, (SearchSite) lastUsedSite); fragmentResults.startSearch(lastUsedQuery, (SearchSite) lastUsedSite);
} }
} }
@OptionsItem(resName = "action_downloadsearch") @OptionsItem(R.id.action_downloadsearch)
protected void downloadSearchModule() { protected void downloadSearchModule() {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/latest-search"))); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.transdroid.org/latest-search")));
} }

18
app/src/main/java/org/transdroid/core/gui/search/SearchResultsAdapter.java

@ -16,16 +16,16 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import java.util.List; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext; import org.androidannotations.annotations.RootContext;
import org.transdroid.core.app.search.SearchResult; import org.transdroid.core.app.search.SearchResult;
import android.content.Context; import java.util.List;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/** /**
* Adapter that contains a list of {@link SearchResult}s. * Adapter that contains a list of {@link SearchResult}s.
@ -41,7 +41,7 @@ public class SearchResultsAdapter extends BaseAdapter {
/** /**
* Allows updating the search results, replacing the old data * Allows updating the search results, replacing the old data
* @param newRssfeeds The new list of search results * @param results The new list of search results
*/ */
public void update(List<SearchResult> results) { public void update(List<SearchResult> results) {
this.results = results; this.results = results;
@ -55,15 +55,17 @@ public class SearchResultsAdapter extends BaseAdapter {
@Override @Override
public int getCount() { public int getCount() {
if (results == null) if (results == null) {
return 0; return 0;
}
return results.size(); return results.size();
} }
@Override @Override
public SearchResult getItem(int position) { public SearchResult getItem(int position) {
if (results == null) if (results == null) {
return null; return null;
}
return results.get(position); return results.get(position);
} }

172
app/src/main/java/org/transdroid/core/gui/search/SearchResultsFragment.java

@ -17,8 +17,11 @@
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -29,6 +32,9 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background; import org.androidannotations.annotations.Background;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
@ -42,20 +48,19 @@ import org.transdroid.core.app.search.SearchHelper;
import org.transdroid.core.app.search.SearchHelper.SearchSortOrder; import org.transdroid.core.app.search.SearchHelper.SearchSortOrder;
import org.transdroid.core.app.search.SearchResult; import org.transdroid.core.app.search.SearchResult;
import org.transdroid.core.app.search.SearchSite; import org.transdroid.core.app.search.SearchSite;
import org.transdroid.core.app.settings.*; import org.transdroid.core.app.settings.SystemSettings_;
import org.transdroid.core.gui.*; import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.*; import org.transdroid.core.gui.navigation.NavigationHelper_;
import org.transdroid.core.gui.navigation.SelectionManagerMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
* Fragment that lists the items in a specific RSS feed * Fragment that lists the items in a specific RSS feed
* @author Eric Kok * @author Eric Kok
*/ */
@EFragment(resName = "fragment_searchresults") @EFragment(R.layout.fragment_searchresults)
public class SearchResultsFragment extends Fragment { public class SearchResultsFragment extends Fragment {
@InstanceState @InstanceState
@ -66,80 +71,8 @@ public class SearchResultsFragment extends Fragment {
protected SearchHelper searchHelper; protected SearchHelper searchHelper;
// Views // Views
@ViewById(resName = "searchresults_list") @ViewById(R.id.searchresults_list)
protected ListView resultsList; protected ListView resultsList;
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);
selectionManagerMode = new SelectionManagerMode(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<SearchResult> checked = new ArrayList<SearchResult>();
for (int i = 0; i < resultsList.getCheckedItemPositions().size(); i++) {
if (resultsList.getCheckedItemPositions().valueAt(i)) {
checked.add(resultsAdapter.getItem(resultsList.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).getTorrentUrl();
titles[i] = checked.get(i).getName();
}
intent.putExtra("TORRENT_URLS", urls);
intent.putExtra("TORRENT_TITLES", titles);
if (resultsSource != null) {
intent.putExtra("PRIVATE_SOURCE", resultsSource);
}
startActivity(intent);
mode.finish();
return true;
} else if (itemId == R.id.action_showdetails) {
SearchResult first = checked.get(0);
// Open the torrent's web page in the browser
if (checked.size() > 1) {
Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getName()),
Toast.LENGTH_LONG).show();
}
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getDetailsUrl())));
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) {
selectionManagerMode.onDestroyActionMode(mode);
}
};
@Bean @Bean
protected SearchResultsAdapter resultsAdapter; protected SearchResultsAdapter resultsAdapter;
@ViewById @ViewById
@ -196,10 +129,10 @@ public class SearchResultsFragment extends Fragment {
emptyText.setVisibility(View.GONE); emptyText.setVisibility(View.GONE);
} }
@ItemClick(resName = "searchresults_list") @ItemClick(R.id.searchresults_list)
protected void onItemClicked(SearchResult item) { protected void onItemClicked(SearchResult item) {
if (item.getTorrentUrl() == null) { if (item.getTorrentUrl() == null) {
Crouton.showText(getActivity(), R.string.error_notorrentfile, NavigationHelper.CROUTON_ERROR_STYLE); SnackbarManager.show(Snackbar.with(getActivity()).text(R.string.error_notorrentfile).colorResource(R.color.red));
return; return;
} }
// Don't broadcast this intent; we can safely assume this is intended for Transdroid only // Don't broadcast this intent; we can safely assume this is intended for Transdroid only
@ -212,4 +145,81 @@ public class SearchResultsFragment extends Fragment {
startActivity(i); 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 = ((ActionBarActivity) 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<SearchResult> checked = new ArrayList<SearchResult>();
for (int i = 0; i < resultsList.getCheckedItemPositions().size(); i++) {
if (resultsList.getCheckedItemPositions().valueAt(i)) {
checked.add(resultsAdapter.getItem(resultsList.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).getTorrentUrl();
titles[i] = checked.get(i).getName();
}
intent.putExtra("TORRENT_URLS", urls);
intent.putExtra("TORRENT_TITLES", titles);
if (resultsSource != null) {
intent.putExtra("PRIVATE_SOURCE", resultsSource);
}
startActivity(intent);
mode.finish();
return true;
} else if (itemId == R.id.action_showdetails) {
SearchResult first = checked.get(0);
// Open the torrent's web page in the browser
if (checked.size() > 1) {
Toast.makeText(getActivity(), getString(R.string.search_openingdetails, first.getName()), Toast.LENGTH_LONG).show();
}
if (TextUtils.isEmpty(first.getDetailsUrl())) {
Toast.makeText(getActivity(), getString(R.string.error_invalid_url_form, first.getName()), Toast.LENGTH_LONG).show();
return false;
}
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(first.getDetailsUrl())));
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) {
selectionManagerMode.onDestroyActionMode(mode);
}
};
} }

9
app/src/main/java/org/transdroid/core/gui/search/SearchSettingSelectionView.java

@ -16,18 +16,19 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import android.content.Context; import android.content.Context;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
/** /**
* View that shows, as part of the action bar spinner, which {@link SearchSetting} is currently chosen. * View that shows, as part of the action bar spinner, which {@link SearchSetting} is currently chosen.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName = "actionbar_searchsite") @EViewGroup(R.layout.actionbar_searchsite)
public class SearchSettingSelectionView extends FrameLayout { public class SearchSettingSelectionView extends FrameLayout {
@ViewById @ViewById

11
app/src/main/java/org/transdroid/core/gui/search/SearchSettingsDropDownAdapter.java

@ -16,18 +16,17 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import java.util.List; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import org.transdroid.core.gui.lists.SimpleListItem; import org.transdroid.core.gui.lists.SimpleListItem;
import org.transdroid.core.gui.navigation.FilterListItemAdapter; import org.transdroid.core.gui.navigation.FilterListItemAdapter;
import android.content.Context; import java.util.List;
import android.view.View;
import android.view.ViewGroup;
/** /**
* List adapter that holds search settings, that is, web searches and in-app search sites, displayed as content to a * List adapter that holds search settings, that is, web searches and in-app search sites, displayed as content to a Spinner instead of a ListView.
* Spinner instead of a ListView.
* @author Eric Kok * @author Eric Kok
*/ */
public class SearchSettingsDropDownAdapter extends FilterListItemAdapter { public class SearchSettingsDropDownAdapter extends FilterListItemAdapter {

20
app/src/main/java/org/transdroid/core/gui/search/SearchSiteView.java

@ -16,23 +16,24 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import android.content.Context;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EViewGroup; import org.androidannotations.annotations.EViewGroup;
import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.ViewById;
import org.transdroid.R;
import org.transdroid.core.app.settings.RssfeedSetting; import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import android.content.Context;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
/** /**
* View that represents some {@link RssfeedSetting} object and displays name as well as loads a favicon for the feed's * 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
* site and can load how many new items are available. * items are available.
* @author Eric Kok * @author Eric Kok
*/ */
@EViewGroup(resName = "list_item_searchsite") @EViewGroup(R.layout.list_item_searchsite)
public class SearchSiteView extends LinearLayout { public class SearchSiteView extends LinearLayout {
private static final String GETFVO_URL = "http://g.etfv.co/%1$s"; private static final String GETFVO_URL = "http://g.etfv.co/%1$s";
@ -57,8 +58,7 @@ public class SearchSiteView extends LinearLayout {
// Clear and then asynchronously load the site's favicon // Clear and then asynchronously load the site's favicon
// Uses the g.etfv.co service to resolve the favicon of any URL // Uses the g.etfv.co service to resolve the favicon of any URL
faviconImage.setImageDrawable(null); faviconImage.setImageDrawable(null);
navigationHelper.getImageCache().displayImage(String.format(GETFVO_URL, rssfeedLoader.getBaseUrl()), navigationHelper.getImageCache().displayImage(String.format(GETFVO_URL, rssfeedLoader.getBaseUrl()), faviconImage);
faviconImage);
} }

16
app/src/main/java/org/transdroid/core/gui/search/SearchSitesAdapter.java

@ -16,17 +16,17 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import java.util.List; import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.EBean;
import org.androidannotations.annotations.RootContext; import org.androidannotations.annotations.RootContext;
import org.transdroid.core.app.search.SearchSite; import org.transdroid.core.app.search.SearchSite;
import org.transdroid.core.app.settings.WebsearchSetting; import org.transdroid.core.app.settings.WebsearchSetting;
import android.content.Context; import java.util.List;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
/** /**
* Adapter that contains a list of {@link SearchSetting}s, either {@link SearchSite} or {@link WebsearchSetting}. * Adapter that contains a list of {@link SearchSetting}s, either {@link SearchSite} or {@link WebsearchSetting}.
@ -56,15 +56,17 @@ public class SearchSitesAdapter extends BaseAdapter {
@Override @Override
public int getCount() { public int getCount() {
if (sites == null) if (sites == null) {
return 0; return 0;
}
return sites.size(); return sites.size();
} }
@Override @Override
public SearchSetting getItem(int position) { public SearchSetting getItem(int position) {
if (sites == null) if (sites == null) {
return null; return null;
}
return sites.get(position); return sites.get(position);
} }

57
app/src/main/java/org/transdroid/core/gui/search/UrlEntryDialog.java

@ -16,56 +16,43 @@
*/ */
package org.transdroid.core.gui.search; package org.transdroid.core.gui.search;
import org.transdroid.core.gui.TorrentsActivity;
import org.transdroid.core.gui.navigation.NavigationHelper;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.DialogFragment;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.inputmethod.InputMethodManager; import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import com.afollestad.materialdialogs.MaterialDialog;
import org.transdroid.R;
import org.transdroid.core.gui.TorrentsActivity;
import org.transdroid.core.gui.navigation.NavigationHelper;
public class UrlEntryDialog { public class UrlEntryDialog {
/** /**
* Opens a dialog that allows entry of a single URL string, which (on confirmation) will be supplied to the calling * Opens a dialog that allows entry of a single URL string, which (on confirmation) will be supplied to the calling activity's {@link
* activity's {@link TorrentsActivity#addTorrentByUrl(String, String) method}. * TorrentsActivity#addTorrentByUrl(String, String) method}.
* @param activity The activity that opens (and owns) this dialog * @param activity The activity that opens (and owns) this dialog
*/ */
@SuppressLint("ValidFragment") public static void show(final TorrentsActivity activity) {
public static void startUrlEntry(final TorrentsActivity activity) { View inputLayout = LayoutInflater.from(activity).inflate(R.layout.dialog_url, null);
new DialogFragment() { final EditText urlEdit = (EditText) inputLayout.findViewById(R.id.url_edit);
@TargetApi(Build.VERSION_CODES.HONEYCOMB) ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
public android.app.Dialog onCreateDialog(android.os.Bundle savedInstanceState) {
final EditText urlInput = new EditText(activity);
urlInput.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
if (android.os.Build.VERSION.SDK_INT >= 11) {
ClipboardManager clipboard = (ClipboardManager) activity
.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClip().getItemCount() > 0) { if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClip().getItemCount() > 0) {
CharSequence content = clipboard.getPrimaryClip().getItemAt(0).coerceToText(activity); CharSequence content = clipboard.getPrimaryClip().getItemAt(0).coerceToText(activity);
urlInput.setText(content); urlEdit.setText(content);
}
} }
((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput( new MaterialDialog.Builder(activity).customView(inputLayout, false).positiveText(android.R.string.ok).negativeText(android.R.string.cancel)
InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); .callback(new MaterialDialog.ButtonCallback() {
return new AlertDialog.Builder(activity).setView(urlInput)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onPositive(MaterialDialog dialog) {
// Assume text entry box input as URL and treat the filename (after the last /) as title String url = urlEdit.getText().toString();
String url = urlInput.getText().toString();
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
if (activity != null && !TextUtils.isEmpty(url)) { if (!TextUtils.isEmpty(url)) {
String title = NavigationHelper.extractNameFromUri(uri); String title = NavigationHelper.extractNameFromUri(uri);
if (uri.getScheme() != null && uri.getScheme().equals("magnet")) { if (uri.getScheme() != null && uri.getScheme().equals("magnet")) {
activity.addTorrentByMagnetUrl(url, title); activity.addTorrentByMagnetUrl(url, title);
@ -74,9 +61,7 @@ public class UrlEntryDialog {
} }
} }
} }
}).setNegativeButton(android.R.string.cancel, null).create(); }).show();
};
}.show(activity.getFragmentManager(), "urlentry");
} }
} }

4
app/src/main/java/org/transdroid/core/gui/settings/HelpSettingsActivity.java

@ -37,7 +37,7 @@ import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
@EActivity @EActivity
public class HelpSettingsActivity extends PreferenceActivity { public class HelpSettingsActivity extends PreferenceCompatActivity {
protected static final int DIALOG_CHANGELOG = 0; protected static final int DIALOG_CHANGELOG = 0;
protected static final int DIALOG_ABOUT = 1; protected static final int DIALOG_ABOUT = 1;
@ -57,7 +57,7 @@ public class HelpSettingsActivity extends PreferenceActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Just load the system-related preferences from XML // Just load the system-related preferences from XML
addPreferencesFromResource(R.xml.pref_help); addPreferencesFromResource(R.xml.pref_help);

9
app/src/main/java/org/transdroid/core/gui/settings/KeyBoundPreferencesActivity.java

@ -28,7 +28,6 @@ import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.text.method.PasswordTransformationMethod; import android.text.method.PasswordTransformationMethod;
@ -43,13 +42,13 @@ import android.text.method.PasswordTransformationMethod;
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
public abstract class KeyBoundPreferencesActivity extends PreferenceActivity { public abstract class KeyBoundPreferencesActivity extends PreferenceCompatActivity {
@Extra @Extra
protected int key = -1; protected int key = -1;
private SharedPreferences sharedPrefs; private SharedPreferences sharedPrefs;
private Map<String, String> originalSummaries = new HashMap<String, String>(); private Map<String, String> originalSummaries = new HashMap<>();
/** /**
* Should be called during the activity {@link #onCreate(android.os.Bundle)} (but after super.onCreate(Bundle)) to * Should be called during the activity {@link #onCreate(android.os.Bundle)} (but after super.onCreate(Bundle)) to
@ -78,14 +77,14 @@ public abstract class KeyBoundPreferencesActivity extends PreferenceActivity {
// Monitor preference changes // Monitor preference changes
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener( PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(
onPreferenceChangeListener); onPreferenceChangeListener);
}; }
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
// Stop monitoring preference changes // Stop monitoring preference changes
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener( PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(
onPreferenceChangeListener); onPreferenceChangeListener);
}; }
private OnSharedPreferenceChangeListener onPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private OnSharedPreferenceChangeListener onPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override

77
app/src/main/java/org/transdroid/core/gui/settings/MainSettingsActivity.java

@ -28,8 +28,6 @@ import android.os.Bundle;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener; import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.view.View;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
@ -41,9 +39,8 @@ import org.transdroid.core.app.settings.ApplicationSettings;
import org.transdroid.core.app.settings.RssfeedSetting; import org.transdroid.core.app.settings.RssfeedSetting;
import org.transdroid.core.app.settings.ServerSetting; import org.transdroid.core.app.settings.ServerSetting;
import org.transdroid.core.app.settings.WebsearchSetting; import org.transdroid.core.app.settings.WebsearchSetting;
import org.transdroid.core.gui.*; import org.transdroid.core.gui.TorrentsActivity_;
import org.transdroid.core.gui.navigation.NavigationHelper; import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.settings.OverflowPreference.OnOverflowClicked;
import org.transdroid.core.gui.settings.RssfeedPreference.OnRssfeedClickedListener; import org.transdroid.core.gui.settings.RssfeedPreference.OnRssfeedClickedListener;
import org.transdroid.core.gui.settings.ServerPreference.OnServerClickedListener; import org.transdroid.core.gui.settings.ServerPreference.OnServerClickedListener;
import org.transdroid.core.gui.settings.WebsearchPreference.OnWebsearchClickedListener; import org.transdroid.core.gui.settings.WebsearchPreference.OnWebsearchClickedListener;
@ -55,21 +52,14 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* The main activity that provides access to all application settings. It shows the configured serves, web search sites * The main activity that provides access to all application settings. It shows the configured serves, web search sites and RSS feeds along with other
* and RSS feeds along with other general settings. * general settings.
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
public class MainSettingsActivity extends PreferenceActivity { public class MainSettingsActivity extends PreferenceCompatActivity {
protected static final int DIALOG_ADDSEEDBOX = 0; protected static final int DIALOG_ADDSEEDBOX = 0;
private OnOverflowClicked onOverflowClicked = new OnOverflowClicked() {
@SuppressWarnings("deprecation")
@Override
public void onOverflowClicked(View overflowButton) {
showDialog(DIALOG_ADDSEEDBOX);
}
};
@Bean @Bean
protected NavigationHelper navigationHelper; protected NavigationHelper navigationHelper;
@Bean @Bean
@ -78,8 +68,12 @@ public class MainSettingsActivity extends PreferenceActivity {
protected SearchHelper searchHelper; protected SearchHelper searchHelper;
protected SharedPreferences prefs; protected SharedPreferences prefs;
private OnPreferenceClickListener onAddServer = new OnPreferenceClickListener() { private OnPreferenceClickListener onAddServer = new OnPreferenceClickListener() {
@SuppressWarnings("deprecation")
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
if (navigationHelper.enableSeedboxes())
showDialog(DIALOG_ADDSEEDBOX);
else
ServerSettingsActivity_.intent(MainSettingsActivity.this).start(); ServerSettingsActivity_.intent(MainSettingsActivity.this).start();
return true; return true;
} }
@ -130,8 +124,7 @@ public class MainSettingsActivity extends PreferenceActivity {
public void onSeedboxClicked(ServerSetting serverSetting, SeedboxProvider provider, int seedboxOffset) { public void onSeedboxClicked(ServerSetting serverSetting, SeedboxProvider provider, int seedboxOffset) {
// NOTE: The seedboxOffset is the seedbox type-unique order that we need to supply uin the Extras bundle to // NOTE: The seedboxOffset is the seedbox type-unique order that we need to supply uin the Extras bundle to
// edit this specific seedbox // edit this specific seedbox
startActivity(provider.getSettings().getSettingsActivityIntent(MainSettingsActivity.this) startActivity(provider.getSettings().getSettingsActivityIntent(MainSettingsActivity.this).putExtra("key", seedboxOffset));
.putExtra("key", seedboxOffset));
} }
}; };
private OnWebsearchClickedListener onWebsearchClicked = new OnWebsearchClickedListener() { private OnWebsearchClickedListener onWebsearchClicked = new OnWebsearchClickedListener() {
@ -150,8 +143,10 @@ public class MainSettingsActivity extends PreferenceActivity {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
// Start the configuration activity for this specific chosen seedbox // Start the configuration activity for this specific chosen seedbox
startActivity( if (which == 0)
SeedboxProvider.values()[which].getSettings().getSettingsActivityIntent(MainSettingsActivity.this)); ServerSettingsActivity_.intent(MainSettingsActivity.this).start();
else
startActivity(SeedboxProvider.values()[which - 1].getSettings().getSettingsActivityIntent(MainSettingsActivity.this));
} }
}; };
@ -166,7 +161,7 @@ public class MainSettingsActivity extends PreferenceActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
prefs = getPreferenceManager().getSharedPreferences(); prefs = getPreferenceManager().getSharedPreferences();
if (getPreferenceScreen() != null) { if (getPreferenceScreen() != null) {
@ -178,13 +173,7 @@ public class MainSettingsActivity extends PreferenceActivity {
// Load the preference menu and attach actions // Load the preference menu and attach actions
addPreferencesFromResource(R.xml.pref_main); addPreferencesFromResource(R.xml.pref_main);
OverflowPreference addServerPrefernce = (OverflowPreference) findPreference("header_addserver"); findPreference("header_addserver").setOnPreferenceClickListener(onAddServer);
addServerPrefernce.setOnPreferenceClickListener(onAddServer);
if (navigationHelper.enableSeedboxes()) {
addServerPrefernce.setOnOverflowClickedListener(onOverflowClicked);
} else {
addServerPrefernce.hideOverflowButton();
}
if (enableSearchUi) { if (enableSearchUi) {
findPreference("header_addwebsearch").setOnPreferenceClickListener(onAddWebsearch); findPreference("header_addwebsearch").setOnPreferenceClickListener(onAddWebsearch);
} }
@ -196,8 +185,8 @@ public class MainSettingsActivity extends PreferenceActivity {
findPreference("header_help").setOnPreferenceClickListener(onHelpSettings); findPreference("header_help").setOnPreferenceClickListener(onHelpSettings);
// Keep a list of the server codes and names (for default server selection) // Keep a list of the server codes and names (for default server selection)
List<String> serverCodes = new ArrayList<String>(); List<String> serverCodes = new ArrayList<>();
List<String> serverNames = new ArrayList<String>(); List<String> serverNames = new ArrayList<>();
serverCodes.add(Integer.toString(ApplicationSettings.DEFAULTSERVER_LASTUSED)); serverCodes.add(Integer.toString(ApplicationSettings.DEFAULTSERVER_LASTUSED));
serverCodes.add(Integer.toString(ApplicationSettings.DEFAULTSERVER_ASKONADD)); serverCodes.add(Integer.toString(ApplicationSettings.DEFAULTSERVER_ASKONADD));
serverNames.add(getString(R.string.pref_defaultserver_lastused)); serverNames.add(getString(R.string.pref_defaultserver_lastused));
@ -206,8 +195,8 @@ public class MainSettingsActivity extends PreferenceActivity {
// Add existing servers // Add existing servers
List<ServerSetting> servers = applicationSettings.getNormalServerSettings(); List<ServerSetting> servers = applicationSettings.getNormalServerSettings();
for (ServerSetting serverSetting : servers) { for (ServerSetting serverSetting : servers) {
getPreferenceScreen().addPreference(new ServerPreference(this).setServerSetting(serverSetting) getPreferenceScreen()
.setOnServerClickedListener(onServerClicked)); .addPreference(new ServerPreference(this).setServerSetting(serverSetting).setOnServerClickedListener(onServerClicked));
if (serverSetting.getUniqueIdentifier() != null) { if (serverSetting.getUniqueIdentifier() != null) {
serverCodes.add(Integer.toString(serverSetting.getOrder())); serverCodes.add(Integer.toString(serverSetting.getOrder()));
serverNames.add(serverSetting.getName()); serverNames.add(serverSetting.getName());
@ -219,8 +208,7 @@ public class MainSettingsActivity extends PreferenceActivity {
for (SeedboxProvider provider : SeedboxProvider.values()) { for (SeedboxProvider provider : SeedboxProvider.values()) {
int seedboxOffset = 0; int seedboxOffset = 0;
for (ServerSetting seedbox : provider.getSettings().getAllServerSettings(prefs, orderOffset)) { for (ServerSetting seedbox : provider.getSettings().getAllServerSettings(prefs, orderOffset)) {
getPreferenceScreen().addPreference( getPreferenceScreen().addPreference(new SeedboxPreference(this).setProvider(provider).setServerSetting(seedbox)
new SeedboxPreference(this).setProvider(provider).setServerSetting(seedbox)
.setOnSeedboxClickedListener(onSeedboxClicked, seedboxOffset)); .setOnSeedboxClickedListener(onSeedboxClicked, seedboxOffset));
orderOffset++; orderOffset++;
seedboxOffset++; seedboxOffset++;
@ -242,8 +230,8 @@ public class MainSettingsActivity extends PreferenceActivity {
} else { } else {
List<RssfeedSetting> rssfeeds = applicationSettings.getRssfeedSettings(); List<RssfeedSetting> rssfeeds = applicationSettings.getRssfeedSettings();
for (RssfeedSetting rssfeedSetting : rssfeeds) { for (RssfeedSetting rssfeedSetting : rssfeeds) {
getPreferenceScreen().addPreference(new RssfeedPreference(this).setRssfeedSetting(rssfeedSetting) getPreferenceScreen()
.setOnRssfeedClickedListener(onRssfeedClicked)); .addPreference(new RssfeedPreference(this).setRssfeedSetting(rssfeedSetting).setOnRssfeedClickedListener(onRssfeedClicked));
} }
} }
@ -256,8 +244,8 @@ public class MainSettingsActivity extends PreferenceActivity {
// Add existing websearch sites // Add existing websearch sites
List<WebsearchSetting> websearches = applicationSettings.getWebsearchSettings(); List<WebsearchSetting> websearches = applicationSettings.getWebsearchSettings();
for (WebsearchSetting websearchSetting : websearches) { for (WebsearchSetting websearchSetting : websearches) {
getPreferenceScreen().addPreference(new WebsearchPreference(this).setWebsearchSetting(websearchSetting) getPreferenceScreen().addPreference(
.setOnWebsearchClickedListener(onWebsearchClicked)); new WebsearchPreference(this).setWebsearchSetting(websearchSetting).setOnWebsearchClickedListener(onWebsearchClicked));
} }
// Construct list of all available search sites, in-app and web // Construct list of all available search sites, in-app and web
@ -265,10 +253,10 @@ public class MainSettingsActivity extends PreferenceActivity {
// Retrieve the available in-app search sites (using the Torrent Search package) // Retrieve the available in-app search sites (using the Torrent Search package)
List<SearchSite> searchsites = searchHelper.getAvailableSites(); List<SearchSite> searchsites = searchHelper.getAvailableSites();
if (searchsites == null) { if (searchsites == null) {
searchsites = new ArrayList<SearchSite>(); searchsites = new ArrayList<>();
} }
List<String> siteNames = new ArrayList<String>(websearches.size() + searchsites.size()); List<String> siteNames = new ArrayList<>(websearches.size() + searchsites.size());
List<String> siteValues = new ArrayList<String>(websearches.size() + searchsites.size()); List<String> siteValues = new ArrayList<>(websearches.size() + searchsites.size());
for (SearchSite searchSite : searchsites) { for (SearchSite searchSite : searchsites) {
siteNames.add(searchSite.getName()); siteNames.add(searchSite.getName());
siteValues.add(searchSite.getKey()); siteValues.add(searchSite.getKey());
@ -295,14 +283,15 @@ public class MainSettingsActivity extends PreferenceActivity {
super.onBuildHeaders(target); super.onBuildHeaders(target);
} }
@Override
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_ADDSEEDBOX: case DIALOG_ADDSEEDBOX:
// Open dialog to pick one of the supported seedbox providers // Open dialog to pick one of the supported seedbox providers (or a normal server)
String[] seedboxes = new String[SeedboxProvider.values().length]; String[] seedboxes = new String[SeedboxProvider.values().length + 1];
for (int i = 0; i < seedboxes.length; i++) { seedboxes[0] = getString(R.string.pref_addserver_normal);
seedboxes[i] = getString(R.string.pref_seedbox_addseedbox, for (int i = 0; i < seedboxes.length - 1; i++) {
SeedboxProvider.values()[i].getSettings().getName()); seedboxes[i + 1] = getString(R.string.pref_seedbox_addseedbox, SeedboxProvider.values()[i].getSettings().getName());
} }
return new AlertDialog.Builder(this).setItems(seedboxes, onAddSeedbox).create(); return new AlertDialog.Builder(this).setItems(seedboxes, onAddSeedbox).create();
} }

20
app/src/main/java/org/transdroid/core/gui/settings/NotificationSettingsActivity.java

@ -16,24 +16,22 @@
*/ */
package org.transdroid.core.gui.settings; package org.transdroid.core.gui.settings;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OptionsItem;
import org.transdroid.R;
import org.transdroid.core.app.settings.NotificationSettings;
import org.transdroid.core.service.BootReceiver;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OptionsItem;
import org.transdroid.R;
import org.transdroid.core.app.settings.NotificationSettings;
import org.transdroid.core.service.BootReceiver;
@EActivity @EActivity
public class NotificationSettingsActivity extends PreferenceActivity implements public class NotificationSettingsActivity extends PreferenceCompatActivity implements OnSharedPreferenceChangeListener {
OnSharedPreferenceChangeListener {
@Bean @Bean
protected NotificationSettings notificationSettings; protected NotificationSettings notificationSettings;
@ -43,7 +41,7 @@ public class NotificationSettingsActivity extends PreferenceActivity implements
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the notification-related preferences from XML and update availability thereof // Load the notification-related preferences from XML and update availability thereof
addPreferencesFromResource(R.xml.pref_notifications); addPreferencesFromResource(R.xml.pref_notifications);

117
app/src/main/java/org/transdroid/core/gui/settings/OverflowPreference.java

@ -1,117 +0,0 @@
package org.transdroid.core.gui.settings;
import org.transdroid.R;
import android.content.Context;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
/**
* A {@link Preference} item that shows an extra overflow button at the right side of the screen. The action attached to
* this button is set using {@link #setOnOverflowClickedListener(OnOverflowClicked)}. Normal clicks on this preference
* are handled in the standard way.
* @author Eric Kok
*/
public class OverflowPreference extends Preference {
private OnPreferenceClickListener onPreferenceClickListener = null;
private OnOverflowClicked onOverflowClickedListener = null;
private ImageButton overflowButton = null;
public OverflowPreference(Context context) {
super(context);
init();
}
public OverflowPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public OverflowPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
@Override
protected View onCreateView(ViewGroup parent) {
View layout = super.onCreateView(parent);
// Since the Preference layout is now created, we can attach the proper click listeners
layout.setClickable(true);
layout.setFocusable(true);
// When setting the background drawable the padding on this Preference layout disappears, so add it again
int bottom = layout.getPaddingBottom();
int top = layout.getPaddingTop();
int right = layout.getPaddingRight();
int left = layout.getPaddingLeft();
layout.setBackgroundResource(R.drawable.selectable_background_holo_light);
layout.setPadding(left, top, right, bottom);
layout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onPreferenceClickListener != null)
onPreferenceClickListener.onPreferenceClick(OverflowPreference.this);
}
});
overflowButton = (ImageButton) layout.findViewById(R.id.overflow_button);
overflowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onOverflowClickedListener != null)
onOverflowClickedListener.onOverflowClicked(v);
}
});
return layout;
}
public void init() {
// Load an overflow-style image button as custom widget in the right of this Preference layout
setWidgetLayoutResource(R.layout.pref_withoverflow);
}
/**
* Hides the overflow button (on the right side of the Preference) from the UI.
*/
public void hideOverflowButton() {
if (overflowButton != null) {
overflowButton.setVisibility(View.GONE);
}
}
/**
* Shows (after hiding it) the overflow button (on the right side of the Preference) in the UI.
*/
public void showOverflowButton() {
if (overflowButton != null) {
overflowButton.setVisibility(View.VISIBLE);
}
}
@Override
public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
// Instead of the build-in list item click behaviour, we have to manually assign the click listener to this
// Preference item, as we stole the focus behaviour when we added a Button to the Preference layout
this.onPreferenceClickListener = onPreferenceClickListener;
}
/**
* Registers the listener for clicks on the overflow button contained in this preference.
* @param onOverflowClickedListener The overflow button click listener
*/
public void setOnOverflowClickedListener(OnOverflowClicked onOverflowClickedListener) {
this.onOverflowClickedListener = onOverflowClickedListener;
}
/**
* An interface to be implemented by any activity (or otherwise) that wants to handle events when the contained
* overflow button is clicked.
*/
public interface OnOverflowClicked {
public void onOverflowClicked(View overflowButton);
}
}

72
app/src/main/java/org/transdroid/core/gui/settings/PreferenceCompatActivity.java

@ -0,0 +1,72 @@
package org.transdroid.core.gui.settings;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatCallback;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.view.ActionMode;
public class PreferenceCompatActivity extends PreferenceActivity implements AppCompatCallback {
private AppCompatDelegate acd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
acd = AppCompatDelegate.create(this, this);
acd.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
acd.onPostCreate(savedInstanceState);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
acd.onConfigurationChanged(newConfig);
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
acd.setTitle(title);
}
@Override
protected void onStop() {
super.onStop();
acd.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
acd.onDestroy();
}
public ActionBar getSupportActionBar() {
return acd.getSupportActionBar();
}
@Override
public void onSupportActionModeStarted(ActionMode actionMode) {
}
@Override
public void onSupportActionModeFinished(ActionMode actionMode) {
}
@Nullable
@Override
public ActionMode onWindowStartingSupportActionMode(ActionMode.Callback callback) {
return null;
}
}

7
app/src/main/java/org/transdroid/core/gui/settings/RssfeedSettingsActivity.java

@ -37,7 +37,7 @@ import android.os.Bundle;
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@OptionsMenu(resName = "activity_deleteableprefs") @OptionsMenu(R.menu.activity_deleteableprefs)
public class RssfeedSettingsActivity extends KeyBoundPreferencesActivity { public class RssfeedSettingsActivity extends KeyBoundPreferencesActivity {
private static final int DIALOG_CONFIRMREMOVE = 0; private static final int DIALOG_CONFIRMREMOVE = 0;
@ -46,7 +46,7 @@ public class RssfeedSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_rssfeed, ApplicationSettings_.getInstance_(this).getMaxRssfeed()); init(R.xml.pref_rssfeed, ApplicationSettings_.getInstance_(this).getMaxRssfeed());
@ -65,11 +65,12 @@ public class RssfeedSettingsActivity extends KeyBoundPreferencesActivity {
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@OptionsItem(resName = "action_removesettings") @OptionsItem(R.id.action_removesettings)
protected void removeSettings() { protected void removeSettings() {
showDialog(DIALOG_CONFIRMREMOVE); showDialog(DIALOG_CONFIRMREMOVE);
} }
@Override
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_CONFIRMREMOVE: case DIALOG_CONFIRMREMOVE:

7
app/src/main/java/org/transdroid/core/gui/settings/ServerSettingsActivity.java

@ -41,7 +41,7 @@ import android.preference.PreferenceManager;
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@OptionsMenu(resName = "activity_deleteableprefs") @OptionsMenu(R.menu.activity_deleteableprefs)
public class ServerSettingsActivity extends KeyBoundPreferencesActivity { public class ServerSettingsActivity extends KeyBoundPreferencesActivity {
private static final int DIALOG_CONFIRMREMOVE = 0; private static final int DIALOG_CONFIRMREMOVE = 0;
@ -52,7 +52,7 @@ public class ServerSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_server, ApplicationSettings_.getInstance_(this).getMaxNormalServer()); init(R.xml.pref_server, ApplicationSettings_.getInstance_(this).getMaxNormalServer());
@ -89,11 +89,12 @@ public class ServerSettingsActivity extends KeyBoundPreferencesActivity {
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@OptionsItem(resName = "action_removesettings") @OptionsItem(R.id.action_removesettings)
protected void removeSettings() { protected void removeSettings() {
showDialog(DIALOG_CONFIRMREMOVE); showDialog(DIALOG_CONFIRMREMOVE);
} }
@Override
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_CONFIRMREMOVE: case DIALOG_CONFIRMREMOVE:

49
app/src/main/java/org/transdroid/core/gui/settings/SystemSettingsActivity.java

@ -32,6 +32,9 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.TextUtils; import android.text.TextUtils;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OnActivityResult; import org.androidannotations.annotations.OnActivityResult;
@ -49,10 +52,8 @@ import org.transdroid.core.service.BootReceiver;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import de.keyboardsurfer.android.widget.crouton.Crouton;
@EActivity @EActivity
public class SystemSettingsActivity extends PreferenceActivity { public class SystemSettingsActivity extends PreferenceCompatActivity {
protected static final int DIALOG_IMPORTSETTINGS = 0; protected static final int DIALOG_IMPORTSETTINGS = 0;
private OnPreferenceClickListener onImportSettingsClick = new OnPreferenceClickListener() { private OnPreferenceClickListener onImportSettingsClick = new OnPreferenceClickListener() {
@ -95,8 +96,7 @@ public class SystemSettingsActivity extends PreferenceActivity {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
SearchHistoryProvider.clearHistory(getApplicationContext()); SearchHistoryProvider.clearHistory(getApplicationContext());
Crouton.showText(SystemSettingsActivity.this, R.string.pref_clearsearch_success, SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_clearsearch_success));
NavigationHelper.CROUTON_INFO_STYLE);
return true; return true;
} }
}; };
@ -106,15 +106,13 @@ public class SystemSettingsActivity extends PreferenceActivity {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try { try {
settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); settingsPersistence.importSettingsFromFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE);
Crouton.showText(SystemSettingsActivity.this, R.string.pref_import_success, SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success));
NavigationHelper.CROUTON_INFO_STYLE);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
Crouton.showText(SystemSettingsActivity.this, R.string.error_file_not_found, SnackbarManager
NavigationHelper.CROUTON_ERROR_STYLE); .show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found).colorResource(R.color.red));
} catch (JSONException e) { } catch (JSONException e) {
Crouton.showText(SystemSettingsActivity.this, SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this)
getString(R.string.error_no_valid_settings_file, getString(R.string.app_name)), .text(getString(R.string.error_no_valid_settings_file, getString(R.string.app_name))).colorResource(R.color.red));
NavigationHelper.CROUTON_ERROR_STYLE);
} }
} }
}; };
@ -130,14 +128,10 @@ public class SystemSettingsActivity extends PreferenceActivity {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
try { try {
settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE); settingsPersistence.exportSettingsToFile(prefs, SettingsPersistence.DEFAULT_SETTINGS_FILE);
Crouton.showText(SystemSettingsActivity.this, R.string.pref_export_success, SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_export_success));
NavigationHelper.CROUTON_INFO_STYLE); } catch (JSONException | IOException e) {
} catch (JSONException e) { SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file)
Crouton.showText(SystemSettingsActivity.this, R.string.error_cant_write_settings_file, .colorResource(R.color.red));
NavigationHelper.CROUTON_ERROR_STYLE);
} catch (IOException e) {
Crouton.showText(SystemSettingsActivity.this, R.string.error_cant_write_settings_file,
NavigationHelper.CROUTON_ERROR_STYLE);
} }
} }
}; };
@ -149,8 +143,8 @@ public class SystemSettingsActivity extends PreferenceActivity {
String settings = settingsPersistence.exportSettingsAsString(prefs); String settings = settingsPersistence.exportSettingsAsString(prefs);
BarcodeHelper.shareContentBarcode(SystemSettingsActivity.this, settings); BarcodeHelper.shareContentBarcode(SystemSettingsActivity.this, settings);
} catch (JSONException e) { } catch (JSONException e) {
Crouton.showText(SystemSettingsActivity.this, R.string.error_cant_write_settings_file, SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_cant_write_settings_file)
NavigationHelper.CROUTON_ERROR_STYLE); .colorResource(R.color.red));
} }
} }
}; };
@ -160,7 +154,7 @@ public class SystemSettingsActivity extends PreferenceActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Just load the system-related preferences from XML // Just load the system-related preferences from XML
addPreferencesFromResource(R.xml.pref_system); addPreferencesFromResource(R.xml.pref_system);
@ -185,17 +179,18 @@ public class SystemSettingsActivity extends PreferenceActivity {
@OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS) @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_QRSETTINGS)
public void onQrCodeScanned(int resultCode, Intent data) { public void onQrCodeScanned(int resultCode, Intent data) {
// We should have received Intent extras with the QR-decoded data representing Transdroid settings // We should have received Intent extras with the QR-decoded data representing Transdroid settings
if (data == null || !data.hasExtra("SCAN_RESULT"))
return; // Cancelled scan; ignore
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SystemSettingsActivity.this);
String contents = data.getStringExtra("SCAN_RESULT"); String contents = data.getStringExtra("SCAN_RESULT");
String formatName = data.getStringExtra("SCAN_RESULT_FORMAT"); String formatName = data.getStringExtra("SCAN_RESULT_FORMAT");
if (formatName != null && formatName.equals("QR_CODE") && !TextUtils.isEmpty(contents)) { if (formatName != null && formatName.equals("QR_CODE") && !TextUtils.isEmpty(contents)) {
try { try {
settingsPersistence.importSettingsAsString(prefs, contents); settingsPersistence.importSettingsAsString(prefs, contents);
Crouton.showText(SystemSettingsActivity.this, R.string.pref_import_success, SnackbarManager.show(Snackbar.with(SystemSettingsActivity.this).text(R.string.pref_import_success));
NavigationHelper.CROUTON_INFO_STYLE);
} catch (JSONException e) { } catch (JSONException e) {
Crouton.showText(SystemSettingsActivity.this, R.string.error_file_not_found, SnackbarManager
NavigationHelper.CROUTON_ERROR_STYLE); .show(Snackbar.with(SystemSettingsActivity.this).text(R.string.error_file_not_found).colorResource(R.color.red));
} }
} }
} }

6
app/src/main/java/org/transdroid/core/gui/settings/WebsearchPreference.java

@ -16,11 +16,11 @@
*/ */
package org.transdroid.core.gui.settings; package org.transdroid.core.gui.settings;
import org.transdroid.core.app.settings.WebsearchSetting;
import android.content.Context; import android.content.Context;
import android.preference.Preference; import android.preference.Preference;
import org.transdroid.core.app.settings.WebsearchSetting;
/** /**
* Represents a {@link WebsearchSetting} in a preferences screen. * Represents a {@link WebsearchSetting} in a preferences screen.
* @author Eric Kok * @author Eric Kok
@ -70,7 +70,7 @@ public class WebsearchPreference extends Preference {
}; };
public interface OnWebsearchClickedListener { public interface OnWebsearchClickedListener {
public void onWebsearchClicked(WebsearchSetting serverSetting); void onWebsearchClicked(WebsearchSetting serverSetting);
} }
} }

7
app/src/main/java/org/transdroid/core/gui/settings/WebsearchSettingsActivity.java

@ -37,7 +37,7 @@ import android.os.Bundle;
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@OptionsMenu(resName="activity_deleteableprefs") @OptionsMenu(R.menu.activity_deleteableprefs)
public class WebsearchSettingsActivity extends KeyBoundPreferencesActivity { public class WebsearchSettingsActivity extends KeyBoundPreferencesActivity {
private static final int DIALOG_CONFIRMREMOVE = 0; private static final int DIALOG_CONFIRMREMOVE = 0;
@ -46,7 +46,7 @@ public class WebsearchSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_websearch, ApplicationSettings_.getInstance_(this).getMaxWebsearch()); init(R.xml.pref_websearch, ApplicationSettings_.getInstance_(this).getMaxWebsearch());
@ -63,11 +63,12 @@ public class WebsearchSettingsActivity extends KeyBoundPreferencesActivity {
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@OptionsItem(resName = "action_removesettings") @OptionsItem(R.id.action_removesettings)
protected void removeSettings() { protected void removeSettings() {
showDialog(DIALOG_CONFIRMREMOVE); showDialog(DIALOG_CONFIRMREMOVE);
} }
@Override
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_CONFIRMREMOVE: case DIALOG_CONFIRMREMOVE:

2
app/src/main/java/org/transdroid/core/seedbox/SeedstuffSettingsActivity.java

@ -42,7 +42,7 @@ public class SeedstuffSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_seedbox_seedstuff, init(R.xml.pref_seedbox_seedstuff,

2
app/src/main/java/org/transdroid/core/seedbox/XirvikDediSettingsActivity.java

@ -42,7 +42,7 @@ public class XirvikDediSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_seedbox_xirvikdedi, init(R.xml.pref_seedbox_xirvikdedi,

2
app/src/main/java/org/transdroid/core/seedbox/XirvikSemiSettingsActivity.java

@ -42,7 +42,7 @@ public class XirvikSemiSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_seedbox_xirviksemi, init(R.xml.pref_seedbox_xirviksemi,

48
app/src/main/java/org/transdroid/core/seedbox/XirvikSharedSettingsActivity.java

@ -16,7 +16,18 @@
*/ */
package org.transdroid.core.seedbox; package org.transdroid.core.seedbox;
import java.io.InputStream; import android.annotation.TargetApi;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.PreferenceManager;
import com.nispok.snackbar.Snackbar;
import com.nispok.snackbar.SnackbarManager;
import org.androidannotations.annotations.Bean; import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.EActivity;
@ -27,25 +38,15 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.transdroid.R; import org.transdroid.R;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.core.gui.navigation.NavigationHelper;
import org.transdroid.core.gui.settings.KeyBoundPreferencesActivity; import org.transdroid.core.gui.settings.KeyBoundPreferencesActivity;
import org.transdroid.core.gui.settings.*; import org.transdroid.core.gui.settings.MainSettingsActivity_;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import android.annotation.TargetApi; import java.io.InputStream;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.PreferenceManager;
import de.keyboardsurfer.android.widget.crouton.Crouton;
/** /**
* Activity that allows for the configuration of a Xirvik shared seedbox. The key can be supplied to update an existing * Activity that allows for the configuration of a Xirvik shared seedbox. The key can be supplied to update an existing server setting instead of
* server setting instead of creating a new one. * creating a new one.
* @author Eric Kok * @author Eric Kok
*/ */
@EActivity @EActivity
@ -59,12 +60,11 @@ public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Load the raw preferences to show in this screen // Load the raw preferences to show in this screen
init(R.xml.pref_seedbox_xirvikshared, init(R.xml.pref_seedbox_xirvikshared,
SeedboxProvider.XirvikShared.getSettings().getMaxSeedboxOrder( SeedboxProvider.XirvikShared.getSettings().getMaxSeedboxOrder(PreferenceManager.getDefaultSharedPreferences(this)));
PreferenceManager.getDefaultSharedPreferences(this)));
initTextPreference("seedbox_xirvikshared_name"); initTextPreference("seedbox_xirvikshared_name");
initTextPreference("seedbox_xirvikshared_server"); initTextPreference("seedbox_xirvikshared_server");
initTextPreference("seedbox_xirvikshared_user"); initTextPreference("seedbox_xirvikshared_user");
@ -82,15 +82,14 @@ public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
try { try {
// When the shared server settings change, we also have to update the RPC mount point to use // When the shared server settings change, we also have to update the RPC mount point to use
SharedPreferences prefs = PreferenceManager SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(XirvikSharedSettingsActivity.this);
.getDefaultSharedPreferences(XirvikSharedSettingsActivity.this);
String server = prefs.getString("seedbox_xirvikshared_server_" + key, null); String server = prefs.getString("seedbox_xirvikshared_server_" + key, null);
String user = prefs.getString("seedbox_xirvikshared_user_" + key, null); String user = prefs.getString("seedbox_xirvikshared_user_" + key, null);
String pass = prefs.getString("seedbox_xirvikshared_pass_" + key, null); String pass = prefs.getString("seedbox_xirvikshared_pass_" + key, null);
// Retrieve the RPC mount point setting from the server itself // Retrieve the RPC mount point setting from the server itself
DefaultHttpClient httpclient = HttpHelper.createStandardHttpClient(true, user, pass, true, null, DefaultHttpClient httpclient =
HttpHelper.DEFAULT_CONNECTION_TIMEOUT, server, 443); HttpHelper.createStandardHttpClient(true, user, pass, true, null, HttpHelper.DEFAULT_CONNECTION_TIMEOUT, server, 443);
String url = "https://" + server + ":443/browsers_addons/transdroid_autoconf.txt"; String url = "https://" + server + ":443/browsers_addons/transdroid_autoconf.txt";
HttpResponse request = httpclient.execute(new HttpGet(url)); HttpResponse request = httpclient.execute(new HttpGet(url));
InputStream stream = request.getEntity().getContent(); InputStream stream = request.getEntity().getContent();
@ -103,8 +102,7 @@ public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
} catch (Exception e) { } catch (Exception e) {
log.d(XirvikSharedSettingsActivity.this, log.d(XirvikSharedSettingsActivity.this, "Could not retrieve the Xirvik shared seedbox RPC mount point setting: " + e.toString());
"Could not retrieve the Xirvik shared seedbox RPC mount point setting: " + e.toString());
return null; return null;
} }
@ -123,7 +121,7 @@ public class XirvikSharedSettingsActivity extends KeyBoundPreferencesActivity {
Editor edit = PreferenceManager.getDefaultSharedPreferences(XirvikSharedSettingsActivity.this).edit(); Editor edit = PreferenceManager.getDefaultSharedPreferences(XirvikSharedSettingsActivity.this).edit();
EditTextPreference pref = (EditTextPreference) findPreference("seedbox_xirvikshared_rpc_" + key); EditTextPreference pref = (EditTextPreference) findPreference("seedbox_xirvikshared_rpc_" + key);
if (result == null) { if (result == null) {
Crouton.showText(this, R.string.pref_seedbox_xirviknofolder, NavigationHelper.CROUTON_ERROR_STYLE); SnackbarManager.show(Snackbar.with(this).text(R.string.pref_seedbox_xirviknofolder).colorResource(R.color.red));
edit.remove("seedbox_xirvikshared_rpc_" + key); edit.remove("seedbox_xirvikshared_rpc_" + key);
pref.setSummary(""); pref.setSummary("");
} else { } else {

10
app/src/main/java/org/transdroid/core/service/ConnectivityHelper.java

@ -32,16 +32,8 @@ public class ConnectivityHelper {
@SystemService @SystemService
protected WifiManager wifiManager; protected WifiManager wifiManager;
public ConnectivityHelper(Context context) {
}
@SuppressWarnings("deprecation")
public boolean shouldPerformBackgroundActions() { public boolean shouldPerformBackgroundActions() {
// First check the old background data setting (this will always be true for ICS+) // Check the current active network whether we are connected
if (!connectivityManager.getBackgroundDataSetting())
return false;
// Still good? Check the current active network instead
return connectivityManager.getActiveNetworkInfo() != null return connectivityManager.getActiveNetworkInfo() != null
&& connectivityManager.getActiveNetworkInfo().isConnected(); && connectivityManager.getActiveNetworkInfo().isConnected();
} }

10
app/src/main/java/org/transdroid/core/widget/ListWidgetConfigActivity.java

@ -17,12 +17,12 @@
package org.transdroid.core.widget; package org.transdroid.core.widget;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.CheckBox; import android.widget.CheckBox;
@ -66,7 +66,7 @@ import java.util.List;
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
@EActivity(resName = "activity_widgetconfig") @EActivity(resName = "activity_widgetconfig")
public class ListWidgetConfigActivity extends Activity { public class ListWidgetConfigActivity extends ActionBarActivity {
// Views and adapters // Views and adapters
@ViewById @ViewById
@ -182,11 +182,11 @@ public class ListWidgetConfigActivity extends Activity {
// Set up action bar with a done button // Set up action bar with a done button
// Inspired by NoNonsenseNotes's ListWidgetConfig.java (Apache License, Version 2.0) // Inspired by NoNonsenseNotes's ListWidgetConfig.java (Apache License, Version 2.0)
getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE); ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
View doneButtonFrame = getLayoutInflater().inflate(R.layout.actionbar_donebutton, null); View doneButtonFrame = getLayoutInflater().inflate(R.layout.actionbar_donebutton, null);
doneButtonFrame.findViewById(R.id.actionbar_done).setOnClickListener(doneClicked); doneButtonFrame.findViewById(R.id.actionbar_done).setOnClickListener(doneClicked);
getActionBar().setCustomView(doneButtonFrame); getSupportActionBar().setCustomView(doneButtonFrame);
} }

21
app/src/main/java/org/transdroid/daemon/Daemon.java

@ -29,6 +29,7 @@ import org.transdroid.daemon.Rtorrent.RtorrentAdapter;
import org.transdroid.daemon.Synology.SynologyAdapter; import org.transdroid.daemon.Synology.SynologyAdapter;
import org.transdroid.daemon.Tfb4rt.Tfb4rtAdapter; import org.transdroid.daemon.Tfb4rt.Tfb4rtAdapter;
import org.transdroid.daemon.Transmission.TransmissionAdapter; import org.transdroid.daemon.Transmission.TransmissionAdapter;
import org.transdroid.daemon.Ttorrent.TtorrentAdapter;
import org.transdroid.daemon.Utorrent.UtorrentAdapter; import org.transdroid.daemon.Utorrent.UtorrentAdapter;
import org.transdroid.daemon.Vuze.VuzeAdapter; import org.transdroid.daemon.Vuze.VuzeAdapter;
@ -95,6 +96,11 @@ public enum Daemon {
return new Tfb4rtAdapter(settings); return new Tfb4rtAdapter(settings);
} }
}, },
tTorrent {
public IDaemonAdapter createAdapter(DaemonSettings settings) {
return new TtorrentAdapter(settings);
}
},
Synology { Synology {
public IDaemonAdapter createAdapter(DaemonSettings settings) { public IDaemonAdapter createAdapter(DaemonSettings settings) {
return new SynologyAdapter(settings); return new SynologyAdapter(settings);
@ -157,6 +163,8 @@ public enum Daemon {
return "daemon_synology"; return "daemon_synology";
case Tfb4rt: case Tfb4rt:
return "daemon_tfb4rt"; return "daemon_tfb4rt";
case tTorrent:
return "daemon_ttorrent";
case Transmission: case Transmission:
return "daemon_transmission"; return "daemon_transmission";
case uTorrent: case uTorrent:
@ -216,6 +224,9 @@ public enum Daemon {
if (daemonCode.equals("daemon_tfb4rt")) { if (daemonCode.equals("daemon_tfb4rt")) {
return Tfb4rt; return Tfb4rt;
} }
if (daemonCode.equals("daemon_ttorrent")) {
return tTorrent;
}
if (daemonCode.equals("daemon_transmission")) { if (daemonCode.equals("daemon_transmission")) {
return Transmission; return Transmission;
} }
@ -265,6 +276,8 @@ public enum Daemon {
return 6884; return 6884;
case Aria2: case Aria2:
return 6800; return 6800;
case tTorrent:
return 1080;
} }
return 8080; return 8080;
} }
@ -278,7 +291,7 @@ public enum Daemon {
} }
public static boolean supportsFileListing(Daemon type) { public static boolean supportsFileListing(Daemon type) {
return type == Synology || type == Transmission || type == uTorrent || type == BitTorrent || type == KTorrent || type == Deluge || type == rTorrent || type == Vuze || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == Aria2 || type == Dummy; return type == Synology || type == Transmission || type == uTorrent || type == BitTorrent || type == KTorrent || type == Deluge || type == rTorrent || type == Vuze || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == Aria2 || type == tTorrent || type == Dummy;
} }
public static boolean supportsFineDetails(Daemon type) { public static boolean supportsFineDetails(Daemon type) {
@ -315,15 +328,15 @@ public enum Daemon {
} }
public static boolean supportsAddByMagnetUrl(Daemon type) { public static boolean supportsAddByMagnetUrl(Daemon type) {
return type == uTorrent || type == BitTorrent || type == Transmission || type == Synology || type == Deluge || type == Bitflu || type == KTorrent || type == rTorrent || type == qBittorrent || type == BitComet || type == Aria2 || type == Dummy; return type == uTorrent || type == BitTorrent || type == Transmission || type == Synology || type == Deluge || type == Bitflu || type == KTorrent || type == rTorrent || type == qBittorrent || type == BitComet || type == Aria2 || type == tTorrent || type == Dummy;
} }
public static boolean supportsRemoveWithData(Daemon type) { public static boolean supportsRemoveWithData(Daemon type) {
return type == uTorrent || type == Vuze || type == Transmission || type == Deluge || type == BitTorrent || type == Tfb4rt || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == rTorrent || type == Aria2 || type == Dummy; return type == uTorrent || type == Vuze || type == Transmission || type == Deluge || type == BitTorrent || type == Tfb4rt || type == DLinkRouterBT || type == Bitflu || type == qBittorrent || type == BuffaloNas || type == BitComet || type == rTorrent || type == Aria2 || type == tTorrent || type == Dummy;
} }
public static boolean supportsFilePrioritySetting(Daemon type) { public static boolean supportsFilePrioritySetting(Daemon type) {
return type == BitTorrent || type == uTorrent || type == Transmission || type == KTorrent || type == rTorrent || type == Vuze || type == Deluge || type == qBittorrent || type == Dummy; return type == BitTorrent || type == uTorrent || type == Transmission || type == KTorrent || type == rTorrent || type == Vuze || type == Deluge || type == qBittorrent || type == tTorrent || type == Dummy;
} }
public static boolean supportsDateAdded(Daemon type) { public static boolean supportsDateAdded(Daemon type) {

192
app/src/main/java/org/transdroid/daemon/Qbittorrent/QbittorrentAdapter.java

@ -17,20 +17,16 @@
*/ */
package org.transdroid.daemon.Qbittorrent; package org.transdroid.daemon.Qbittorrent;
import java.io.File; import com.android.internalcopy.http.multipart.FilePart;
import java.io.FileNotFoundException; import com.android.internalcopy.http.multipart.MultipartEntity;
import java.io.UnsupportedEncodingException; import com.android.internalcopy.http.multipart.Part;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HTTP;
@ -40,6 +36,7 @@ import org.json.JSONObject;
import org.transdroid.core.gui.log.Log; import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon; import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException; import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings; import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter; import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority; import org.transdroid.daemon.Priority;
@ -47,7 +44,6 @@ import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentDetails; import org.transdroid.daemon.TorrentDetails;
import org.transdroid.daemon.TorrentFile; import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus; import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.task.AddByFileTask; import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask; import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask; import org.transdroid.daemon.task.AddByUrlTask;
@ -65,9 +61,15 @@ import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask; import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetTransferRatesTask; import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity; import java.io.File;
import com.android.internalcopy.http.multipart.Part; import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** /**
* The daemon adapter for the qBittorrent torrent client. * The daemon adapter for the qBittorrent torrent client.
@ -80,26 +82,47 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private DaemonSettings settings; private DaemonSettings settings;
private DefaultHttpClient httpclient; private DefaultHttpClient httpclient;
private int version = -1; private int version = -1;
private int apiVersion = -1;
public QbittorrentAdapter(DaemonSettings settings) { public QbittorrentAdapter(DaemonSettings settings) {
this.settings = settings; this.settings = settings;
} }
private synchronized void ensureVersion(Log log) throws DaemonException { private synchronized void ensureVersion(Log log) throws DaemonException {
if (version > 0) // Still need to retrieve the API and qBittorrent version numbers from the server?
if (version > 0 && apiVersion > 0)
return; return;
// We still need to retrieve the version number from the server
// Do this by getting the web interface about page and trying to parse the version number try {
// Format is something like 'qBittorrent v2.9.7 (Web UI)'
// The API version is only supported since qBittorrent 3.2, so otherwise we assume version 1
try {
String apiVerText = makeRequest(log, "/version/api");
apiVersion = Integer.parseInt(apiVerText.trim());
} catch (DaemonException | NumberFormatException e) {
apiVersion = 1;
}
log.d(LOG_NAME, "qBittorrent API version is " + apiVersion);
// The qBittorent version is only supported since 3.2; for earlier versions we parse the about dialog and parse it
String versionText = "";
if (apiVersion > 1) {
// Format is something like 'v3.2.0'
versionText = makeRequest(log, "/version/qbittorrent").substring(1);
} else {
// Format is something like 'qBittorrent v2.9.7 (Web UI)' or 'qBittorrent v3.0.0-alpha5 (Web UI)'
String about = makeRequest(log, "/about.html"); String about = makeRequest(log, "/about.html");
String aboutStartText = "qBittorrent v"; String aboutStartText = "qBittorrent v";
String aboutEndText = " (Web UI)"; String aboutEndText = " (Web UI)";
int aboutStart = about.indexOf(aboutStartText); int aboutStart = about.indexOf(aboutStartText);
int aboutEnd = about.indexOf(aboutEndText); int aboutEnd = about.indexOf(aboutEndText);
try {
if (aboutStart >= 0 && aboutEnd > aboutStart) { if (aboutStart >= 0 && aboutEnd > aboutStart) {
versionText = about.substring(aboutStart + aboutStartText.length(), aboutEnd);
}
}
// String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .) // String found: now parse a version like 2.9.7 as a number like 20907 (allowing 10 places for each .)
String[] parts = about.substring(aboutStart + aboutStartText.length(), aboutEnd).split("\\."); String[] parts = versionText.split("\\.");
if (parts.length > 0) { if (parts.length > 0) {
version = Integer.parseInt(parts[0]) * 100 * 100; version = Integer.parseInt(parts[0]) * 100 * 100;
if (parts.length > 1) { if (parts.length > 1) {
@ -122,11 +145,47 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
} }
} }
}
} catch (NumberFormatException e) { } catch (Exception e) {
}
// Unable to establish version number; assume an old version by setting it to version 1 // Unable to establish version number; assume an old version by setting it to version 1
version = 10000; version = 10000;
apiVersion = 1;
}
}
private synchronized void ensureAuthenticated(Log log) throws DaemonException {
// API changed in 3.2.0, login is now handled by its own request, which provides you a cookie.
// If we don't have that cookie, let's try and get it.
if (apiVersion < 2) {
return;
}
// Have we already authenticated? Check if we have the cookie that we need
List<Cookie> cookies = httpclient.getCookieStore().getCookies();
for (Cookie c : cookies) {
if (c.getName().equals("SID")) {
// And here it is! Okay, no need authenticate again.
return;
}
}
makeRequest(log, "/login", new BasicNameValuePair("username", settings.getUsername()),
new BasicNameValuePair("password", settings.getPassword()));
// The HttpClient will automatically remember the cookie for us, no need to parse it out.
// However, we would like to see if authentication was successful or not...
cookies = httpclient.getCookieStore().getCookies();
for (Cookie c : cookies) {
if (c.getName().equals("SID")) {
// Good. Let's get out of here.
return;
}
}
// No cookie found, we didn't authenticate.
throw new DaemonException(ExceptionType.AuthenticationFailure, "Server rejected our login");
} }
@Override @Override
@ -134,26 +193,37 @@ public class QbittorrentAdapter implements IDaemonAdapter {
try { try {
ensureVersion(log); ensureVersion(log);
ensureAuthenticated(log);
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
String path;
if (version >= 30200) {
path = "/query/torrents";
} else if (version >= 30000) {
path = "/json/torrents";
} else {
path = "/json/events";
}
// Request all torrents from server // Request all torrents from server
JSONArray result = new JSONArray(makeRequest(log, version >= 30000 ? "/json/torrents" : "/json/events")); JSONArray result = new JSONArray(makeRequest(log, path));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null); return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null);
case GetTorrentDetails: case GetTorrentDetails:
// Request tracker and error details for a specific teacher // Request tracker and error details for a specific teacher
String mhash = task.getTargetTorrent().getUniqueID(); String mhash = task.getTargetTorrent().getUniqueID();
JSONArray messages = new JSONArray(makeRequest(log, "/json/propertiesTrackers/" + mhash)); JSONArray messages =
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesTrackers/" : "/json/propertiesTrackers/") + mhash));
parseJsonTorrentDetails(messages)); return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages));
case GetFileList: case GetFileList:
// Request files listing for a specific torrent // Request files listing for a specific torrent
String fhash = task.getTargetTorrent().getUniqueID(); String fhash = task.getTargetTorrent().getUniqueID();
JSONArray files = new JSONArray(makeRequest(log, "/json/propertiesFiles/" + fhash)); JSONArray files =
new JSONArray(makeRequest(log, (version >= 30200 ? "/query/propertiesFiles/" : "/json/propertiesFiles/") + fhash));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files)); return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files));
case AddByFile: case AddByFile:
@ -223,9 +293,8 @@ public class QbittorrentAdapter implements IDaemonAdapter {
} }
// We have to make a separate request per file, it seems // We have to make a separate request per file, it seems
for (TorrentFile file : setPrio.getForFiles()) { for (TorrentFile file : setPrio.getForFiles()) {
makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent() makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()),
.getUniqueID()), new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair( new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio));
"priority", newPrio));
} }
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
@ -241,13 +310,12 @@ public class QbittorrentAdapter implements IDaemonAdapter {
JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences")); JSONObject prefs = new JSONObject(makeRequest(log, "/json/preferences"));
prefs.put("dl_limit", dl); prefs.put("dl_limit", dl);
prefs.put("up_limit", ul); prefs.put("up_limit", ul);
makeRequest(log, "/command/setPreferences", makeRequest(log, "/command/setPreferences", new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8)));
new BasicNameValuePair("json", URLEncoder.encode(prefs.toString(), HTTP.UTF_8)));
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
default: default:
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.MethodUnsupported, return new DaemonTaskFailureResult(task,
task.getMethod() + " is not supported by " + getType())); new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType()));
} }
} catch (JSONException e) { } catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString())); return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
@ -264,7 +332,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// Setup request using POST // Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path)); HttpPost httppost = new HttpPost(buildWebUIUrl(path));
List<NameValuePair> nvps = new ArrayList<NameValuePair>(); List<NameValuePair> nvps = new ArrayList<>();
Collections.addAll(nvps, params); Collections.addAll(nvps, params);
httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
return makeWebRequest(httppost, log); return makeWebRequest(httppost, log);
@ -347,8 +415,8 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException { private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException {
ArrayList<String> trackers = new ArrayList<String>(); ArrayList<String> trackers = new ArrayList<>();
ArrayList<String> errors = new ArrayList<String>(); ArrayList<String> errors = new ArrayList<>();
// Parse response // Parse response
if (messages.length() > 0) { if (messages.length() > 0) {
@ -369,15 +437,37 @@ public class QbittorrentAdapter implements IDaemonAdapter {
private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException { private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException {
// Parse response // Parse response
ArrayList<Torrent> torrents = new ArrayList<Torrent>(); ArrayList<Torrent> torrents = new ArrayList<>();
for (int i = 0; i < response.length(); i++) { for (int i = 0; i < response.length(); i++) {
JSONObject tor = response.getJSONObject(i); JSONObject tor = response.getJSONObject(i);
int leechers[] = parsePeers(tor.getString("num_leechs"));
int seeders[] = parsePeers(tor.getString("num_seeds"));
long size = parseSize(tor.getString("size"));
double ratio = parseRatio(tor.getString("ratio"));
double progress = tor.getDouble("progress"); double progress = tor.getDouble("progress");
int dlspeed = parseSpeed(tor.getString("dlspeed")); int leechers[];
int seeders[];
double ratio;
long size;
int dlspeed;
int upspeed;
if (apiVersion >= 2) {
leechers = new int[2];
leechers[0] = tor.getInt("num_leechs");
leechers[1] = tor.getInt("num_complete") + tor.getInt("num_incomplete");
seeders = new int[2];
seeders[0] = tor.getInt("num_seeds");
seeders[1] = tor.getInt("num_complete");
size = tor.getLong("size");
ratio = tor.getDouble("ratio");
dlspeed = tor.getInt("dlspeed");
upspeed = tor.getInt("upspeed");
} else {
leechers = parsePeers(tor.getString("num_leechs"));
seeders = parsePeers(tor.getString("num_seeds"));
size = parseSize(tor.getString("size"));
ratio = parseRatio(tor.getString("ratio"));
dlspeed = parseSpeed(tor.getString("dlspeed"));
upspeed = parseSpeed(tor.getString("upspeed"));
}
long eta = -1L; long eta = -1L;
if (dlspeed > 0) if (dlspeed > 0)
eta = (long) (size - (size * progress)) / dlspeed; eta = (long) (size - (size * progress)) / dlspeed;
@ -391,7 +481,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
parseStatus(tor.getString("state")), parseStatus(tor.getString("state")),
null, null,
dlspeed, dlspeed,
parseSpeed(tor.getString("upspeed")), upspeed,
seeders[0], seeders[0],
seeders[1], seeders[1],
leechers[0], leechers[0],
@ -454,8 +544,7 @@ public class QbittorrentAdapter implements IDaemonAdapter {
// In some situations it it just a "6" string // In some situations it it just a "6" string
String[] parts = seeds.split(" "); String[] parts = seeds.split(" ");
if (parts.length > 1) { if (parts.length > 1) {
return new int[] { Integer.parseInt(parts[0]), return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1].substring(1, parts[1].length() - 1))};
Integer.parseInt(parts[1].substring(1, parts[1].length() - 1)) };
} }
return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[0])}; return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[0])};
} }
@ -525,9 +614,16 @@ public class QbittorrentAdapter implements IDaemonAdapter {
ArrayList<TorrentFile> torrentfiles = new ArrayList<TorrentFile>(); ArrayList<TorrentFile> torrentfiles = new ArrayList<TorrentFile>();
for (int i = 0; i < response.length(); i++) { for (int i = 0; i < response.length(); i++) {
JSONObject file = response.getJSONObject(i); JSONObject file = response.getJSONObject(i);
long size = parseSize(file.getString("size"));
torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file long size;
.getDouble("progress")), parsePriority(file.getInt("priority")))); if (apiVersion >= 2) {
size = file.getLong("size");
} else {
size = parseSize(file.getString("size"));
}
torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file.getDouble("progress")),
parsePriority(file.getInt("priority"))));
} }
// Return the list // Return the list

106
app/src/main/java/org/transdroid/daemon/Rtorrent/RtorrentAdapter.java

@ -82,6 +82,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
private DaemonSettings settings; private DaemonSettings settings;
private XMLRPCClient rpcclient; private XMLRPCClient rpcclient;
private List<Label> lastKnownLabels = null; private List<Label> lastKnownLabels = null;
private Integer version = null;
public RtorrentAdapter(DaemonSettings settings) { public RtorrentAdapter(DaemonSettings settings) {
this.settings = settings; this.settings = settings;
@ -91,36 +92,46 @@ public class RtorrentAdapter implements IDaemonAdapter {
public DaemonTaskResult executeTask(Log log, DaemonTask task) { public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try { try {
// Ensure a version number is know to switch to the right methods
if (version == null) {
try {
Object versionObject = makeRtorrentCall(log, "system.client_version", new String[0]);
String[] versionRaw = versionObject.toString().split("\\.");
version = (Integer.parseInt(versionRaw[0]) * 10000) + (Integer.parseInt(versionRaw[1]) * 100) + Integer.parseInt(versionRaw[2]);
} catch (Exception e) {
version = 10000;
}
}
switch (task.getMethod()) { switch (task.getMethod()) {
case Retrieve: case Retrieve:
// @formatter:off // @formatter:off
Object result = makeRtorrentCall(log,"d.multicall", Object result = makeRtorrentCall(log, "d.multicall2",
new String[] { "main", new String[] { "", "main",
"d.get_hash=", "d.hash=",
"d.get_name=", "d.name=",
"d.get_state=", "d.state=",
"d.get_down_rate=", "d.down.rate=",
"d.get_up_rate=", "d.up.rate=",
"d.get_peers_connected=", "d.peers_connected=",
"d.get_peers_not_connected=", "d.peers_not_connected=",
"d.get_peers_accounted=", "d.peers_accounted=",
"d.get_bytes_done=", "d.bytes_done=",
"d.get_up_total=", "d.up.total=",
"d.get_size_bytes=", "d.size_bytes=",
"d.get_creation_date=", "d.creation_date=",
"d.get_left_bytes=", "d.left_bytes=",
"d.get_complete=", "d.complete=",
"d.is_active=", "d.is_active=",
"d.is_hash_checking=", "d.is_hash_checking=",
"d.get_base_path=", "d.base_path=",
"d.get_base_filename=", "d.base_filename=",
"d.get_message=", "d.message=",
"d.get_custom=addtime", "d.custom=addtime",
"d.get_custom=seedingtime", "d.custom=seedingtime",
"d.get_custom1=", "d.custom1=",
"d.get_peers_complete=", "d.peers_complete=",
"d.get_peers_accounted=" }); "d.peers_accounted=" });
// @formatter:on // @formatter:on
return new RetrieveTaskSuccessResult((RetrieveTask) task, onTorrentsRetrieved(result), return new RetrieveTaskSuccessResult((RetrieveTask) task, onTorrentsRetrieved(result),
lastKnownLabels); lastKnownLabels);
@ -131,7 +142,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
Object dresult = makeRtorrentCall(log,"t.multicall", new String[] { Object dresult = makeRtorrentCall(log,"t.multicall", new String[] {
task.getTargetTorrent().getUniqueID(), task.getTargetTorrent().getUniqueID(),
"", "",
"t.get_url=" }); "t.url=" });
// @formatter:on // @formatter:on
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task,
onTorrentDetailsRetrieved(log, dresult)); onTorrentDetailsRetrieved(log, dresult));
@ -142,13 +153,13 @@ public class RtorrentAdapter implements IDaemonAdapter {
Object fresult = makeRtorrentCall(log,"f.multicall", new String[] { Object fresult = makeRtorrentCall(log,"f.multicall", new String[] {
task.getTargetTorrent().getUniqueID(), task.getTargetTorrent().getUniqueID(),
"", "",
"f.get_path=", "f.path=",
"f.get_size_bytes=", "f.size_bytes=",
"f.get_priority=", "f.priority=",
"f.get_completed_chunks=", "f.completed_chunks=",
"f.get_size_chunks=", "f.size_chunks=",
"f.get_priority=", "f.priority=",
"f.get_frozen_path=" }); "f.frozen_path=" });
// @formatter:on // @formatter:on
return new GetFileListTaskSuccessResult((GetFileListTask) task, return new GetFileListTaskSuccessResult((GetFileListTask) task,
onTorrentFilesRetrieved(fresult, task.getTargetTorrent())); onTorrentFilesRetrieved(fresult, task.getTargetTorrent()));
@ -167,22 +178,35 @@ public class RtorrentAdapter implements IDaemonAdapter {
byte[] bytes = baos.toByteArray(); byte[] bytes = baos.toByteArray();
int size = (int) file.length() * 2; int size = (int) file.length() * 2;
final int XMLRPC_EXTRA_PADDING = 1280; final int XMLRPC_EXTRA_PADDING = 1280;
if (version >= 907) {
makeRtorrentCall(log, "network.xmlrpc.size_limit.set", new Object[]{size + XMLRPC_EXTRA_PADDING});
makeRtorrentCall(log, "load.raw_start", new Object[]{bytes});
} else {
makeRtorrentCall(log, "set_xmlrpc_size_limit", new Object[]{size + XMLRPC_EXTRA_PADDING}); makeRtorrentCall(log, "set_xmlrpc_size_limit", new Object[]{size + XMLRPC_EXTRA_PADDING});
makeRtorrentCall(log, "load_raw_start", new Object[]{bytes}); makeRtorrentCall(log, "load_raw_start", new Object[]{bytes});
}
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByUrl: case AddByUrl:
// Request to add a torrent by URL // Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl(); String url = ((AddByUrlTask) task).getUrl();
if (version >= 907) {
makeRtorrentCall(log, "load.start", new String[]{"", url});
} else {
makeRtorrentCall(log, "load_start", new String[]{url}); makeRtorrentCall(log, "load_start", new String[]{url});
}
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl: case AddByMagnetUrl:
// Request to add a magnet link by URL // Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl(); String magnet = ((AddByMagnetUrlTask) task).getUrl();
if (version >= 907) {
makeRtorrentCall(log, "load.start", new String[]{"", magnet});
} else {
makeRtorrentCall(log, "load_start", new String[]{magnet}); makeRtorrentCall(log, "load_start", new String[]{magnet});
}
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Remove: case Remove:
@ -190,7 +214,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
// Remove a torrent // Remove a torrent
RemoveTask removeTask = (RemoveTask) task; RemoveTask removeTask = (RemoveTask) task;
if (removeTask.includingData()) { if (removeTask.includingData()) {
makeRtorrentCall(log, "d.set_custom5", makeRtorrentCall(log, "d.custom5.set",
new String[]{task.getTargetTorrent().getUniqueID(), "1"}); new String[]{task.getTargetTorrent().getUniqueID(), "1"});
} }
makeRtorrentCall(log, "d.erase", new String[]{task.getTargetTorrent().getUniqueID()}); makeRtorrentCall(log, "d.erase", new String[]{task.getTargetTorrent().getUniqueID()});
@ -205,7 +229,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
case PauseAll: case PauseAll:
// Resume all torrents // Resume all torrents
makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.pause="}); makeRtorrentCall(log, "d.multicall2", new String[]{"","main", "d.pause="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Resume: case Resume:
@ -217,7 +241,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
case ResumeAll: case ResumeAll:
// Resume all torrents // Resume all torrents
makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.resume="}); makeRtorrentCall(log, "d.multicall2", new String[]{"", "main", "d.resume="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Stop: case Stop:
@ -229,7 +253,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
case StopAll: case StopAll:
// Stop all torrents // Stop all torrents
makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.stop="}); makeRtorrentCall(log, "d.multicall2", new String[]{"", "main", "d.stop="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case Start: case Start:
@ -241,7 +265,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
case StartAll: case StartAll:
// Start all torrents // Start all torrents
makeRtorrentCall(log, "d.multicall", new String[]{"main", "d.start="}); makeRtorrentCall(log, "d.multicall2", new String[]{"", "main", "d.start="});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetFilePriorities: case SetFilePriorities:
@ -251,7 +275,7 @@ public class RtorrentAdapter implements IDaemonAdapter {
String newPriority = "" + convertPriority(prioTask.getNewPriority()); String newPriority = "" + convertPriority(prioTask.getNewPriority());
// One at a time; rTorrent doesn't seem to support a multicall on a selective number of files // One at a time; rTorrent doesn't seem to support a multicall on a selective number of files
for (TorrentFile forFile : prioTask.getForFiles()) { for (TorrentFile forFile : prioTask.getForFiles()) {
makeRtorrentCall(log, "f.set_priority", makeRtorrentCall(log, "f.priority.set",
new String[]{task.getTargetTorrent().getUniqueID() + ":f" + forFile.getKey(), new String[]{task.getTargetTorrent().getUniqueID() + ":f" + forFile.getKey(),
newPriority}); newPriority});
} }
@ -261,16 +285,16 @@ public class RtorrentAdapter implements IDaemonAdapter {
// Request to set the maximum transfer rates // Request to set the maximum transfer rates
SetTransferRatesTask ratesTask = (SetTransferRatesTask) task; SetTransferRatesTask ratesTask = (SetTransferRatesTask) task;
makeRtorrentCall(log, "set_download_rate", new String[]{(ratesTask.getDownloadRate() == null ? "0" : makeRtorrentCall(log, "throttle.global_down.max_rate.set", new String[]{"",(ratesTask.getDownloadRate() == null ? "0" :
ratesTask.getDownloadRate().toString() + "k")}); ratesTask.getDownloadRate().toString() + "k")});
makeRtorrentCall(log, "set_upload_rate", new String[]{ makeRtorrentCall(log, "throttle.global_up.max_rate.set", new String[]{"",
(ratesTask.getUploadRate() == null ? "0" : ratesTask.getUploadRate().toString() + "k")}); (ratesTask.getUploadRate() == null ? "0" : ratesTask.getUploadRate().toString() + "k")});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);
case SetLabel: case SetLabel:
SetLabelTask labelTask = (SetLabelTask) task; SetLabelTask labelTask = (SetLabelTask) task;
makeRtorrentCall(log, "d.set_custom1", makeRtorrentCall(log, "d.custom1.set",
new String[]{task.getTargetTorrent().getUniqueID(), labelTask.getNewLabel()}); new String[]{task.getTargetTorrent().getUniqueID(), labelTask.getNewLabel()});
return new DaemonTaskSuccessResult(task); return new DaemonTaskSuccessResult(task);

450
app/src/main/java/org/transdroid/daemon/Ttorrent/TtorrentAdapter.java

@ -0,0 +1,450 @@
/*
* This file is part of Transdroid <http://www.transdroid.org>
*
* Transdroid is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Transdroid is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Transdroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.transdroid.daemon.Ttorrent;
import com.android.internalcopy.http.multipart.FilePart;
import com.android.internalcopy.http.multipart.MultipartEntity;
import com.android.internalcopy.http.multipart.Part;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.transdroid.core.gui.log.Log;
import org.transdroid.daemon.Daemon;
import org.transdroid.daemon.DaemonException;
import org.transdroid.daemon.DaemonException.ExceptionType;
import org.transdroid.daemon.DaemonSettings;
import org.transdroid.daemon.IDaemonAdapter;
import org.transdroid.daemon.Priority;
import org.transdroid.daemon.Torrent;
import org.transdroid.daemon.TorrentDetails;
import org.transdroid.daemon.TorrentFile;
import org.transdroid.daemon.TorrentStatus;
import org.transdroid.daemon.task.AddByFileTask;
import org.transdroid.daemon.task.AddByMagnetUrlTask;
import org.transdroid.daemon.task.AddByUrlTask;
import org.transdroid.daemon.task.DaemonTask;
import org.transdroid.daemon.task.DaemonTaskFailureResult;
import org.transdroid.daemon.task.DaemonTaskResult;
import org.transdroid.daemon.task.DaemonTaskSuccessResult;
import org.transdroid.daemon.task.GetFileListTask;
import org.transdroid.daemon.task.GetFileListTaskSuccessResult;
import org.transdroid.daemon.task.GetTorrentDetailsTask;
import org.transdroid.daemon.task.GetTorrentDetailsTaskSuccessResult;
import org.transdroid.daemon.task.RemoveTask;
import org.transdroid.daemon.task.RetrieveTask;
import org.transdroid.daemon.task.RetrieveTaskSuccessResult;
import org.transdroid.daemon.task.SetFilePriorityTask;
import org.transdroid.daemon.task.SetTransferRatesTask;
import org.transdroid.daemon.util.HttpHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* The daemon adapter for the tTorrent Android torrent client.
* @author erickok
*/
public class TtorrentAdapter implements IDaemonAdapter {
private static final String LOG_NAME = "tTorrent daemon";
private DaemonSettings settings;
private DefaultHttpClient httpclient;
public TtorrentAdapter(DaemonSettings settings) {
this.settings = settings;
}
@Override
public DaemonTaskResult executeTask(Log log, DaemonTask task) {
try {
switch (task.getMethod()) {
case Retrieve:
// Request all torrents from server
JSONArray result = new JSONArray(makeRequest(log, "/json/events"));
return new RetrieveTaskSuccessResult((RetrieveTask) task, parseJsonTorrents(result), null);
case GetTorrentDetails:
// Request tracker and error details for a specific teacher
String mhash = task.getTargetTorrent().getUniqueID();
JSONArray messages =
new JSONArray(makeRequest(log, "/json/propertiesTrackers/" + mhash));
return new GetTorrentDetailsTaskSuccessResult((GetTorrentDetailsTask) task, parseJsonTorrentDetails(messages));
case GetFileList:
// Request files listing for a specific torrent
String fhash = task.getTargetTorrent().getUniqueID();
JSONArray files =
new JSONArray(makeRequest(log, "/json/propertiesFiles/" + fhash));
return new GetFileListTaskSuccessResult((GetFileListTask) task, parseJsonFiles(files));
case AddByFile:
// Upload a local .torrent file
String ufile = ((AddByFileTask) task).getFile();
makeUploadRequest("/command/upload", ufile, log);
return new DaemonTaskSuccessResult(task);
case AddByUrl:
// Request to add a torrent by URL
String url = ((AddByUrlTask) task).getUrl();
makeRequest(log, "/command/download", new BasicNameValuePair("urls", url));
return new DaemonTaskSuccessResult(task);
case AddByMagnetUrl:
// Request to add a magnet link by URL
String magnet = ((AddByMagnetUrlTask) task).getUrl();
makeRequest(log, "/command/download", new BasicNameValuePair("urls", magnet));
return new DaemonTaskSuccessResult(task);
case Remove:
// Remove a torrent
RemoveTask removeTask = (RemoveTask) task;
makeRequest(log, (removeTask.includingData() ? "/command/deletePerm" : "/command/delete"),
new BasicNameValuePair("hashes", removeTask.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case Pause:
// Pause a torrent
makeRequest(log, "/command/pause", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case PauseAll:
// Resume all torrents
makeRequest(log, "/command/pauseall");
return new DaemonTaskSuccessResult(task);
case Resume:
// Resume a torrent
makeRequest(log, "/command/resume", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()));
return new DaemonTaskSuccessResult(task);
case ResumeAll:
// Resume all torrents
makeRequest(log, "/command/resumeall");
return new DaemonTaskSuccessResult(task);
case SetFilePriorities:
// Update the priorities to a set of files
SetFilePriorityTask setPrio = (SetFilePriorityTask) task;
String newPrio = "0";
if (setPrio.getNewPriority() == Priority.Low) {
newPrio = "1";
} else if (setPrio.getNewPriority() == Priority.Normal) {
newPrio = "2";
} else if (setPrio.getNewPriority() == Priority.High) {
newPrio = "7";
}
// We have to make a separate request per file, it seems
for (TorrentFile file : setPrio.getForFiles()) {
makeRequest(log, "/command/setFilePrio", new BasicNameValuePair("hash", task.getTargetTorrent().getUniqueID()),
new BasicNameValuePair("id", file.getKey()), new BasicNameValuePair("priority", newPrio));
}
return new DaemonTaskSuccessResult(task);
default:
return new DaemonTaskFailureResult(task,
new DaemonException(ExceptionType.MethodUnsupported, task.getMethod() + " is not supported by " + getType()));
}
} catch (JSONException e) {
return new DaemonTaskFailureResult(task, new DaemonException(ExceptionType.ParsingFailed, e.toString()));
} catch (DaemonException e) {
return new DaemonTaskFailureResult(task, e);
}
}
private String makeRequest(Log log, String path, NameValuePair... params) throws DaemonException {
try {
// Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path));
List<NameValuePair> nvps = new ArrayList<>();
Collections.addAll(nvps, params);
httppost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
return makeWebRequest(httppost, log);
} catch (UnsupportedEncodingException e) {
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
private String makeUploadRequest(String path, String file, Log log) throws DaemonException {
try {
// Setup request using POST
HttpPost httppost = new HttpPost(buildWebUIUrl(path));
File upload = new File(URI.create(file));
Part[] parts = {new FilePart("torrentfile", upload)};
httppost.setEntity(new MultipartEntity(parts, httppost.getParams()));
return makeWebRequest(httppost, log);
} catch (FileNotFoundException e) {
throw new DaemonException(ExceptionType.FileAccessError, e.toString());
}
}
private String makeWebRequest(HttpPost httppost, Log log) throws DaemonException {
try {
// Initialise the HTTP client
if (httpclient == null) {
initialise();
}
// Execute
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
if (entity != null) {
// Read JSON response
java.io.InputStream instream = entity.getContent();
String result = HttpHelper.convertStreamToString(instream);
instream.close();
// Return raw result
return result;
}
log.d(LOG_NAME, "Error: No entity in HTTP response");
throw new DaemonException(ExceptionType.UnexpectedResponse, "No HTTP entity object in response.");
} catch (Exception e) {
log.d(LOG_NAME, "Error: " + e.toString());
throw new DaemonException(ExceptionType.ConnectionError, e.toString());
}
}
/**
* Instantiates an HTTP client with proper credentials that can be used for all tTorrent requests.
* @throws DaemonException On conflicting or missing settings
*/
private void initialise() throws DaemonException {
httpclient = HttpHelper.createStandardHttpClient(settings, true);
}
/**
* Build the URL of the web UI request from the user settings
* @return The URL to request
*/
private String buildWebUIUrl(String path) {
return (settings.getSsl() ? "https://" : "http://") + settings.getAddress() + ":" + settings.getPort() + path;
}
private TorrentDetails parseJsonTorrentDetails(JSONArray messages) throws JSONException {
ArrayList<String> trackers = new ArrayList<>();
ArrayList<String> errors = new ArrayList<>();
// Parse response
if (messages.length() > 0) {
for (int i = 0; i < messages.length(); i++) {
JSONObject tor = messages.getJSONObject(i);
trackers.add(tor.getString("url"));
String msg = tor.getString("msg");
if (msg != null && !msg.equals(""))
errors.add(msg);
}
}
// Return the list
return new TorrentDetails(trackers, errors);
}
private ArrayList<Torrent> parseJsonTorrents(JSONArray response) throws JSONException {
// Parse response
ArrayList<Torrent> torrents = new ArrayList<>();
for (int i = 0; i < response.length(); i++) {
JSONObject tor = response.getJSONObject(i);
double progress = tor.getDouble("progress");
int leechers[] = parsePeers(tor.getString("num_leechs"));
int seeders[] = parsePeers(tor.getString("num_seeds"));
long size = parseSize(tor.getString("size"));
double ratio = parseRatio(tor.getString("ratio"));
int dlspeed = (int) parseSize(tor.getString("dlspeed"));
int upspeed = (int) parseSize(tor.getString("upspeed"));
long eta = -1L;
if (dlspeed > 0)
eta = (long) (size - (size * progress)) / dlspeed;
// @formatter:off
torrents.add(new Torrent(
(long) i,
tor.getString("hash"),
tor.getString("name"),
parseStatus(tor.getString("state")),
null,
dlspeed,
upspeed,
seeders[0],
seeders[1],
leechers[0],
leechers[1],
(int) eta,
(long) (size * progress),
(long) (size * ratio),
size,
(float) progress,
0f,
null,
null,
null,
null,
settings.getType()));
// @formatter:on
}
// Return the list
return torrents;
}
private double parseRatio(String string) {
// Ratio is given in "1.5" string format
try {
return Double.parseDouble(string);
} catch (Exception e) {
return 0D;
}
}
private long parseSize(String string) {
if (string.equals("Unknown"))
return -1;
// Sizes are given in "1562690683 B"-like string format
String[] parts = string.split(" ");
try {
return Long.parseLong(parts[0]);
} catch (Exception e) {
return -1L;
}
}
private int[] parsePeers(String seeds) {
// Peers (seeders or leechers) are defined in a string like "num_seeds":"66 (27)" but we are also compatible with the old
// "num_seeds":"66 (27)" format
String[] parts = seeds.split(" ");
if (parts.length > 1) {
return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1].substring(1, parts[1].length() - 1))};
}
return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[0])};
}
private TorrentStatus parseStatus(String state) {
// Status is given as a descriptive string
if (state.equals("downloading")) {
return TorrentStatus.Downloading;
} else if (state.equals("uploading")) {
return TorrentStatus.Seeding;
} else if (state.equals("pausedDL")) {
return TorrentStatus.Paused;
} else if (state.equals("pausedUL")) {
return TorrentStatus.Paused;
} else if (state.equals("stalledUP")) {
return TorrentStatus.Seeding;
} else if (state.equals("stalledDL")) {
return TorrentStatus.Downloading;
} else if (state.equals("checkingUP")) {
return TorrentStatus.Checking;
} else if (state.equals("checkingDL")) {
return TorrentStatus.Checking;
} else if (state.equals("queuedDL")) {
return TorrentStatus.Queued;
} else if (state.equals("queuedUL")) {
return TorrentStatus.Queued;
}
return TorrentStatus.Unknown;
}
private ArrayList<TorrentFile> parseJsonFiles(JSONArray response) throws JSONException {
// Parse response
ArrayList<TorrentFile> torrentfiles = new ArrayList<>();
for (int i = 0; i < response.length(); i++) {
JSONObject file = response.getJSONObject(i);
long size = parseSize(file.getString("size"));
torrentfiles.add(new TorrentFile("" + i, file.getString("name"), null, null, size, (long) (size * file.getDouble("progress")),
parsePriority(file.getInt("priority"))));
}
// Return the list
return torrentfiles;
}
private Priority parsePriority(int priority) {
// Priority is an integer
// Actually 1 = Normal, 2 = High, 7 = Maximum, but adjust this to Transdroid values
if (priority == 0) {
return Priority.Off;
} else if (priority == 1) {
return Priority.Low;
} else if (priority == 2) {
return Priority.Normal;
}
return Priority.High;
}
@Override
public Daemon getType() {
return settings.getType();
}
@Override
public DaemonSettings getSettings() {
return this.settings;
}
}

BIN
app/src/main/res/drawable-hdpi/ab_bottom_solid_transdroid.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

BIN
app/src/main/res/drawable-hdpi/ab_bottom_solid_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

BIN
app/src/main/res/drawable-hdpi/ab_solid_transdroid.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

BIN
app/src/main/res/drawable-hdpi/ab_solid_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

BIN
app/src/main/res/drawable-hdpi/ab_stacked_solid_transdroid.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

BIN
app/src/main/res/drawable-hdpi/ab_stacked_solid_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

BIN
app/src/main/res/drawable-hdpi/ab_texture_tile_transdroid2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 B

BIN
app/src/main/res/drawable-hdpi/ab_transparent_transdroid.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

BIN
app/src/main/res/drawable-hdpi/ab_transparent_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

BIN
app/src/main/res/drawable-hdpi/abc_list_focused_holo.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

BIN
app/src/main/res/drawable-hdpi/abc_list_longpressed_holo.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

BIN
app/src/main/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

BIN
app/src/main/res/drawable-hdpi/abc_list_pressed_holo_light.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

BIN
app/src/main/res/drawable-hdpi/abc_list_selector_disabled_holo_dark.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

BIN
app/src/main/res/drawable-hdpi/abc_list_selector_disabled_holo_light.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 B

BIN
app/src/main/res/drawable-hdpi/btn_cab_done_default_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 B

BIN
app/src/main/res/drawable-hdpi/btn_cab_done_focused_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

BIN
app/src/main/res/drawable-hdpi/btn_cab_done_pressed_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

BIN
app/src/main/res/drawable-hdpi/cab_background_bottom_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

BIN
app/src/main/res/drawable-hdpi/cab_background_top_transdroid2.9.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

BIN
app/src/main/res/drawable-hdpi/ic_action_add.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

BIN
app/src/main/res/drawable-hdpi/ic_action_barcode.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
app/src/main/res/drawable-hdpi/ic_action_copy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save